Files
ded1cff4ca0ad7e70e3977e79eb421daa868a8da
T
NSP script: a title ID no profile has is looked up by its main build (general, from issue #114). A regional release of a build a profile registers has another title ID (Advance Wars 1+2: Re-Boot Camp's main 320a17744aefd67fec5738ca0c629795 under 0100300012FFF000 as well as 0100300012F2A000), so game.py by_title found nothing and the script surveyed it as an unknown game with a provisional profile. same_build now extracts only the code modules (nx_extract --manifest: no game files written) of the base game with its newest update, then of the base game alone, and game.py by_build looks main's build ID up in every profile's build rows; a match packs from a profile derived from that one (derive_profile), with the folder's title ID and main.npdm's program ID (the host reports that program ID and answers save data queries for it) and shaders split from the title's own files, since its RomFS may differ. The closing message says the derived build is the profile's registered build under another title ID instead of a version it was not brought up on. No two of the 56 registered main builds are in more than one profile. New test test_nsp_alias.py (by_build, the update-then-base order, the derived profile's IDs); the 44 tool tests pass. Checked: the script on Advance Wars' NSP with its title ID made to read as 0100300012FFF000 found profile 0100300012F2A000's v0 in 2 s, packed for the Mac (20,424 shaders) and the build ran 90 s headless with no red line; the real title packs as before, with no lookup. docs/development.md says so
patch_nso.py split into the general rewriter and per-build patch files (GitHub issue #63): every game's bring-up edited the one file, so PRs for unrelated games conflicted. dm/tools/patch_nso.py (1,711 → about 1,100 lines) keeps the general passes and the driver; the instruction encoders move unchanged to dm/tools/nso_asm.py; a game's own patches (CUTS, OBS and the frame-step stubs, now FRAME_STEP = {build ID: stub(ctx)}) move verbatim with their comments to dm/games/<id>/patches.py (16 profiles: Tears of the Kingdom, Breath of the Wild, Age of Calamity and Paper Mario: The Thousand-Year Door with their stubs), and the cuts of Nintendo's sdk module builds, 6 of the 15 shipped by 2 to 4 games, to dm/tools/sdk_patches.py. The rewriter loads every patch file, whatever the game, and applies those of a module's build ID at the points it did before (cuts before the scan, gates after the rewrite, stubs after the gates); a build ID claimed twice stops it, and its command line needs no DM_GAME. build_src.sha256 lists the rewriter's files and every patch file (the same for every module). NSP script: the rewrite step's reuse key counts nso_asm.py and only the patch files that name one of the game's builds (patch_files), profile_files leaves patches.py out (only the rewrite step reads it), and a derived profile gets none. dm/tools/tests/test_patches.py: no build claimed twice, the entries' forms, a game's file names only its own profile's non-sdk builds, sdk_patches.py only sdk builds some profile ships, and the reuse key's patch files. Checked byte-identical: every extracted module on the game drive (131 builds) rewritten with the previous and the new rewriter gives the same outputs but build_src.sha256, 28 of the 30 patched builds among them (all four frame-step mains), and 7 fail alike (rtld, and New Super Mario Bros. U Deluxe's, which no profile rewrites); the other two (Super Mario Bros. Wonder's mains and Absolum's, no game files here) by their CUTS / OBS data, equal to the previous tables. AGENTS.md, docs, dm/games/README.md, the profiles' comments and the host comments that named a game's cut in patch_nso.py now name its patches.py or sdk_patches.py
Pikmin 4 (0100B7C00933A000, update 1.1.1, Unreal Engine 4.26.2 on SDK 10.4.8): game profile, the allocator and engine-region cuts, the SDK 10.4.8 sdk build's address-region cuts, two rewriter cases and a general nifm fix. Plays on the iPhone (IPA 10.8 GB, user). On the Mac from the boot through the title (a VP9 movie behind the logo), the main menu, a new game (the auto-save notice, players, creature activity level), the opening and its cutscenes into the Olimar prologue in play, and Continue from a save: 30 fps as on the Switch (the game steps a fixed frame time, bUseFixedFrameRate=True, and asks for rhi.SyncInterval=2), Wwise sound through its own AudioOut sink, about 2.6-2.8 GB resident and 1.7 GB of Metal allocations, no red line. patches.py: Unreal's FMallocBinned3 is made on the first allocation as in 4.27 (GCreateMalloc main+0x353f770 mallocs 0x1c38 bytes, constructor main+0x35ba610, the one store of the vtable main+0x8da4758 outside its two destructors, found from the Binned3 error strings through the relocated slots); the constructor call is cut and observation gate OBS_UE_MALLOC on the ldp x29, x30 after it hands the object the host allocator (hle_uemalloc.c, the 21-slot UE4_27 layout). This nninitStartup reserves the engine region only when -SwitchVirtualAddressSpaceSize= is absent (ldr x8,[x22,#0x130]; cbnz x8 past the default and its AllocateAddressRegion, main+0x353f564): with the argument the base stayed 0 and the first commit, AllocateMemoryPages(0, 2 MB) as the RHI started (main+0x353ff8c, 0xe03), went into Unreal's fatal error. The two orr #0xf80000000 (62 GB) are cut to mov #0xfe000000 (4064 MB) and the profile has no game_args, in Engage's 5 GB block. sdk_patches.py: the SDK 10.4.8 sdk build's AddressRegionManager is the 10.4.1 build's at the same offsets; the node area is cut to 62 MB and the 1 GB alignment of large regions to 32 MB. patch_nso.py (general): an adrp inside an exclusive window that uses x18 (main+0x43da14c: ldaxr; cmp; b.ne; adrp x10 + add; mov x18,x26; stlxr) is re-encoded for the copy's address (window_adrp), and scratch_dead accepts a ret as it does a bl (the gate sits on an epilogue, and the check reads the image after the cut, so the bl before it is a nop); both only apply where the rewrite used to stop, so every other module rewrites byte for byte as before. hle_net.c (general): a submitted nifm request stays on hold for 50 ms, then settles as unavailable, as Horizon's asynchronous request does. Answered at once, about one boot in six (3 in 4 with four Mac hosts at once) waited at the black loading screen for good: the boot state machine (main+0x1f2f0e0) sat in state 7 polling the BCAT runnable's status, whose constructor (main+0x203f3b0) starts FBCATThread and only then stores 1, and whose Run (main+0x203f510) submits the request, waits while on hold and stores 6; a thread that ran first finished before the constructor's store, which overwrote the 6. With the hold 12 of 12 boots under load reached the title. Profile: shader groups as DRAGON QUEST VII Reimagined's (15,727 distinct shaders, every one an LZ4 block in the pak's two shader code archives; all build for the Mac and iOS), pad_layout full, a prewarm list recorded on the Mac over the route above (806 lines). Checked: the NSP script packs it from the NSPs alone (Mac and iOS); after the rebase onto
0045c1d the 41 tool tests pass and the host builds. README (games table, a Mac picture of the prologue), AGENTS.md (54 -> 55), docs/development.md and docs/agents 01 / 03 say so
Pikmin 4 (0100B7C00933A000, update 1.1.1, Unreal Engine 4.26.2 on SDK 10.4.8): game profile, the allocator and engine-region cuts, the SDK 10.4.8 sdk build's address-region cuts, two rewriter cases and a general nifm fix. Plays on the iPhone (IPA 10.8 GB, user). On the Mac from the boot through the title (a VP9 movie behind the logo), the main menu, a new game (the auto-save notice, players, creature activity level), the opening and its cutscenes into the Olimar prologue in play, and Continue from a save: 30 fps as on the Switch (the game steps a fixed frame time, bUseFixedFrameRate=True, and asks for rhi.SyncInterval=2), Wwise sound through its own AudioOut sink, about 2.6-2.8 GB resident and 1.7 GB of Metal allocations, no red line. patches.py: Unreal's FMallocBinned3 is made on the first allocation as in 4.27 (GCreateMalloc main+0x353f770 mallocs 0x1c38 bytes, constructor main+0x35ba610, the one store of the vtable main+0x8da4758 outside its two destructors, found from the Binned3 error strings through the relocated slots); the constructor call is cut and observation gate OBS_UE_MALLOC on the ldp x29, x30 after it hands the object the host allocator (hle_uemalloc.c, the 21-slot UE4_27 layout). This nninitStartup reserves the engine region only when -SwitchVirtualAddressSpaceSize= is absent (ldr x8,[x22,#0x130]; cbnz x8 past the default and its AllocateAddressRegion, main+0x353f564): with the argument the base stayed 0 and the first commit, AllocateMemoryPages(0, 2 MB) as the RHI started (main+0x353ff8c, 0xe03), went into Unreal's fatal error. The two orr #0xf80000000 (62 GB) are cut to mov #0xfe000000 (4064 MB) and the profile has no game_args, in Engage's 5 GB block. sdk_patches.py: the SDK 10.4.8 sdk build's AddressRegionManager is the 10.4.1 build's at the same offsets; the node area is cut to 62 MB and the 1 GB alignment of large regions to 32 MB. patch_nso.py (general): an adrp inside an exclusive window that uses x18 (main+0x43da14c: ldaxr; cmp; b.ne; adrp x10 + add; mov x18,x26; stlxr) is re-encoded for the copy's address (window_adrp), and scratch_dead accepts a ret as it does a bl (the gate sits on an epilogue, and the check reads the image after the cut, so the bl before it is a nop); both only apply where the rewrite used to stop, so every other module rewrites byte for byte as before. hle_net.c (general): a submitted nifm request stays on hold for 50 ms, then settles as unavailable, as Horizon's asynchronous request does. Answered at once, about one boot in six (3 in 4 with four Mac hosts at once) waited at the black loading screen for good: the boot state machine (main+0x1f2f0e0) sat in state 7 polling the BCAT runnable's status, whose constructor (main+0x203f3b0) starts FBCATThread and only then stores 1, and whose Run (main+0x203f510) submits the request, waits while on hold and stores 6; a thread that ran first finished before the constructor's store, which overwrote the 6. With the hold 12 of 12 boots under load reached the title. Profile: shader groups as DRAGON QUEST VII Reimagined's (15,727 distinct shaders, every one an LZ4 block in the pak's two shader code archives; all build for the Mac and iOS), pad_layout full, a prewarm list recorded on the Mac over the route above (806 lines). Checked: the NSP script packs it from the NSPs alone (Mac and iOS); after the rebase onto
0045c1d the 41 tool tests pass and the host builds. README (games table, a Mac picture of the prologue), AGENTS.md (54 -> 55), docs/development.md and docs/agents 01 / 03 say so