
- Title ID
- 010093801237C000
- Tested build
- 2.1.0
- Nintendo SDK
- 11.4.3
- Engine
- MercurySteam's engine
- Graphics API
- NVN
Build this game
Keep [010093801237C000] in your file names, put the base game, update and DLC in one folder and run the script. Follow the build guide, then the sideloading guide. Needs an iPhone 15 Pro or newer, or an M1 iPad or later.
Technical notes
The project's own record of bringing this game up: what its profile needs and why, what was found and what is still open.
Update 2.1.0 (v327680) over the base game, SDK 11.4.3. MercurySteam's own engine on NVN; scripts are Lua 5.1 bytecode (.lc), audio is NintendoWare Atk. Modules main, subsdk0 (NintendoSDK_movie 11.4.3) and sdk; subsdk0 and sdk are the builds 13 Sentinels: Aegis Rim ships (01003FC01670C000), and the sdk is also Monster Hunter Rise's and Big Brain Academy's. Plays on the iPhone; gameplay at 60 fps, cutscenes at 30, as on the Switch.
What it needs
| Setting | Where | Why |
|---|---|---|
2.25 GB block: mem_reserve 0x90000000, mem_alias 0x10000000, mem_stack 0x10000000, app_pool 0xCD500000 |
game.tsv |
no system resource; 256 MB in front + 256 MB alias region + 256 MB stack region + 1.5 GB heap region. See Memory |
modules main subsdk0 sdk |
game.tsv |
subsdk0 is the movie library, which the game's movies go through |
shader_groups /system/shd/=shd |
game.tsv |
every shader is in system/shd/*.bshdat; see Files and shaders |
Memory
- The NPDM declares no system resource (META +0x14 = 0), so there is no virtual address memory: the game uses
SetHeapSize.main's bss is about 1.8 GB (0x70cd2fa0, the engine's static heap); it is part of the module image, not of the block. - Measured (
DM_SVC=1): the alias region gets one page (4 KB) mapped during SDK initialization; thread stacks go into the stack region throughMapMemory; the heap request is pool − used =0x58800000(1.38 GB, with the 1.8 GB bss counted as used). The block needs only 2.25 GB; withmain's 1.9 GB image that is about 4.3 GB, below the 5 GB proven on a phone (D2R). The application pool is still the Switch's 3285 MB. - Resolved:
SetHeapSizeasked for almost the whole application pool and failed at boot (0xc9400000, larger than the heap region). The SDK sizes the heap as total − used memory, and the host reported "used" as heap + 64 MB, which covers ordinary modules but not Dread's 1.8 GB bss, which the Switch counts as used. Used memory now adds the bss of modules in the front-patch layout (big_bss_bytesinhle_svc.c); the game then asks for 1.38 GB. - The submit-thread autorelease leak was first seen here: GPU allocated climbing while the residency set,
shared texturesand the upload counts stay flat, because an objc call on the guest submit thread outsideg_sub_poolleaks what it autoreleases (05, Memory growth; Breath of the Wild's issue #9 had the same cause indefer_drain).
Rewriter
- Resolved: a
B()range assertion inpatch_nso.py(site_plain) formainonly. With the 1.8 GB bss, a patch area appended after the image starts 1.9 GB from the code, and noB(±128 MB) reaches it. When an appended patch area would start beyond 112 MB, the rewriter now puts it 32 MB before the module base (negativePATCH_OFF);patched.binthen holds only text + ro + data (FILE_IMAGE_LEN) followed by the patch bytes, and the loader maps the bss and the key page as zero pages (IMAGE_SIZE). - The spin-wait search with the lock, test and store rules finds one more loop in this game than the earlier search (development.md, Power).
Movies
The game plays VP9 WebM movies through the SDK's own demuxer and a decoder in native-texture mode.
- Current:
movie::Extractor(libwebm insubsdk0) runs natively. Resolved: the host used to forceSetDataSourceto fail (Engage's Unity demuxes by itself), so the game crashed on a null right after the failed open. - Current: the decoder is created with output format 3 and mode 2 (
DecoderMode_NativeTexture). The client'smovie::Buffercarries theNVNtextureobject itself as its data, 8 bytes of capacity (anRGBX8_SRGBtexture embedded in the game's texture pool entry,main+0x226c98), plusSetNvnDev/SetNvnSync. The host converts the VP9 picture to RGBA (BT.709 / BT.601 and range from the stream), writes it into the texture's block-linear storage likeWriteTexelsand marks the sync as signalled (nvn_host_write_texture/nvn_host_signal_syncinhle_nvn.c); the replay layer re-uploads the dirtied pages. - Resolved: the game calls the complete-object destructor
movie::Decoder::~Decoder(D1), which the host now takes over too; the SDK's own one destroyed the host object as aDecoderImpl. - Current: the game's
RegisterClientEventsstruct is overwritten by its second movie. The host first copied the four event pointers; it now keeps the pointer and reads the entry each time it signals, as the SDK does (md_eventinhle_movie.c), and when the client's entry is not a readable, 8-byte aligned pointer it falls back to the entry's value at registration (this game's case). - The game ends a movie with
Flush,Flush,Stop, destroy, and callsStartafter aFlushwhen it loops. As with MediaCodec,Flushannounces nothing, and no input or output index is handed out while the decoder is stopped. The "input available" afterStartis signalled about 2 ms later from a guest thread, as the SDK's codec thread would; pending signals are dropped atStop,Flushand destruction. - Resolved: a picture 16 frames ahead, then back (GitHub issue #700). One player thread acquires each output and another gets it into the texture and releases it, the pattern the issue found in TSUKIHIME. Mac, 2026-10-30: fresh save,
DM_KEYS=4:A*20/0.7,DM_MOVIE_TRACE=1, 75 s, no shader libraries for the current chain (draws skipped; the movie threads run as usual). Before, every one of the 2,119 releases found its slot already returned and 15 pictures were 533 ms (16 frames) ahead of the one before, the next going back; after, the 2,124 releases each returned a slot the game held, and the pictures went in order (the only steps back were the 8 loops,Flush,Flush,Start). Logs:run/issue700-old.logandrun/issue700-new.login the NSP work folder. Fix: A native-texture movie shows a picture 16 frames ahead, then goes back. - Resolved: crashes around movie changes in the game's own player threads. The main thread (priority 44, core 0) posts link / unlink requests into the dispatcher's ring (
main+0x222cc8) and leaves work for a second thread (both priority 30, core 0 only), then frees and rebuilds the movie object without an explicit acknowledgement. The shared same-core wake handoff now supplies the ordering. The build-specific ring waits (OBS 19 / 20 atmain+0x2247a4/main+0x2248e4,mainbuild646761f6…) and the profile'score_preempt onare removed. - Mac confirmation (2026-10-26): with both former workarounds disabled, the old host crashed in 2 of 3 fresh-save runs, at
sdk+0x347eb4called frommain+0x225338(17.2 / 18.7 s). The shared handoff passed the isolated dispatch-delay regression and 13 fresh-save movie-transition runs with the current Mac shader libraries (45–65 s each), with both former workarounds disabled. This includes the regenerated main module with zero observation gates. Temporary read-only probes saw all 33 sampled request posts already drained without waiting. Game files, logs and isolated saves are in the NSP work folder'srun/movie-sync/. iOS validation of this replacement remains open.
Frame rate
- Current: cutscenes at 30 fps, gameplay at 60, as on the Switch. The game keeps present interval 1 throughout. In a cutscene its main loop spins in 1 ms steps until the cutscene's audio clock advances, then renders a frame; that clock moves with a 30 Hz sound thread.
- Resolved: cutscenes at 36 ms per frame instead of 33.3 (about 27 fps; gameplay was fine at 60). The sound thread (
main+0xf95b0) reads a millisecond clock, updates, then sleeps "33 ms minus this round's work"; macOS timer slack (nanosleepandmach_wait_untilovershoot by about 10% at any QoS: 33 ms → 36.4 ms, 5 ms → 6.1 ms, 1 ms → 1.26 ms) made its period 36.9 ms. The cutscene loop (main+0x119a3d0) renders a frame whenever the audio play position, which that thread refreshes, moves (played samples ÷ the sample rate of the cutscene's stream).SleepThreadnow sleeps three quarters of the remaining time per step until within 20 µs (sleep_preciseinhle_thread.c): cutscenes at 33.3 ms, the sound thread at 33.2 ms.
Rendering
- Resolved: scenes and characters black, only foliage, light shafts and the HUD drawn. NVN's
BindProgram(program, stageBits)binds per stage, and a null program unbinds just those stages. The depth prepass callsBindProgram(program, vertex | fragment), thenBindProgram(null, fragment), and draws depth with the vertex shader alone. The host replaced its single bound program on every bind, so a null bind meant "no program bound", and all of the prepass's ~140 draws per frame were dropped. The depth buffer stayed empty, and the G-buffer passes, which test depthEQUALwithout writing, passed no pixel. The replay now tracks the program per stage (bind_stagesinnvn_mtl.m) and builds a composite program when the stages come from different programs or the fragment stage is unbound (a vertex-only pipeline for the prepass). The combinations the game uses: (program, 0x3), (null, 0x2), (null, 0x7). Found through the pass dumps' newZlines for dropped draws: 144Z … draw skipped: no program boundlines in a frame whose G-buffer pass had draws but stayed black. - Resolved:
WriteTexelsone texel at a time. The game's set-pixel helper (main+0x160e80) writes a 1×1 region per call (probably the map / minimap mask). The host used to decode the whole level, patch it and encode it again for every call; after a few minutes of play the main thread spent whole frames there (frames stopped at about 175 s). Non-3D regions are now written in place by block-linear address (write_region_blinhle_nvn.c);t_nvnchecks 15 regions in RGBA8, BC1 and a two-layer RGBA16F array, from 1×1 to the whole level, byte for byte against the old whole-level path. - Inline uniform updates: the cutscene frames have about 180 passes with one draw each, because the game updates uniforms inline (
CommandBufferUpdateUniformBuffer) between draws and every copy or update ended the host's render pass (about 5 ms of submit time per frame on the Mac). An inline uniform update inside a pass no longer ends it (the game ended about 246 passes a frame this way,DM_NVN_UB_DEFER=0restores the old behaviour); its attachment loads and stores per frame stayed unchanged, as its updates already covered what the draws read (development.md, Power). - Texture handles use Mario's layout,
0x20 + 8 × slot: theTEX/TEXShandle immediates in its shader files' own SASS text are words 0x8–0x22 in steps of 2. Its full build leaves no unresolved bindings and no unfillable driver-bank reads; the other binding rows are not measured separately. - Depth mode: the game keeps GL's (near = −w).
- It logged
depth-stencil clear with a scissor or partial stencil mask, cleared wholeonce while loading on its smoke route.
Services
- Resolved, SDK 11 names:
fs::CommitSaveData(mount)is the older name offs::Commit;fs::DeleteDirectory(an empty directory) was missing too. - Resolved: abort in
nn::sm::Initializeundernn::oe::FinishStartupLogo, right after the first frames: it reaches the applet proxy througham::GetApplicationFunctions. The host has no startup logo: a no-op hook (hle_oe.c). - Resolved: abort in
nn::sm::Initializeundernn::hid::VibrationNode::Update. The SDK's vibration graph flushes queued values withhid::detail::SendVibrationValues, which opens the hid service; the game sets handles on itsVibrationTargets even when the host reports no vibration device. The flush calls it through the PLT, so anHLE_FORCEon that symbol catches it and returns success (hle_hid.c).
Audio
- Resolved: an Atk task aborted in a
switchdefault (UnexpectedDefaultImpl, thenAbortImpl) while a stream sound loaded (main+0x1225a8c). The stream path wasrom:/system/snd/../../sounds/streams/…, and the host answered "not found" for any..component. Horizon's path normalizer resolves.and..lexically and fails only above the mount root;host_path_exnow does the same (norm_restinhle_fs.c).
Files and shaders
- Every shader is in
system/shd/*.bshdat(MSHD containers; nothing inmainor the.pkgpacks), which is also the profile'sromfs_marker(system/shd). Each stage is a control section (its length at control +0x20, at least 0x880 bytes, sometimes followed by a small compiler-option table such asfastmath true), then the code area at the next code header. Every file holds vertex / pixel stages in alternating order (1, 2 or 4 programs) and is paired two by two. The control section carries NV assembly text after its first 0x880 bytes, and the SASS disassembly follows each code area, so the binding layout can be read straight from the files. - 478 files, 1,342 stages, 1,010 unique keys (359 vertex, 651 pixel), 659 pairs.
Mac routes
Final gameplay validation (2026-10-26, current)
The final #158/#213 review fixes on base c6c4fac were tested in the saved playable cave room, beyond the earlier movie-only smoke coverage. On a Mac M4 Pro (macOS 27.0.1), four isolated copies of run/save ran off / on / on / off with all three optimization switches at 0 or 1. Encoding thread and present snapshots stayed 1, cut 64, silent audio, current 8393d85a4eae shaders and one warmed pipeline archive. Host/source/save hashes were identical between modes. Each run reached frame 4,600 and exited cleanly; frames 2,001-4,200 supplied 2,200 timing samples per run, with no new shader loads or pipelines. The route includes standing, walking, shooting and jumping; screenshots verified those actions and both modes' rendered scene.
| Metric (mean of the two runs) | Off | On |
|---|---|---|
| Frame interval | 16.689 ms | 16.690 ms |
| GPU time | 4.007 ms | 4.014 ms |
| Submission CPU | 0.569 ms | 0.604 ms |
Neither slice merging nor compute hoisting activated in this room. GPU results varied between repeat runs (on means 3.829 and 4.199 ms) and show no reproducible gain; submission CPU rose about 0.035 ms. Both modes stayed about 59.9 fps without a red line or crash. HUD fading and second-based input holds vary the pass/draw counts and camera position slightly, so screenshot comparisons establish no obvious regression, not pixel identity or an identical workload at every frame. GPU clocks were not fixed. This is broader NVN gameplay coverage, not evidence of a pass-optimization benefit here; BotW's active-switch comparison records the performance result where both activate. iOS runtime validation remains open.
Artifacts in the NSP work folder's run/perf-final-gameplay-20261026/: source/save/host identities, summary.json, per-run logs, frame timings, analyses, screenshots and reproduction scripts. An earlier movement pilot's AudioUnit callback warning is excluded; all measured runs use the same silent sink. Reproduction starts with DM_KEYS=4:A*20/0.7, then frame-anchored right/shoot/jump/left sequences beginning at frame 3,100 (the complete key string is in each run.json).
After integration with main's pipeline archive and profiling changes (7753b7b), two additional on/off frame-loop checks reached frame 4,400 and exited without a red line or crash. Display-link stalls and missing requested screenshots exclude these from the gameplay/timing evidence above. Separate source identities, hosts, saves and logs are in run/perf-main-integration-20261026/; BotW's integration note records the passing integration tests and the subsequent unrelated main update.
Earlier smoke routes
- Synchronization smoke check (2026-10-26, Mac M4 Pro): #158 and #213 combined with their review fixes, current
8393d85a4eaeMac libraries, final movie-handoff modules, isolated fresh saves. A 60 s run with fast wake, both pass optimizations and deferred encoding completed six movie configurations and 3,397 presented frames; the comparison with all three optimizations disabled and direct encoding completed six and 3,334. No red line or crash in either successful run; frame 1,800 was visually checked in the opening movie. This covers movie/render transitions, not gameplay or a performance comparison, and neither pass optimization activated. Logs, saves and shots:run/movie-sync/perf-order-on-0/andperf-order-off-fixed-0/in the NSP work folder. An earlier comparison aborted because the relocated host lackedDM_NVN_SHMAP; the successful comparison sets that absolute path explicitly. General completion and dependency fixes: SyncWait completion, slice merging and compute hoisting. iOS runtime validation remains open. - One
DM_KEYSswitch runs through the title, a new game and the story recap (05, Scripted input and thread scheduling). The key string itself is not in the docs.
Found on this game
General host changes first needed here; each applies to every game:
- Synchronization review gameplay validation: Final gameplay validation records the #158/#213 comparison and its limits; the general fixes and synthetic regressions are linked under the earlier smoke routes.
- Patch areas before the module base for modules with a giant bss (negative
PATCH_OFF,FILE_IMAGE_LEN,IMAGE_SIZE): Patch area before the module base for a large bss. - Used memory counts the bss of front-patch modules (
big_bss_bytes): Heap sized from the application pool less used memory. - Recheck after handoffs were limited to the threads a wake makes runnable (issue #247, 2026-10-26, Mac): fresh saves,
DM_KEYS=4:A*20/0.7, 60 s each, no shader libraries for chain8393d85a4eae(draws skipped; the movie threads run as usual). A control host with the handoff turned off crashed in 1 of 3 runs atsdk+0x347eb4called frommain+0x225338, the lifetime crash above. The limited handoff passed 12 of 12 runs, each with three decoders and two teardowns, about 12,400 handoffs and 1.2–1.6 s of handoff waits. Runs are in the NSP work folder'sbug06/. sleep_preciseagainst macOS timer slack, same-core wake handoffs (svc_thread/dm_yield_to_core_peers): Timer slack in guest sleeps (sleep_precise), Same-core wake handoffs.- Native-texture movies (
nvn_host_write_texture,nvn_host_signal_sync), theD1destructor,md_event, the SDK 11 names,FinishStartupLogo,SendVibrationValuesand lexical..(norm_rest): Decoding into the client's NVN texture, The complete-object destructor, Client events read through the registered pointer, Older SDK names for the same calls, The startup logo, Vibration values flushed by the SDK's vibration graph, Paths with.and..components. - Native-texture slots kept until the client's release (
md_rendered): A native-texture movie shows a picture 16 frames ahead, then goes back. - Per-stage program binding (
bind_stages, composite programs,Zlines in pass dumps), in-placeWriteTexels(write_region_bl) and the MSHD reader: Programs bound per stage,WriteTexelsregions written in place by block-linear address and MSHD in MercurySteam's bshdat files. - The submit-thread autorelease leak (
g_sub_pool): 05, Memory growth. - Inline uniform updates inside a pass (
DM_NVN_UB_DEFER): development.md, Power.
Open
- 04's Metroid Dread section, now Inline uniform updates inside a render pass, said every copy or update ends the host's render pass (about 180 one-draw passes in cutscenes); development.md, Power says an inline uniform update no longer does (about 246 passes a frame before). The cutscene pass count with the deferred update is not given.
game.tsvopens with "Bring-up has just started: the memory values are Mario's until measured", while its memory section gives measured values.- dm/games/README.md, Adding a game gives
0x90000000as this game's heap region in the guesser's 4 GB layout; ingame.tsv0x90000000is the whole block (mem_reserve) and the heap region is 1.5 GB. - Only the merged 2.1.0 copy is extracted; the base game's own build IDs are not in the profile.
These notes are republished from the nxnative repository under its MIT license (license and attribution). The original, which may be newer, is the game's notes on GitHub. This site hosts no game files.