Commit Graph
326 Commits
Author SHA1 Message Date
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
seeddsandv0lumehi a6787033eb Mac, in fullscreen: a screen whose refresh rate is not a multiple of the game's frame rate switches to one that is (144 → 120 Hz), from PR #79
60 fps on a 144 Hz screen cannot be paced evenly: the display link counts a tick on the refresh nearest each 1/60 s
slot (link_range's virtual ticks), so frames stay up two and three refreshes in turn (13.9 / 20.8 ms) and Apple's HUD
shows the frame interval jumping between them. link_sync now tells the front end the link's rate (60, 30, or 120
with DM_PRESENT_LINK=2; front_screen_match), and in fullscreen front_mac.m switches a screen whose mode is not a
multiple of it to the mode of the same size with the highest rate that is, with kCGConfigureForAppOnly, so macOS
switches it back when the host quits; leaving fullscreen or that screen switches it back at once. A window keeps
the screen's rate and the virtual ticks: the switch changes the whole screen for every app and most external
screens go black for a second or more. DM_SCREEN_MATCH=0 keeps the rate in fullscreen too.

Changes to the PR: the switch only in fullscreen, with the way back on leaving it (the PR switched in a window
too); no switch without a window (the windowless host's main thread runs the guest); the rate to match is
atomic; the link takes a rate change only once its object exists, so one made while its thread starts is not lost,
and sets the range directly instead of logging a pacing mode -1; the screen this host switched takes its rate
from its mode, which maximumFramesPerSecond may not show yet. The link now also sets its range again when the
window moves to another screen, which kept the first screen's range before.

Checked on this Mac's built-in 120 Hz screen (a multiple of every link rate) with a harness that drives
front_mac.m asked for 50 fps: no switch in the window, 120 → 50 Hz in fullscreen, 120 Hz again on leaving it, and
120 Hz after quitting in fullscreen; at 60 fps and with DM_SCREEN_MATCH=0 nothing switched. Hades windowed and
fullscreen (DM_FULLSCREEN=1) paced at 60 as before, the unchanged host alike. nvn_mtl.m passes an iOS SDK
syntax check. The link's new range after a switch has not run here (it needs a 144 or 165 Hz screen); the PR's
author measured the windowed switch on a 144 Hz XB273U at 2560x1440: 120 Hz, the HUD's frame interval steady at
16.7 ms, 144 Hz again after quitting.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
2026-10-24 00:39:05 +08:00
seedds 86d9ba1239 One translation chain version for every game: DMSH_CHAIN in dm/gfx/nvn/dmsh_chain.h replaces the 47 dm/games/<id>/dmsh_chain.h, which every chain change rewrote and some were left behind (GitHub issue #78). The version hashed each profile's shader tables and nvn_* / shader_* keys too, once the only thing that kept the host off a library built from older tables; each shader's cache key (dmsh_build.py shader_key) has covered them since, so the version now covers only the chain's tools (dmsh_chain.py, dmsh_build.py, dmsh_fetch.py, dmsh_vin.py, dmsh_geometry.py, dmsh_facts.py, the translator's source, ios_metal.py, the lock) and the options, and needs no DM_GAME. What the per-game part still did is done by the library itself: a full dmsh_build.py run stamps b-<V>/inputs.tsv (Mac and iOS) with the game and the digest of the profile's inputs (dmsh_chain.py inputs / --inputs: the four shader tables, bf_want.tsv, which the variants use but the version never counted, and the keys), starts from no shader outputs (a partial one from none of the keys it builds), so a key that left the map, a shader that no longer builds or a variant no longer made keeps no old library, refuses a directory stamped for another game, and writes the profile's c1_full.tsv and cb_extent.tsv from its Mac outputs; a partial build keeps the stamp only while it names the current inputs. c1_guard.py / cb_extent.py head their table with the library's chain version and stamp (table_head), and the host takes a table only beside that stamp, logs the stamp and reports one of another game; build_ipa.sh reads the one header and stops when the iOS libraries' stamp is not this profile's current inputs or c1_full.tsv names another. NSP script: chain_header writes the one header when stale, before any host is built; provisional and derived profiles no longer write one; chain_tables makes a table again when it does not name the reused library's stamp (False without one). Makefiles take the header beside nvn_mtl.m; Xcode projects prepared earlier point at a profile header that is gone and are told to prepare again. Tests: test_nsp_chain.py (the committed header is current, stale and missing ones are written, tables against the stamp). Checked on The Great Ace Attorney Chronicles: a full build gave 460 of 460 shaders and both tables (0 and 409 entries, as before), the Mac host booted with the stamp and both tables past frame 900; one added facts row rebuilt one shader under the same chain version with a new digest and new tables, and the host refused the old tables beside the new stamp; a full build removed planted outputs, another game's build into that directory stopped, a partial build after an input change dropped the stamp; build_ipa.sh stopped on a stale stamp, a missing one and a stale or old-form c1_full.tsv; three NSP packs built (no table lines: dmsh_build made them), reused everything, and remade deleted tables from the reused build. Graphics unit tests give the same results as before (on this Mac t_refl needs ~/dm-work's libraries and several GPU tests fail on the previous commit too). AGENTS.md's chain procedure (header once after a tool change; a full shader build makes the tables), dm/games/README.md, dm/gfx/README.md and docs/development.md say so 2026-10-23 23:37:03 +08:00
seedds f4f0a85d6e 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 2026-10-23 23:36:21 +08:00
seedds 4c7adf4619 c1_full.tsv and cb_extent.tsv are no longer committed: each checkout makes them from its own shader build's Mac outputs (git ignores them in dm/games/*/), as the NSP script does after its iOS shader build. They follow from the MSL this Mac built, every chain change rewrote about 90 of them, and 56704a0 could only carry most over unchecked; 81 files, 18.7 MB. The NSP script makes a missing one as a stale one (chain_tables now says whether both name the chain: with no Mac outputs a reuse builds again and a build stops), and no step's reuse key counts them (profile_files), so the run after they were made does not split again. build_ipa.sh stops for an NVN game whose c1_full.tsv is missing or names another chain, with the c1_guard.py command, before generating anything (the host would give every c1 its own 64 KiB). Checked on The Great Ace Attorney Chronicles from a checkout without them: the first pack made both (0 and 409 entries, as committed), the second reused the split and the iOS shaders with no table lines, a third with them deleted made them from the reused build's Mac outputs; build_ipa.sh stops on a missing and a stale c1_full.tsv. AGENTS.md's chain procedure, dm/games/README.md, dm/gfx/README.md and docs/development.md say so 2026-10-23 22:24:10 +08:00
seedds a2e6f4a18c NSP script: a registered profile's chain files that a chain change left behind are made again (GitHub issue #78: after bb585cd, 45 of 47 profiles' dmsh_chain.h no longer held the version their source gives, and Breath of the Wild's pack stopped at dmsh_build). chain_header writes dmsh_chain.h again, before any host is built, when it does not hold the version dmsh_chain.py gives (it follows from the source alone; a current one is left untouched, so the host is not rebuilt). chain_tables makes c1_full.tsv (c1_guard.py) or cb_extent.tsv (cb_extent.py) again from the build's Mac outputs when its header names another chain, which the host would ignore (every c1 its own 64 KiB, whole uniform bindings copied), also when the libraries are reused; provisional and derived profiles make both as before. The two tables are the shader step's outputs, so its reuse key no longer counts them. Tests: dm/tools/tests/test_nsp_chain.py (stale, missing and current headers and tables), and chain_tables with the real tools on Breath of the Wild's Mac cache (47 s, the committed list's 2 entries) 2026-10-23 22:11:09 +08:00
seedds 56704a0be1 Super Mario Galaxy 2's half-white Mario on the iPhone (GitHub issue 72): min / max / clamp with a NaN operand give the other operand on every GPU, in the Vulkan, GL and NVN shader chains
- Cause: Galaxy 2's GX vertex lighting (Hagi's SPIR-V) normalizes the light directions the game leaves at (0, 0, 0) for its characters' key and fill lights and takes fast::max(0.0, dot(ldir, NaN)) as the spot term. fast:: leaves a NaN operand's result to the GPU: the Switch's and the M4 Pro's return 0, so the attenuation is 1; the A20 Pro's did not, and the half of Mario that the fill light faces came out white (the fill light sits at the camera's left in view space, so its terminator runs down the middle of a character facing the camera). Found on the Mac by dumping the light uniforms of the character draws (every one has both directions at zero) and a Metal test of the expression on the M4 Pro.
- vk_shaders.py, gl_shaders.py and dmsh_build.py (precise): spirv-cross's fast::min / max / clamp are written as precise:: (IEEE fmin / fmax, which give Maxwell's FMNMX and saturation results); the iOS Vulkan and GL libraries are compiled with -fmetal-math-mode=relaxed, as the NVN chain's are. Plain or fast:: min / max / clamp compile to the fast_ intrinsics in every Metal math mode, so relaxed alone does not do it. The NVN chain had the same calls, unguarded by its isnan selects (Paper Mario: The Thousand-Year Door: 124,000 fast::max, 98,000 fast::clamp, 41,000 fast::min).
- dmsh_geometry.py capture: clip distances as spirv-cross writes them (gl_ClipDistance [[clip_distance]] [8], and gl_ClipDistance_N [[user(clipN)]] for the pixel stage); since bb585cd the shader build stopped for every game with such vertex shaders (Luigi's Mansion 3, Monster Hunter Rise). Checked against all 1,423 distinct vertex output lines of the games' libraries on this Mac.
- Shader chain: every profile's dmsh_chain.h rewritten, Mario Kart 8 Deluxe's included (bb585cd had left 45 of the 46 before it on the version before it, which dmsh_build.py refuses, so the NSP script's iOS shader step stopped for those games). Rebuilt for the Mac and iOS from shader packages checked record by record against the profiles' maps: Luigi's Mansion 3 and Monster Hunter Rise on this chain; Paper Mario: The Thousand-Year Door, 13 Sentinels, ANIMAL WELL, FINAL FANTASY TACTICS, Raging Loop, Metroid Dread, Three Houses, DARK SOULS: REMASTERED, Cuphead and Fire Emblem Engage on the one before the capture fix (none of their vertex shaders writes clip distances). Every shader builds but Monster Hunter Rise's 2 (55 before). Their c1_full / cb_extent lists changed only by rows of geometry shaders built as compute kernels since bb585cd (Monster Hunter Rise's cb_extent and the other profiles' lists carried over, each header line naming what was checked).
- Verified: Galaxy 2 on an iPhone (A20 Pro), Mario lit as on the Switch; Galaxy 2 on the Mac along the storybook route, same red lines; Paper Mario on the Mac with fast:: and precise:: libraries from the same MSL: same pictures and red line, GPU time p50 6.73 / 7.00 ms and p95 9.77 / 9.09 ms (the Mac was busy with shader builds during both)
2026-10-23 22:08:26 +08:00
seedds 11be43d882 README / AGENTS: Mario Kart 8 Deluxe plays on the iPhone (games table with a Mac picture of Mario Kart Stadium's anti-gravity section, IPA ~11.9 GB, Mii heads not drawn without the firmware; AGENTS.md list 46 → 47 with its notes; the profile says so). On an A20 Pro: 60 fps docked, about 6 ms of GPU time a frame, 2.4 GB footprint with 3.7 GB of headroom, no audio underruns 2026-10-23 20:37:01 +08:00
seedds 6ec201c756 Mario Kart 8 Deluxe (0100152000022000, update 4.0.0, Nintendo's sead / agl / gsys on SDK 4.4.0, the oldest SDK so far): game profile and five general changes. Plays on the Mac and an iPhone from boot through the title's demo race, the Mii pick and the menus into Grand Prix races at 60 fps with sound, and a second boot loads the save. The Mii database (hle_mii.c: the six default Miis every console has, which the game lists at boot without reading the count, none of the console's own, random Miis for the demo race, the editor cancelled) and a MiiModel system data stand-in (0100000000000802 mounted as MiiResource:, written by the host with the layout main's Mii renderer reads: no textures and every shape one degenerate triangle, because the faceline is drawn without a check; the firmware is not the host's to ship, so Mii heads draw nothing). The add-on content cache sized as Horizon's RomFS working memory (its licence is decrypted in a 0x3130-byte heap that also holds the 4 KB the host used to answer). Device save data as a save of its own when main also imports MountSaveData (at boot the game deletes CommonSave:/userdata.dat, which in the shared directory was UserSave's; Animal Crossing, device save only, keeps its island where it was). SDK 4's DeviceSink (command 0xf, 0x130 bytes, byte input indices) and the audio renderer's update lock read off each SDK's own AudioRenderSystem::Update (LockMutex +0x1d0 / +0x1d8, SdkMutexType +0x1d8 / +0x1f0 / +0x1f8: it was SDK 16's for every SDK before 20; sound checked on Big Brain Academy and DRAGON QUEST XI S). The zero cube for a 2D array of fewer than six layers in a cube slot. Profile: render_cond on, a recorded prewarm list from boot into the first race; Breath of the Wild and Animal Crossing boot with their DLC caches resized, host unit tests pass 2026-10-23 20:36:56 +08:00
seedds eab15b5a7e Merge remote-tracking branch 'origin/main' into codex/geometry 2026-10-23 16:30:55 +08:00
seedds bb585cd5f2 Add compute-backed geometry shader replay
Capture vertex outputs, run geometry shaders as compute, and rasterize compacted primitives through a general NVN pipeline. Rebuild the pinned translator and AI shader chain, add geometry regression coverage, and document supported behavior and remaining limits.
2026-10-23 16:29:24 +08:00
seedds 7af6fedaad README / AGENTS: AI: THE SOMNIUM FILES - nirvanA Initiative plays on the iPhone (games table with a Mac picture of the first investigation, IPA ~11.6 GB; AGENTS.md list 45 → 46 with its notes; the profile says so) 2026-10-23 16:21:56 +08:00
seedds 0c03385dd9 AI: THE SOMNIUM FILES - nirvanA Initiative (0100713016CCC000, update 1.0.2, Unity 2020.3.30f1 IL2CPP with FMOD, SDK 13.3.5): game profile and three general changes. Plays on the Mac from boot through a new game into the first investigation (the Amabie-TV quiz, its conversation, camera and answer button tutorials) at 60 fps with no red line over 15 minutes; not yet run on the iPhone.
Profile: virtual address memory as Patrick's Parabox (nninitStartup main+0x493300 hands pool − used to StandardAllocator), Ace Attorney Investigations Collection's layout (1 GB pool, 5 GB block), docked, 3D pad layout (the investigation's view is on the right stick), interval 1, 17,069 shaders from the Addressables bundles and Data/Resources (6 geometry shaders, a wireframe effect and Unity's VR blit, do not build), chain 0379eb846acf, a 231-line prewarm list from boot to the first investigation. Measured over 15 minutes (probes on VammManager::AllocateAddressRegion / FreeAddressRegion, the DM_SVC map log): StandardAllocator commits 0.29 GB of its 960 MB, Unity's own regions hold 1.10 GB (1.03 GB mapped), 1.26 GB mapped in all, 1.96 GB resident.

- patch_nso.py: the AddressRegionManager node area cut (1 GB → 62 MB) for this sdk build (sdk+0x37c140 / +0x37c1dc, the constructor at +0x37c130), as for Parabox's SDK 13.3.7 build.
- shader_split.py unity_compute: Unity 2020.3 keys a ComputeShader kernel's variants by keyword set (variantMap) where 2022.3 lists them (uniqueVariants); the split stopped with KeyError 'uniqueVariants' on this game's bundles (147 kernels now). Monster Train 2's tables are unchanged.
- profile_guess.py heap_signals: a heap check with no heap set whose alias region is used up to within 0.25 GB of its end sets a 1 GB pool (Unity 2020 / 2022: StandardAllocator takes pool − used and Unity reserves its own regions beside it). This game's check reached +4.26 GB of 4.38 in 5 s with the hardware pool; with 1 GB it stays at +2.01 GB. dm/games/README.md documents it.
2026-10-23 16:04:58 +08:00
seedds 3f73710bd6 NSP script: cli() restores os.environ after each run. main points DM_GAMES_DIR (and, for --provisional or a derived profile, DM_GAMES_FIRST) at the run's own profile folder for its tools, and Spyder runs the next F5 in the same process: the game lookup (game.py by_title) then found that profile and took it for a registered one. ASTRAL CHAIN's second run printed its profile without (provisional), skipped provisional_prep and the chain header, took the split tables as checked, and stopped at the iOS shaders with FileNotFoundError on the profile's dmsh_chain.h; after a derived build the next run would have packed from the derived profile in place of the game's own. Two runs in one process now both take the provisional path and leave the environment as it was; the progress tests pass 2026-10-23 15:59:45 +08:00
seedds 7ca41c61f4 Rewriter: an offset jump table whose base was set more than 256 instructions back (ASTRAL CHAIN 1.0.1 main+0x6db4a0, SDK 6: cmp w18,#5; b.hi; ldrsw x18,[x24,x18,lsl #2]; add x18,x18,x24; br x18). The function's first block loads six table bases with adrp + add into registers its loop keeps (x24 441 instructions before this load) and a later block one more (x3, 549 before the load at +0x6db70c); jump_offsets followed the base's writer chain 256 instructions back, and the rewrite stopped with jump table 6db4a0. Where nothing writes the base in those 256, its last attempt (far_base) follows the chain to the function's start (table_base far), through no call when the base is caller-saved, and accepts the table only when every target is an instruction of the function. The function-wide search jump_adr_offsets uses for a hoisted base (the one adrp + add pair that fits) finds two for x3 there, the other the local table of a br x17 further on. Three of that main's five br x18 sites need it, their targets checked by hand; main and sdk rewrite. It runs only where every other form has failed: the Tears of the Kingdom, Age of Calamity, Cuphead, DARK SOULS: REMASTERED, Luigi's Mansion 3 and Metroid Dread mains come out byte-identical (patched image and site list) 2026-10-23 15:44:16 +08:00
seedds aea97ec65f Add ANIMAL WELL port verified on iPhone 2026-10-23 15:15:42 +08:00
seedds e20eb1ff0d README / AGENTS: AI: THE SOMNIUM FILES plays on the iPhone (games table with a Mac picture of the first investigation, IPA ~8.3 GB; AGENTS.md list 43 → 44 with its notes) 2026-10-23 15:04:32 +08:00
seedds fa7ad177cf AI: THE SOMNIUM FILES (010089B00D09C000, update 1.0.1, Unity 2017.4.17f1 IL2CPP with FMOD, SDK 6.4.2): game profile (no system resource, a 3.11 GB heap in the 4 GB layout, mem_used for the rewritten modules, docked, 3D pad layout for the investigation's view on the right stick, 13,057 shaders from its AssetBundles with Cuphead's Unity 2017.4 layout, a prewarm list from boot to the first investigation) and two general host fixes. Movies: a released output slot also goes on with an H.264 drain, and the end-of-stream packet's own output takes the first queued picture (hle_movie.c); Unity held all 16 video outputs when the Spike Chunsoft logo movie's end-of-stream packet came, the last pictures and the end-of-stream flag never came out, and the game waited before its title for good (Raging Loop's opening movie still ends and reaches its title). Per-stage programs: each restore of the replay state takes the combined program again from the per-stage bindings (nvn_mtl.m prog_resolve); the precompile prediction over a 280-draw submission reused the ring slot the saved state named, and the draws after it ran another pair's shaders (a pipeline Metal refused, VertexLit's shadow vertex shader with BG_AlphaTest's pixel shader, and a cube / 2D texture mismatch; 2 of 2 old-host runs from one save, 0 of 2 fixed). Plays on the Mac from boot through a new game into the first investigation at 60 fps, 2.27 GB resident, and on the iPhone 2026-10-23 15:04:08 +08:00
seedds 1822616144 Intel Macs (GitHub issue #54): the shader translator's launcher dm/gfx/tools/dmsh_xlate_bin/dmsh_xlate is a universal binary (arm64 and x86_64, the same .NET 10.0.13 host joined with lipo); it was arm64 only, and the NSP script stopped at dmsh_build with Bad CPU type in executable. The lock does not cover the launcher, so the chain version is unchanged. The x86_64 translator under Rosetta gives byte-identical output (Raging Loop, 452 shaders; Ryujinx's constant folding uses only exactly rounded arithmetic). The Mac host runs the game's own arm64 code, so on an Intel Mac a step that boots the game there (a run-time shader capture, a decode with the game's own Oodle or LZ5) stops with a plain error, and a provisional profile's heap check is skipped (build_ipa_from_nsp.py apple_silicon) 2026-10-23 14:42:50 +08:00
seedds 7deea8eedc Tears of the Kingdom: the SDK's address-region node area cut from ~1 GB to 62 MB on the sdk build it shares with Breath of the Wild 1.9.0 (patch_nso.py). The game's resource heaps are address regions in the 2.375 GB alias region; at the first descent from the Great Sky Island it was used up to +2.25 GB with 1.00 GB mapped, and the game took a memory shortage (MSF / R / Rf) and played the memory-shortage Blood Moon, which put Link back at the start (GitHub issue #70; 7 of 8 Mac runs from the issue's save). With the cut: +1.75 GB, no shortage in 3.7 minutes on the island. Probe tools: the write watch reports exclusive and atomic stores, and DM_PROBE_POLL snapshots every thread when a word changes 2026-10-23 14:13:52 +08:00
seedds 0923c713d0 README: drop the project's former name from the d2r-mobile credit 2026-10-23 13:58:24 +08:00
seedds 1482e123a8 Vulkan layer: a descriptor holds the Metal objects it was written with (texture, sampler state, buffer and offset, retained), not pointers to the layer's view / sampler / buffer structs. A set may still name a view the game has destroyed while the replay binds every element of an array the shader takes: Super Mario Galaxy 2 crashed in bind_res reading a freed image view's texture (element 1 of its 8-texture array) when the ship took off after the first galaxy (GitHub issue 68). New test t_vk_desc (make run) 2026-10-23 13:44:16 +08:00
seedds b3d9b24be7 NSP script: the iOS shaders build with the Mac's own Metal compiler and spirv-cross (dm/gfx/tools/own_compilers.py), one note line naming any that differ from dmsh_chain.lock; the translator must still match, and the lock and chain version stay as they are, so a player on another Xcode (GitHub issue #26: Xcode 27.2 beta, metal 32023.922) or a newer spirv-cross needs no --update-lock; a Metal toolchain that does not run is reported before the build (xcodebuild -downloadComponent MetalToolchain) 2026-10-23 13:33:10 +08:00
seeddsandnocmt a08bbdd3ba Texture views from GitHub PR #66: mip counts cut to the texture's levels, and the swizzle composed with the format's own channel mapping (Echoes of Wisdom's hearts); the red / blue swap only for uploaded textures, image slots without the mapping
From the PR (The Legend of Zelda: Echoes of Wisdom, 1.1.0, on the Mac):
- A view whose level count runs past the texture's levels gets the levels it has, as deko3d's descriptor writer clips the maximum level (the game views levels 1 + 7 of a seven-level texture, which Metal's validation refuses); a base level past them is an empty slot (red line 109).
- tex_by_id composes the descriptor's swizzle with the format's mapping (nvn_fmt_to_mtl: alpha 1 for RGBX-type formats, red and blue swapped for the RGB565 family), which was computed and never applied: the hearts' RGB565 mask drew as red rectangles.

On top of the PR:
- The red / blue swap is for bits in the guest's order, so it applies only to a texture uploaded from guest memory (an NvPkg in the serial table) or read from it in place (a texture on a pool buffer). What Metal renders or clears into a 16-bit texture is in Metal's order: with the PR, a target cleared to red sampled blue.
- Metal gives a swizzled view only the read usage (a base of usage 0x7, its swizzled view 0x1), and the image binder sent such a slot to the dummy image: with the PR every RGBX8 / RGB565 image lost its writes (red line 32). An image takes no channel mapping, as a Vulkan storage image view must have the identity swizzle, so img_fit gives the slot the same view without one (kept in the slot's image view, as for a class change).
- The on-chip read merge needs the sampled view's swizzle to be plain: a tile read takes no channel mapping, so an RGBX attachment merged into the next pass read its stored alpha.
- Graphics tests: 13 of the 27 had stopped building. Every test that includes nvn_mtl.m now links hle_nvn.c, the large host stack and weak host stubs (t_host_stubs.c) through one Makefile variable, the tests that stubbed prog_key for want of hle_nvn.c drop the stub, t_present_crop / t_mip_extent rename hle_nvn.c's static g_ft (nvn_mtl.m has one too), and the budget test gets DM_GAME.

t_view_swizzle (203 checks) reads the mappings back from the GPU through tex_by_id: the PR's cases, now with uploaded textures for the swap, plus RGB565 / BGR565 targets Metal cleared to red (red, no view) and an RGBX8 image (sampled alpha 1, writes through img_fit's view stored as written); without the upload rule it fails 9. t_view_levels 34 checks. Every graphics test builds; the ones still failing fail the same with main's nvn_mtl.m: t_refl 2 (reflection samples in a local cache), t_reorder_chain 30, t_mip_extent 2, t_rt_range 4 to 8 per mode, t_pfv 1, t_rt_range_ro (a missing JSON file).

Co-authored-by: nocmt <26828323+nocmt@users.noreply.github.com>
2026-10-23 11:10:36 +08:00
seedds f73c1e3ec6 iOS launcher: no notes under the settings 2026-10-23 10:54:26 +08:00
seedds d2ff028439 LICENSE: copyright holder seedds 2026-10-23 10:49:32 +08:00
seedds fe23c10307 Audio watchdog: the time is read under g_aos_lock, so an AudioUnit just restarted on the return from the background is not restarted again
The watchdog read the clock before it took g_aos_lock. The return from the background restarts the AudioUnit under that lock and stamps its last callback time (au_go), so a watchdog tick that had read the clock just before waited for the lock and then compared an older time with the newer stamp: the unsigned difference wrapped ("has not called back for 18446744073662 ms", DRAGON QUEST VII Reimagined on an iPhone after 10.7 s in the background) and a running AudioUnit was handed to the silent sink and restarted. The clock is now read after the lock is taken, as every au_go caller does. Mac: 30 s of Luigi's Mansion 3 with real output, no watchdog line
2026-10-23 10:33:35 +08:00
seeddsandv0lumehi 2f69480260 iOS launcher: a settings screen before the game starts, drawn from one table of player-facing settings (dm/games/settings.tsv; GitHub PR #62, issue #64)
The launcher from v0lumehi's PR #62 (Launcher.swift, SwiftUI: the game's icon, name and version, Play, and the settings), built as their issue #64 proposes: dm/games/settings.tsv declares each player-facing setting once (the environment variable it sets, its kind: choice, range or toggle, its default and the condition under which a game offers it). build_ipa.sh runs dm/games/settings.py, which keeps the rows that hold for the game and fills in its defaults: the host's own from game_profile.h (DM_GAME_FPS_TARGET, DM_GAME_OPMODE), the build's (DM_LANG, DM_SHOW_FPS), the game's languages from its control data, and the render scale only for a build with the rewriter's render-scale stub, which patch_nso.py now records in main's table (RENDER_SCALE: Breath of the Wild 1.9.0, Tears of the Kingdom 1.4.3). The result goes into the app as dm/launcher.json, and the launcher draws one row per setting from it, so a new setting is one table row. Diagnostic switches stay environment-only. Breath of the Wild offers frame rate, render scale, console mode, 15 languages, the frame-rate counter and Apple's performance HUD.

Differences from the PR's launcher:
- The host's loading still starts at app launch. Only the loader read the frame rate and the render scale that early; it now just maps the frame step state page, and dm_fps_state_apply (main.c, when the game starts) writes both, so a boot is no slower with the launcher (the PR started all loading when Play was tapped). The game view, the pad and the frame-rate counter are made after Play, since they read the launcher's switches.
- The language choice applies: main.m sets the packed language as a default before the launcher runs, which the PR then took for a setting from env.txt and kept. main.m now lists the variables an env file set (DM_ENV_FILE_KEYS); only those are kept, and the launcher shows them greyed out.
- No new profile key for render scale (the rewriter's table tells), and the scale stops at 100 % (the above-1080p part is not in main). No Mac launcher.

The choices are kept in UserDefaults; DM_LAUNCHER=0 in Documents/env.txt skips the screen, as does an app without launcher.json. README: the settings screen in the Play section; dm/games/README.md and docs/development.md describe the table.

Checked: Launcher.swift type-checks against the iOS SDK and the Breath of the Wild IPA builds with it (launcher.json and RENDER_SCALE inside); the Mac host applies DM_FPS_TARGET=30 from the first present and DM_RENDER_SCALE=0.75 when the game starts. Not yet run on an iPhone.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
2026-10-23 10:22:42 +08:00
seeddsandv0lumehi 8a4b1dc820 Faster NVN → Metal translation from GitHub PR #62: Metal encoding on its own thread (on by default on iOS), uniform shadows copied by a worker, uniform updates kept across render passes
Taken from v0lumehi's PR #62, the parts that hold for every game (none is gated by a title, build or profile; all but the Mac front end reach only the NVN games):
- nvn_rec.h: the replay's Metal calls (encoders, draws, state, argument table writes, commits, event signals) go to a worker thread as records, in the same order. The submit thread waits for it at the end of each submission and before anything that waits for the GPU or uses another queue. On by default on iOS, DM_NVN_ENC_THREAD=1 on the Mac.
- Uniform shadows of 512 bytes or more (ubp_get) are copied by a worker; a commit waits for the copies queued before it, a CPU read of a shadow for its own (DM_NVN_UB_COPY_THREAD=0 copies on the submit thread as before).
- Deferred inline uniform updates are kept across render pass ends (DM_NVN_UB_LAZY, at most 160) and land before the next copy, a draw that reads their memory other than through uniform bindings the shadows cover, or the end of the submission, instead of in a copy encoder after every pass.
- A two-level address → pool cache (by 4 KB page, then the last 8 pools found), and the pass dump check answered first when it is off.
- DM_NVN_GPUPROF=f or f1-f2: every encoder's GPU start and duration, from Metal 4 counter heap timestamps.
- Mac: the drawable follows the window in pixels (in fullscreen it had stayed at 1280x720), the process declares latency-critical activity, and DM_METAL_HUD=1 shows Apple's performance HUD (on the iPhone too, from Documents/env.txt).

On top of the PR:
- A dispatch lands the kept updates first when it reads or writes their memory through a storage buffer or its indirect arguments, or reads a part of a uniform binding the shadows cannot give (ubd_conflict_cs, with the per-stage check it shares with draws, ubd_conflict_sh). Dispatches open the copy encoder through blit_open, which never landed them, so they ran before the updates. Transform feedback draws land them first, as every pass end did before.
- A deferred update that a newer one covers whole is dropped from the landing list. Breath of the Wild rewrites the same 64 bytes before every draw group, so the list held up to 160 repeats that every binding lookup scanned: the port cost 16,800 instructions a draw against main's 13,200, and costs 13,500 with the drop.
- The encoding thread records encoder labels (DM_NVN_ENC_LABEL) and counter heap timestamps (DM_NVN_GPUPROF): without them either diagnostic stopped the host on iOS.
- DM_NVN_UBP_CHECK waits for the copy worker before it reads a shadow. It compared unfilled shadows (293,447 "different"); now 232,000 equal and 0 different with the encoding thread on, 234,000 and 0 with it off.

Left out: Breath of the Wild above 60 fps and above 1080p (rewriter cuts for its build and a profile key), the Mac GPU filler and screen mode switch, and the iOS launcher.

Breath of the Wild on the Mac (the Great Plateau save, 60 fps cap, beside main under the same load): submit thread 4.30 against 4.48 ms a frame, GPU p95 6.2 against 7.0-7.3 ms, 1.000 of real time, no red lines; with the encoding thread the submit thread takes 3.16 ms (9,700 instructions a draw). t_nvn 100/100 with the encoding thread off and on; the hosts for TOTK and Hades build.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
2026-10-23 09:29:42 +08:00