- Log messages, script output, test labels, Makefile errors and comments are English. Red log lines start with "[RED] " (was 【红】). - Shader map stage column: vertex / pixel / compute / geometry / hull / domain (generators, dmsh_build / dmsh_facts and nvn_mtl.m stage_of follow). - The NSP script's regexes match the new shader_split.py / dmsh_build.py summary lines; patch_nso cut scopes are all / host-only. - Host UI text table (dm_i18n_tab.h) is English only; dm_lang_zh() removed. - Text that matches game data (t_swkbd.c, t_textbox.c) is written as \x escapes. - Shader chain: the chain files lose their bilingual comments, so every profile has a new chain version. dmsh_xlate.dll rebuilt from the translated Program.cs (SPIR-V byte-identical on 4,500 shaders of Wonder, TOTK and Age of Calamity), lock and dmsh_chain.h headers regenerated. c1_full.tsv restamped for Dread, 13 Sentinels and Wonder; the other profiles follow when their rebuild finishes. - AGENTS.md and docs/agents: the language rule now covers logs; quoted log lines updated.
6.5 KiB
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) Hyrule Warriors: Age of Calamity (Koei Tecmo's KTGL, SDK 9.6.0, RDB archives, shaders as GLSLC outputs) and Super Mario Galaxy (a Wii game on NERD's Hagi layer: recompiled PowerPC code, SDK 18.3.5, Vulkan with SPIR-V generated at run time, a 2.9 GB bss). 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 start log lines that mark something unimplemented or wrong with
[RED]. - 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).