Cuphead (Unity 2017.4) main+0x1bb872c loads the table entry into x17 and only the add writes x18: cmp w17,#7; ldrsw x17,[x15,x17,lsl #2]; add x18,x17,x15; br x18. jump_offsets now takes the loaded register from the add; the walk from the index back to the bounds check is unchanged. Ported from GitHub pull request #1 onto the current jump_offsets (the PR's other two shapes already pass since Link's Awakening's subsdk work). All 35 rewritable modules of the 9 extracted titles give byte-identical output. Co-authored-by: Jeffrey Wang <11900676+armyja@users.noreply.github.com>
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) and The Legend of Zelda: Link's Awakening (Grezzo's engine on NintendoWare, SDK 8.2.0, lower-left window origin). 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. The README's Chinese half is the only Chinese text. - 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).