Commit Graph
131 Commits
Author SHA1 Message Date
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 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 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
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 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 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 aea97ec65f Add ANIMAL WELL port verified on iPhone 2026-10-23 15:15:42 +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 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
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 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 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
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 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 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 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 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