Tears of the Kingdom (two profile keys, off for every other game): - render_cond: NVN conditional rendering is evaluated at replay from the timestamp reports' counter 0; before, every conditional clear ran and the lighting passes drew nothing. - tex_dirty_upload: textures the game fills from the CPU without an NVN flush (its 121-layer ASTC terrain arrays) are found through write-protected pages and uploaded; before, they stayed empty (magenta in ASTC). Breath of the Wild, issue #9 (iPhone 16 Pro Max killed for memory in the first shrine; an 8 GB iPhone gives the app 3,375 MB): - Finalized textures' serials no longer stay in the serial table for good: live serials are marked at nvnTextureInitialize, dead ones resolve through a 512-entry table (about 10 a frame in BotW before, 276k after 25k frames). - The memory-pool tracking table grows past 4,096 entries (BotW reached 4,006). - Most of the growth was the defer_drain leak fixed in 0fc0ce4: the shrine now stays at 2.8-2.9 GB resident on the Mac, about 500 MB under the limit. - Falls back to 30 fps at the right speed when the GPU can't hold 60: two cuts make the frame-interval controller always use its GPU-time path, and the profile key gpu_timestamps segment gives each timestamp report its segment's GPU start / end (the default, submission, is unchanged). Under load, interval 2 held at 0.93-0.98 of real time (0.78-0.95 before). The check that it still holds 60 on a quiet machine is pending. NVN: clears inside a pass, with a partial channel mask or a partial scissor are drawn as a triangle in the open pass instead of dropped, and 8-bit indices written by the GPU earlier in the submission are widened on the GPU (TOTK red lines 6 / 7 / 48). Adapted from MacNativeGames (MIT, dm/third_party/macnativegames). NVN vertex fetch: 8 / 16-bit integer attributes fed to an input of the other sign are widened by their own sign (CPU conversion, red 96 once); before, the signedness swap widened them the other way and Three Houses' terrain heights below 0 left the screen (no ground under close-up cameras). The pass dump's d line shows polygon offset and depth clamp. NSP script: a GLSLC capture stage for profiles with shader_glslc_capture (Three Houses' 9 run-time shaders), so only the NSPs and prod.keys are needed. AGENTS.md: only the coordinating session runs the multi-game smoke test. Each part was checked on its own game on the Mac. The multi-game smoke test runs after this push (Wonder and Engage already passed on the same changes minus the vertex-fetch fix). The translator fix and its chain rebuild are not in this commit.
Porting a Switch title: agent guide
These pages are for an agent asked to analyse a Switch NSP and port it with the native-execution approach of this repository and d2r-mobile: the game's own aarch64 code runs natively on Apple hardware, a rewriter patches only the instructions Apple's kernel cannot run, the host replaces Horizon system services, and NVN is reimplemented on Metal.
Everything here was learned on Diablo II: Resurrected (SDK ~12, NVN 53, the original target of d2r-mobile) and Super Mario Bros. Wonder (SDK 16.2.0, NVN 55, this repository; its update 1.2.1 uses SDK 16.2.12), then extended by Fire Emblem Engage (Unity, SDK 12.3.7), Metroid Dread (MercurySteam's engine, SDK 11.4.3) 13 Sentinels: Aegis Rim (Vanillaware's engine, SDK 11.4.3, the same sdk and movie module as Dread) Super Mario 3D World + Bowser's Fury (Nintendo EPD's sead / agl engine, SDK 10.4.0, NVN API 53) The Legend of Zelda: Breath of the Wild (EPD's sead / ksys engine, SDK 20.5.22, DT_RELR relocations, update 1.9.0), The Legend of Zelda: Link's Awakening (Grezzo's engine on NintendoWare, SDK 8.2.0, lower-left window origin) and Hyrule Warriors: Age of Calamity (Koei Tecmo's KTGL, SDK 9.6.0, RDB archives, shaders as GLSLC outputs). Where titles differed, each is given; the next title will differ again, so treat every number below as "measure it, don't assume it".
| Page | Covers |
|---|---|
| 01-extract.md | Keys, NSP → NCA → ExeFS / RomFS, update NSPs, the workspace layout |
| 02-survey.md | Static survey of a new title before touching the host: SDK version, imports vs. host hooks, SVCs, instructions to rewrite, NVN surface, data containers, Ghidra / REA setup |
| 03-bringup.md | Getting the host to boot the game: the run loop, how to read the log, and a catalogue of every failure pattern met so far with its root cause and fix |
| 04-graphics.md | NVN on Metal for a new title: object sizes, shader containers and keys, binding layouts, bindless textures, depth mode, index sizes, swizzle, transient render targets, the diagnostic switches |
| 05-debugging.md | Recipes: crash reports, lldb scripts, Ghidra headless decompilation, objdump, memory growth, render-pass and texture dumps, on-device diagnostics, offline pipeline link checks |
Ground rules
- Bring your own files. Keys, NSPs and anything extracted from them stay in the game's work folder (
$W=<NSP folder>/<title>-work, next to the NSPs on the user's external drive; the home folder is nearly full), never in the repository, never in a commit, never printed. Print key names and lengths only. - Measure, then change. Each fix in this repository started from a concrete observation (a crash address and its decompilation, a census over all shaders, a memory profile). Write the evidence into the code comment next to the fix, the way the existing code does; the next title will need it.
- One repository, one profile per title. nxnative (first called smbw-mobile) started as a copy of d2r-mobile's generic parts. Title- or SDK-specific constants live in the game profile
dm/games/<id>/(dm/games/README.md). That covers program ID, known builds, modules, address-space partition, NVN driver tables, the shader tables and the app identity. The code reads the profile throughgame_profile.h(host) ordm/games/game.py(tools). Behaviour that fits only one title's code is gated by that title's build ID (dm_main_build()), never left on for every title. Some things are still built into the translator: the texture / sampler handle bases and the depth mode (see 04).dmsh_build.pyrefuses a profile whose bases differ from the translator's. - Write code comments in English, and keep the
【红】prefix for red log lines that mark something unimplemented or wrong. - Test where it runs. The Mac host (
dm/host/dmhost) is the development loop; the iOS app reuses the same host and graphics code. Never claim something works on iOS without running it there.
Checklist for a new title
-
Run
scripts/build_ipa_from_nsp.pyon the title's NSP folder. With no profile for its title ID, it writes<NSP folder>/<title ID>_survey/survey.md:- modules and build IDs, SDK version and components, engine and graphics API markers;
- RomFS containers;
- the rewrite-site census and imports vs. host hooks.
It extracts only the code modules plus a RomFS list for the survey. Unless given
--survey-only, it then writes a provisional profile into<NSP folder>/<title ID>_build/profile/<title ID>/, extracts the game and packs a best-effort IPA. That profile is the starting point for step 3: the values are guesses (dm/games/README.md). For Mac runs, exportDM_GAMES_DIR=<NSP folder>/<title ID>_build/profileuntil the profile moves intodm/games/. -
Extract base (and update, if in scope) — 01. Finish the survey — 02. Write down: SDK version, NVN API version, module list, imports the host lacks, SVCs, instruction census, NVN loader pattern, shader containers.
-
Add a game profile
dm/games/<title ID>/game.tsv, named after the base game's title ID (start from010015100B514000/game.tsv: identity,buildrows,modules; keep the memory / NVN values until they are measured) and build the host for it withmake -C dm/host GAME=<id>. -
Run the rewriter (
DM_RULESET=native) and check its counts againstinsn_census.py. -
Boot on the Mac and iterate on the failure catalogue in 03 until the game reaches its frame loop.
-
Extract and translate the shaders, find the binding layout by census, and get the first frame — 04.
-
Check memory over a minute of running (
footprint, the host's memory report line); fix growth before moving on. -
Input, audio, then the iOS bundle (d2r-mobile's
dm/ios, RomFS inside the app). -
Test on a phone: an unsigned IPA the user sideloads, switches through
env.txt, results throughDocuments/log.txt(05). Several bugs only showed there (the command-buffer byte and the audio buffer size in 03, pipeline link failures in later levels in 04). -
For a game update: extract it over the base game, diff the imports, carry the per-build addresses over, and rebuild the shader tables for both versions (01).