Commit Graph
354 Commits
Author SHA1 Message Date
seedds 0f99c353d3 Haptics: a Core Haptics engine that fails to start is replaced, not retried for good (general, from PR #143). A Bluetooth Switch Pro controller that vibrates through iOS Settings' Identify stayed silent in a game, which logged Core Haptics -4810 (server init failed): the adapter made each engine once per route and called startAndReturnError on that same broken engine at every later effect, and its stop/reset handlers were installed only after the start. Now haptics_apple.m makes an engine when the first effect needs it (the phone's too, so after the audio session is active), installs the handlers before starting it, and a failure to make or start the engine, make or start its player or send its parameters releases that engine: a fresh game signal makes a new one after a backoff of 1, 2, 4 or 8 s, effects from before the failure are not replayed, and the old engine's callbacks are stale (generation). A controller whose left and right handle engines cannot be made or started falls back to its default locality until it reconnects; a player or parameter failure (a service reset, say) is retried on the same handle. Idle release (1 s of silence), focus loss and engine resets stop an engine but keep it, so the next effect restarts the same engine, as before. Errors name the target, the stage, the error domain and code and the underlying error, and the log names the controller, its localities and the output channels. From PR #143 (nanda86), with two changes: the PR dropped the engine at every stop, so each rumble after a second of silence made a new engine (a new connection to the haptics service on a controller, a new initWithAudioSession: engine on the phone), and any handle failure moved the route to the default locality for good. t_haptics_apple covers each failure stage, the backoff, the fallback (and no fallback on a player failure), stale callbacks, a restarted engine after idle release, a disconnect and a controller without haptics, with fake controllers and engines. Checked: the host unit tests pass (GAME=0100DCA0064A6000), haptics_apple.m compiles for iOS arm64 with -Wall -Werror. Not checked: a physical controller or the iPhone's own haptics 2026-10-25 09:55:44 +08:00
seedds ca658c9dde NVN BufferInitialize: a buffer past the end of its pool is a one-line note, not a red line (general, found on Breath of the Wild). Breath of the Wild creates one 256 KB buffer (offset 0) over a 144 KB pool when the pause menu opens and near a save commit (Mac and iPhone: the same line, once each time). The NVN driver does not check a buffer's range against its pool, and the host never reads a buffer's size to address memory: every GPU access takes an address and a length from its command and resolves it in the pool's memory (mtl_addr / pool_find), which reports a range that leaves a pool on its own (RED_ONCE). The buffer's only use is BufferGetAddress and one texture-to-buffer copy (nvnCommandBufferCopyTextureToBuffer, called from main+0xec2b80 on the unpatched module) of the screen picture's own extent, which resolved inside the pool (no "destination is in no memory pool" line), then it is finalized in the same function; it is never mapped. Now a non-red [nvn] line, once per run. Checked on the Mac (BotW, a copy of the save): the pause menu opens with the inventory drawn as before and the line is a note; the host tests and the gfx run list pass. From PR #145 (v0lumehi). Also checked on current main: the host unit tests and the gfx run list pass. Side finding of the PR, not changed here: in the world, Breath of the Wild's uniform buffer binds of 0x750 bytes at +0xa00 of one-page pools straddle the pool end (pool_find returns -1 for them) 2026-10-25 09:36:51 +08:00
seedds 188c1d8f99 DM_HITCH_DUMP for every graphics API, and safe against exiting threads and the dyld lock (follow-up to #134's review). The present time dm_present_ns was written only in the NVN present (hle_nvn.c), so the dump never fired for the Vulkan and GL games; it is now written in present_enqueue (nvn_mtl.m), which mtl_present (NVN), mtl_present_ext (Vulkan: hle_vk.m; GL: hle_gl.m) all reach, and the NVN-only write is gone. hitch_main took thr_t pointers under G, released G, and read the threads afterwards, so a thread that exits and is freed during a dump was a use-after-free; and it called dladdr while the target thread was suspended, which deadlocks if that thread holds the dyld lock. Now hitch_grab copies what a line needs (handle, name, wait state, svc, registers, the raw pc / lr / frame-pointer chain) into a hitch_t while holding G (thread exit and thr_free need G, so no listed thread is freed or leaves), and between suspend and resume only mach calls and memory reads run; hitch_log then looks the symbols up (module table, dladdr) and logs after every thread runs again. The tests' weak dm_present_ns definitions (t_host_stubs.c, t_nvn.c) stay as they are. Checked: the host unit tests and the gfx run list pass (GAME=01007EF00011E000); the Mac host builds for Breath of the Wild (NVN) and Hades (Vulkan). From PR #137 (v0lumehi). Also checked on current main: the host unit tests and the gfx run list pass; on the Mac with DM_HITCH_DUMP=20:2, Mario vs. Donkey Kong (NVN) logged two dumps in its opening movie and Balatro (GL, which never dumped before: no present time was written outside NVN) one, every guest thread with its guest frames and host symbols, and both games ran on to the end of the run. Not checked: a Vulkan game (none on the drives; Vulkan presents reach present_enqueue through mtl_present_ext as GL's do) 2026-10-25 09:36:51 +08:00
seedds 9c1e449470 Bayonetta 2 (01007960049A0000, update 1.2 = v131072, PlatinumGames' engine in its Wii U form on SDK 3.6.0, the oldest SDK so far): game profile, a CRI CPK reader for the shader split, and two general NVN fixes. Plays on the iPhone (IPA 12.5 GB, user). On the Mac from a fresh save: the boot, the PlatinumGames logo movie (USM), the title, a new game (slot 1, the auto-save notice, Story, Normal), the opening narration (which cannot be skipped on a first play), the prologue cutscene (skipped: Plus, Down, A, A), the tutorial (closed: Plus, Down x4, A), Verse 1 on the jet, Rodin's cutscene and the next fight with an Umbran Climax and a torture attack, with Wwise sound through two 6-channel AudioOuts: 60 fps in play (frame time p50 16.67 ms, p95 about 17.5 ms; GPU 2-3.5 ms a frame on an M6), 30 in its cutscenes as the game asks, about 1.6 GB resident and 1.75 GB of Metal allocations. No host change was needed for SDK 3.6.0 itself (NVN API 53); no virtual address memory (a 3.11 GB heap in the 4 GB layout, mem_used 102 MB). Files: six CPK archives (9.5 GB, 6,091 files, 12 GB of them CRILAYLA-compressed) of PlatinumGames DAT archives. New dm/gfx/tools/cpk.py: the @UTF tables (and the masked ones of Unicorn Overlord's CPKs, the same tables as before once unmasked), the TOC and CRILAYLA (a decoder over a bit string, about 29 MB/s); shader_split.py cpk_jobs splits the CPKs into jobs and scans each entry (a CPK it cannot read is scanned as one file, with a [RED] line). Shaders: 1,856 BNSH files of one stage each (.vso / .pso) in shader.dat, shader2.dat and shadereff.dat of data000.cpk, all built for the Mac and iOS. nvn_mtl.m, both general: a cleared attachment larger than the render pass (Metal's render area is the smallest attachment) is now cleared in a pass of its own and loaded by the pass, as NVN clears the whole target (rp_begin_ex_): the game's 32x32 and 256x256 passes cleared only that corner of its full-size depth target, so the opening narration drew in a corner of a black screen and the opening scenes were speckled; and Downsample of a single-sample texture is a filtered scaling copy of the whole level that may convert the format (copy_t2t, its bloom chain 320x176 -> 80x48 and its 1280x720 BGRA8 frame into an RGBA8 texture), which logged red lines before. build_ipa_from_nsp.py: a survey note for CPK archives. Profile: the 3D pad layout (the camera is on the right stick; the game's touch controls are handheld only), a prewarm list recorded on the Mac over the route above (974 lines). Checked: the graphics run list, the host unit tests and the 49 tool tests pass; Unicorn Overlord's CPKs split as before. Not checked: 13 Sentinels and Catherine: Full Body's CPKs (no files on the drives), the clear fix in other games beyond the unit tests. README (games gallery, a Mac picture of Verse 1), AGENTS.md (58 -> 59), docs/development.md and docs/agents 01 / 03 / 04 say so 2026-10-25 08:56:02 +08:00
seedds 6a0687721a NSZ and XCZ files in the NSP folder (from PR #140): the NSP script converts them itself instead of a second script. scan_nsps lists *.nsz and *.xcz beside *.nsp and *.xci (an NSZ is a PFS0 with the NSP's ticket, so title_of reads it as it is; a compressed file with an NSP of the same name beside it is skipped), and decompress_nsz converts each once with nsz -D, run from the Python running the script (Spyder does not have its bin folder on PATH), into <title ID>_build/nsp/, before the build ID lookup or the survey reads them. nsz writes into an empty temporary folder and the file is moved into nsp/ only when nsz exits 0 and made it, so a partial file left by a failure or an interruption is never taken for a whole one (with -w left out, nsz itself skips an output whose title and version are already there and exits 0); a conversion is reused while its source has the same name, size and modification time. nsz -D prints an NCA hash mismatch but goes on and exits 0 (Decompressor.decompress passes raiseVerificationException=False), so a damaged file is left to nx_extract's own hash check (--allow-damaged). Without the nsz package the script stops with one line: pip install nsz. README: one line in the packing steps; docs/development.md says how it works. AGENTS.md: only build_ipa_from_nsp.py has to run in Spyder with F5; the other scripts in scripts/ run from a terminal (the user). New test dm/tools/tests/test_nsp_nsz.py (nsz stood in for). Checked: the new test, test_nsp_alias, test_nsp_chain, test_nsp_mac, test_nsp_progress and test_xcode_workflow pass; ANIMAL WELL's base game and update compressed with nsz 5.0.0 -C, then the script on a folder holding only the two NSZ files: both converted (byte for byte the original NSPs), the IPA built (exit 0), and a second run reused both conversions. Not checked: an XCZ, the IPA on the iPhone 2026-10-25 02:34:18 +08:00
seedds b87a0cc6aa Upload batches flushed outside a submission are handed to the encode thread at once (general, from PR #141). With the encode thread on (the iOS default, DM_NVN_ENC_THREAD), an upload batch's commit is a record in the encode thread's current block (rec_commit_upload), which is handed over when the block fills or a submission ends. Outside a submission (no replay command buffer: a texture zeroed at creation, tex_zero_fill, or a packaged texture uploaded between submissions) up_flush meant to commit the batch on the spot, but the record stayed in the block until the next submission, and every wait for it waited for that submission: nvnQueueFinish (mtl_finish_gpu, on any thread) or up_begin on a busy slot. A game whose last submitting thread creates a texture and then calls nvnQueueFinish before it submits again waited for good. up_flush now hands the block over when it runs outside a submission (it runs on the submit thread only, up_owner_check). From PR #141 (LNPANDY, found analysing a Hades II save-load freeze on an iPhone 14 Pro): mtl_finish_gpu hands over the block before it waits when it runs on the submit thread, as up_begin does since 9cb7af3 and q_cb does; nvnQueueFinish cannot run inside a submission (one nvnQueueSubmitCommands call, which ends with rec_drain), so that line matters for the window above and for the diagnostics that wait mid-submission (td_dump, DM_NVN_T2T_TRACE). Checked: dmhost builds for Super Mario Bros. Wonder (NVN) and Hades (Vulkan), the host unit tests pass, the graphics run list passes, and with DM_NVN_ENC_THREAD=1 every test passes but t_rt_range, which stops on main alike (DmRecEnc has no stand-in for the copyFromTexture:...toBuffer:...options: form). Not checked: a game, the iPhone 2026-10-25 02:23:42 +08:00
seedds 156d3c2a18 CI on GitHub's macOS runners (from PR #132): .github/workflows/tests.yml runs, for pull requests, pushes to main and manual dispatch, three jobs on macos-26 in parallel: the host unit tests and the graphics tests that need no GPU (t_dirty_slots, t_dirty_race, t_pace, t_fstep, t_hmap, t_cover, t_pso_skey, t_refl and t_rt_range_ro, the last two skipping their sample checks); every test_*.py of dm/tools/tests, dm/ios/tests and dm/gfx/tests on Python 3.14 with lz4, capstone and fonttools; and a build check: dmhost for Wonder (NVN), Hades (Vulkan) and Digimon Story Cyber Sleuth (GL), every graphics test compiled but not run, and the iOS host sources compiled for the iPhone SDK by a new helper, dm/ios/tools/compile_check.py (the host Makefile's file list and flags as gen_build.py takes them, the host's own command first so its header paths come before libvpx's; 133 files, no game files, signing or Xcode project). Read-only permissions, superseded runs cancelled, a timeout per job, logs uploaded only on failure, actions pinned to commit SHAs (checkout v7.0.1, setup-python v7.1.0, upload-artifact v7.0.2, each checked against its tag). The GPU tests stay local (the hosted runners are virtual Macs without Metal 4, issue #112), and so does profile_guess.py --check (it reads game extractions). test_xcode_workflow.py: the stand-in profile carries the registered profile's title_id, which report_profile_status reads since ce17300 (the test had 3 errors on main). Checked by replaying the three jobs on this Mac on current main: all pass, the tool tests in a fresh Python 3.14 venv with only the three packages 2026-10-25 01:58:01 +08:00
seedds 3f4d973ac5 Mario vs. Donkey Kong (0100B99019412000, update 1.0.1 over the cartridge base game, Nintendo's sead / agl / gsys engine on SDK 15.3.0): game profile plus three general host changes. Plays on the iPhone (IPA 1.6 GB, user). On the Mac: the opening movie with sound, the title (Press L+R), file select, the play style choice, the first cutscene, level 1-1 (both colour switches, the spring, a time-out and its restart), the pause menu, leaving the level, level select and the save it shows: 60 fps (frame interval p99 about 17 ms), sound, about 1 GB resident in the level and flat while it runs, CPU about 30% of a core, no red line on that route.
Movie decoder (hle_movie.c, every game), both found on the opening movie (Movie/Cutscene01.mp4, H.264 and AAC through movie::Extractor):
- AAC decoders offer at most 32 outputs (MD_NOUT_AAC; Vorbis keeps 64, video 16). The game's movie player keeps the outputs it holds in a ring of 32 entries per track (main+0xe93d4c: 0x200 bytes) and pushes with no bounds check (main+0xe94140), so with 64 the 33rd AAC output wrote its index (0x3f) and time over the next heap block, and the game crashed in sead::ExpHeap's free a second into the movie, at the same address every boot. The console's AAC decoder cannot offer more than 32, or the game would overflow there too; Monster Train 2 peaked at 30 held.
- An AAC decoder's GetOutputBufferSize is one frame (1024 samples per channel, 2048 with SBR), what each output holds. The player sizes its own AudioOut buffers by it, decodes into them and appends each whole, advancing its audio clock by size / 4 samples (main+0xe92a74); with the 128 KB Vorbis bound every 4 KB AAC frame played as 0.68 s, the clock crawled and the movie froze on its first second. Vorbis keeps the bound (its packets vary in length).

Advanced blend modes (hle_nvn.c, nvn_obj.h, nvn_mtl.m, every game): the blend state keeps the mode, the premultiplied-source flag and the overlap (with their getters), and the pipeline blends the Porter-Duff modes (ZERO, SRC, DST, SRC/DST_OVER, _IN, _OUT, _ATOP, XOR, the PLUS modes) with the fixed-function factors they amount to under uncorrelated overlap (adv_blend); the others keep the state's functions with a red line naming the mode. The game's levels set SRC_OVER on one blend state, which the host used to ignore.

DM_PROBE_POLL takes a value filter (=<value>): every change is logged, the threads are snapshotted and dumped only at changes to that value. That is how the ring overflow was caught: DM_PROBE_WATCH on the heap link made the movie's file reads into the same page fail.

Profile: the provisional profile's guesses held (no system resource, a 3.14 GB heap in the 4 GB layout, modules main subsdk0 sdk), plus render_cond on (the game skips clears under render conditions; without it every level was black behind its HUD). 12,691 shaders (BNSH / BAHS in the zstd RomFS and main), all translated. Pad only (main imports no touch screen), the default 2D layout. A prewarm list recorded on the Mac from a fresh save over the route above (550 lines; with it nothing was built on demand). Checked: the NSP script packs it from the NSPs with the profile in dm/games; the graphics run list and the 49 tool tests pass; after rebasing on 6d34984 (spin waits) the game boots into level 1-1 as before. README (gallery, a Mac picture of level 1-1; 2D layout), AGENTS.md (57 -> 58) and docs/development.md, docs/agents 03 / 04 / 05 say so
2026-10-25 01:19:47 +08:00
seedds 6d34984488 Spin waits that take a spin lock (general, from PR #134, found on Breath of the Wild; fixes #129). In steady play on the Mac, Breath of the Wild had frames with 30-210 ms of CPU work every 1-10 s: the main thread and the two workers spinning in one job queue poll (main+0xef62dc). The first item of some of its jobs is for the thread of core 0; the others wait until it is taken, taking and releasing the queue's lock (an exclusive test-and-set, then a compare-and-clear) and writing a status word on every pass, so the spin wait search (a store or an exclusive store ends a path) did not take it. Polled at full speed by the two workers, the lock went back and forth between their cores, and the main thread, whose item it was, lost nearly every exclusive store: in a 40 s Time Profiler trace it never held the lock while each worker held it in about 750 samples. patch_nso.py "Spin waits": a lock taken and released on the same address on every path back to the head (spin_lock 'acq' / 'rel', counted like the exclusive adds), a test loop on a word inside the region ('tst', whose branch back goes to the original loop and its own gate), and up to four stores whose value and address registers the region never writes ('stc', only in a region that loads something). Such stores write fixed values to fixed addresses, so a loop can change its own state for a few hundred passes at most, well inside the gate's 2 ms of polling, before its passes repeat. A store of a value a pass changes ends its path and the search runs again without it (the path that takes an item stores the new cursor); when the region with stores does not cover the old one's back-edges, the old one is used. Through the gate the workers leave the lock a moment between passes and the main thread takes its item at once. Zora's Domain on an M2 Max at 120 Hz (one host, 42 s of steady play per run, three runs each, before -> after): standing still, frames with more than 30 ms of work 2.8 / 6.3 / 3.5 -> 0 per 30 s, the frame's work p99 20-21 -> 8.3-8.4 ms; turning the camera, CPU hitches 11.9 / 13.3 / 6.3 -> 0, p99 21-25 -> 9-15 ms; process CPU per frame unchanged. Every module on the game drives rewritten with the old and the new search (206: 108 NSOs and Super Smash Bros. Ultimate's 98 NROs): 175 byte for byte as before (Hades, Super Smash Bros. Ultimate, Super Mario Odyssey among them), 7 fail alike under both (every rtld, New Super Mario Bros. U Deluxe), and 24 take new or wider loops, all but Breath of the Wild's queue through the store rule alone: Monster Hunter Rise's main 90 -> 166 (72 of them one inlined countdown whose reset makes the next pass leave), BALL x PIT 87 -> 94, Animal Crossing: New Horizons 162 -> 166, Balatro 9 -> 12, Octopath Traveler 0 75 -> 78, Pikmin 4 80 -> 83, Bayonetta 3 35 -> 37, Breath of the Wild 308 -> 310, one more each in Big Brain Academy, Mario Kart 8 Deluxe, Metroid Dread, Pokemon Legends: Z-A, Stardew Valley, Super Mario Bros. Wonder (both builds) and Tears of the Kingdom; the movie library's thread loop is wider in the subsdk0 of Breath of the Wild, Mario Kart 8 Deluxe, Animal Crossing, BALL x PIT, Monster Train 2 and Octopath Traveler 0, and one loop is wider in the main of AI: THE SOMNIUM FILES and Advance Wars 1+2. No loop of the old search is lost or loses a back-edge. Each distinct shape was read in its disassembly: one-pass resets and state setters (the gate is reached only when another thread changes the word), parser states that leave on the next pass, and real flag waits. Rewriting all 206 takes about 4,100 s of CPU instead of 3,250. Diagnostics: DM_HITCH_DUMP=<ms>[:<s>] logs every guest thread (its wait, last SVC, where it is, x0-x2 and x19-x25 in guest code) when no NVN frame was presented for that long, and each guest thread names its pthread '<handle> <name>' (0x1008 Worker) before it enters guest code, the main thread '0x1001 main thread', so sample and Instruments' Time Profiler show which guest thread a sample belongs to. The graphics tests that link hle_nvn.c without the host define dm_present_ns (t_host_stubs.c weak, t_nvn.c). New test dm/tools/tests/test_spin_waits.py. Checked: the host builds, the host unit tests, the 46 tool tests and the gfx run list pass; Breath of the Wild into Zora's Domain and Mario Kart 8 Deluxe for 85 s from boot with the new rewrite (the PR's author), and Monster Hunter Rise from a fresh save for 180 s headless with each rewrite on one host (e72a065's, the chain of its Mac libraries): 60 fps, frame p95 about 17.5 ms, the same red lines, no hang. Not checked: Paper Mario: The Thousand-Year Door, DARK SOULS: REMASTERED, FINAL FANTASY TACTICS and FINAL FANTASY RESONANCE (no files on the drives), the iPhone. docs/development.md (Spin loops, Breath of the Wild) and docs/agents/05-debugging.md say so 2026-10-25 00:48:24 +08:00
seedds 24c38e849a README: the Mario and Zelda games first in the games gallery (Mario, then Zelda, each alphabetical), the other games after them in alphabetical order as before 2026-10-25 00:32:04 +08:00
seedds 98bd33a4ef BALL x PIT (010086A022444000, update 1.293, Unity 6000.0.62f1 on SDK 20.5.19): game profile and the SDK 20.5.19 sdk build's node-area cut, no host change. Plays on the iPhone (IPA 1.1 GB, user). On the Mac from a fresh save through the title, a new game, the intro and the descent into the pit, the first dive and its tutorial (moving, aiming with the right stick and shooting with ZR, gems and XP, the enemy waves that overrun the player and end it), and the base-building tutorial (the sidebar, the build menu, a wheat field and a forest placed, the first harvest): 60 fps (frame time p50 16.66 ms, p95 about 16.8 ms; GPU 1-5 ms a frame), FMOD sound through one stereo AudioOut, about 1.6 GB resident and 0.7 GB of Metal allocations, flat over 10 minutes, no red line. sdk_patches.py: like Hollow Knight: Silksong, Unity 6 takes all its memory through AllocateAddressRegion / AllocateMemoryPages with no heap; this sdk build's AddressRegionManager constructor is the SDK 20.5.22 build's at the same offsets (sdk+0x145cfc; the node area limit +0x145d0c and its occupied length +0x145d78 cut from 0x3fe00000 to 0x3e00000): the alias region's high-water mark +2.00 GB of 4.375 GB, 1.26 GB mapped. The provisional profile was otherwise right as generated (Engage's 5 GB block, sys_resource 16 MB; profile_guess.py --check: ok). Shaders: 38,099 (18,913 vertex, 18,917 pixel, 116 compute, 76 hull and domain, one geometry), 11 of them GLSLC outputs of Unity's player in main; the loose serialized files (resources.assets, sharedassets1.assets, the built-in resources) have no type trees and borrow the Shader tree 10 of the 11 Addressables bundles carry (the same type hash, shader_split.py's deferred pass), so unityfs.py needed no layout; all build for the Mac and iOS. The game draws 1280x720 into 1920x1080 window textures with a window crop (nvnWindowSetCrop 0,0 1280x720), which the present already scales. Profile: pad_layout full (the right stick aims in a dive), a prewarm list recorded on the Mac over the route above (297 lines). Checked: the NSP script packs it from the NSPs alone (Mac and iOS); after the rebase onto ded1cff the 44 tool tests pass. README (games gallery, a Mac picture of the first dive), AGENTS.md (56 -> 57), docs/development.md and docs/agents 03 / 04 say so 2026-10-25 00:24:57 +08:00
seedds ded1cff4ca README: gallery cells aligned to the top (valign), so the pictures of a row line up when their captions take different numbers of lines; GitHub centred them vertically, and 10 of the 19 rows were uneven (measured with GitHub's rendered markdown and stylesheet in headless Chrome), none now 2026-10-24 23:25:01 +08:00
seedds ebc2a9a3a0 README: the games gallery as an HTML table whose three columns are each 33% wide. In a Markdown table GitHub sizes each column by its caption text and shows images at max-width 100%, so with width 256 the pictures still came out 230, 256 and 222 px wide; with equal columns all 56 are 251 px (measured with GitHub's rendered markdown and stylesheet in headless Chrome at GitHub's README width). At phone width a column cannot be narrower than its longest word, so they differ by a few pixels there 2026-10-24 23:22:44 +08:00
seedds 02294dda9e Stardew Valley (0100E65002BB8000, update 1.6.15.13, MonoGame on Sickhead's BRUTE, SDK 20.5.27): game profile, no host change. Plays on the iPhone (IPA 0.6 GB, user). On the Mac: the title, a new farm (character creation with the software keyboard, Skip Intro), the farmhouse, the farm and its tools, the bus stop, Pelican Town by day and night, Pierre's shop and a purchase, the game menu, the mail, a night's sleep and its save, Load from the title, and the Co-op menu (Online Communication goes back to the title with no network): 60 fps (frame time p50 16.67 ms, p95 16.8 ms; GPU about 0.6 ms a frame on an M6; CPU 8% of a core), sound, 0.93 GB resident in play and flat over the last 10 of 23 minutes, no red line. About one frame in 800 takes 24-47 ms of the game's own CPU time (no pipeline build, upload or present in it; the main thread otherwise sleeps 95% of each frame in SleepThread, whose host sleep is exact), probably BRUTE's Boehm collector, not checked further. The two changes this game needed came before it from PR #120 (compressed XNB shaders, the Mac text box releasing the buttons that opened it). Profile: the provisional profile's guesses held (no system resource, a 2.96 GB heap in the 4 GB layout, mem_used 218 MB for the rewritten modules); 65 shaders (64 MonoGame effects in main, one in the LZ4 XNB Content/Effects/ShadowRemove.xnb); pad_layout full (main imports no touch screen or mouse; the D-pad in menus, the right stick for the cursor); a prewarm list recorded on the Mac with prewarm off over the route above (6 lines: SpriteBatch's two shaders and four pipelines; with it nothing was built on demand). Checked: the NSP script packs it from the NSPs with the profile in dm/games; the 41 tool tests pass. README (gallery, a Mac picture of the farm), AGENTS.md (55 -> 56) and docs/development.md say so 2026-10-24 23:20:25 +08:00
seedds c33327c267 README: every gallery picture shown at one size (img width 256). GitHub sizes a table column by its widest content, so a long caption widened its column and the Markdown image stretched with it; all 55 pictures are 16:9 2026-10-24 23:18:37 +08:00
seedds 399fd2ca32 README: the games gallery in 3 columns, sorted by title (ignoring case, accents and a leading The), with shorter captions: the IPA's size, "60 fps with <update>" where that update gives 60 fps (one line above the table says it is 30 on the Switch), and the notes that matter to a player (played by touch, controller only, Japanese, offline, no Mii heads). Update versions not needed for 60 fps are left out, as Pack an IPA says any version works. 55 games in 19 rows instead of 28 2026-10-24 23:13:34 +08:00
seedds 66490fb5f8 README: point to NX2Apple (from issue #130), a community fork with a macOS app that runs the NSP pipeline and packs the RomFS into a sharded archive for a slimmer IPA, for people who prefer a graphical interface to the command line 2026-10-24 23:09:49 +08:00
seedds ce173001ce 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 2026-10-24 23:05:05 +08:00
seedds fa69e20a28 Compressed MonoGame XNB shaders and the Mac text box (general, from PR #120, found on Stardew Valley). shader_split.py: MonoGame's compressed XNB files, ContentCompressedLzx (flag 0x80: a CHMLib-style LZX decoder with a 2^16 window inside XNB block framing) and ContentCompressedLz4 (0x40: one raw LZ4 block), are decompressed before the readers (xnb_decompress); for LZX only the first block is decoded unless it names EffectReader. An MGFX shader whose length runs past the data is no effect (a red line in an XNB). Stardew Valley's 3,549 XNB files (3,535 LZ4, 8 LZX, 6 plain) all decode to their declared sizes, and its one content effect (Content/Effects/ShadowRemove.xnb, LZ4) is now split: 65 shaders, 64 before. Celeste's uncompressed XNBs give its profile's tables byte for byte. front_mac.m: while the text box is up the game window sees no key-up or mouse-up, so the button or click that opened it stayed pressed and was read the moment the box closed (Stardew Valley's character creation re-opened the keyboard after every OK); the keyboard buttons, the WASD stick and the touch are released when the box opens. The box also stays up, saying so, while the text is shorter than ShowKeyboardArg's minimum length (+0x3b0, hle_swkbd.c g_swkbd_min_chars), as the applet keeps OK disabled. Left out of the PR: writing the text's byte count into the String's +8. The SDK's ReadCloseResultAndString (Pikmin 4's sdk+0x39b7d0) reads String{pointer, capacity} and copies at most the capacity, and GetInteractiveOutStorageCallback restores +8 after changing it, so on hardware +8 stays the game's capacity; t_swkbd now checks it is unchanged on every path (17/17). Checked: Stardew Valley packs from its NSPs with a provisional profile and boots on the Mac to the title, and in character creation (DM_KBD) Name, Farm Name and Favorite Thing each take the text from one keyboard call and OK starts the intro, with +8 left as the capacity; the host unit tests and the 41 tool tests pass. Not checked: the Mac text box by hand (a scripted DM_KBD never shows it), and no game here asks for a minimum length. The NSP script's MonoGame survey note and docs/agents/04 say so 2026-10-24 22:17:57 +08:00
seedds fc6c0a2a11 NVN host-lent command segments sized by the request (general, from PR #121, found on Super Mario Odyssey). Odyssey 1.4.1 aborted entering the Wooded Kingdom with host-lent command segments at the 1 GB cap: it hands out many 512-byte grants from an empty pool before recording, each got a 256 KB segment, and a segment taken outside a recording has no guest range to be reclaimed by. The PR gated a 4 KB segment behind a profile key for Odyssey alone; nothing in it is Odyssey's, so hle_nvn.c hostseg_size now applies to every game: an empty-pool grant of 1 to 4 KB outside a recording gets a 4 KB segment (a recording that outgrows it JUMPs on as before), every other loan stays 256 KB, and a record larger than 256 KB with no callback gets a segment that holds it plus a JUMP's room (before, it was written past the segment's end). A free segment is taken again only for a request of its own size, the cap counts every segment's bytes (g_hostseg_bytes), and the overflow lookup matches a segment by its end too, since a small segment can end where another starts. New t_nvn case 5b'' (the request sizes, a small segment outgrown into a JUMP that replays whole through its first handle, an oversized record with OP_RESERVE after it, size-matched reuse, the byte count): 124 checks. Checked: the gfx run list, the host unit tests; on the Mac against main's host from a fresh save for 300 s each, Super Mario Odyssey (46,000 loans, a steady 18 segments of 256 KB on both) and Super Mario Bros. Wonder (no loans on either) run alike with the same single red line, and Pokémon Sword runs 80 s of frames from a save with no new red line. Not checked: the Wooded Kingdom itself (no save there) and the iPhone. docs/agents/03 says so 2026-10-24 22:17:46 +08:00
seedds 46299bb3c3 Graphics tests: the whole run list passes on main again (from PR #118; tests only, no host change). Each failure was a test that no longer matched what the host does on purpose: t_reorder_chain, t_defer_chain, t_mip_extent, t_zskip and t_xv made textures with no pool at one offset, which the sharing table (g_alias) made one Metal texture, so two attachments of one pass overlapped and the validation layer aborted; each test texture now has its own storage offset. t_fuse_check set only S.prog, and the redraw takes the program again from the per-stage bindings (prog_resolve). t_mip_extent seeds and reads back through q_cb(), since new-texture zeroing is queued as an upload batch and wiped the seed, and a scissored colour clear is drawn as a triangle (clear_color_tri) that fills exactly its rectangle, which the test now checks instead of expecting nothing. t_rt_range sets DM_NVN_TBDR=1 (the depth-only pass mode and merged passes are off without it). t_refl and t_rt_range_ro print a SKIP line for each missing reflection sample instead of failing or aborting (the samples are not among Super Mario Bros. Wonder 1.2.1's shaders either; t_rt_range_ro still runs its 18 other cases). Checked: make -C dm/gfx/tests GAME=01007EF00011E000 run exits 0 on an M-series Mac 2026-10-24 22:17:28 +08:00
seedds 7a029f66d9 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 2026-10-24 20:16:15 +08:00
seedds 0045c1dfb6 Pokémon Sword (0100ABF008968000, update 1.3.2, Game Freak's engine on SDK 7.3.2): game profile plus general changes to the shader split, the shader build, the NVN layer and HID / offline services. Plays on the iPhone (IPA 12.4 GB, user). On the Mac from a fresh save through the first boot's choices, the opening, Postwick, Hop's house, the walk to Wedgehurst and Leon's arrival, the starter choice (Scorbunny) and the first battle against Hop (won), and from a save in the bedroom through the title and out of the front door: 30 fps as on the Switch (frame time p50 33.3 / p95 about 35 ms, GPU 4-9 ms a frame on an M6, CPU about half a core), sound, 1.8 GB resident after the title and 2.4 GB after 17 minutes of new areas, 1.9 GB of Metal allocations, no red line on that route. The sdk and movie module (subsdk0) builds are Super Mario Odyssey 1.4.1's; subsdk1 is GLSLC, which main imports but never calls; no virtual address memory (a 3.13 GB heap in the 4 GB layout, mem_used 76 MB). shader_split.py: everything is in 7,699 GFPAK archives (GFLXPACK: file count at +0x10, 0x18-byte file entries of compression, length, stored length and offset; every entry LZ4, zlib and stored read too, Oodle a red line), whose BNSH entries the split now reads (gfpak_members): 3,126 shaders before, 38,361 after with the loose BNSH files and two in main. The 38 shaders still in no map at the title were Silicon Studio's YEBIS post effects: a .tfxo file (3,155 shaders, control sections each followed by its code area of control length + 0x704) in a 39.5 MB zlib stream in main (+0x1c98ed9), which tfxo_streams finds by its zlib header and the inflated file's magic (the header check inflates 4,096 bytes; 64 did not get past the first block header) and tfxo_shaders reads, single-stage programs: 41,516 shaders in all. Of the other games' mains the stream reader finds nothing, and main's loose control section reader (raw_shaders) finds nothing in this one, so every other profile's tables stay as they were. dmsh_build.py / nvn_mtl.m: the G-buffer pixel shaders write RGBA16Uint at colour 1 between two float targets, and the all-integer entry point (fs_uint, main0_u) made Metal refuse the pipeline for the float ones; a pixel shader whose outputs are partly integer bits now also has main0_m, which returns only those as integers (uint_mask_m in the reflection, 336 shaders here), and pso_desc takes it when the bound targets' integer formats are exactly those outputs (pso_mixed_uint). Chain version dec5ee141a9f. nvn_mtl.m tex_by_id: a cube view whose layer count is not a multiple of 6 (a view of one layer, as the player leaves the house) aborted in Metal; it now counts its layers in cubes when they fit, and is read as a 2D array with a red line otherwise. Packaged textures: the game writes beside its textures in the 16 KB host pages they share with other data, so each such write re-uploaded the texture, 73k re-uploads over a few minutes and frames from 33 to about 75 ms; the bytes of a texture's storage that lie in its shared first and last pages are kept after each upload (pkg_edges_take), and a dirty texture whose interior pages were not written and whose edge bytes are unchanged is re-protected without a re-upload (pkg_edges_clean; the packaged-textures log line counts them): 0 re-uploads on the route since. New t_nvn case 6c1 (writes beside a texture, then a real write), 110 checks. hle_nvn.c: nvnDeviceGetWindowOriginMode and nvnDeviceGetDepthMode, which the game reads without setting either. hle_hid.c: the Poké Ball Plus (Palma): boost mode a no-op, connection enable / disable succeed, GetPalmaConnectionHandle and every handle call fail with an invalid handle (the first boot crashed at SetPalmaBoostMode). hle_offline.c: pdm::QueryApplicationPlayStatistics answers each asked title as never played (the game asks about the Let's Go games; it crashed there next), err::ShowNetworkServiceError is logged. Profile: pad_layout full (the camera is on the right stick), shader groups bin / systemresource, a prewarm list recorded on the Mac over the route above (3,273 lines). Checked: the NSP script packs it from the NSPs alone (Mac and iOS); after the rebase onto 6d42bc9 the host unit tests, the 41 tool tests, t_nvn, t_dirty_slots, t_dirty_race, t_integer_blend, t_upgrade_alias, t_clear_ds and t_stencil_view (under validation) pass, and the route out of the house runs as before. README (games table, a Mac picture of Postwick), AGENTS.md (53 -> 54), docs/development.md and docs/agents 01 / 03 / 04 say so 2026-10-24 18:55:11 +08:00
seedds 6d42bc9815 Advance Wars 1+2: Re-Boot Camp (0100300012F2A000, base game 1.0.0, Unity 2020.2.1f3 IL2CPP on SDK 10.4.1): game profile, the SDK 10.4.1 sdk build's address-region cuts and a shader split reader for control sections loose in main. Plays on the iPhone (IPA 7.1 GB, user). On the Mac from the boot through the Bink opening movie, the title, Nell's introduction, a new casual campaign and the first training battle (Field Training, Troop Orders): 60 fps in the menus, 30 fps in battle by the game's own limit (present interval 1, the frame's work 33 ms with CPU and GPU mostly idle), FMOD sound through AudioOut, about 2.5 GB resident and 1.6-1.9 GB of Metal allocations, level over a 15-minute run with no red line. sdk_patches.py: the sdk build is The Great Ace Attorney Chronicles' (no virtual address memory there, so it never needed a cut); this game's NPDM declares 16 MB of system resource and nninitStartup (main+0x365310) hands pool - used to StandardAllocator, whose Initialize (sdk+0x2edf90) aborted at boot. The AddressRegionManager constructor (sdk+0x340e90) loads the 1 GB node area with orr w8 / w9, wzr, #0x3fe00000 (the SDK 7 / 8 form, +0x340ea0 / +0x340f3c), cut to 62 MB, and AllocateAddressRegion's 1 GB alignment of regions of 1 GB and more (+0x34109c) to 32 MB, as on DRAGON QUEST XI S's SDK 7.3.2 build. Profile: the NSP script's heap check set a 1 GB pool, as for the other Unity games with virtual address memory, but the game's malloc then ran dry at the first loading screen and Unity's GPU pool allocation (main+0x96f430) halved its aligned_alloc request down to 0 and spun there (frames stopped 17 s in; found with sample on the Mac host); with 2 GB the alias region is mapped up to 2.26 GB (high-water mark +3.27 GB of 4.375) in Engage's 5 GB block. shader_split.py raw_shaders: Bink 2's Unity plugin builds its programs from 32 control sections and code areas in main's read-only segment that are in no GLSLC output and no BNSH file (2 vertex, 22 pixel, 8 compute), so the movies drew white (two shaders not in the map and a program without a vertex shader as the opening movie started). 26 are 0x20-byte records in .data ({code pointer, control pointer, u32 code length, u32 control length}, found through the relative relocations, RELA or RELR: rel_targets); the code takes the other 6 by address, their code area right after the control section's own length or right before it, and two share one; both neighbours can pass the header and length checks (main+0x4a2c670, a compute shader, would have taken the first 0x100 bytes of the next vertex shader's code), so only a code area whose address the code builds with adrp + add nearest to the control section's is taken (adrp_targets). Compute code areas have no header (SDK 10), so the stage is glslc_stage's, and a code area may not hold a control section. Control sections another reader took from main are left alone (Celeste's MonoGame effect). Of the 25 mains on the game drive the reader adds shaders only to this one, so every other profile's tables stay as they were (Celeste and the other games on the second drive not checked: not mounted). 20,424 shaders, all build for the Mac and iOS. Checked: the NSP script packs it from the NSP alone (Mac and iOS), the tool tests pass. README (games table, a Mac picture of the first battle), AGENTS.md (52 -> 53), dm/games/README.md (the heap check's 1 GB is a starting point), docs/development.md and docs/agents 03 / 04 say so 2026-10-24 17:13:12 +08:00
seedds 7861e7c1a9 Stencil-only views of depth-stencil textures with the format-view usage (general, from PR #106, found on Luigi's Mansion 3): every depth-stencil texture in the NVN and Vulkan layers is made with MTLTextureUsagePixelFormatView. A stencil-mode view, a texture built in stencil mode and the depth-stencil copy read stencil through an X32_Stencil8 view of a Depth32Float_Stencil8 texture, which Metal allows only with that usage: Apple GPUs read the view correctly without it (an M4 Pro, 50 frames of a per-pixel stencil pattern at 256x256, no wrong value), but the API validation layer aborts the process at the first such view, so a validation run of Pokémon Legends: Z-A, DRAGON QUEST VII Reimagined or Luigi's Mansion 3 could not get past it. Upgrading the texture when the view appears would recreate a depth buffer in use, so the usage is set at creation (nvn_mtl.m tex_desc) and the stencil exception in tex_need_pfv is gone. The PR set it in tex_desc only; the Vulkan layer had the same gap, since Vulkan needs no mutable-format flag for a stencil-aspect view, so hle_vk.m vkCreateImage sets it on depth-stencil images too (the OpenGL layer makes no texture views). The cost I had expected when this PR was first left out (bc5ba1b: lossless compression) does not show: on an M4 Pro a 2560x1440 depth-stencil texture has the same allocated size with and without the usage, and eight load / store passes take the same GPU time (1.085 / 1.067 ms against 1.063 / 1.074 ms). New test t_stencil_view (16 checks under MTL_DEBUG_LAYER: both NVN depth-stencil formats filled with a per-pixel depth and stencil, the stencil-mode view and the stencil-built texture are views of the texture's own storage, the copy draw carries every depth and stencil value) and a stencil-aspect view case in t_vk_desc (both depth-stencil formats); with the change taken out both abort under validation, and without validation only the usage checks fail. Checked: every other gfx test gives the same result as without the change; on the Mac host against the same build without it, Breath of the Wild for 90 s from boot (60 fps, the same red line) and Super Mario Galaxy 2 for 60 s (60 fps, the same four red lines); Breath of the Wild on the iPhone with the change plays as before (user). docs/agents/04-graphics.md no longer says the stencil view needs no format-view usage 2026-10-24 16:19:08 +08:00
seeddsandv0lumehi 54b91a9f14 iOS: an embedded module with an empty patch area is mapped without it (from PR #94). 87 of Super Smash Bros. Ultimate's 98 NROs have nothing to rewrite (no x18, SVC or TLS register use), so patch_nso.py writes no patch area and gen_embed.py packs a 0-byte x1 piece for them; map_image still remapped it, and vm_remap refuses 0 bytes (KERN_INVALID_ARGUMENT), which stopped the app at launch at the first such NRO, as the PR's author found on an iPhone (FATAL nro1 vm_remap ... (0 bytes) failed: 4). ff64352's iPhone run used a build from before d979c34's loader, so the merged build had not met it. Checked: loader.c compiles for iOS and the Mac with Smash's profile, and the host unit tests pass (the NRO loader test among them); not run on an iPhone (no Smash files here)
Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
2026-10-24 15:36:07 +08:00
seedds 14b68357a3 Run-time shader captures on a Mac without Metal 4 (GitHub issue #112: Balatro could not be packed on GitHub's macos-26 runner, a virtual Mac). The GL and Vulkan layers draw on classic Metal queues of their own and take only the device and the present path from the NVN layer, but mtl_device ran the NVN layer's whole start-up, which aborted on the paravirtualized GPU at the MTLResidencySet (and, past it, at the MTL4CommandQueue), so the GL capture boot stopped at eglGetDisplay and captured nothing. nvn_mtl.m mtl_init: a device without the Metal 4 GPU family, or one where the residency set or the Metal 4 queue cannot be made, now starts only the classic part (queue, shared event) when the caller came through mtl_device_classic (hle_gl.m, hle_vk.m), says so in one red line, and drops every present (present_enqueue gives ticket 0, so nothing waits on the present path), which a headless capture does not need; an NVN game still stops there, with a message naming the device and the reason. DM_TEST_NO_METAL4=1 takes the same path on any Mac. Checked on this Mac (M6): the NSP script for Balatro (--platform mac) with DM_TEST_NO_METAL4=1 captured the 35 shaders gl_capture.tsv lists in 1 s, translated them and built the Mac host; booted headless for 20 s without the switch, the host starts Metal 4 as before (35 shader table entries, no red line, graceful exit), and with the switch and the shader library the game runs the 20 s with presents dropped and exits gracefully. Not checked: the Vulkan capture (Super Mario Galaxy, Galaxy 2; same call, no NSPs here) and a real virtual Mac. docs/development.md (build step 4) says so 2026-10-24 15:33:22 +08:00
seeddsandJoão Karalkovas bc5ba1b1c2 NVN and AudioOut fixes from PRs #105, #107, #108, #109 and #110 (general, found on Luigi's Mansion 3): no blending on integer targets, primitive restart in fan and quad expansion, partial depth-stencil clears, every name of an upgraded texture, the SDK's AudioOut fields. nvn_mtl.m pso_desc (#105): an integer colour target took the game's blend enable, and Metal refuses such a pipeline with an assertion even without the API validation layer (checked on this Mac); Maxwell ignores blending on integer targets, so it is off for them. draw (#110): indexed triangle fans and quads expanded on the CPU read the restart index as a vertex and joined the polygons across it, and the extra triangles cleared parts of Luigi's Mansion 3's cached shadow depth; nvn_primitive.h ends a polygon at the restart index (compared before the base vertex), at most as many triangles as before. OP_CLEAR_DS (#108): a depth-stencil clear whose scissor does not cover the target, or whose stencil mask is partial, cleared the whole target before the first draw, and a partial stencil mask did so in a pass too; both are drawn as a triangle now (clear_ds_tri), full clears stay load actions. Logs on the T7 show who takes the new path: Tears of the Kingdom clears 8x8 tiles of a 64x64 depth target (each clear wiped the whole target), AI: THE SOMNIUM FILES clears 1280x720 of a 1920x1080 one. tex_upgrade (#107): a texture recreated to add a usage replaced only the asking serial and one sharing-table entry, so other serials on the same storage read the old copy; every entry in g_by_serial, g_ghost and g_alias now moves to the new texture in place, g_alias_set follows, and the old one is retired after the submissions that used it. hle_audio.c (#109, reworked): the host left the guest AudioOut zero, and middleware that checks its first word before Stop / Close skipped the close, freed its PCM and the host's consumer read freed memory (SIGBUS on resume in Luigi's Mansion 3). The PR wrote 1 and cleared it on close; the SDK's OpenAudioOut (SDK 8, 12, 18 and 20 checked) writes sample rate, channel count, sample format and state at +0 / +4 / +8 / +0xc, its getters load from there, and CloseAudioOut only sets the state to stopped, so the host now writes the same (au_info). New tests t_integer_blend (63 checks), t_primitive_restart (37), t_clear_ds (43), t_upgrade_alias (10) in the gfx run list, and an AudioOut fields and lifetime case in t_audio (12 guarded open / close / unmap / resume cycles, silent sink and real output). Checked: the four new tests pass under Metal validation; every other gfx test gives the same result as on main (t_fuse_check, t_reorder_chain, t_defer_chain, t_mip_extent, t_rt_range_ro and t_rt_range fail the same cases on both; t_refl finds no reflection samples in this checkout's shader cache); the host unit tests pass; on the Mac host for 90 s from boot, against main built the same way: Breath of the Wild over the title, 60 fps and the same red lines; AI: THE SOMNIUM FILES (shaders built by the NSP script on this chain), whose 1280x720 depth clears at the start of a pass now take the triangle path too (from pass 119), 60 fps, no red line, 5 long frames against 12. Not taken from this batch: #106 (the stencil-only view of a depth-stencil texture works without format-view usage; only the validation layer asks for it, and the PR would put that usage on every depth-stencil buffer), #111 (an opt-in draft with no memory saving shown), #104 (Mac app bundle)
Co-authored-by: João Karalkovas <117093313+Jpkovas@users.noreply.github.com>
2026-10-24 14:58:04 +08:00
seedds 6034fcad89 Bayonetta 3 (01004A4010FEA000, update 1.2.0, PlatinumGames' engine on SDK 13.3.2): game profile plus general changes to the Oodle tool mode, the shader split and the NVN layer. Plays on the iPhone (IPA 14.2 GB). On the Mac from the boot through the title, a new game (Standard, Naive Angel Mode off, brightness, the auto-save notice), the VP9 opening movie and the prologue's first real-time scene and play: 60 fps in play (frame time p50 16.67 / p95 17.5 ms, GPU about 5 ms a frame on an M6), 30 where the game asks for it (title, loading screens), sound, about 2 GB resident and 3.1 GB of Metal allocations, flat after the first scene; the title is up about 15 s after launch with a warm shader cache. hle_oodle.c: everything is in 72 PKZ archives (14 GB) of DAT archives whose entries are Oodle Kraken or stored, and this main reads them only through Oodle's streaming decoder (main+0xcbc698 / +0xcbc7f8), so OodleLZ_Decompress was never linked in and its name string is not there; when a main has no such name the tool mode now finds OodleLZDecoder_MemorySizeNeeded / Create / Destroy / DecodeSome (the two functions that pass oodlelzcompressors.cpp to Oodle's error handler, the first also taking the legacy-compressor message, and the two functions between them, checked) and decodes each request with them; games with OodleLZ_Decompress take the same path as before. shader_split.py: PKZ archives (pkz_entries, pkz_jobs: every entry, decoded by the game's own decoder in the Mac host), DAT archives (pgdat_members; a file that only starts with DAT\0 but whose tables do not fit is scanned as before) and PlatinumGames' shader objects (pg_shader: VSO / PSO / CSO / HSO / DSO, a control section and a code area each, single-stage programs): 8,024 objects, all in root_000.pkz, 6,424 distinct keys; all build but two domain shaders (subgroup operations), which are never drawn. profile_guess.py turns on oodle_packs for Kraken PKZ archives, so the NSP script hands the split the Oodle host. hle_nvn.c: the engine checks each shader object's GPU code version against the driver's range (main+0xd0da20) and refused its first built-in shader, hwfillcolor.vso at 1.15, against the 1.14 the host reported (Super Mario Odyssey's SDK 7 table), so nnMain's start-up failed and the shutdown read an uninitialized pool (main+0xb8462c); a driver runs the code of its own SDK's GLSLC, so the newest version reported now follows the SDK: 1.15 from SDK 8, 1.16 from SDK 13, and at least a GLSLC the game carries, as before. Also nvnWindowGetCrop and nvnSamplerBuilderGetLodClamp, red stubs before. Profile: pad_layout full, mem_used 116 MB, shader groups core / eff, a prewarm list recorded on the Mac over the route above (678 lines). Checked: the NSP script packs it from the NSPs alone (Mac and iOS), the host unit tests and the six tool test files pass, and profile_guess.py --check gives the same results as before. README (games table, a Mac picture of the prologue), AGENTS.md (51 → 52), docs/development.md and docs/agents 01 / 03 / 04 say so 2026-10-24 13:21:44 +08:00
seedds ff64352885 Super Smash Bros. Ultimate (01006A800016E000, update 13.0.5, SDK 8.2.1): game profile, NRO modules on iOS, data.arc shaders. Plays on the iPhone (IPA 17.5 GB, all 99 DLC licences)
On the Mac from update 13.0.5 (v2031616): the opening movie, the title, the first launch's DLC unlocks and Challenges board, the main menu, rulesets, stage and fighter select, 2-player battles (Frigate Orpheon, Big Blue, Great Bay, Find Mii, Prism Tower, Kongo Jungle 64) at 60 fps with about 2.7 ms of GPU per frame and 2 GB resident (1.2 GB of Metal allocations), quitting to the results and a second battle whose fighters' NROs the game loaded again; no red line in battle.

- Profile: modules main subsdk0 sdk (Catherine: Full Body's sdk and movie builds), the 5 GB guest layout, shader groups for data.arc's shader databases, effects and UI layouts, 35,012 shaders.
- patches.py: the engine's allocator (main+0x37c9a20, a static constructor) reserves 62 GB of address space with AllocateAddressRegion and aborted at main+0x37c9bd0. Booted once with a 64 GB alias region it commits from the region's start up (1.73 GB at the title, up to 2.5 GB in a battle) and keeps its page tables in the top 14 MB; all 11 instructions derived from the size are cut to a 4 GB region.
- sdk_patches.py: this SDK 8.2.1 sdk build (5fd4f531, Catherine's too) has the 1 GB AddressRegionManager node area and the 1 GB alignment of large regions (DRAGON QUEST XI S's SDK 7.3.2 layout at sdk+0x336b40 / +0x336d5c); both cut, so the region starts 64 MB into the 4.375 GB alias region.
- NVN: nvnSetExternalDebuggerEnabledNVX, fetched with nvnDeviceGetProcAddress(NULL, …) before the device and called unchecked (pc=0 from main+0x37fe92c), is a no-op in names.txt / hle_nvn.c, as docs/agents/03 already described.
- iOS NRO embedding (the part d979c34 left out): gen_embed.py packs the NROs nro.tsv lists into three sections (__TEXT,__dm_nro_x0, __DM_NRO_W,__img, __TEXT,__dm_nro_x1) with a table of their pieces (__DATA,__dm_nro_tab), since a Mach-O file has at most 255 sections and three per NRO would take 294; loader.c map_image maps an NRO from there (nro_pieces); build_ipa.sh puts nro.tsv beside the rewriter tables. The 98 NROs add 157 MB of code and 51 MB of data to the executable.
- shader_split.py reads data.arc through smash_arc.py (smash_arc_jobs / smash_arc_shaders: each current file scanned, labelled /data.arc/<format>#<hash40>); smash_arc.files(regions=True) gives every region's data of a regional file, without which 128 effect shaders were missing (34,884 of 35,012).
- Docs: README (games table, a Mac picture of Great Bay), AGENTS.md (50 → 51), docs/development.md (game notes, the NRO paragraph, nro_collect.py), docs/agents/01 (data.arc) and 03 (the 4 GB cut and its measurements, iOS NRO embedding).

Checked: the NSP script packs it from the NSPs alone, for the Mac and for iOS; tool tests (41), host unit tests and the NRO loader test pass. Played on an iPhone with this port's build from before it moved onto d979c34's NRO loader; the merged build was checked on the Mac (battles, an NRO loaded again) and builds for iOS.
2026-10-24 13:08:34 +08:00
seedds 066f2afc76 Pokémon Legends: Z-A's GPU page fault and crashes (GitHub issue #101): image atomics that are not followed go to a scratch buffer as large as the image, and the transform feedback draw ends the copy encoder before it opens its own
On the Mac, the fresh-save route of the docs crashed at about frame 3390 and from about frame 5300 page-faulted or crashed on corrupted pointers, with the encoding thread on and off; issue #101 reports an iPhone 17 freezing on kIOGPUCommandBufferCallbackErrorPageFault.

- tf_draw opened its render encoder while the copy encoder (g_blit) was open: ubd_land's inline updates leave it open, as does a copy or a dispatch just before. Metal validation: "Command buffer already has an open command encoder"; the driver then crashed in -[AGXG18GFamilyComputeContext_mtlnext performEndEncoding] (or its dealloc on the encoding thread). Every other helper pass ends g_blit before it opens its own encoder; tf_draw now does too. A host built from before PR #62 crashed the same way.
- A block-linear image's atomics went to the 64 KB dummy buffer, but SPIRV-Cross indexes them by the image bound in the same slot (spvImage2DAtomicCoord: x + y × its width). Z-A's atomic max into a 480x270 r32ui 2D array (texture ID 302, fragment shader 2bd5037003980716) ran about 1.4 MB past the dummy: into zero-copy pools over the game's heap (pointers in the game's objects and the host's program objects had their upper 4 bytes replaced by packed values, rewritten every frame) and into unmapped addresses (the page fault, after which macOS ignores the process's command buffers). MTL_SHADER_VALIDATION=1, which stops out-of-bounds accesses, let the scene play. Such atomics now go to a scratch buffer at least as large as the image (atom_scratch: width × height × layers × texel size, at least the NVN storage size; 3 MB here), grown when a larger image needs it; the results are discarded as before. An unbound image keeps the 64 KB dummy, since its slot holds the 1x1 dummy image. The bring-up host (d34e044) with today's shaders page-faulted the same way.

Three headless Mac runs of the whole route (encoding thread on, off, on) reached frame 12,300 with no GPU error and no crash; before, every run crashed or page-faulted between frames 5230 and 5480. t_nvn 102/102, t_ub 47/47, t_geometry 95/95, t_vbuf 43/43. docs/agents/04-graphics.md and Z-A's notes in docs/development.md say so.
2026-10-24 12:43:39 +08:00
d979c346b9 Integrate demand-first PSOs, far-base tracing and NRO loading
Integrate PRs #75, #90 and #91 with focused correctness fixes:
- Prioritize demanded render pipelines and bound speculative compilation.
- Follow copied jump-table bases and reject W/X register clobbers.
- Collect and rewrite registered NROs, load them on the Mac, and preserve
  runtime activation order through unload/reload and symbol rebinding.
- Preserve refused-only manifests and invalidate collection caches when
  archive readers or symbol parsing change.

Add pipeline scheduling, register tracing, NRO packaging and loader
lifecycle regression coverage. Document that executable NRO embedding
on iOS remains outside this integration.

Validation: 41 Python tests; 48 pipeline scheduling, 102 NVN and 95
geometry checks; three pipeline-key serialization cases; full host test
suite; Mac host build; iOS loader syntax checks; git diff --check.
Gameplay and controlled performance A/B measurements were not run.

Co-authored-by: blindlight86 <25404088+blindlight86@users.noreply.github.com>
Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
2026-10-24 11:38:12 +08:00
seedds f596c75012 Taiko Risshiden V DX (0100346017304000, update 1.1.1, Koei Tecmo's KTGL on SDK 12.3.3): game profile plus two general host changes. Plays on the iPhone by touch (IPA 3.3 GB, Japanese; the game has no English). On the Mac from the notice, the logos and the opening movie through the title, a new game (1560, Kinoshita Tokichiro) with its opening story, the town, a council with a battle proposal, the territory map, a rest at home, a save and a load, and the field battle tutorial, docked with buttons and in handheld mode by touch: 60 fps (frame time p50 16.66 / p95 16.85 ms in the town), sound, about 0.7 GB resident and 1.6 GB of Metal allocations, flat over 15 minutes, no red line on that route. hle_oe.c: oe::SetCpuOverclockEnabled, the first boot's crash (nnMain at main+0x2e1e4c; the SDK's setter reads the apm session the host never opened, null read at sdk+0x362b8, as IsCpuOverclockEnabled did for Monster Hunter Rise); the host keeps the flag and the query reports it. hle_touch.c / hle_hid.c: gestures for hid::GetGestureStates, which answered no entries: the game takes its taps, drags and flicks only from there (main+0x42f1ac acts on types 5-9; its GetTouchScreenState read only tracks the finger), so no tap registered in handheld mode. The host now recognizes them from the same touch points, only in handheld mode as touches, by yuzu's rules (Touch, Press after 0.5 s, Pan, Pinch / Rotate, Cancel on a change in the finger count, then Tap with IsDoubleTap within 0.35 s, Swipe above 400 px/s or Complete, and Idle) plus an 8 px slop before a touch counts as moving, since a fingertip on a phone wobbles; DM_TOUCH_LOG=1 logs the entries, and a DM_CTL line drag <x0> <y0> <x1> <y1> [seconds] moves a finger for testing pans and swipes on the Mac. New unit test t_gesture (43 checks: tap, double tap, slop, slow pan, swipe, press, finger-count cancel, pinch, a press and lift between two reads). Profile: op_mode handheld and pad_layout touch (the Tap layout: the gamepad's corners would cover the cards, the menu icons and the on-screen B button; the layout button brings it back), shaders from eff/ (167 in the G2S container KTGLEffect.g1s, _S2G3000, and 45 GLSLC outputs in main, read by the existing splitter), a prewarm list recorded on the Mac over the route above (18 lines). patches.py: KTGL's frame end (main+0x443e8c) slept in 2 ms rounds until 1/60 s had passed since the frame started, which with about 2 ms of work ended at 18.2 ms: 55 fps in the menus and the town, every 11th frame on screen two vsyncs. The game presents at interval 1, so a cut on this build skips that wait (b.hs → b at main+0x443fd4) and the host's present pacing holds 60; wakeups 2,050 → 320 a second, CPU energy 1.7 → 0.7 mJ a frame. Checked: the NSP script packs it from the NSPs alone (Mac and iOS), the host unit tests and the four tool test files pass. README (games table, a Mac picture of the town), AGENTS.md (49 → 50), docs/development.md and docs/agents/03-bringup.md say so 2026-10-24 11:31:10 +08:00
seedds 3eb1d76f5d Add game rumble for iPhone and Apple game controllers
Preserve per-player motor state and SDK vibration handles, apply batches atomically, and route Core Haptics output through existing controller assignments. Add independent launcher controls and lifecycle tests.

Supersedes #86. Physical feel and controller compatibility still require device testing.
2026-10-24 11:02:21 +08:00
seedds ea3ed8690b dm/gfx/tests/.gitignore: the binaries of t_readback (PR #98) and t_linear_readback (PR #97) 2026-10-24 09:17:54 +08:00
Jpkovas 80bc18896f iOS: add optional haptic feedback for touch-control press edges 2026-10-24 09:12:59 +08:00
Kyle Pyne d4249e5e31 Fix parser-invalid shader split f-string 2026-10-24 09:12:59 +08:00
Kyle Pyne 0633e56de4 Fix nested f-string quoting in NSP build script 2026-10-24 09:12:59 +08:00
Jpkovas 8e8ea51cda AudioOut: explicitly downmix six-channel PCM for stereo output 2026-10-24 09:12:59 +08:00
Jpkovas 672681f83f NVN: preserve buffer-backed storage when upgrading texture usage 2026-10-24 09:12:22 +08:00
Jpkovas cd0e61c23b NVN: materialize pending clears before texture-to-buffer copies 2026-10-24 09:12:22 +08:00
v0lumehi 43b35a689e Present: commit the residency set after resolving the presented texture, for every game. Super Smash Bros. Ultimate 13.0.5 presents its window texture in its first frames before drawing into it; the texture was made on that first resolution in the present path and joined the residency set (res_add), which only a submission commits, so the display command buffer sampled it outside the committed set: kIOGPUCommandBufferCallbackErrorPageFault at the first presents, after which macOS ignored every command buffer of the process (194 command buffer and 174 upload errors in the user's run) and the window stayed pink. Headless runs never present, which is why they looked right. res_commit is free when nothing changed. Smash windowed now: the opening movie, the DLC first-boot screens, the title, the main menu and a first match render right, no GPU error. Breath of the Wild windowed with only this change on the sync-lag branch its libraries match: its title, 60 fps, no GPU error. docs/agents/04-graphics.md says so 2026-10-24 09:12:22 +08:00
arielherselfandClaude Opus 5.5 9cb7af3237 up_begin hands the encode thread what is recorded before it waits for a busy upload slot: the slot's last batch could still be an uncommitted record
With the encode thread on (the default), rec_commit_upload only appends a commit record to the encode thread's current block, which is handed over when it fills or the submission drains. up_begin waited for the next of the three slots to finish its last batch, which could be one of those records: never committed, never finished. Little Nightmares II's first render passes zero-filled enough new targets (tex_zero_fill) to come round to a slot within one block; its RHIThread waited in up_wait_zero inside nvnQueueSubmitCommands for good, the render thread on its lock, and after 10 s Unreal's garbage collector gave up on the render fences (the backtrace with host frames: up_wait_zero ← up_begin ← tex_zero_fill ← tex_mtl ← attach ← rp_begin_ex ← lc_land ← mtl_exec ← h_nvnQueueSubmitCommands). q_cb already drains before it waits for the same reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 09:12:22 +08:00
seedds 6de59702ec ROMANCE OF THE THREE KINGDOMS 8 REMAKE (0100C2A01A730000, update 1.1.3, Koei Tecmo's KTGL on SDK 17.5.3): game profile plus three general changes. Plays on the iPhone with a controller (IPA 16.8 GB); on the Mac from the logo movie, the intro and the title through a new game (the recommended setup, the opening story), the council, espionage, a month's missions, the 3D city view, a visit, a practice duel in 3D and a save and load, at 30 fps as on the Switch with sound, about 2 GB resident in the council and 2.5 GB in the duel, flat on a screen left idle. nx_extract: the update is a multi-program patch, the game's patch (…0000) beside a whole second program (…0001, 34 MB, its own Control NCA) that main starts with nn::oe::ExecuteProgram(1); nx_extract took the first Program NCA, the second program, and stopped with "NCA section 1 does not exist" layering it over the base game. It now takes the base NSP's lowest title ID (program index 0) and the update's Program and Control NCAs of that title ID (Balatro and Mario Kart 8 Deluxe extract as before; the host runs program 0 only). shader_split.py: a newer RDB than Age of Calamity's, 296 .fdata files (PDRK0000 and u64 0x10, then IDRK entries) beside data/*.file; flag 0x400000 marks chunks of a new form (u16 stored length, 8 bytes, the stream; a zero length ends; 16 KB inflated at most), LZ4 blocks with 0x100000 too, zlib otherwise; every other flag value is read as before. All 51,655 entries (49,620 LZ4, 2,026 zlib, 9 stored) inflate to the length in their header. The shaders are G2S (_S2G5000 / 6000 / 7000) and _DOK0000 containers again: 59,214 GLSLC outputs there and 149 in main (149 before the change), all built for the Mac and iOS. hle_audren.c: SDK 17.5.3's OpenAudioRenderer with an event calls the form without one through the PLT (sdk+0x1e489c), which the loader binds to the host's hook, so ar_open ran twice for one renderer: the inner call started the render thread before the event existed and the outer one logged a second renderer; the inner call (t_in_open) now goes straight to the SDK. Profile: pad_layout full (the dialogue boxes' auto and log buttons are the right stick and R3; the game reads a USB mouse, not the touch screen), mem_used 80 MB, a prewarm list recorded on the Mac over the route above (451 entries). Left as is: three shaders get textures of another type on some slots (texture type mismatch, once each), with nothing wrong on screen. Checked: the NSP script packs it from the NSPs alone (Mac and iOS shaders, 1 h 19 min and 1 h 29 min on a 12-core M6), the four tool test files pass (34 tests), profile_guess.py gives the profile's memory keys. README (games table, a Mac picture of the duel), AGENTS.md (48 → 49), docs/development.md and docs/agents say so 2026-10-24 08:29:17 +08:00
seedds ebe75c9d5d test_xcode_workflow.py: the provisional-warning test calls report_profile_status with its artifact name ('Xcode project'), the argument 98a91a6 (--platform mac) put in place of prepare_xcode; it failed with a TypeError since. The five tool and workflow test files pass (37 tests) 2026-10-24 01:52:16 +08:00
seedds c392d56af0 Balatro (0100CD801CE5E000, update 1.1.3, LÖVE 11.4 "love-nx" with LuaJIT and SDL on SDK 18.3.2): game profile and the GL layer LÖVE needs, plus four general changes. Plays on the iPhone by touch; on the Mac from the intro and the title into a run and its tutorial, at 60 fps with sound, about 0.65 GB resident and flat over three minutes. The game is Lua source in the RomFS and renders with NVIDIA's desktop OpenGL through EGL, as Digimon does. GL layer (dm/gfx/gl/hle_gl.m): LÖVE reads GL_CONTEXT_PROFILE_MASK and takes its core-profile paths only when it says core (with a compatibility profile it streams vertices from client memory, which Metal cannot draw from), so the layer answers core and implements what those paths use, about 150 functions in all against Digimon's 79: persistently mapped buffers (glBufferStorage, glMapBufferRange: the shared MTLBuffer's own memory), glBufferSubData in command order, fences (a fence is the serial of the command buffer being recorded; a wait on one still recording commits it), glBindAttribLocation (the game's attribute index of each Metal input, set at the link) and current attribute values (glVertexAttrib4f: LÖVE's VertexColor and ConstantColor are disabled arrays), glGetActiveUniform and the uniform read-backs, every glUniform* form, glTexStorage2D / 3D with their level counts, 3D and 2D array textures (LÖVE makes a default of each type on 96 units), GL_TEXTURE_SWIZZLE_* (its font glyphs are RG8 read as R, R, R, G), glGenerateMipmap as a blit (the game's images are mipmapped), instanced and base-vertex draws, separate read and draw framebuffers, glBlitFramebuffer (colour, same size), glReadPixels, glClearBufferfv, glFrontFace and the EGL calls SDL makes; stack arguments of the functions with more than eight are 64-bit, as AAPCS64 lays them out. DM_GL_STUBS=1 gives every GL name the layer lacks a stub of its own that reports its first call, instead of a null pointer. Shaders: LÖVE puts its 35 GLSL sources together at run time in Lua, so the NSP script captures them on the Mac as it does Super Mario Galaxy's SPIR-V (capture_shaders now serves both, profile gl_capture.tsv; profile_guess.py's gl_capture rule: an OpenGL game with no GLSL source in its RomFS or main), and gl_shaders.py keeps a bool uniform's declared type (a uint in SPIR-V; LÖVE's Shader:send takes only a boolean for a bool) and takes from main only strings that start with '#version <number>' (Balatro's main has glslang's messages). General: profile_guess.py leaves vkGetInstanceProcAddr, SDL's Vulkan loader, out of its Vulkan count (Balatro's provisional profile said Vulkan; Hades and both Galaxies import 99 or 100 Vulkan functions); OpenAudioRenderer without an event now starts the host's render thread too (hle_audren.c ar_open: only the form with an event did, and such a game was silent); loader_census.py follows registers in a straight line (adrp, add, loads through relocated GOT entries, mov, csel), for LÖVE's glad, whose pointers are globals its code keeps the addresses of in registers; and the iPhone gamepad has a third layout, Tap (profile pad_layout touch, which needs op_mode handheld; DM_GAME_PAD_LAYOUT replaces DM_GAME_PAD_FULL): no control but the layout button, every touch to the game, offered in the layout cycle only in handheld mode. Profile: op_mode handheld (the Switch build plays by touch there), pad_layout touch, gl_capture.tsv. A Lua error makes Balatro quit (its Switch error handler), and love-nx's video worker is then deleted twice (main+0x339c00): the bring-up notes say how the errors were found. Checked: the NSP script packs it from the NSPs alone (the capture in under a second, 35 shaders for the Mac and iOS, an 82 MB IPA), tool tests pass, profile_guess.py --check changes no other profile; Digimon was not rerun (its files are not on hand). README (games table, a Mac picture of the title), AGENTS.md (47 → 48), docs/development.md, docs/agents and the READMEs of dm/games and dm/gfx say so 2026-10-24 01:50:19 +08:00
seeddsandv0lumehi 4e91a3d053 Readable-tile variants compile again for pixel shaders with an integer-output entry point (main0_u), from PR #83
Since 626e6a6 a pixel shader whose outputs look like integer writes gets a second entry point, main0_u, appended
after main0. dmsh_fetch.variant, which makes the framebuffer-fetch variant (_bf), rewrote samples to fb0..fb7 in
the whole source, main0_u included, but declared them only on main0, and renamed main0_out's members, which
main0_u still reads: every variant of such a shader failed to compile (use of undeclared identifier 'fb0'), and
the shader lost the tile read (its render passes are not merged). The host takes main0_u from the shader's own
library (nvn_mtl.m entry_u), never from the variant, so the variant is now made from the source cut at
struct main0_out_u, which dmsh_build.py always appends with main0_u at the end. A main0 with no parameters
also got its fb parameters with a leading comma (main0(, float4 fb0 ...)); output-mapping-only variants only.

Checked on Breath of the Wild's library MSL (chain 534b10d1d578, 10,560 pixel shaders), old and new variant()
side by side: the 8,659 shaders without main0_u give byte-identical variants, with and without
output-mapping-only; all 1,639 changed variants of shaders with main0_u failed to compile before and compile now
(Mac), and 1,897 of 1,897 output-mapping-only ones when forced for every key (30 of them the leading comma; no
profile's bf_want.tsv lists a key today); 40 of them compiled for iOS too. A full Mac dmsh_build.py run on the
new chain 657c42552d82: 19,592 of 19,592 shaders and 6,254 variants with no compile failure (main's chain would
lose the 1,639), stamped, both tables made; the Mac host booted Breath of the Wild with it past frame 5,100, its
title presses into play, with no red line. dm/tools/tests pass. dmsh_fetch.py is part of the chain, so
dm/gfx/nvn/dmsh_chain.h is written again and every game's shaders rebuild at their next build.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
2026-10-24 01:40:58 +08:00
seeddsandv0lumehi 76545a58a7 Mac: each controller is a player of its own, for local co-op (GitHub issue #74), from PR #84
Every GameController fed the same input path (front_mac.m), so the last one moved won, and hle_hid.c reported
exactly one npad whatever the game supports. Now the first controller is player 1, with the keyboard, the next
player 2 and so on up to 8 (pad_slots.h; a removed controller frees its player and gets it back when a
controller of the same name reconnects). Player 1 stays on No1 docked and Handheld in handheld mode, player n is
on No n (No1 stays empty in handheld mode: games take Handheld and No1 both for player 1). The game's supported
npad ids decide how many play: a one-player game ({No1, Handheld}) gets every controller as player 1, a
controller past a game's last player is not used, as on the Switch. A connect or disconnect signals that npad's
style-set event through hle_signal_bound_system_event (hle_thread.c), which signals only an event still bound,
from the macOS main thread where the notifications arrive; player LEDs follow the Switch's patterns; the
controller-support applet reports the connected players, capped at the game's maximum; only player 1 shakes.
DM_PAD_MERGE=1 merges every controller into player 1 as before; DM_KBD_PLAYER=own makes the keyboard player 1 on
its own. One controller, or the keyboard alone, behaves as before. iOS is unchanged: DmGamepad.m still feeds
player 1 and never reports further players.

Rebased onto a678703 (front_mac.m's includes, the Input row of docs/development.md). Checked: host unit tests
pass with the new t_hid (44 checks: slots and reconnects, npads docked and handheld, one- and four-player games,
events, LEDs, the applet); the Mac host builds for Breath of the Wild and Mario Kart 8 Deluxe with no new warning;
hle_hid.c and hle_thread.c pass an iOS SDK syntax check; Hades in a window with the keyboard and DM_KEYS reads
the supported ids (0x10f), and its scripted presses take it past the title. Two controllers were not run here;
the PR's author played Mario Kart 8 Deluxe in 2-player split screen with an Xbox Wireless Controller and a
DUALSHOCK 4, each its own kart, and a controller disconnected and reconnected during play got its slot back.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
2026-10-24 01:40:58 +08:00
seeddsandv0lumehi 98a91a6749 NSP script: --platform mac (PLATFORM = 'mac') builds only what running a game on this Mac needs, from PR #82
A development option; the Mac is the development host, not a platform for players. The same steps up to the
shaders (extraction, DLC, rewrite, run-time captures and decodes, heap check, shader split), then only the
Mac's: dmsh_build.py without IOSOUT (an inherited one is dropped), a Vulkan or GL game's library without --ios,
the Mac host for the profile into <id>_build/tools/dmhost (build_mac_host, through DM_GAMES_DIR /
DM_GAMES_FIRST for a provisional or derived profile) and the button-icon font into <id>_build/font. No iOS
shaders, Xcode project or IPA; the extracted files are kept whatever KEEP_EXTRACTED says, and the last line is
the command that runs the game (its save in <id>_build/run/save, DM_NVN_SHMAP for an NVN game, DM_AOC_DIR
with DLC, the --env-file switches). It refuses --prepare-xcode and an Intel Mac.

Reuse: the Mac libraries have a key of their own (shader/mac-b-<chain>.key beside the cache folder,
shader/vk.key / gl.key), which an iOS build writes too, so a Mac-only build after an IPA reuses its shaders;
either key only while the Mac libraries carry a full build's stamp (inputs.tsv). An iOS build after a Mac-only
one translates again (dmsh_build.py's per-shader keys include the iOS compile convention) and takes the Mac
libraries from the Metal compile cache. build_mac_host now has a reuse key too (the compile line, the sources and
headers of the folders it compiles, the generated profile header, libvpx, the third-party headers, clang and the
SDK), which also saves the rebuild for the Oodle / LZ5 decodes and the run-time captures. build_vk_shaders and
build_gl_shaders share layer_shaders. The default (ios) path builds as before.

Checked: dm/tools/tests 34 of 34 (11 in the new test_nsp_mac.py); Hades in Mac mode ran the Mac stages only,
printed the run command and exited 0, and a second run reused every stage, the host included (2 s); the host it
built boots Hades from that command. Breath of the Wild (NVN, with DLC) in Mac mode restored its 19,592 shaders
from the cache, printed a command with DM_AOC_DIR and DM_NVN_SHMAP, and the host booted it from that command into
play. Not run end to end here: a GL, provisional or derived game in Mac mode, and an IPA after a Mac-only build
(unit tests with the tools stubbed).

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
2026-10-24 01:40:58 +08:00
seedds e98a11562a shader_split.py reads GLSLC outputs of several stages: Baba Is You (01002CD00A51C000, no profile) keeps its shaders as 120 GLSLC outputs in RomFS/Assets.dat, each a pixel and a vertex shader compiled together, then a reflection section, and glslc_shaders took only outputs of one control section, so the NSP script's split found 0 shaders and the pack stopped one step later with "no iOS shader libraries". An output with more than one control section is now read by its section headers (glslc_section returns every GPU code section, glslc_program checks each stage and needs distinct stages); its stages are labelled #GLSLC@<offset>#var, so var_pairs pairs them with each other and separable_pairs leaves them out of the single-stage cover. The NSP script stops at the split when it finds no shader, with the reason, instead of at the iOS shader step. Checked: Baba Is You splits into 131 shaders (115 pixel, 16 vertex) and 115 pairs, and its pack built BabaIsYou.ipa from the provisional profile with 131 of 131 shaders ready (not run on an iPhone); the split's tables for 13 Sentinels, AI: THE SOMNIUM FILES, Raging Loop, Metroid Dread and Age of Calamity are byte-identical to their profiles', and no main on the game drive holds an output of several stages. docs/agents/04-graphics.md says so 2026-10-24 00:47:48 +08:00