Commit Graph
100 Commits
Author SHA1 Message Date
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
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
seedds e454ac912a Luigi's Mansion 3 plays on the iPhone: README games table, AGENTS.md list
- The user confirmed the build on the iPhone
- README: games table row and picture (the hotel lobby); AGENTS.md: the list (forty-three games) and a note on the port; development.md: status
2026-10-23 08:38:28 +08:00
seedds 626e6a6188 Luigi's Mansion 3 plays on the Mac at 60 fps: profile 0100DCA0064A6000 (update 1.4.0, SDK 7.3.2)
- Files and shaders: Next Level Games' engine keeps every RomFS file but the Wwise banks in archives of its own, <name>.dict indexing zlib chunks in <name>.data; update 1.4.0 ships <name>.patch beside the unchanged base files, a variant bitmask and per-section records that rebuild each section from base and patch bytes (the game's algorithm, main+0x1c8d0 / 0x1cbd0). shader_split.py nlg_shaders reads both: 5,317 shaders (5,149 in the base game), all in init, control sections back to back in one section and their code areas in the same order in another, one NVN program per stage (SINGLE_STAGE '#NLG'); 46 of the first 103 the title uses exist only in the update (docs/agents 01)
- Shader chain, for every game: a texture array over the driver table c2 (Ryujinx's texture array from a buffer, fp_t2das_cb2_0: 586 shadow-cascade pixel shaders indexed by (0x80 + cascade offset) / 8) is named tex_c2a / samp_c2a with its c2 offset (dmsh_build.py c2_array), and the replay fills element k from the slot whose c2 word lies at that offset + 8k (nvn_refl NVN_RB_TEXA / NVN_RB_SAMPA, nvn_mtl.m c2_handle). A pixel shader that writes an output from integer bits, or nothing but constants, gets a second entry point main0_u whose colour outputs are the unsigned vectors of the same bits (dmsh_build.py fs_uint); a pipeline whose written colour attachments are all unsigned integer formats takes it (pso_all_uint). The G-buffer is R32Uint + RGBA8Uint: about 1,400 pipelines failed to build and every scene draw was skipped. Every other profile's dmsh_chain.h rewritten with its c1_full / cb_extent lists carried over (the change touches no c1 or uniform read); Luigi's Mansion 3 built for Mac and iOS
- Host, for every game: the game patches texture handles into its recorded command buffers in place, at the command start + 0x10 it took from GetCommandMemoryUsed before nvnCommandBufferBindTexture (main+0x7c8964; the store at main+0xb7d0d4), so a single texture bind is recorded in the hardware's layout, {stage, slot, handle at +0x10, texture / sampler pool bases} (OP_BIND_TEX1, nvn_obj.h bind_rec decodes it at every replay site); the host's own layout put the patched handle over the bind count, the post passes read stale handles (a stretched icon over the title, a white screen in a new game) and a corrupted record now gets a red line. t_nvn: the handle patched at the command start + 0x10 is the one replayed (102 checks)
- Host, for every game: a depth texture drawn through a colour view of the same pixel size (a D16 1024x1024 texture through an R16 view; Metal has no colour view of a depth texture, and the boot aborted in pipeline validation) draws into a colour shadow, copied in before and back after the pass as a compressed target is (nvn_mtl.m shrt_block, D16 and D32F); nvnCommandBufferSetDebugLabel; nvnBlendStateSetAdvancedMode (red line for a mode other than none); a draw whose program has a geometry shader with no library gets a red line naming it (it is drawn without that stage, with the vertex shader's positions as clip coordinates)
- Frame rate: 60 fps (30 on the Switch). The frame limiter (main+0x7a3e70, a frame callback called with the milliseconds since the frame began, main+0xb7c308) runs jobs or sleeps until 33.333 ms have passed, a float in read-only data (main+0x13c94dc), and the game steps by the time that really passed: the same 2 s walk and 1.5 s turn from the same save end at the same spot at 30 and 60 fps, and the title's animation matches at time scale 1.00 (DM_NVN_REGLUM). hle_fpslock.c gets rows with no pointer (the field at main + offset; the loader leaves read-only data writable, the image's data block on iOS too) and a frame-time kind: 1000 / fps_target, at 60 less 0.5 ms, since the work after the limiter made an exact 16.667 ms 59.6 fps with a frame shown twice every 2.5 s; slightly shorter, the presents set the pace (lobby 60.0 fps, display interval p99 18.69 → 16.67 ms). Profile fps_target 60 with present_interval display (the game also asks for interval 2); DM_FPS_TARGET=30 keeps the game's 33.333 ms
- Not done: the tessellation stages (15 hull, 18 domain shaders: two programs draw twice a frame for about 30 frames, in the title scene behind the file select as it slides out before a new game; skipped, since Metal 4 has no tessellation stage) and 10 geometry shaders (none drawn from the title to the lobby). Vertex shaders that feed them can be found from the shader headers (docs/agents 04)
- Mac: the boot notices, the title, a new game, the opening cutscene (the bus ride, the arrival at the hotel) and the lobby in play at 60 fps, about 3.7 GB phys_footprint (3.0 GB of Metal allocations); red lines at run time: the geometry shader that does not build, the conditional render the host draws anyway, primitive restart 0xff (no 8-bit strip seen). Diagnostics: DM_NVN_TREG_ID=n logs that texture ID's registrations; the pass dump prints the handles of texture binds
2026-10-23 08:38:28 +08:00
seedds 8d175f8d24 FINAL FANTASY RESONANCE plays on the iPhone: README games table, AGENTS.md list
- The user confirmed the build on the iPhone
- README: games table row and picture (the first battle: the Archwitch's attack on the soldiers); AGENTS.md: the list (forty-two games) and a note on the port; development.md: status
2026-10-23 07:25:33 +08:00
seedds 2000f9274a FINAL FANTASY RESONANCE plays on the Mac at 60 fps: profile 0100606026F12000 (update 1.0.1, SDK 20.5.17)
- Engine: Unreal Engine 5.6.1 (Square Enix, the desktop renderer), the newest Unreal so far; everything in one IoStore container beside one pak; the sdk and subsdk0 builds are STEINS;GATE RE:BOOT's
- Rewriter, for every module: code before the first unwind entry is one function (each site there was a function of one instruction, so a byte jump table at main+0x219234 went unseen), and a function longer than 256 KB is analysed in pieces cut after a ret / br / b that no conditional branch or b within 64 KB crosses, with far branches and jump table targets checked against windows and retry blocks afterwards (this main's 82 MB without unwind entries took 100 GB of memory as one function; now 2.7 GB resident). Checked: DRAGON QUEST VII Reimagined's main byte-identical to its rewrite by the previous patch_nso.py; FINAL FANTASY RESONANCE's, Octopath Traveler 0's and DRAGON QUEST XI S's byte-identical between two different cut placements; only DQ VII's main among the 120 modules of 33 games on hand has other sites before its first unwind entry (none in a window or jump table)
- Shaders: no archive in the pak; the two libraries are IoStore shader library chunks whose tables and shader groups are Oodle-compressed, read through the game's own Oodle in the Mac host's tool mode with no boot (uepak.py Toc / io_archive / OodleHost; library names from CityHash64 of the manifest's archive names) and written as decoded version 2 archives; the NSP script uses them for any game with IoStore shader libraries. The pak's single-block files store block size 0 (Pak.entry). 73,496 unique shaders; 11 do not build (3 geometry, 8 compute with 3D image atomics), none met so far. 2,077 particle vertex shaders pick their texture slot at run time (tex_dyn_slots); CopyStencilToLightingChannelsPS writes integers to an R8Uint target (shader_facts_manual.tsv; without it the sprites were dark silhouettes)
- Host, for every game: a shader with run-time texture handles in a game without tex_dyn_slots freed its reflection twice and aborted the host (nvn_mtl.m); oe::MuteRecording / UnmuteRecording (no-ops)
- Memory: the FMallocBinned3 constructor call is cut and the host's allocator runs Unreal's heap (hle_uemalloc.c layout UE5_6: UE 5.5+ FMalloc has 32 slots, with MallocZeroed and two more TLS cache calls); the SDK's node area is cut to 62 MB. The engine's own page allocator only grows its use of the engine region (2.03 GB of address space over seven minutes, about 1 GB mapped), and a 2 GB region aborted in the first battle, so the region is 4064 MB in Engage's 5 GB block (this sdk build aligns regions to 2 MB, no alignment cut needed)
- Frame rate: 60 fps (30 on the Switch): the host writes rhi.SyncInterval 1 (hle_fpslock.c rows for this build; profile fps_target 60); the game steps by real time (DM_NVN_REGLUM traces at 30 and 60 fps match from the load through a walk)
- Mac: first-boot settings, the title, a new game, the opening cutscene, the first field and its autosave, Continue, and the first two battles at 59.7 fps, 2.3-2.6 GB resident, flat over seven minutes, no red line apart from a few texture type mismatches at one moment of the new game's load in some runs. Docs: docs/agents 01, 03, 04 and development.md
2026-10-23 07:22:09 +08:00
seedds 0b3c7b1200 Tears of the Kingdom's crash in mtl_exec after minutes of play (GitHub issue #61): a command memory link never writes past what GetCommandMemoryUsed reported
The replay jumped to 0x1000000003, a CALL record's header: a JUMP's target had been overwritten by the next recording. Tears of the Kingdom gives each model's prerecorded lists fixed slots of the size a recording of that list measured with GetCommandMemoryUsed, slot k at base + size × k, and passes AddCommandMemory max(size, 512) (main+0xbb120; its memory callback is empty). The host kept 24 bytes free after every record for "a JUMP or an END", so a 496-byte list's END went to a host segment and the 16-byte JUMP at 488 reached 8 bytes into the next slot, whose first record landed on the JUMP's target.

The host now keeps 16 bytes (one JUMP) after every record but the END, lets the END take a segment's last bytes, and links only recordings that have records. Every other record is at least 16 bytes, so a JUMP only stands where a record was to go. Lists that fit their slot exactly no longer go to host segments either (the 504-byte lists Tears of the Kingdom records at load did). t_nvn "slotted lists" records lists of 64 to 1064 bytes into such slots: on the old recorder the 496-byte one broke.
2026-10-23 02:45:50 +08:00
seedds 8b4db4153e DRAGON QUEST VII Reimagined at 60 fps (30 on the Switch): profile fps_target 60
The host writes Unreal's t.MaxFPS 60 and rhi.SyncInterval 1 into the game's console variables (hle_fpslock.c rows for main f7ec03e4); these are the two limits StevensND's 60 fps mod for the base main lifts with code patches (UEngine::SetMaxFPS returning at once, the name r.VSync broken). Unreal steps the game by real time: on the Mac 59.8 fps, the same route at 30 and 60 matches at time scale 1.00, same resolution, no new red line.
2026-10-23 02:15:45 +08:00
seeddsandohxiaoguang e10277be9a Movies: 5.1 Vorbis sound decodes right, as stb_vorbis's type-2 residues span every channel of their submap (GitHub PR #60)
stb_vorbis cut a type-2 residue's interleaved vector at n * 2 values even when its submap holds more channels, so the rest of the packet was read as the wrong data: the 5.1 tracks libvorbis encodes came out distorted and gurgling, with pops. The bound is now n * ch, as in the Vorbis spec and libvorbis's res2_inverse, and the temporary-memory estimate for a caller-owned buffer counts every channel of the stream for type-2 residues (with the decode fix alone, a 6-channel stream overran the advertised buffer). Upstream stb still has the bug (nothings/stb #879, #1594).
Link's Awakening's opening and title movies were the report; DRAGON QUEST XI S's movies open only 6-channel Vorbis tracks too. Its 23 6-channel cutscene and title tracks, decoded as the host does, now match FFmpeg to within 0.02 PCM16 units, where main's decoder was off by an RMS of 1,700 to 70,000 units.
dm/host/tests/t_vorbis builds tiny Vorbis streams in memory (types 0/1/2, mono to 8 channels, two submaps) and checks heap and fixed-buffer decoding through the pull and push APIs, with and without STB_VORBIS_DIVIDES_IN_RESIDUE; it needs no game profile. dmhost rebuilds when stb_vorbis.c changes.
On top of the PR: the test rules sit with the others, and the stb_vorbis README and the per-game notes say what the fix repairs.

Co-authored-by: ohxiaoguang <44696970+ohxiaoguang@users.noreply.github.com>
2026-10-23 01:10:18 +08:00
seeddsandAnimax Deng 6da1882871 NSP script: --prepare-xcode makes a per-game Xcode project to sign and run with your own developer account (GitHub PR #59)
Each game gets its own project in <NSP folder>/<title ID>_build/xcode, registered in a shared NxNative.xcworkspace; a target phase copies the RomFS, shader libraries and add-on content into the app before Xcode signs it, and preparing again keeps the bundle ID and team chosen in Xcode. The unsigned IPA build is unchanged.
On top of the PR: the resource phase stops when the profile's shader chain is no longer the prepared shaders' (the host loads only its own chain), prepared projects ask for the Increased Memory Limit capability (DM_ENTITLEMENTS overrides), the source links are made again on every preparation, and scheme names keep non-ASCII letters. The PR's TOML launcher is left out.

Co-authored-by: Animax Deng <Animax.deng@gmail.com>
2026-10-23 00:37:52 +08:00
seedds 1c584dc3e9 DRAGON QUEST VII Reimagined plays on the iPhone: README games table, AGENTS.md list
- The user gave the go-ahead after the iPhone build
- README: games table row and picture (Maribel and the hero by the boat at night, the scene the image-handle size query fix brought back); AGENTS.md: the list (forty-one games) and a note on the port; development.md: status
2026-10-23 00:05:08 +08:00
seedds e72a06510e DRAGON QUEST VII Reimagined plays on the Mac: profile 0100A9D01C446000 (update 1.2.0, SDK 19.3.1)
- Files and shaders: Unreal Engine 4.27 with IoStore containers beside one pak (version 11, its footer magic 0xcc bytes from the end, uepak.py magic_back); the 62,901 shaders of the pak's two shader code archives are LZ4 blocks, which uepak.py decodes itself (lz4_shaders), so the NSP script runs no Oodle host when no record is Oodle. One global shader writes integers into an R8Uint target (shader_facts_manual.tsv)
- Shader chain, for every game: a texture query on an image handle (c2 0x120 + 8 r: Unreal's GetDimensions on the RWTexture3D a compute shader writes) had no type in a facts table built without PC shaders, and the 2D default took a 3D image's depth from its width; the translator now translates such a shader again with the image types of its first translation (dmsh_xlate ImageTypesOf; dmsh_xlate.dll rebuilt, dmsh_chain.lock updated). Host, for every game: a texture binding of slot 32-39 reads the image bound in the image slot that shares its c2 word (nvn_mtl.m tex_slot_h; Hades II's texture slot 34 without an image still reads the texture). Unreal's volumetric lightmap indirection patch (7b958a7b21e72801) read the empty texture slot 32's 1x1 size and wrote only cell (0, 0, 0), so in the opening cutscene every character and prop sampled the atlas origin, a daytime level's bricks baked 2^15 brighter (white figures about 10^4 too bright). Monster Hunter Rise has four such queries. Every other profile's dmsh_chain.h rewritten with its c1_full / cb_extent lists carried over (the change touches no c1 or uniform read); DRAGON QUEST VII Reimagined rebuilt for Mac and iOS
- Host, for every game: stencil-mode texture builders (nvnTextureBuilderSetDepthStencilMode STENCIL) read the stencil of their depth-stencil storage (the hero was drawn as a dithered silhouette); the audio renderer at 32 kHz on the CPU path (the SDK makes its upsampler only for DSP rendering: UpsamplerManager::Allocate returns null and the host resamples 3:2); GetAudioDeviceOutputVolume; the shop and Nintendo Account calls of the title (ec Finalize / Cancel / GetErrorCode / ShowShopSubscriptionDetails, GetAuthorizationCode / GetState); kpr::GetKeyCodeMap; the movie output format's nv12-width / nv12-height beside nv12-uv-stride (Sofdec's VP9 movies were grey with bands)
- Memory: MallocBinned3 reserves 37 GB of pools in the engine region; its creation is cut and an observation gate hands the engine the host's malloc zone (patch_nso.py cut + gate OBS_UE_MALLOC, hle_uemalloc.c layout UE4_27; patch_nso.py places a gate where the scratch registers it uses are dead, scratch_dead); the region is 2 GB (game_args) with the SDK's node-area cut on this sdk build, in a 3.75 GB guest block; about 2.6 GB resident on the Mac
- Frame rate: this build's t.MaxFPS and rhi.SyncInterval in hle_fpslock.c; the profile keeps the game's 30 fps
- Diagnostics: DM_CTL (Mac) drives a running game line by line: presses, shots, pass and texture dumps, texlist (textures of a target with storage and block-linear layout), memdump, peek, Unreal console variables (cvarf / cvari), env; the pass dump prints buffer-to-texture copies and buffer textures' format, size and storage, and DM_NVN_CBDUMP saves the source bytes of 3D uploads (docs/agents 04, 05)
- Mac: the logos and opening movie, the title, a new adventure log, the opening cutscene (the well, the night scene with Maribel by the boat) into play at Pilchard Bay, and a saved log continued, at 30 fps with saves; no red line at run time apart from 62 of the 63 geometry shaders (one-pass cube shadow maps), which do not build. Unit tests: the graphics tests that build pass (t_refl's two sample libraries are not on this machine); 13 that compile nvn_mtl.m with hle_nvn.c already failed to build on main (g_ft defined in both, hle_nvn.c symbols missing from their stubs)
2026-10-22 23:13:12 +08:00
seedds 7bb270cccc Animal Crossing: New Horizons plays on the iPhone: README games table, AGENTS.md list
- The user confirmed the build on the iPhone
- README: games table row and picture (the arrival on the island's dock); AGENTS.md: the list (forty games) and a note on the port; development.md: status
2026-10-22 22:42:50 +08:00
seedds c2750b89b8 Animal Crossing: New Horizons plays on the Mac: profile 01006F8002326000 (update 3.0.3, SDK 20.5.4)
- Nintendo EPD's engine (code name Park; sead / agl / gsys, NintendoWare, Havok, Pia and NEX) on NVN; 9,766 BNSH shaders in zstd and Yaz0 SARC files and main, the in-flight movie VP9 + Vorbis through movie::Extractor, the Happy Home Paradise add-on mounted. A new game runs through Tom Nook's counter, the movie and the island's creation to the player on the dock at 30 fps; not yet seen: a save, which the game first makes at the end of day 1
- Yield back-off, for every game: a yield whose caller's x19–x28 moved since the last one is progress (the SDK's YieldThread reaches the SVC through tail branches, so they are the loop's own): it returns at once with no host call and the run starts again; polls keep those registers and back off as before. The game's SaveProc builds a new island in grid walks that yield once per cell: 77 s of black screen after the movie, now about 10 s. Breath of the Wild's PlacementMgr and RadarMgr yield per batch item too: outdoors on the Mac CPU energy 0.35–0.74 → 0.21–0.34 W, CPU 76–83% → 96–106% of a core, frame interval p99 19 → 20–21 ms
- Rewriter, for every game: halfword jump tables get landing pads up to their largest entry (half_count), not all 65,536: this main's patch area went from 42 MB, out of a branch's reach, to 7.9 MB; the other games' modules change only in such tables
- Host: main's own HIPC client for olsc:u (cloud backup settings) is answered by host sessions (hle_sf.c); device save data is the host save directory, and a second mount name over one save shares its transaction; Commit with a CommitOption, GetSaveDataRestoreFlag; the adjustable user clock, GetStandardSteadyClockCurrentTimePoint, oe::GetRegionCode, SDK 20's album share calls; video movie decoders keep 16 output slots (the game's player holds every one it is offered)
- Profile: render_cond on (the game skips clears with conditional rendering; without it every frame was black), pad_layout full, mem_used 122 MB, the prewarm list recorded from boot to the dock, the chain header and tables for the current chain version
2026-10-22 22:41:39 +08:00
seeddsandnocmt 1765d53aba Shared fonts: LoadFont gets the TTF when a game hands it the host's BFTTF header (GitHub PR #55): The Legend of Zelda: Echoes of Wisdom imports Decode but loads its shared fonts without it
Co-authored-by: nocmt <26828323+nocmt@users.noreply.github.com>
2026-10-22 21:15:31 +08:00
seedds d5b380b3a8 Digimon Story Cyber Sleuth: Complete Edition plays on the iPhone: README games table, AGENTS.md list 2026-10-22 20:48:44 +08:00
seedds 26231779dd Digimon Story Cyber Sleuth: Complete Edition: the chain header for the current chain version; the survey's notes know OpenGL games 2026-10-22 20:48:44 +08:00
seedds 298fa96f0b Digimon Story Cyber Sleuth: Complete Edition plays on the Mac: profile 010014E00DB56000 (update 1.0.3, SDK 6.4.0), an OpenGL layer on Metal (dm/gfx/gl), its GLSL translated offline (gl_shaders.py), MVGL archives (mvgl.py) 2026-10-22 20:43:37 +08:00
seedds 1eaaab88ca Breath of the Wild's sky is blue again (GitHub issue #52): every game fills a texture from storage it flushed before the texture's first use
The game copies its precomputed sky scattering tables (System/KSys/sky.skybin) into CPU-cached pools and flushes them; the host made the 256x32x16 3D table a GPU texture of zeros, so the sky came out brownish grey. nvnMemoryPoolFlushMappedRange now records the flushed ranges (nvn_flushed in hle_nvn.c), and tex_init_fill fills a new GPU texture from such storage once, as it did for CPU_NO_ACCESS pools. DM_NVN_FLUSH_FILL=0 turns it off; t_nvn covers the range bookkeeping.
2026-10-22 19:16:38 +08:00
seedds e0cbc7bb2c Catherine: Full Body plays on the iPhone: README games table, AGENTS.md list
- The user confirmed the build on the iPhone
- README: games table row and picture (Vincent climbing the first Nightmare stage); AGENTS.md: the list (thirty-eight games) and a note on the port; development.md: status
2026-10-22 18:49:44 +08:00
seedds 2bdad7d19c Catherine: Full Body plays on the Mac: profile 0100BF00112C0000 (update 1.0.1a, SDK 8.2.1)
- Atlus's GFD engine with CRI middleware (CPK archives, ADX2 sound, Sofdec2 movies through movie::Decoder); 3,128 GLSLC shaders: 3,050 loose files in data/shaders_binary, 76 in main and 2 the game compiles at boot (captured on the Mac)
- Shader chain, for every game: this GLSLC (GPU code 1.15) writes each vertex shader as a VertexA part (shader type 0, the position) and a VertexB part (the other outputs) in one code area; shader_split.py reads type 0 as a vertex shader (sph_stage) and dmsh_build.py merges the two parts as Ryujinx does (vertex_ab). Without it every varying was lost and the screen was black. Every other profile's dmsh_chain.h rewritten with its c1_full / cb_extent lists carried over (the merge touches only type-0 vertex shaders, which no other game has); The Great Ace Attorney Chronicles checked on the Mac
- Host, for every game: nvnDeviceGetInteger reports at least the GPU code version of the game's own GLSLC (the game refused to compile its built-in shaders against SDK 7's 1.5–1.14); nn::settings::GetAvailableLanguageCodeCount; nv12-uv-stride in the movie decoder's output format (CRI's player reads it unchecked and crashed on the first picture); a segment commit at the first pass boundary after a buffer → texture copy, since the game reuses the copy's staging memory a frame later (the subtitle band came out as another texture's blocks)
- Memory: mem_used 96 MB (the rewritten modules take 92 MB, so SetHeapSize failed and the game ran on a 32 MB heap)
- Prewarm list recorded from boot through the intro into the first Nightmare stage
2026-10-22 18:49:00 +08:00
seedds 4847d88c6a README: DLC is optional, as the update is
- The script packs the DLC NSPs in the folder and none without them; the host then reports no add-on content, as a Switch without it
2026-10-22 18:26:29 +08:00
seedds afa99e5831 NSP script: the update is optional and any version builds (a derived profile for versions the profile was not brought up on)
- The newest version in the folder is packed (-p / PACKAGE picks another). A registered version with every shader_versions version present builds from the profile as before
- Any other version (the base game alone, another update, other build IDs) builds from a profile derived from it in <id>_build/profile: the profile's keys, that version's build row, and shader tables made from its own files; a red line names the modules whose per-build fixes do not apply
- Host prewarm skips shader keys the shader map does not have (a list of another game version), counted in the "prewarm done" line
- Tested with Hollow Knight: Silksong: the base game alone (1.0.27776) packs with its own 2,244 shaders and plays the opening on the Mac; base + 1.0.30000 builds as before
- README: the update is optional; Silksong's row no longer names it
2026-10-22 18:25:12 +08:00
seedds e18a9fdc21 Hollow Knight: Silksong plays on the iPhone: README games table, AGENTS.md list
- The user confirmed the build on the iPhone
- README: games table row and picture (Hornet's needle slash in Moss Grotto); AGENTS.md: the list (thirty-seven games) and a note on the port; development.md: per-game notes
2026-10-22 17:51:13 +08:00
seedds 33aa059ade Hollow Knight: Silksong plays on the Mac: profile 010013C00E930000 (update 1.0.30000, SDK 20.5.8)
- Unity 6000.0.50f1 IL2CPP with FMOD; the bundles carry their type trees, so unityfs.py reads Unity 6's Shader objects with no layout written out (2,450 shaders, the one geometry shader does not build)
- Host: nn::hid::GetDebugPadStates, which Unity 6 polls every frame and the SDK's own aborts on (Result 0x2aca)
- Rewriter: the address-region node area cut on this sdk build (alias high-water mark +3.00 GB → +2.26 GB in the first room)
- profile_guess.py: every game with an NPDM system resource gets the 5 GB block; heapless ones (Silksong, Absolum, Pokémon Legends: Z-A) used to get a 256 MB alias region
- Prewarm list recorded from boot to the first two rooms of Moss Grotto
2026-10-22 17:51:13 +08:00
seedds 677345b093 Paper Mario: The Thousand-Year Door plays on the iPhone: README games table, AGENTS.md list
- The user confirmed the spin wait build on the iPhone (the first build ran Rogueport at 24 fps: its three job workers polled the job list with no SVC, 99c3f91)
- README: games table row and picture (Rogueport's docks); AGENTS.md: the list (thirty-six games) and a note on the port; development.md: status
2026-10-22 16:36:07 +08:00
seedds 99c3f91481 Spin waits: the rewriter finds guest poll loops in every module and the host lets them sleep (Paper Mario: The Thousand-Year Door's job workers at 24 fps on the iPhone)
A loop that polls memory with no SVC costs nothing on the Switch, where a higher-priority thread of its core preempts it; here it takes a host core. TTYD's three
gfx::ParallelManager workers (main+0xa8e680) spun at 94% of a core each in Rogueport on an iPhone. patch_nso.py now copies every loop that provably cannot leave by
itself (only loads, register and flag arithmetic, direct branches and barriers on every path back to its head, nothing live at the head written; an exclusive add undone
on every path allowed) into the patch area; the copy's back-edges pass a spin gate, where the host polls on for 2 ms and then sleeps with the yield back-off
(dm_spin_wait; DM_SPIN, DM_SPIN_POLL). Mac Rogueport route beside the committed build: frame p95 29-37 -> 18-22 ms, CPU 93-129% -> 38-57%. The x18 scan decodes in
blocks without details, so the rewrite is faster than before (TTYD main 20.5 -> 11.7 s, Tears of the Kingdom 101.5 -> 64.2 s); its sites are unchanged.
2026-10-22 16:32:01 +08:00
seedds 51a3fb4442 README: Dark Souls Remastered's picture under a new name, so the cached old one is not shown 2026-10-22 15:32:34 +08:00
seedds f5607c2d7c README: Dark Souls Remastered's picture is the character creation screen 2026-10-22 15:31:06 +08:00
seedds 3621cf5122 DARK SOULS: REMASTERED plays on the iPhone at 60 fps and normal speed: README games table, AGENTS.md list
- The game does not step by the time that passed: its frame update (main+0x6a337c) takes a fixed 1/30 s, loaded at main+0x667904, so with only the frame limiter at 60 it ran twice as fast on the iPhone. A second cut loads the 1/60 that rodata already holds (main+0x167a834), the same two words as the community's 60 fps cheat for this build (patch_nso.py). The user confirmed normal speed on the iPhone
- The earlier note that the game steps by elapsed time came from a 0.8 s walk compared by eye on the Mac; the docs now say so, keep the frame-count conversions of that 1/30 as they are, and note that ladders misplay at 60 with the cheat too
- README: games table row and picture; AGENTS.md: the list (thirty-five games) and a note on the port; SDK 4.5.0 is now the oldest so far, not Cuphead's 5.5.0
2026-10-22 15:26:21 +08:00
seedds 65d4a4d1ba DARK SOULS: REMASTERED plays on the Mac at 60 fps: profile 01004AB00A260000 (update 1.0.3, SDK 4.5.0)
- FromSoftware's engine (dantelion2) with Havok and FMOD on SDK 4.5.0, the oldest so far; subsdk0 is the movie module (H.264 MP4, decoded by the host). The logos and opening movie, the title (offline), the first-run settings, character creation with the name through the inline keyboard, the opening cutscene and the Undead Asylum cell, at 60 fps with music and sound; about 1.6 GB resident, flat. Not yet tested on an iPhone
- Shaders: 3,099, of which 3,040 are GLSLC outputs, one per .vpo / .fpo entry of six BND3 archives inside the dvdbnd archives (.bdt indexed by .bhd5, DCX zlib entries), read by shader_split.py (bhd5_entries, undcx, bnd3_members, bdt_shaders), and 59 more in main
- SDK 4's GLSLC writes control sections whose +0x20 is 12 bytes short of their length; outputs the old rule rejects are read through their section headers (glslc_section). On every output the old rule keeps (1,073 in five games) both give the same key; it also recovers 8 shaders of Fire Emblem: Three Houses and 3 of Age of Calamity (GLSLC 0x1d.0), whose tables and chains are regenerated (6661409043d9, 55b1235ad307; Three Houses' prologue battle checked on the Mac)
- SDK 4 calls: the movie decoder constructor without a priority and movie::Buffer without a sync object; friends::GetBlockedUserList without a Uid (empty, success); the UTF-16 profanity masking by name (the name entry crashed in the SDK's version)
- Poll / Select with nothing to wait on (no descriptors, or only negative ones) wait out their timeout and return 0, as on Horizon, in every mangling including SDK 4's Select: the game's network thread spun a core at 100% (dm_guest_sleep_ns)
- Graphics: handles read from uniform buffers take the base of the descriptor pool bound at replay (OP_SET_TEX_POOL / OP_SET_SAMP_POOL now tracked): with 12 sampler pools holding different samplers under one ID, the map walls took a clamp-to-edge sampler and smeared sideways. The bound pools' bases live in each command buffer (cmd_t) instead of a 64-entry list that overflowed
- 60 fps (30 on the Switch): a cut on the render thread's frame limiter constant (main+0x706fa4); the game steps by the time that passed, so the same input walks the same distance at 30 and 60
- The real-time declaration of the acquiring thread covers 1.1 x the window's peak as well as 1.25 x the p90 (rt_comp_tick): frames past the declaration stalled 30-60 ms, about once a second in this game at 30 and at 60 fps. This is the same rule for every game; CPU energy here is unchanged
- Profile: pad_layout full (the game confirms with B, positional); docs: 03-bringup, 04-graphics, development notes
2026-10-22 14:18:00 +08:00
seeddsandnocmt b302d40083 Mac window: clicks on the window buttons reach AppKit, ⌘Q and ⌃⌘F work, touches land on the 16:9 picture (from PR #48)
The mouse-as-touchscreen monitor swallowed every left click in the game
window, so the close and fullscreen buttons never saw their clicks (and the
game took them as touches). The content view, DmGameView, now takes the
clicks itself; the title bar, the window buttons and the resize edges keep
theirs. Touches are normalized to the centered 16:9 picture the present fits
into the view and clamped, as on the iPhone (DmGamepad.m), so they no longer
drift in fullscreen or in a window of another shape.

AppKit runs ⌘ shortcuts only through menu items, so the window now has a
main menu: Quit (⌘Q) and Window > Enter Full Screen (⌃⌘F). Only key-down
events with ⌘ go to it; a key released while ⌘ is held still reaches the
game, or it would stay pressed.

Co-authored-by: nocmt <nocmtone@gmail.com>
2026-10-22 14:12:46 +08:00
seeddsandnocmt f1c4a6bd67 NSP script: --progress json for a program that shows the build (from PR #49)
stdout carries one JSON object per line: stage, step, progress (done, total,
unit, est, note, frac, eta, elapsed; null while unknown), stage_end and say,
which carries every message, errors and a failed step's log tail included.
A count change goes out at most every 0.5 s, any other change at once; the
ticker now runs in json mode too and sends a count that was held back, so a
stage whose count moves rarely (the rewrite) no longer shows a stale count
until it ends. json mode never prompts; close() stops the ticker by an event
of its own and keeps the mode, so a second build in one Spyder session gets
its redraws again.

dm/tools/tests/test_nsp_progress.py covers the events, the held-back count,
the failed step's tail, the CLI error and the bar / lines / off output,
with no game files, keys or Xcode.

Co-authored-by: nocmt <nocmtone@gmail.com>
2026-10-22 13:59:35 +08:00
seedds f7b8fede8f NSP script: the fps counter is on by default (SHOW_FPS = True) 2026-10-22 13:51:15 +08:00
seedds c719b3e2c5 Raging Loop plays on the iPhone: README games table, AGENTS.md list 2026-10-22 13:30:52 +08:00
seedds 849e6918b5 Paper Mario: The Thousand-Year Door plays on the Mac at 60 fps: profile 0100ECD018EBE000 (update 1.0.1, SDK 15.3.2)
- Intelligent Systems' engine on NintendoWare (G3d, Vfx, Atk) with AngelScript, SDK 15.3.2 (Celeste's sdk build), NVN; subsdk0 is the movie module (WebM, VP9 + Vorbis). The title, a new file, the storybook prologue and the opening into Rogueport, with Mario under control on the docks, with music and sound; no crash in eight minutes. Not yet tested on an iPhone
- Shaders: the game names its zstd files .zst (8,726 of 9,959), so the split found none; shader_split.py now decompresses zstd by its magic. 11,858 BNSH in shaders/*.bfsha and effect/particle/*.ptcl; no other profile's RomFS on hand changes (Super Mario Galaxy's GDDR3.zst is a Vulkan game's)
- nn::settings::GetAvailableLanguageCodes (the language lookup in nnMain aborted in sm::Initialize)
- Memory: malloc's StandardAllocator reserves 62 GB of address space with virtual address memory, cut to 4 GB on main; Absolum's node area and 32 MB alignment cuts on this sdk build (Celeste shares it and has no virtual address memory: its rewrite differs only in those three words and it still boots); Engage's 5 GB block
- Rewriter: sub sp, sp, xN rounds xN up to 16 first (site_sp_align). AngelScript's ARM64 call bridges reserve stack arguments by their summed size (4 for one int), which Horizon tolerates and Apple's kernel faults on; of the 22 games' modules on hand only this main has the form, so every other rewrite is byte-identical. insn_census.py counts the sites
- Graphics: tex_dyn_slots for 2,386 pixel shaders that pick a combined texture slot at run time; arrays of handles in a uniform buffer (the reflection shaders: element k = the handle at byte 8k), read by the host's reflection reader and bound by the replay with no chain change; array elements of another type get the zero cube silently; ClearColori / ClearColorui; a format-converting copy into a packaged texture upgrades it to render-target use; an unbound uniform slot reads the zero bank. t_refl case for the handle array
- 60 fps (30 on the Switch): the game steps its clocks by the 60 Hz vsyncs it measures between frames, at least 2. Cuts on 1.0.1's main lower the floor to 1 and give the base clock the host's frame step (FRAME_STEP stub at main+0x2e77f8); profile fps_target 60 with fps_hold off (holding late frames made them slower: the frame waits for three render workers, idle during a hold, and the hold stuck at 20 fps). Game time 1.000-1.003 of real time on the Mac; fades take as long as at 30 fps. DM_FPS_TARGET=30 keeps 30
- Profile: pad_layout full (the D-pad opens Gear and Journal), prewarm list recorded through the opening; docs: 03-bringup, 04-graphics, development notes, profile README
2026-10-22 13:03:03 +08:00
seedds abcdbbdd4d Raging Loop plays on the Mac: profile 010020400E1C2000 (base game 1.0.0, SDK 7.3.2)
Unity 2018.3.10f1 IL2CPP with FMOD; its sdk and movie module are DRAGON QUEST XI S's builds, so
that build's rewriter cuts apply as they are, and no host change was needed. Brought up on the Mac
through the opening movie, the title, options, the tutorial and the prologue (choices, menu,
scenario chart, log) at 60 fps as on the Switch, with sound and saves; no red line at run time,
0.6 GB resident at the title and 0.77 GB after ten minutes of story.

- unityfs.py: Unity 2018.3's Shader layout (SHADER_2018_3, type hash 8db76007...). The game has no
  bundles and its serialized files carry no type trees, so the shader split stopped. Written from
  AssetRipper's TypeTreeDumps for 2018.3.10f1 and checked node for node against the dump (1,325
  nodes with the alignment flags); all 48 Shader objects read to their exact end (450 subprograms,
  452 shaders with main's GLSLC outputs). Other profiles' hashes are untouched.
- Memory: the hardware pool (3.2 GB) in Engage's 5 GB block. Unity 2018.3 reserves no address
  regions of its own, so every mapped page lies in StandardAllocator's region (377 MB at the peak).
- Input: the game reads no touch, so it stays docked with the 2D pad; the left stick moves the menu
  cursor.

Development notes, the graphics catalogue and the survey's list of Unity versions read.
2026-10-22 12:21:03 +08:00
seedds 26a72dd562 NSP script: DLC and iOS shaders reused across reruns, one Xcode build folder per game
A rerun after an interrupted IPA pack did a lot of the build again:

- The iOS shaders were keyed on the shader split's key, and that covers every dm/gfx/tools/*.py.
  A pull that changed only the Unity readers (shader_split.py, unityfs.py) made Monster Hunter
  Rise split again and then restore all 63,111 shaders from the cache (9 minutes). The iOS shaders
  are now keyed on the shader package's content and on what dmsh_build.py loads (the repository
  modules it imports, at any depth, and the chain lock). A split that runs again and writes the
  same package leaves them reused; hashing Rise's 510 MB package takes 0.3 s.
- The DLC was extracted on every run (Rise: 252 DLC, 16-30 s). It is now reused while the DLC NSPs
  (names, sizes, times) and nx_extract's sources are unchanged (aoc.key).
- Xcode's custom build location setting overrode -derivedDataPath, so every game's app was built
  in one shared ~/Library/Developer/Xcode/DerivedData/Build: a build after another game's compiled
  all 129 files, and two games built at once wrote the same app. build_ipa.sh sets SYMROOT and
  OBJROOT under the game's work folder; a repeat build there compiled nothing (1 s).

Checked: Celeste with its split forced to run again (same package, iOS shaders reused), Rise's DLC
twice (15.9 s, then 0.1 s), and an Xcode build into a path with spaces and an apostrophe.
Each game's next build runs its iOS shader step once more, to write the new key.
2026-10-22 11:33:33 +08:00
seedds 53d7aad5df Monster Train 2 at 60 fps (30 on the Switch): profile present_interval display
The game asks for present interval 2 (Unity's 30 fps setting) and nothing else holds it at 30;
display mode presents a frame every tick and the iPhone's display link runs at 60 Hz. Its
animations follow the time that passed: from one save, screenshots at the same moments of the move
to the merchant match at 30 and 60 fps (mean difference 3.9, against 11.0 for frame counting).
DM_PRESENT_INTERVAL=game keeps 30.

At 60 fps a ReadFile into texture pool memory failed with EFAULT in two Mac runs of three: the
dirty-page tracker can write-protect the pages again between dm_guest_write_prepare and pread.
h_fs_ReadFile now prepares the rest of the buffer again and reads on (up to eight times); four runs
at 60 fps: one retry, no failed read.
2026-10-22 10:38:41 +08:00
seedds 708a1d73d5 Monster Train 2 plays on the iPhone: profile 010051701FB46000 (update 1.1.1, SDK 18.3.5)
Unity 2022.3.54f1 IL2CPP on Super Mario Galaxy's sdk build, with Ace Attorney Investigations
Collection's memory layout (1 GB pool, 5 GB block). Three general changes made it play:

- svcGetInfo type 24 (FreeThreadCount): the host answered 0, and Unity creates a thread only when
  nn::os::GetFreeThreadCount is at least 1, so its main thread waited at boot for a thread it never
  asked for. The host answers 96 (pm's application limit) less the game's live threads, as the
  kernel does.
- Movie audio decoders keep 64 outputs (32 since DRAGON QUEST XI S): Unity's movie player starts
  only once it holds about half a second of audio, measured at 27 outputs with a peak of 30. Trace
  lines (DM_MOVIE_TRACE) name the decoder.
- shader_split.py reads Unity ComputeShader objects (unity_compute), with Unity 2022.3.54f1's layout
  in unityfs.py: the post-processing stack's 85 kernels, the colour grading LUT baker among them, so
  the battle scene is no longer black. Files whose ComputeShader has no known tree are reported and
  skipped: Cuphead's and Engage's tables split byte-identical.

Also nvnSyncSetDebugLabel (a no-op). The game reads no touch, so it stays docked with the 3D pad
layout. README games table, AGENTS.md game list, development notes and the bring-up / graphics
catalogues.
2026-10-22 10:16:13 +08:00
seedds 69d99b85e9 DRAGON QUEST XI S at 60 fps (30 on the Switch): profile fps_target 60
The host writes 60 into Unreal's t.MaxFPS and 1 into nvn.SyncInterval (hle_fpslock.c, this build's rows); DM_FPS_TARGET=30 in Documents/env.txt keeps the game's 30. Mac (M4 Pro): the field holds 60 fps (frame time p95 17 ms, GPU 6.6 ms p50) at normal speed with sound. README, AGENTS.md and the development note say so
2026-10-22 09:53:26 +08:00
seedds 4f0b555e75 DRAGON QUEST XI S plays on the iPhone: README games table, AGENTS.md game list, development notes
The user confirmed the IPA from df0820e plays on the iPhone. README image from Cobblestone Tor on the Mac
2026-10-22 09:35:33 +08:00
seedds df0820eb68 DRAGON QUEST XI S plays on the Mac: profile 01006C300E9F0000 (update 1.0.4, SDK 7.3.2)
New profile for Square Enix's Unreal Engine 4.18 game: from the opening movie and the title
through the prologue into free play at Cobblestone Tor, 30 fps with sound and saves, no red
line. An IPA builds from the NSP folder; it has not run on an iPhone yet.

- Files and shaders: Unreal 4.18 legacy (version 4) paks with LZ5 blocks, decoded on the Mac
  with the game's own LZ5_decompress_safe (uepak.py, hle_oodle.c serving after nninitStartup);
  38,892 shaders inline in the cooked packages as zlib-compressed LSLG records (shader_split.py
  ue_legacy_jobs, ue_inline_shaders; the NSP script and profile_guess know both pak kinds).
- Memory: Unreal's address region capped at 4064 MB (game_args, MB up to 4095); SDK 7.3.2 cuts
  in patch_nso.py (node area 62 MB, 32 MB alignment for large regions; the build Odyssey shares,
  which boots unchanged) for Engage's 5 GB block. Rewriter: a jump table whose bounds check sits
  right before the load (check_before_copy).
- Host, for every game: audio catch-up frames wait for queue space (Square Enix's sound driver
  never waits on the renderer event: 11,000 updates a second before); movie decoder events read
  at signal time as the SDK does, audio decoders keep 32 outputs (the player plays only once
  every track holds one: the opening movie never started in half the boots), and no input after
  an end-of-stream packet until Flush / Stop (a skipped movie mapped 2.25 GB of end-of-stream
  samples); ec shop accessors, friends, auto-sleep, album, file data cache and movie hooks.
- Graphics, for every game: a geometry shader that only picks the layer (Unreal's
  FWriteToSliceGS, the colour-grading LUT) folded into the vertex shader's layer entry points
  (dmsh_build.py gs_slice / vs_layer_entry, nvn_mtl.m prog_fold_gs); cube-map array depth counted
  in layer-faces in the Metal texture, the upload and the guest layout (t_nvn checks it);
  nvnTextureGetTextureAddress and the command memory size / free getters.
- Shader chain changed (dmsh_build.py): DQ11 rebuilt for Mac and iOS; every other profile's
  dmsh_chain.h rewritten with its c1_full / cb_extent lists carried over (no c1 or uniform read
  touched). Mac checks on this host: Big Brain Academy (movie loops, 23 catch-up renders as
  before), Monster Hunter Rise (movies), Super Mario Odyssey (boot on the cut sdk).
2026-10-22 09:32:16 +08:00
seedds 0beed52f4a Celeste plays on the iPhone: README games table and touch layouts, AGENTS.md game list, development notes
The user confirmed the IPA from 00de15f plays on the iPhone. README image from the prologue on the Mac
2026-10-22 09:12:09 +08:00
seedds 00de15f095 Celeste plays on the Mac: profile 01002B30028F6000 (update 1.4.1.1, SDK 15.3.2)
- MonoGame for the Switch, its C# and the game's compiled to C++ by Sickhead's BRUTE into main; FMOD Studio through AudioOut; only main and sdk. No host change: the logos, the title, a new file and the prologue at 60 fps with music and sound, no red line, about 1.65 GB resident, flat over five minutes. Not yet tested on an iPhone
- The shaders are MonoGame effects (MGFX version 8): 13 in Content/Effects/*.xnb and SpriteBatch's built-in one in main. shader_split.py reads them (mgfx_effect), each effect parsed to its end; a shader's bytecode is u32 control length, the control section, u32 code length, the code area. 26 shaders (20 pixel, 6 vertex), every binding decodes. A pass may name no vertex shader and keep SpriteBatch's (10 of the 13 effects), so they pair as single-stage programs. Cuphead's and 13 Sentinels' tables split again unchanged
- NSP script: a provisional profile never got the sys_resource the survey lists, so the host answered 32 MB of system resource for every new game; write_profile writes it now (Celeste: 0, as its NPDM declares)
- Survey: XNB files and the MonoGame and FMOD markers (profile_guess.py), and a note on where a MonoGame game's shaders are
- Prewarm list recorded through boot, the title, a new file and the prologue
2026-10-22 09:09:55 +08:00
seedds 80b59c5bed README: sign with the Increased Memory Limit capability for the bigger games (from #30)
The IPA carries no entitlements (build_ipa.sh builds it unsigned), so the memory limit depends on how the player signs it. Without com.apple.developer.kernel.increased-memory-limit iOS gave the app 3,376 MB on an iPhone 18 Pro Max and closed Hades II within minutes (#30); with it the limit is 6,144 MB, which every iPhone log.txt here shows (footprint + os_proc_available_memory headroom). Peak footprints in those logs: Hyrule Warriors: Age of Calamity 5,359 MiB, Hades II 3,466-3,842 MiB (#30), Breath of the Wild 3,196 MiB, Cuphead 3,184 MiB.
2026-10-22 08:49:20 +08:00
seedds c5fa7d6c58 Three Houses' and Engage's shader tables split again: the NSP script stopped at the shader split for both (#35)
Since 56b8061 the split reads the GLSLC outputs of every main and pairs only vertex shaders of single-stage sources (GLSLC, Unreal, MT Framework) with the cover, but these two profiles kept the tables of the old split, so the NSP script's comparison failed ("the split shader tables differ from game profile ...").

- Fire Emblem: Three Houses: shader_pairs.tsv 415 -> 398 rows. Its 17 BNSH vertex shaders (nn::ui2d's __ArchiveShader.bnsh in DATA1.bin and two BNSH archives in main) keep the pairs of their own variations and lose only the cover 042f574560d4d29c, so vs_pad gives them only the outputs their own pixel shaders read. nn::gfx binds a variation's stages as one program, and the 246 pipelines in the Mac pso_user.tsv agree: 6 of the 17 were drawn, each only with a pixel shader of its own variation.
- Fire Emblem Engage: the 11 GLSLC shaders Unity has in main are added (shader_map.tsv 9270 -> 9281 rows, one compute row in shader_facts.tsv, 6 cover pairs for its 3 vertex shaders). No existing row changed.

Chain procedure for both: dmsh_chain.h (Three Houses b-58aa432ce3c0, Engage b-1e9d905b402b), Mac and iOS libraries rebuilt (the same metal failures as before: Three Houses 2, Engage 1), c1_full.tsv and cb_extent.tsv regenerated (Three Houses: header lines only). Mac boots of 100 s: Three Houses pre-builds the same 234 pipelines as before, those with the 17 vertex shaders among them, with no pipeline failure and only its known cube texture red line; Engage has no red lines.
2026-10-22 08:42:06 +08:00
seedds 9dd2149f99 README: device requirements above the games table 2026-10-22 08:38:07 +08:00
seeddsandJY626 0adbe8c61a Mac window: physical controllers play too, with the iPhone's buttons by position (from PR #45)
With DM_WINDOW=1 every connected pad with an extended gamepad profile goes to dm_pad_set, the path the iPhone's controllers take, ORed with the keyboard (a non-zero stick wins). The buttons are mapped by position as DmGamepad.m read_pad does: the right face button is A, the bottom B, the top X, the left Y; shoulders L R, triggers ZL ZR, Menu + and Options -, both sticks and their presses. Connecting and disconnecting are logged and rescan the pads; when the last one goes away its buttons and sticks are released.

Changed from the PR: the PR ORed the pad into the keyboard's dm_input_set in push() and mapped the labels straight across (an Xbox pad's bottom A was the Switch's A), so one pad pressed different buttons on the Mac and the iPhone. No wireless discovery: pads paired in the system's Bluetooth settings connect on their own.

Built for Super Mario Odyssey; not yet run with a controller here (the PR played Odyssey with an XInput pad on an M1 Pro).

Co-authored-by: JY626 <151988630+JY626@users.noreply.github.com>
2026-10-22 08:15:31 +08:00
seedds ea12221d58 README: Big Brain Academy: Brain vs. Brain and Patrick's Parabox play on the iPhone
AGENTS.md lists them among the playable games (thirty), with what each needed; the README shows
screenshots from Mac runs (Whack Match; Patrick's Parabox's Intro). Both start in the 2D touch layout;
in Big Brain Academy the touches no control takes go to the game's touch screen (handheld). The
profiles and the per-game notes say playable on an iPhone.
2026-10-22 01:36:18 +08:00
seeddsandRaghav Agarwal e61b142c56 Movie output buffers written from their start, AudioUnit setup off the game's stack, clock snapshot arithmetic answered by the host (from PR #36)
- movie::Buffer::Data() is Base() + Offset() and Capacity() the whole buffer (STEINS;GATE RE:BOOT's subsdk0), so a PCM or picture output whose read offset the client had advanced was written past its end, over the guest heap. GetOutputBuffer resets the range first. Native-texture mode is left as it is: there Data() is the texture object the client points it at. An end-of-stream packet as long as the whole input buffer carries no data, as one with time -1 already did.
- Creating the output AudioUnit takes about 24 KB of stack (measured on the Mac), more than some game threads have left, and OpenAudioOut runs on the game's thread: it now runs on a host thread, once per AudioOut. Not HLE_FORCE_BIG, which would hide the call site the open hooks log.
- CalculateSpanBetween and CalculateStandardUserSystemClockDifferenceByUser go through the time service session, which the host never opens: AbortImpl in the SDKs of STEINS;GATE RE:BOOT and Tears of the Kingdom, which imports both. The host computes what the service does: the span from the steady time points (the network times when the sources differ, else an error), and the user clock difference from the user clock offsets (0 when their sources differ or both have automatic correction on). CalculateSpanBetween is not in the PR.
- Snapshots carry the user clock context of GetSystemClockContext, which stays the same until the host clock is set; worked out per snapshot, it moved by a second at random, which the user difference would report as a clock change.
- t_time: 14 checks on host snapshots and edited copies; it fails without the two hooks.

Mac: Monster Hunter Rise headless for 75 s with real (muted) audio: OpenAudioOut from the game's thread, AAC and H.264 movies through GetOutputBuffer, 60 fps, no movie red lines. No profile with a matching shader cache on this drive calls the two time functions.

Co-authored-by: Raghav Agarwal <88780872+futurist-raghav@users.noreply.github.com>
2026-10-22 01:27:49 +08:00
seedds 1a782136af Patrick's Parabox plays on the Mac: profile 0100E44016C22000 (update 1.0.1, SDK 13.3.7) 2026-10-22 01:23:24 +08:00
seedds 64cdee954c Big Brain Academy: Brain vs. Brain plays on the Mac: profile 0100620012D6E000 (update 1.2.0, SDK 11.4.3)
- NintendoWare Bezel Engine 1.56: nearly all of RomFS is in Bezel Engine archives (.bea, magic SCNE, every entry a zstd frame). shader_split.py reads them (bea_members): 2,125 shaders, most of them the engine's own in System.nx.bea. The 12 geometry shaders of Bezel's line renderer do not build (the main menu chart's outlines are missing); ten pixel shaders that choose their texture handle between two of the game's uniform buffers at run time do not load. The survey names Bezel Engine archives (profile_guess.py, the NSP script's notes)
- nninitStartup picks virtual address memory (a 60 GB StandardAllocator region) or a heap by IsVirtualAddressMemoryEnabled, which the host answered yes for every game. The new profile key sys_resource reports the NPDM's system resource (0 here); the survey writes it for new games, profiles without it keep the 32 MB answer
- SDK 11 calls that reach a service: GetHardwareOpusDecoderWorkBufferSize(int, int), the IR camera (every camera unconnected), LoadCachedNintendoAccountInfo, Enable / DisableIndividualFileDataCache, ldn::GetNetworkConfig and the socket overloads with plain sockaddr
- Movie decoder: the activity previews loop with Flush and Start from four threads. An input index the game held across a flush went back to the free list and was handed out twice; the fifth pending packet overwrote the decoder's mutex and the process died in 2 runs of 5. A send now claims such an index, held indices are withheld for 250 ms after a flush, pend[] cannot overflow, a flush keeps each slot's last picture and an output with no picture leaves the client texture as it was (the black flash at every loop). DM_MOVIE_TRACE=1 logs every decoder call
- The crash handler takes SIGABRT; a command buffer with no GPU start time no longer counts the whole clock as GPU time
- Handheld with Boost (op_mode, perf_mode): handheld, the game reads the touch screen; Boost gives 1920x1080
- 60 fps, sound, previews and saves; the title, enrollment, the menus and activities of every category. About 1.5 GB resident at the first activity, 1.86 GB after 15 minutes of activities (the game's pools as each activity loads). Prewarm list recorded through enrollment and a full activity. Not yet tested on an iPhone
2026-10-22 00:54:39 +08:00
seeddsandMeNass89 6400306b25 Display link: frames in order, one per tick, at most one behind (from PR #31)
Each tick took the newest queued frame and dropped the older ones, so when two frames landed in the same refresh (a 60 fps game whose frames take 16-17.5 ms) one was never shown and the next stayed on screen 33 ms. A tick now takes the oldest queued frame, and with three or more waiting it skips to the one before the newest, so a frame is shown at most one tick late. Dropped frames are still counted. DM_PRESENT_FIFO=0 takes the newest, as before.

The PR measured Dark Souls Remastered on an M4 Pro: presents within 14.7-18.7 ms 98.17 % -> 99.98 %, game enqueue to display p50 +2 ms. Not yet measured here: a windowed Super Mario Odyssey A/B on the Mac never had focus (the game suspends itself out of focus and its frames were discarded), and nothing has run on an iPhone yet.

Co-authored-by: MeNass89 <267055942+MeNass89@users.noreply.github.com>
2026-10-22 00:35:00 +08:00
seeddsandBeeeeeMo 8e1e8d7763 pctl free-communication permission query answered offline; host tests link again (from PR #38)
nn::pctl::CheckFreeCommunicationPermission(bool) had no takeover: the SDK's version calls the pctl service (pctl::detail::ipc::GetService), which the host never opens. It now answers false with or without UI, as TryBeginFreeCommunication and IsFreeCommunicationAvailable do. DRAGON QUEST XI S imports it; none of the profiles do yet.

t_pctl resolves the hooks by their mangled names in the real swforce registrations: 37 checks, and it fails when the new hook is missing.

t_condvar_stub.c defines g_key18, which hle_thread.c reads: make -C dm/host/tests run did not link on main. All 13 test programs pass.

Co-authored-by: BeeeeeMo <14192706+BeeeeeMo@users.noreply.github.com>
2026-10-22 00:35:00 +08:00
seedds 87e647f5bb Age of Calamity's last red lines, fixed for every game: buffer textures read by 1D integer fetches are bound as 2D rows, and shadow vertex inputs replace the CPU vertex conversion
- Row wrap: Maxwell reads a texture buffer and a 1D texture with the same instructions (TLD with integer coordinates only, TLDS Texture1DLodZero) and the bound descriptor decides. Without a type answer the translator read such a slot as 2D at (x, 0) and the host bound a dummy for a buffer texture, so it read 0. The vendored translator (InstEmitTexture.cs, marked nxnative:) now addresses those fetches as (x & 0x3FFF, x >> 14) and reports the slot (fetch1d); the host binds a buffer texture there as a linear 2D texture of 16,384-texel rows over the same memory (tbuf_as_2d, nvn_tbuf_rows), cached by texture serial. KTGL's light and decal lists: Age of Calamity 16,438 shaders, Three Houses 321 (torches now light its map, blood decals are lit); FFT 5, Monster Hunter Rise 3
- Shadow vertex inputs: for each vertex shader with integer inputs the build writes <key>_bv (dmsh_vin.py), which declares every integer input again as float4 and as the other-sign integer at attribute indices 16-30 and ORs them into the input; the host sends each attribute to the index of its format's class and the zero buffer to the others (vin_ct, vin_on). Age of Calamity's Mac battle: 0 vertices converted on the CPU instead of 545,000 per submission, submit + present 10.0 -> 5.7 ms a frame, CPU 3.97 -> 2.44 W, the same picture. DM_NVN_VCONV_CPU=1 gives the CPU conversion back
- A texture type mismatch no view bridges is logged once per shader and slot with its texture ID, NVN target and formats (tex_mis_note); it showed Age of Calamity's remaining one: 3D slots 8 and 9 of three vertex shaders bound with 2D textures (bound as is, as before)
- Chain procedure for every profile: the 18 with game files on this drive rebuilt for Mac and iOS (c1_full.tsv / cb_extent.tsv: header lines only; new cb_extent.tsv for Hades II, Cuphead and Monster Hunter Rise), the others header-only with their lists carried over. Mac smoke runs of 13 games: no new red lines
- Tests: t_tbuf2d (row aliases against texture_buffer views), t_vin (shadow inputs against the CPU conversion, every format class), test_dmsh_vin.py, nvn_tbuf_rows in t_nvn
2026-10-22 00:28:29 +08:00
seedds ca2c35c1ae README: STEINS;GATE RE:BOOT plays on the iPhone
AGENTS.md lists it among the playable games (twenty-eight), with what it needed; the README
shows a screenshot from a Mac run. It starts in the 2D touch layout; its quick save (R3) is in the 3D one.
2026-10-21 23:21:01 +08:00
seedds 37c24c6841 STEINS;GATE RE:BOOT plays on the Mac and the iPhone: profile 01008BC024FDE000 (update 1.0.3, SDK 20.5.17)
- M2's ADV engine (m2lib, E-mote characters, PSB archives); 87 H.264 / AAC movies through the SDK's movie library in subsdk0; 487 shaders in five BNSH in main; 60 fps, 1280x720 in both modes by the game's own choice
- nn::audio::SetAudioOutVolume / GetAudioOutVolume: a gain on the samples the host plays; the SDK's versions read the audout session the host never made (null read at the first movie)
- Storage-sharing texture table: a new texture that takes over the storage of an entry under another description evicts it, unless a live texture object still resolves to it or a lookup found it in the last 16 submissions. The game decodes every picture into one reused region of a pool that lives for good: 1.58 GB of GPU memory 24,000 frames into a new game before, about 1.0-1.15 GB over 15 minutes of story after. Captain Toad's opening course unchanged (no takeover). The memory line counts unreferenced entries and takeovers
- DM_PROBE_WATCH=<address>[+<length>]: who writes a guest word, with the page kept protected and every store to it carried out by the fault handler through a writable alias; found the game's render size setter
- Probe backtraces read each frame with vm_read_overwrite (code without frame pointers keeps data in x29)
- Prewarm list recorded from boot through the prologue into the story
2026-10-21 23:18:16 +08:00
seedds 54eac07b0e NSP script: game language option (--language / LANGUAGE, English by default), checked against the languages in the game's control data and packed as DMLanguage in Info.plist 2026-10-21 23:08:03 +08:00
seedds 23a11da7e9 README: IPA size and notes under each game's picture, no games table 2026-10-21 22:08:20 +08:00
seedds f678e30d1d README: The Great Ace Attorney Chronicles plays on the iPhone
AGENTS.md lists it among the playable games (twenty-seven), with what it needed; the game notes describe the port. It starts in the 2D layout; the screenshot (the first episode's trial) is from a Mac run.
2026-10-21 20:40:48 +08:00
seedds 85e6dc18e5 The Great Ace Attorney Chronicles plays on the Mac and the iPhone: profile 010036E00FB20000 (base game 1.0.0, SDK 10.4.1)
- Capcom's MT Framework: ARC archives (version 7, zlib entries) under nativeNXx64. Its 460 shaders (230 vertex, 230 pixel) are in one shader package (SPK, sc\NX_64\root in sa/NX_64/root.arc), each a code area followed by its control section; shader_split.py reads it (mtarc_members, mtspk_shaders) and pairs the vertex shaders with the single-stage cover. All build, every binding decodes (Mario's combined layout)
- SDK 10 calls that reach a service, from the import list against the hooks: oe::DisableAlbumImageTakenNotification (the first boot's crash), oe::IsInStoreDemoModeEnabled, vi::ListDisplays, fs::RenameDirectory, account save thumbnails, the ec::ShowShop overloads without an ApplicationId, friends::Start*FriendRequest, and ngc::ProfanityFilter's UTF-8 masking, SetMaskMode and Finalize called by name. nvnDeviceSetIntermediateShaderCache and nvnCommandBufferDiscardColor / DiscardDepthStencil are no-ops
- Black screen: the engine copies its textures into a CPU-cached pool with the CPU after creating them and never flushes (main has no cache maintenance at all): tex_flush_upload and tex_dirty_upload, as FINAL FANTASY TACTICS
- 30 fps (the game's own pacing), sound, H.264 movies, saves, the DLC reported in Special Contents; about 1.1 GB resident, flat over 14 minutes from the title to the first trial
- Prewarm list recorded through the first-boot notices, the title, the opening movie and the first episode's opening scenes
- The survey names MT Framework ARC archives (profile_guess.py, the NSP script's notes)
2026-10-21 20:37:40 +08:00
seedds e37af0dc87 README: Brain Memory Training Academy plays on the iPhone
AGENTS.md lists it among the playable games (twenty-six), with what it needed; the game notes describe the port. It starts in the 2D layout; the screenshot is from a Mac run.
2026-10-21 20:33:04 +08:00