9.7 KiB
9.7 KiB
Packing
Build tools
Homebrew .NET is on PATH but apphosts cannot find the runtime
- Symptom: the first extraction stops with
nx_extract exit code 131and.NET location: Not found, even thoughdotnetis onPATH. The shader translator can fail the same way. - Cause: standalone .NET apphosts search runtime roots and registered installations, not
PATH(.NET runtime discovery). Homebrew's formula installs the runtime underlibexecand itsbin/dotnetcan be a wrapper that setsDOTNET_ROOTonly for its own child (Homebrew caveat). Resolving that wrapper's symlink givesbin, not the runtime root. - Fix:
scripts/build_ipa_from_nsp.pyconfigure_dotnetderives the root from theMicrosoft.NETCore.Appentry indotnet --list-runtimes, using the build's environment, before any extraction or survey. It exportsDOTNET_ROOTboth to that environment and toos.environ, for tools launched with an explicit environment and those that inherit it. Caller-supplied roots, including architecture-specific overrides, retain precedence. A failed or malformed probe leaves the environment unchanged rather than guessing the wrapper's directory. - Found on: Balatro, first extraction with Homebrew .NET (PR #284). Applies to every game's build on that installation setup.
IPA archives
A streamed file changes size and the IPA fails its CRC check
- Symptom: packing succeeds, but
unzip -tor Python'sZipFile.testzip()reports a bad CRC for a large deflated file. - Cause:
pack_ipa.pywrote the uncompressed size collected before reading. Its streaming check compared the output size only for stored files, so a compressible file that grew or shrank still published an archive with the old size. - Fix: count uncompressed bytes as they are read and compare them with the listed size before completing either streamed method. A mismatch stops the pack and removes the partial archive, preserving the previous IPA and its reuse index. Regression tests cover growth and shrinkage with both deflate and store.
- Found on: synthetic IPA inputs while reviewing PR #279; no game files required.
Compressed inputs
NSZ decompression rejects --keys
- Symptom:
decompress NSZstops immediately withnsz: error: unrecognized arguments: --keys …. - Cause:
nsz4.6.1 has no--keysargument. It loadskeys.txtbesidesys.argv[0], or falls back to~/.switch/prod.keys, while importing its modules, before parsing arguments (4.6.1 key loader). Newer upstream code supports--keys; stagingkeys.txtalso supports 4.6.1. - Fix:
scripts/build_ipa_from_nsp.pydecompress_nszlinks the file selected by-kaskeys.txtin the conversion's temporary folder and runsnsz -Dthere, withsys.argv[0]set tonszbefore import. Input and output paths are absolute. The temporary folder and link are removed on success or failure; the user's default keys are untouched. - Found on: Super Mario Odyssey, converting its v0 NSZ. Applies to NSZ and XCZ inputs for every game.
Desktop build worker
Prepare game fails at os.setsid()
- Symptom: clicking Prepare game stops before any build tools start, with
PermissionError: [Errno 1] Operation not permittedatbuild_session.pyos.setsid(). - Cause: Foundation
Processalready launches the worker as its own process-group leader on macOS. POSIX rejectssetsid()for a group leader. A normal Python subprocess launch did not expose this difference, so the original regression test missed the GUI path. - Fix: reuse the existing group when
os.getpgrp() == os.getpid(); callsetsid()only for a worker that still shares its parent's group. Cancellation still signals the worker's separate group. Do not suppress unrelated permission failures. Added regression coverage for FoundationProcess, an existing group leader, an existing session leader and a normal subprocess launch, including cancellation. - Found on: a user's clean desktop installation, before game detection; reproduced locally with a Swift Foundation launcher. Applies to every Mac/iPhone game preparation, independent of the game, keys or saves. Released in 1.4.2/build 7.
Graphics regression command fails in a workspace path containing spaces
- Symptom:
check_t_transform_feedbackreportsCode/.../t_transform_feedback: No such file or directorybefore running its checks. - Cause: the Makefile passed
$(CURDIR)into a shell environment assignment without quoting; the space split the executable path into a command. - Fix: quote the complete
DM_TEST_TRANSFORM_FEEDBACKvalue. - Found on: the full upstream-main integration in this fork's
Claude Code/ios_gamesworkspace; independent of game data.
Bundled-pipeline regression fixture targets a newer macOS than the host
- Symptom:
t_pso_bundlebuilds an archive but reports ten failures after Metal rejects its deployment target. - Cause: the fixture used
pso_offline.py's SDK-major default (27) on a Mac running macOS 26.6. Production intentionally falls back to compiling bases on-device when it cannot load such an archive. - Fix: the native fixture explicitly passes the current macOS major as
--os-min, so it tests archive reuse on the system actually running it; keep its scratch fixture on failure for diagnostics. Production fallback remains intact. - Found on: full-main GPU validation with Xcode 27.1 on macOS 26.6.1, Apple M2 Max.
Desktop preparation shows a spinner with no build activity
- Symptom: preparation appears stuck; the player cannot inspect compilation output until a failure.
- Cause: the UI hid its transcript unless
failedwas true, parsed only coarse text fragments, and did not surface the compiler logs that the upstream builder intentionally writes separately. - Fix: request upstream JSON progress through the wrappers, frame complete UTF-8 records in the native model, and show stage counters/fractions only when reported. The group-preserving worker streams console output and bounded tails of changed regular per-tool logs, retains a private session log and preserves the original complete compiler files. The UI exposes both log locations, elapsed time, a quiet-output notice and error details; preview truncation never deletes the original logs. Mac rebuilds retain bundle/Title ID metadata; a previous preparation is not evidence of an iPhone installation.
- Found on: desktop builds reported by the repository owner; validated with synthetic streaming/rotation/failure/cancellation fixtures and the production Swift model. See personal desktop workflow.
Native sideload toolchain install rejects a workspace path with spaces
- Symptom: the official Rust component shell installer rejects part of
--prefixas an unknown option; a partial LLVM setup can also makerust-objcopyfail to loadlibLLVM.dylib. - Cause: the installer splits a prefix containing spaces, while the new compiler's object tool requires its matching LLVM-tools component.
- Fix: install checksum-pinned official component manifests directly in an external cache, including LLVM-tools; validate archive paths/links and use an explicit ARM64 Cargo target. Keep the system Rust installation unchanged. Synthetic archive tests cover spaces, reuse and traversal refusal.
- Found on: the native helper build in this fork's workspace; no game or Apple account required.
A sideload signer changes an existing game's bundle identifier
- Symptom: a replacement signer can append a second team suffix or produce another app identifier, separating the user from the existing app container.
- Cause: isideload 0.5.0 unconditionally appended the team ID to the supplied bundle ID, even when the caller already knows the effective installed identifier.
- Fix: the small vendored MIT patch preserves the caller's exact final ID. Our helper selects an actual existing device app, checks that it matches the game, verifies the signed/provisioned identity and never uninstalls the previous app to force an update. Original preparation IPAs stay unsigned. Tests cover custom identifiers and mismatch refusal; real-account/device migration still requires validation.
- Found on: source review and synthetic tests for the integrated installer, with no existing user installation modified.
A complete iOS IPA can still select older external game data
- Symptom: a complete game update includes new bundled RomFS but an existing Documents/romfs can take priority when the app boots.
- Cause: the legacy iOS boot search intentionally checks Documents first to support external game-content overrides; preserving the container across an update also preserves that older directory.
- Fix: the native signer marks bundles containing RomFS with
DMPreferBundledRomFS. The iOS boot search honors the flag by checking bundled content first, with Documents as fallback. Unflagged apps retain the old override behavior. The desktop full-mode preparation gate requires a new preparation record; it does not silently reuse an older runtime. No save or external content is deleted. Physical mode migration remains unvalidated. - Found on: native installer source review, not a verified gameplay regression.