45 KiB
Game profiles
Each game the port knows has a folder dm/games/<id>/, named after the base game's title ID: Mario's is 010015100B514000, Engage's 0100A6301214E000. The folder holds:
game.tsv: the game's constants, onekey<TAB>valuerow each, with comments on their own lines.- The shader tables that
shader_split.pywrites:shader_map.tsv(+.conflicts.tsv),shader_facts.tsv,shader_pairs.tsv, andpso_pairs.tsv(the pairs of agl BAHS program variations, read only by the pipeline prebuild; a profile without it still packs). shader_facts_manual.tsv(optional): hand-written translation facts inshader_facts.tsv's row format, for what the machine code cannot tell (Link's Awakening: a vertex input the integer-input guess gets wrong; Breath of the Wild and Tears of the Kingdom: integer fragment outputs, which a game without a PC version has no other source for).shader_split.pymerges it over the generated rows; since the table is on the chain, changing it changes only that profile's chain version.pso_manifest.tsv: the prewarm seed list.vk_capture.tsv(a Vulkan game that generates its shaders at run time, Super Mario Galaxy): the keys of the SPIR-V modules it generates.gl_capture.tsv(an OpenGL game that puts its GLSL together at run time, Balatro): the files (<key>.vert/<key>.frag) of the shaders it compiles.bf_want.tsv.README.md: the game's notes: what its profile needs and why, what was found on it, how to reach a scene on the Mac, what is still open. Listed in Games.patches.py(optional): the rewriter's patches of the game's own builds (main), keyed by NSO build ID:CUTS(byte cuts, each original asserted),OBS(observation gates) andFRAME_STEP(frame-step stubs).dm/tools/patch_nso.pyloads every profile's file and applies those of a module's build, wherever it comes from; a build claimed twice stops the rewrite. Cuts of Nintendo'ssdkmodules, which several games ship, are indm/tools/sdk_patches.py. Opt-inMATCH_CUTScan locate verified cuts in unregistered builds of the selected game by function fingerprints; see Function-matched cuts for enrollment and limits. A game without patches has no file, and a derived profile uses its repository profile's rules.dm/tools/tests/test_patches.pychecks that a file names only its own profile's builds.
The chain version is not in the profile: one version serves every game (dm/gfx/nvn/dmsh_chain.h, written by dmsh_chain.py --header). Two more tables live in the profile folder but are not committed (git ignores them). A full dmsh_build.py run makes both from its Mac outputs, so each checkout makes its own, and stamps its libraries with the digest of the profile's inputs (b-<V>/inputs.tsv, dmsh_chain.py --inputs). The host takes a table only when its header names its library's chain version and that digest.
c1_full.tsv: written byc1_guard.py. Without it every shader's c1 gets its own 64 KiB.cb_extent.tsv: how far into each uniform slot every shader can read, written bycb_extent.py. Without it a uniform block the game updates in part inside a render pass is copied whole.
Everything game-specific belongs here. Code reads the profile; it does not hard-code a game.
| Who | Picks the profile with | Reads |
|---|---|---|
Shader tools (shader_split.py, dmsh_chain.py, dmsh_build.py, dmsh_facts.py) |
DM_GAME=<id> |
tables, shader_* keys |
Host (make -C dm/host, dm/gfx/tests) |
GAME=<id> |
gen/<id>/game_profile.h, generated from game.tsv by game.py header |
dm/ios/tools/build_ipa.sh |
GAME=<id> |
app name, bundle ID, IPA name, modules, RomFS marker, tables |
scripts/build_ipa_from_nsp.py |
the NSPs' title ID | everything above. With no matching profile it writes a survey, then a provisional profile outside the repository and a best-effort IPA (--survey-only stops after the survey) |
None of them has a default game: without one they stop and list the profiles. To query a profile:
python3 dm/games/game.py listlists the profiles.python3 dm/games/game.py <id> <key>prints one key.
Launcher settings. settings.tsv lists the player-facing settings once (the environment variable each sets, its kind, default, the condition under which a game offers it and its section on the single settings screen, Game, Controls or Performance; Reset to tested defaults forgets all launcher choices so the profile's defaults apply again; its header describes the columns). A profile key a setting defaults to (DM_GAME_<NAME>) is the value the game was tested with, which the player page docs/settings.md explains. build_ipa.sh runs settings.py <id> <rewritten modules> <control.nacp> <out.json>, which keeps the rows that hold for the game: a profile macro (DM_GAME_FPS_TARGET!=0), a key in main's rewriter table (RENDER_SCALE, written by patch_nso.py for the builds with a render-scale stub), the game's languages. It writes them into the app as dm/launcher.json, from which the iOS launcher (dm/ios/Sources/Launcher.swift) draws its screen. A new player setting is one row; diagnostic switches stay environment-only.
Games
One row per profile; the notes are each profile's README.md.
| Game | Title ID | Build | SDK | Engine | Graphics |
|---|---|---|---|---|---|
| 13 Sentinels: Aegis Rim | 01003FC01670C000 |
1.10 | 11.4.3 | Vanillaware's engine | NVN |
| 13 Sentinels: Aegis Rim, Asian release | 01008D7016438000 |
1.00 (base) | 11.4.3 | Vanillaware's engine | NVN |
| Absolum | 0100A83022A22000 |
1.1.1 | 18.3.2 | FNA / .NET NativeAOT (FNA3D's NVN driver) | NVN |
| Ace Attorney Investigations Collection | 010005501E68C000 |
1.0.0.2 | 17.5.3 | Unity 2022.3.17f1 IL2CPP | NVN |
| Advance Wars 1+2: Re-Boot Camp | 0100300012F2A000 |
1.0.0 (base) | 10.4.1 | Unity 2020.2.1f3 IL2CPP | NVN |
| AI: THE SOMNIUM FILES | 010089B00D09C000 |
1.0.1 | 6.4.2 | Unity 2017.4.17f1 IL2CPP | NVN |
| ASTRAL CHAIN | 01007300020FA000 |
1.0.1 | 6.4.0 | PlatinumGames' engine | NVN |
| AI: THE SOMNIUM FILES - nirvanA Initiative | 0100713016CCC000 |
1.0.2 | 13.3.5 | Unity 2020.3.30f1 IL2CPP | NVN |
| ANIMAL WELL | 010020D01AD24000 |
1.0.10 | 20.5.7 | its own (not named) | NVN |
| Animal Crossing: New Horizons | 01006F8002326000 |
3.0.3 | 20.5.4 | Nintendo EPD (sead / agl / gsys) | NVN |
| BALL x PIT | 010086A022444000 |
1.293 | 20.5.19 | Unity 6000.0.62f1 IL2CPP | NVN |
| Balatro | 0100CD801CE5E000 |
1.1.3 | 18.3.2 | LÖVE 11.4 (love-nx, LuaJIT, SDL) | OpenGL |
| Bayonetta | 010076F0049A2000 |
1.1 | 3.6.0 | PlatinumGames' engine (Wii U form) | NVN |
| Bayonetta 2 | 01007960049A0000 |
1.2 | 3.6.0 | PlatinumGames' engine (Wii U form) | NVN |
| Bayonetta 3 | 01004A4010FEA000 |
1.2.0 | 13.3.2 | PlatinumGames' engine | NVN |
| Big Brain Academy: Brain vs. Brain | 0100620012D6E000 |
1.2.0 | 11.4.3 | NintendoWare Bezel Engine 1.56 | NVN |
| Brain Memory Training Academy | 0100F5D01DA3A000 |
1.0.0 (base) | 16.2.4 | Unity 2022.3.5f1 IL2CPP | NVN |
| Captain Toad: Treasure Tracker | 01009BF0072D4000 |
1.4.2 | 8.2.0 | Nintendo EPD Tokyo (al over sead / agl) |
NVN |
| Catherine: Full Body | 0100BF00112C0000 |
1.0.1a | 8.2.1 | Atlus GFD (CRI middleware) | NVN |
| Celeste | 01002B30028F6000 |
1.4.1.1 | 15.3.2 | MonoGame (C# → C++ by BRUTE) | NVN |
| Cuphead | 0100A5C00D162000 |
1.3.7 | 5.5.0 | Unity 2017.4.9f1 IL2CPP | NVN |
| DARK SOULS: REMASTERED | 01004AB00A260000 |
1.0.3 | 4.5.0 | FromSoftware's engine | NVN |
| Digimon Story Cyber Sleuth: Complete Edition | 010014E00DB56000 |
1.0.3 | 6.4.0 | Media.Vision MVGL | OpenGL |
| DRAGON QUEST VII Reimagined | 0100A9D01C446000 |
1.2.0 | 19.3.1 | Unreal Engine 4.27 | NVN |
| DRAGON QUEST XI S: Echoes of an Elusive Age – Definitive Edition | 01006C300E9F0000 |
1.0.4 | 7.3.2 | Unreal Engine 4.18 | NVN |
| FINAL FANTASY RESONANCE | 0100606026F12000 |
1.0.1 | 20.5.17 | Unreal Engine 5.6.1 | NVN |
| FINAL FANTASY TACTICS - The Ivalice Chronicles | 010038B015560000 |
1.4.0 | 19.3.9 | Square Enix's engine | NVN |
| Fire Emblem Engage | 0100A6301214E000 |
2.0.0 | 12.3.7 | Unity 2020.3.18f1 IL2CPP, Wwise | NVN |
| Fire Emblem: Three Houses | 010055D009F78000 |
1.2.0 | 6.5.0 | Koei Tecmo's KTGL | NVN |
| Ghost Trick: Phantom Detective | 010029B018432000 |
1.0.0 (base) | 13.3.0 | Capcom's RE Engine, Wwise | NVN |
| Hades | 0100535012974000 |
1.0.38233 | 9.3.0 | Supergiant Games' engine (The Forge) | Vulkan |
| Hades II | 0100A00019DE0000 |
base game | 19.3.9 | Supergiant Games' engine | NVN |
| Hollow Knight: Silksong | 010013C00E930000 |
1.0.30000 | 20.5.8 | Unity 6000.0.50f1 IL2CPP | NVN |
| Hyrule Warriors: Age of Calamity | 01002B00111A2000 |
1.3.0 | 9.6.0 | Koei Tecmo's KTGL | NVN |
| Luigi's Mansion 3 | 0100DCA0064A6000 |
1.4.0 | 7.3.2 | Next Level Games' engine | NVN |
| Mario Kart 8 Deluxe | 0100152000022000 |
4.0.0 | 4.4.0 | Nintendo (sead / agl / gsys, NintendoWare, Pia) | NVN |
| Mario vs. Donkey Kong | 0100B99019412000 |
1.0.1 (cartridge base) | 15.3.0 | Nintendo (sead / agl / gsys) | NVN |
| Metroid Dread | 010093801237C000 |
2.1.0 | 11.4.3 | MercurySteam's engine | NVN |
| Monster Hunter Rise | 0100B04011742000 |
16.0.2 | 11.4.3 | Capcom's RE Engine | NVN |
| Monster Train 2 | 010051701FB46000 |
1.1.1 | 18.3.5 | Unity 2022.3.54f1 IL2CPP | NVN |
| NieR:Automata The End of YoRHa Edition | 010056B015FE8000 |
1.0.3 | 12.3.9 | PlatinumGames' engine | NVN |
| Octopath Traveler 0 | 01005270232F2000 |
1.0.5 | 19.3.13 | Unreal Engine 5 | NVN |
| OCTOPATH TRAVELER II | 0100A3501946E000 |
1.1.1 | 12.3.2 | Unreal Engine 4.27 | NVN |
| Paper Mario: The Thousand-Year Door | 0100ECD018EBE000 |
1.0.1 | 15.3.2 | Intelligent Systems' engine on NintendoWare | NVN |
| Patrick's Parabox | 0100E44016C22000 |
1.0.1 | 13.3.7 | Unity 2020.3.30f1 IL2CPP | NVN |
| Persona 5 Royal | 01005CA01580E000 |
1.0.2 | 13.3.2 | Atlus GFD / Needle (CRI middleware) | NVN |
| Pikmin 4 | 0100B7C00933A000 |
1.1.1 | 10.4.8 | Unreal Engine 4.26.2 | NVN |
| Pokémon Legends: Z-A | 0100F43008C44000 |
2.0.2 | 19.3.11 | Game Freak's Trinity engine | NVN |
| Pokémon Shield | 01008DB008C2C000 |
1.3.2 | 7.3.2 | Game Freak's engine (before Trinity) | NVN |
| Pokémon Sword | 0100ABF008968000 |
1.3.2 | 7.3.2 | Game Freak's engine (before Trinity) | NVN |
| Portal | 01007BB017812000 |
1.0.3 | 13.3.0 | Valve's Source engine (28 NROs) | NVN |
| Raging Loop | 010020400E1C2000 |
1.0.0 (base) | 7.3.2 | Unity 2018.3.10f1 IL2CPP | NVN |
| Rhythm Heaven Groove | 0100D9F01D474000 |
1.0.0 (base) | 21.4.0 | Nintendo's own 2D engine | NVN |
| ROMANCE OF THE THREE KINGDOMS 8 REMAKE | 0100C2A01A730000 |
1.1.3 | 17.5.3 | Koei Tecmo's KTGL | NVN |
| STEINS;GATE RE:BOOT | 01008BC024FDE000 |
1.0.3 | 20.5.17 | M2's ADV engine | NVN |
| Stardew Valley | 0100E65002BB8000 |
1.6.15.13 | 20.5.27 | MonoGame (C# → C++ by BRUTE) | NVN |
| Super Mario 3D World + Bowser's Fury | 010028600EBDA000 |
1.2.2 | 10.4.0 | Nintendo EPD (sead / agl, NintendoWare) | NVN |
| Super Mario Bros. Wonder | 010015100B514000 |
1.2.1 (and 1.0.0) | 16.2.12 (1.0.0: 16.2.0) | Nintendo EPD (sead / agl) | NVN |
| Super Mario Galaxy | 010099C022B96000 |
1.3.1 | 18.3.5 | NERD's Hagi Wii layer (recompiled PowerPC) | Vulkan |
| Super Mario Galaxy 2 | 0100FD8022DAA000 |
1.4.0 | 18.3.5 | NERD's Hagi Wii layer (recompiled PowerPC) | Vulkan |
| Super Mario Odyssey | 0100000000010000 |
1.4.1 | 7.3.2 | Nintendo EPD (sead / agl, NintendoWare) | NVN |
| Super Smash Bros. Ultimate | 01006A800016E000 |
13.0.5 | 8.2.1 | Bandai Namco / Sora's engine | NVN |
| Taiko Risshiden V DX | 0100346017304000 |
1.1.1 | 12.3.3 | Koei Tecmo's KTGL | NVN |
| The Great Ace Attorney Chronicles | 010036E00FB20000 |
1.0.0 (base) | 10.4.1 | Capcom's MT Framework | NVN |
| The Legend of Zelda: Breath of the Wild | 01007EF00011E000 |
1.9.0 | 20.5.22 | Nintendo EPD (sead / agl / gsys / ksys, Havok) | NVN |
| The Legend of Zelda: Echoes of Wisdom | 01008CF01BAAC000 |
1.1.0 | 20.5.1 | NintendoWare (G3d / Ui2d / Vfx) | NVN |
| The Legend of Zelda: Link's Awakening | 01006BB00C6F0000 |
1.1.0 | 8.2.0 | Grezzo's engine on NintendoWare | NVN |
| The Legend of Zelda: Tears of the Kingdom | 0100F2C0115B6000 |
1.4.3 | 20.5.22 | Nintendo EPD (sead / agl / gsys) | NVN |
| Unicorn Overlord | 010069401ADB8000 |
1.05 | 16.2.4 | Vanillaware's engine | NVN |
Keys
| Key | Meaning |
|---|---|
title_id, program_id |
Identity. program_id is what svcGetInfo(ProgramId) reports. The game's name is not stored: the NSP script reads it from the NSP's control data (NACP) |
name |
The game's name, only a label for game.py list, error messages and the Mac window title. The iOS app's name is the game name in the base NSP's control data and its bundle ID is org.nxnative.<title ID in lowercase>; the NSP script's APP_NAME / BUNDLE_ID and build_ipa.sh's DM_DISPLAY_NAME / DM_BUNDLE_ID override them |
ipa, app_category |
iOS app: the default IPA file name and the App Store category |
build <ver> <display version> <module>=<build ID>… |
One row per version the game was brought up on. The NSP script builds any version (the base game alone, any update): a registered one from the profile as it is, any other from a profile derived from it (<id>_build/profile: the same keys, that version's own shader tables; derive_profile), with a red line naming the modules whose build no row registers, which the per-build cuts and host fixes do not reach. The host reports the display version by main's build ID |
package |
The version the profile's tables, prewarm list and notes were made on. The NSP script packs the newest version in the folder (-p / PACKAGE picks another) |
shader_versions |
Versions whose union the shader tables are (base game first). A registered version is built from the profile as it is only when all of them are in the folder; otherwise from a derived profile, with tables from the packed version alone |
modules |
Code modules rewritten and loaded, in load order. rtld is replaced by the host |
romfs_marker |
A path that must exist in the RomFS; empty checks the directory itself for a program without a data section (ANIMAL WELL) |
zip_store |
Extensions the IPA stores without compressing |
mem_reserve, mem_alias, mem_stack, app_pool |
Guest address-space partition and application pool (hle_svc.c) |
mem_used |
Memory GetInfo(UsedMemorySize) reports beside the heap (default 0x4000000, 64 MB). SetHeapSize checks a request against the real size of the loaded modules, so a game that sizes its heap as total − used and has more than 64 MB of modules needs at least their size here. Hyrule Warriors: Age of Calamity: 66.6 MB of modules, so the default made SetHeapSize(pool − 64 MB) fail and nnMain return at once; it sets 72 MB. Cuphead: 80 MB of modules, 84 MB |
sys_resource |
The system resource size the NPDM declares (main.npdm META +0x14), which GetInfo(TotalSystemResourceSize) reports (hle_svc.c). The SDK's IsVirtualAddressMemoryEnabled is "that size is not 0", and some games choose their memory setup by it: Big Brain Academy: Brain vs. Brain's nninitStartup asks StandardAllocator for a 60 GB address region with virtual address memory and sets a heap without it, and its NPDM declares none (0). The survey writes it from the NPDM for every new game; a profile without it gets the host's earlier answer, 32 MB, for every game |
game_args |
Arguments the game gets when the command line gives none (the iOS app, the NSP script's Mac runs), split at spaces; dmhost <dir> -- … overrides them. The SDK hands them to the game before nninitStartup, as on hardware (main.c). Octopath Traveler 0 caps the address region Unreal's nninitStartup reserves (62 GB by default) with -SwitchVirtualAddressSpaceSize=2048 (MB) |
game_env |
KEY=VALUE pairs, separated by spaces, that the game's getenv returns (hle_os.c; any other name goes to the SDK's own getenv, whose environment is empty as on Horizon). For runtimes that take their settings from the environment. Absolum (.NET NativeAOT) sets DOTNET_GCRegionRange=60000000: the GC reserves 5 × its heap hard limit as its region range otherwise, 3.75 GB, which only the Switch's 64 GB alias region holds. DM_GAME_ENV overrides it, also from Documents/env.txt |
mem_map_limit |
on / off (the default): MapPhysicalMemory fails with ResultLimitReached when it would take the process past the application pool (the pool less the used memory GetInfo reports), as on Horizon (hle_svc.c). Pokémon Legends: Z-A allocates and frees everything it can at startup and the SDK keeps the freed pages mapped until a map fails, then unmaps them and retries; without the limit they stay mapped and the game maps past the pool. Off by default: Ace Attorney Investigations Collection maps a little more than the 1 GB pool its profile reports |
present_interval |
game (the default): frames are paced by the present interval the game sets, as on the Switch (a frame presented with interval 2 stays on screen for two display ticks; a game that keeps asking for 2 for a second gets the 30 fps target, and on iOS the display link then runs at 30 Hz; Mario 1.2.1 switches to 2 for single frames after a slow one; Engage asks for 2 on the Switch, and a rewriter cut on its 2.0.0 main makes it ask for 1, see 0100A6301214E000/game.tsv). display: one frame per display tick whatever the game asks, which is only right for games whose logic does not count frames (D2R's runs on its own 25 Hz clock; Monster Train 2 asks for 2 and steps by Unity's frame time, so it runs at 60 fps: screenshots at the same moments match at 30 and 60 fps). DM_PRESENT_INTERVAL=game / display overrides it, also from Documents/env.txt |
present_snap |
on / off (the default): present snapshots by default (nvn_mtl.m mtl_snap_on, a ring of 2 host textures): each present is copied and the game's swapchain texture is free at once, so the GPU does not idle between frames (Present snapshots). A setting the game was tested with: the iOS launcher's Frame buffering defaults to it, and DM_PRESENT_SNAP / Documents/env.txt override it. Breath of the Wild sets it on |
pso_mtl4 |
on / off (the default): on iOS, render pipelines through MTL4Compiler by default, with the pipeline archive, the prepare step, the hybrid pipelines and the prebuild (on by default on the Mac; DM_PSO_MTL4=1 / 0 overrides). A setting the game was tested with on an iPhone. Breath of the Wild sets it on |
fps_target |
Frame rate cap of the rewriter's frame step, for builds that have one (FRAME_STEP in the profile's patches.py: Tears of the Kingdom 1.4.3, Breath of the Wild 1.9.0, Hyrule Warriors: Age of Calamity 1.3.0, Paper Mario: The Thousand-Year Door 1.0.1, ASTRAL CHAIN 1.0.1), or the engine's own frame limit the host writes into game memory (hle_fpslock.c: Monster Hunter Rise 16.0.2, which also needs present_interval display; Octopath Traveler 0 1.0.5, Unreal's t.MaxFPS and rhi.SyncInterval, so the game asks for present interval 1 itself; Luigi's Mansion 3 1.4.0, its frame limiter's milliseconds per frame, with present_interval display). 0 (the default): the game's own step. 30 / 60: the host decides each frame's step in whole vsyncs and keeps the frame on screen that long (hle_nvn.c fstep_tick, nvn_fstep.h): 1 at a 60 cap, 2 when 60 does not hold or at a 30 cap (and while a prerendered movie plays), plus one vsync while the game is behind real time after a late frame. Presents follow that hold instead of the interval the game sets. DM_FPS_TARGET overrides it, also from Documents/env.txt; the log line frame step: game time … of real time checks the game's speed |
fps_hold |
on (the default): with fps_target, three late frames in a row raise the hold above the cap's vsyncs (30 or 20 fps), and steady frames that fit try the lower hold again. off: frames are never held longer than the cap's vsyncs; each is shown as soon as it is ready. For a game whose updates do not wait for the presents and step by the cap whatever the hold (Age of Calamity): there the hold only paces the screen. DM_FPS_HOLD=on / off overrides it, also from Documents/env.txt Paper Mario: The Thousand-Year Door sets it off too: its frame waits for three render worker threads, and holding frames left them idle, so a frame cost more the longer the hold (the hold stuck at 3 vsyncs, 20 fps, on a loaded Mac; 32–58 fps without holds) |
core_preempt |
on / off (the default): additionally check higher-priority runnable threads pinned to the caller's core after every thread SVC (hle_thread.c). DM_PREEMPT=0 / 1 overrides it. Wake-triggered same-core handoffs apply to every game with either setting and wait until the peer blocks, with no wall-clock cap. Metroid Dread no longer needs this key or its movie request-ring gates; see Same-core wake handoffs |
alloc_big_align |
on / off (the default): nn::mem::StandardAllocator::Allocate aligns blocks of 64 KB or more to 64 KB (hle_os.c). Vanillaware's engine (13 Sentinels: Aegis Rim, Unicorn Overlord) takes a memory pool's storage from malloc and cuts ring heaps out of it at 64 KB alignment relative to the pool's GPU address. On the Switch that address is big-page aligned; the host's is the CPU address, only 4 KB aligned, so the last ring heap does not fit and keeps base 0, and the game writes through a null pointer (13 Sentinels entering its first battle, Unicorn Overlord past the title screen) |
shared_font |
Optional, decided by the game files without it (the SDK version the host reads from the sdk module, loader.c dm_sdk_major, and main's imports, dm_main_imports): what the shared-font address points at. bfttf (SDK 11 and later, when main imports ScalableFontEngineHelper::Decode) = the 8-byte BFTTF header (with the "already decoded" marker) followed by the TTF, which Mario passes to Decode; ttf = the TTF itself, which SDK 10 and older games hand straight to ScalableFontEngine::LoadFont (Super Mario 3D World + Bowser's Fury: with the header the engine fails with error 403 and the game crashes looking up a glyph; Odyssey, Link's Awakening, Age of Calamity and Three Houses alike) and Unity's player to FreeType (Patrick's Parabox: with the header its Japanese, Korean and Chinese text drew nothing). No profile sets it |
device_api |
Optional, decided by the SDK version without it: the NVN API version major.minor that nvnDeviceGetInteger reports, 55.15 for SDK 11 and later (what Mario requires) and 53.313 for SDK 10 and older (hle_nvn.c nvn_api). Games check it and skip creating their device on a mismatch: Super Mario 3D World + Bowser's Fury (SDK 10) requires major 53 and minor ≥ 313. No profile sets it. DM_NVN_API overrides. Not part of the shader chain version (only shader_* keys are) |
op_mode |
The operation mode the game sees at start: console (the default, docked) or handheld. Super Mario Galaxy and Captain Toad: Treasure Tracker use handheld: docked, their pointer is the Joy-Con gyro, which the host does not have; handheld, it is the touch screen, which the iPhone front end feeds with the touches no gamepad control takes (in the 3D layout, outside the right third of the screen, which is the right stick; DmGamepad.m). Handheld also renders at 720p. DM_OPMODE=0 / 1 overrides, also from Documents/env.txt |
perf_mode |
follow (the default): the performance mode follows the operation mode (Boost docked, Normal handheld); boost: Boost whatever the operation mode (hle_oe.c), a combination a Switch 1 never reports, so a profile choice. Rhythm Heaven Groove plays handheld with Boost: docked means a TV to the game, which then asks for a latency calibration, and only Boost gives its 1920x1080 draw size (handheld Normal draws 1280x720) |
pad_layout |
The iPhone virtual gamepad's layout on first launch: simple (the default) = 2D (left stick, A/B/X/Y, shoulders, − / +), full = 3D (adds a D-pad, a right stick and L3 / R3; the right stick is not drawn: a touch landing in the right third of the screen on no control holds it, centred where it lands), touch = Tap (no control but the layout button: every touch goes to the game's touch screen; needs op_mode handheld, Balatro). The layout button at the top centre goes to the next layout in game (Tap only in handheld mode) and the app remembers the player's choice (DmGamepad.m) |
feedback_snap |
Fragment shader keys (prefixes) whose draws sample a colour attachment of their own render pass and must read a snapshot instead (fb_prepare in nvn_mtl.m). Writing and sampling one attachment in a render pass is undefined in Metal: the Mac usually happens to read the old content, the iPhone sometimes reads whole 16×16 blocks as magenta. Each such draw costs a pass split and a copy, so only the shaders seen to break are listed (Super Mario 3D World + Bowser's Fury: its final composite; Captain Toad: Treasure Tracker: the same agl program, whose blocks showed on the Mac too). DM_NVN_FB_SNAP=0 / all / <keys> overrides, also from Documents/env.txt |
tex_flush_upload |
on / off (the default): a texture the game fills from the CPU in place (no packaged layout, no WriteTexels) and publishes with nvnMemoryPoolFlushMappedRange is uploaded from guest memory like a packaged one, and re-uploaded when its pages change (ft_add in hle_nvn.c). Tears of the Kingdom streams all its textures this way; without it the 3D scene is black. Off by default because a flushed range in another game could cover an image a shader writes, whose GPU content the upload would replace. Without the key, every game still gets a texture's storage once when the game flushed it before the texture's first use (nvn_flushed in hle_nvn.c: Breath of the Wild's sky tables); the key is for textures the CPU writes again after that |
render_cond |
on / off (the default): SetRenderEnableConditional / SetRenderEnable are recorded and evaluated at replay, and a timestamp report's counter field is 0 as on the GPU (OP_RENDER_COND in nvn_mtl.m). Tears of the Kingdom wraps its clears in conditions that skip clearing a target nothing has drawn into since its last clear; without the key every clear runs and its 3D scene is black. Animal Crossing: New Horizons too: four clear-only passes wiped its composite before the window copy, and every frame was black. Off by default: other games draw everything, as before |
tex_dirty_upload |
on / off (the default), needs tex_flush_upload: a registered texture whose pages the CPU writes after nvnTextureInitialize without any NVN flush is uploaded as well (its pages are write-protected at registration, checked on first use; nvn_ft_written in hle_nvn.c). Tears of the Kingdom fills its ASTC terrain material arrays this way; without it the cave is magenta. FINAL FANTASY TACTICS - The Ivalice Chronicles loads its UI textures straight into CPU-uncached pools, which need no flush, after creating them; without both keys its screen stays black. The Great Ace Attorney Chronicles copies every texture into a CPU-cached pool after creating it and never flushes (its main has no cache maintenance at all): black too |
tex_dyn_slots |
on / off (the default): a shader whose texture handle the translator could not trace to one slot gets Ryujinx's fallback arrays (`_<t |
gpu_timestamps |
What a timestamp report (nvnCommandBufferReportCounter) records: submission (the default) = the time the whole submission completed, the same for every report in it; segment = the GPU start (TIMESTAMP_TOP) or end (the other types) of the segment the report was encoded in (the host commits a submission in segments of 64 draws, sub_cut in nvn_mtl.m), so two reports of one submission bracket the GPU time between them, as on hardware. Breath of the Wild needs segment: its frame-interval controller and its dynamic resolution read the GPU time between the frame's first and last report, and with one time for both they read 0 (the game kept 60 fps in slow motion when the GPU could not keep up). DM_NVN_GPU_TS=segment / submission overrides it, also from Documents/env.txt |
vconv_range |
on / off (the default): vertex attributes the replay converts on the CPU (integer shader inputs fed float-type formats and the like, vconv_bind in nvn_mtl.m) are converted only over the vertices each draw reads (first / count, or the smallest and largest index of a direct indexed draw, kept while the index pages stay unwritten) instead of the whole bound range. Age of Calamity binds whole mesh pools: in a battle that was millions of vertices a frame. Indirect draws, per-instance streams and indices with a GPU write queued on them keep the whole range. DM_NVN_VCONV_CHECK=1 scans the indices again at every reuse and reports differences; DM_NVN_VCONV_RANGE=0 / 1 overrides it |
shader_groups, shader_note |
shader_split.py source groups and the map header note (Engage: engine for data.unity3d and Resources/, assets for the Addressables bundles) |
What the game files decide at every build
These are not profile keys: the tools read them from the game files each time, by rules checked against every profile (dm/games/profile_guess.py shader_sources).
- Graphics API. A game whose
mainimports Vulkan functions and notnvnBootstrapLoaderrenders with Vulkan, which the host implements on Metal (dm/gfx/vk/hle_vk.m; Hades, Super Mario Galaxy). Its shaders are its SPIR-V files, translated bydm/gfx/tools/vk_shaders.py <RomFS> <shader dir> [--ios <dir>]into<dir>/vk/, andbuild_ipa.shships$MSL_IOS/vkinstead of the NVN chain libraries (GFXAPI, which the NSP script passes; by hand the shader libraries there tell). The NVN shader tables stay empty. - SPIR-V generated at run time. A Vulkan game without SPIR-V files in its RomFS generates its shaders at run time (Super Mario Galaxy: Hagi's GX shaders, 3,089 modules created from its pipeline database while it shows "Initializing system..."). The NSP script captures them on the Mac with
DM_VK_CAPTURE=<dir>(dm/gfx/vk/hle_vk.m), booting the game in the Mac host until every key the profile'svk_capture.tsvlists is there, andvk_shaders.pytranslates the capture folder with the RomFS. An OpenGL game with no GLSL source in its RomFS ormain(Balatro: LÖVE puts its shaders together in Lua) is captured the same way withDM_GL_CAPTURE=<dir>(dm/gfx/gl/hle_gl.m) until every file ingl_capture.tsvis there, andgl_shaders.pytranslates the capture. - Shaders compiled at run time. A game whose
mainholds GLSL sources and callsglslcCompilein a module it loads compiles some shaders at run time with its own GLSLC (Fire Emblem: Three Houses: 9 built-in KTGL shaders at boot), so no game file holds them. The NSP script captures them on the Mac withDM_GLSLC_CAPTURE=<dir>(dm/host/hle_glslc.c), booting the game in the Mac host until every capture file the profile'sshader_map.tsvnames is there, andshader_split.pyreads<version dir>/glslc_capture/*.glslc(beside that version'sRomFS) or$DM_GLSLC_CAPTURE; without the folder it prints a red line, those shaders get no translation and the tables differ from the profile's. - Unreal paks. An Unreal Engine game keeps its shaders in the shader code archives of its paks (
*.ushaderbytecode; a pak is Unreal's when its footer has Unreal's magic,uepak.pyunreal_paks), each shader Oodle-compressed, which only the game's own Oodle decodes. The NSP script (decode_ue_shaders) takes the archives out of the paks withdm/gfx/tools/uepak.py(the pak index is AES-encrypted with a key it finds inmain), boots the game in the Mac host withDM_OODLE_JOB(dm/host/hle_oodle.c: the game'sOodleLZ_Decompress, found as the one function that references the library's own name string, decodes every shader right beforennMain, then the host exits) and writes the decoded archives to<version dir>/ue_shaders, whereshader_split.pyreads them (DM_UE_SHADERSoverrides; without them it stops). Octopath Traveler 0: 10,596 shaders in 2 s. Unreal Engine 4.26 and later compress shaders with LZ4 unless the project picks another format (DRAGON QUEST VII Reimagined, 4.27: 64,949 shaders, its packages in IoStore containers beside the pak);uepak.pydecodes those itself (lz4_shaders) and the NSP script skips the host run. A pak whose index lists cooked packages and no.ushaderbytecode(uepak.pyinline_shaders: legacy version 4 paks, and OCTOPATH TRAVELER II's version 11 pak withbShareMaterialShaderCodeoff) keeps its shaders inline in the packages;shader_split.pyreads those file by file (ue_legacy_jobs) and the NSP script decodes no archive. - Trinity packs. Game Freak's Trinity engine keeps nearly every file in Oodle-compressed packs (
arc/data.trpfs, files startingONEPACK; Pokémon Legends: Z-A).shader_split.pydecodes them with the game's ownOodleLZ_Decompress, run by the Mac host in its tool mode (dm/host/hle_oodle.c,DM_OODLE_SERVE). The split needsDM_OODLE_HOST(the Mac host) andDM_OODLE_MODULES(the rewritten modules); the NSP script builds the host and sets both when the split runs. - GLSLC outputs in
mainand loose files.shader_split.pyreads GLSLC compiler outputs (magic0x19866891, each one checked whole) in every game'smain: 13 Sentinels and Unicorn Overlord keep all their shaders there, the KTGL games some; Unity's player has 11 of its own beside the game's shaders in its bundles (Engage, Ace Attorney Investigations Collection). It reads them as well in any RomFS file no other reader takes: FINAL FANTASY TACTICS - The Ivalice Chronicles keeps 257 in its technique files (resources_nx/release/master/shader/*.tec), Absolum (FNA3D) 163 inshaders.nx.bin. - MonoGame effects.
shader_split.pyreads MonoGame's MGFX effects wherever they are (mgfx_effect): Celeste keeps 13 in its XNB files (Content/Effects/*.xnb) and SpriteBatch's built-in one is inmain. - Single-stage programs. A GLSLC output, an Unreal shader record or a MonoGame effect's shader is a program of one stage, combined with others per draw (a MonoGame pass may name no vertex shader and keep SpriteBatch's), so its real vertex / pixel pairs exist only at run time.
shader_split.pypairs each such vertex shader with a cover of pixel shaders that together read every input any pixel shader reads, anddmsh_build.py'svs_padmakes it link with any pixel shader (13 Sentinels, Unicorn Overlord, the KTGL games, Octopath Traveler 0, Celeste). BNSH variations, Unity programs and the MSHD and SDF files pair their own shaders. - NVN driver tables. The driver's constant segments have one layout in every game (
dmsh_build.py, filled bynvn_mtl.m): c2 holds the handles bound withnvnCommandBufferBindTextureat0x20 + 8 × slot, images at0x120 + 8 × slot, separate textures at0x168 + 8 × slotand separate samplers at0x568 + 8 × slot; c0 the storage buffer tables (vertex 0x110, compute 0x310, pixel 0x510). Separate texture binds are texture slots 128 + n of the host's table (NVN_SEPTEX_SLOT0), so a game may bind both kinds in one frame (Monster Hunter Rise: combined slot 0 beside separate slots 0-4), and a combined slot past 31 (Hades II binds slot 34) lies in the image table, where a bound texture wins over an unbound image. The translator (dmsh_xlate/Program.cs) numbers slots the same way when it looks up per-slot texture / image types inshader_facts.tsv.
Adding a game
-
Survey. Run
scripts/build_ipa_from_nsp.pyon the game's NSP folder. Without a profile it extracts only the code modules and a RomFS list, then writes<NSP folder>/<title ID>_survey/survey.md. The survey covers modules and build IDs, SDK, engine and graphics API markers, RomFS containers, the rewrite-site census, and imports compared with the host hooks. Unless told--survey-only, the script then writes a provisional profile to<NSP folder>/<title ID>_build/profile/<title ID>/(outside the repository) and carries on to a best-effort IPA.dm/games/profile_guess.pyfills itsgame.tsvfrom the game files, each key under the reason for it (the survey lists them too):- identity and name, the build row it found, the largest RomFS file as
romfs_marker, big file types inzip_store,shader_groupsby RomFS top-level folder; modules: a subsdk is loaded only whenmainimports something from it that the host does not supply itself, or, once the rewrite stage has collected the game's NROs, when one of them does (nro_modules: Portal's Source engine NROs use the movie module and GLSLC, which itsmaindoes not). Under the Vulkan layer the Vulkan loader and GLSLC are dropped; movie modules and a GLSLC thatmaincalls stay. NVIDIA's OpenGL and Vulkan drivers are never loaded: they program the GPU through NvRm, which the host does not have (Absolum imports EGL for SDL3's GL support and renders with NVN);- the memory partition. Without a system resource in the NPDM: the 4 GB layout, its heap region the pool − 64 MB − the bss of giant modules (Metroid Dread: 0x90000000). When
mainnever sets a heap: a 1 GB block (Super Mario Galaxy). With a system resource: Engage's 5 GB block with the hardware pool, which the heap check corrects when the game sets a heap (without one the game maps everything in the alias region itself: Hollow Knight: Silksong, Absolum, Pokémon Legends: Z-A); - Engine presets: Hagi (
hagi.config) getsop_mode handheldandpad_layout full.
python3 dm/games/profile_guess.py --checkruns these rules on every profile indm/games(with the extractions under/Volumes/T7/nsp) and lists the keys where the guess differs.After the rewrite,
mem_usedis set when the rewritten modules exceed the default 64 MB (a game without a system resource); keep it when the profile moves intodm/games(Cuphead without it: a 0.03 GB heap and an abort at the first allocation). A game with a system resource that sets a heap then gets a heap check: it boots in the Mac host (headless, no shader libraries) until its first heap request, or 5 s intonnMainwithout one, at most 30 s (HEAP_CHECK_SECS). A request the heap region cannot hold (SetHeapSize OOM) sets the alias / heap split (Tears of the Kingdom asks for 0x86000000, which gives its hand-made 0x98000000 alias region; Wonder's 1.44 GB gives 0xc0000000), and a pool check that fails for good setsmem_used. A run with no heap set whose alias region is used up to within 0.25 GB of its end sets a 1 GB pool:nninitStartuphanded pool − used to StandardAllocator as one address region and the game reserves regions of its own beside it (Unity 2020 and 2022: AI: THE SOMNIUM FILES - nirvanA Initiative reached +4.26 GB of 4.38 in 5 s with the hardware pool; Unity 2018.3's Raging Loop maps only inside StandardAllocator's region and keeps it). The 1 GB is a starting point, not a measurement: Advance Wars 1+2: Re-Boot Camp (Unity 2020.2) ran its malloc dry at its first loading screen with it and needs 2 GB. The profile gets a# heap check:line, and later runs keep its keys (delete the line to check again;--no-heap-check/HEAP_CHECK = Falseskips it). Other games skip the check, so it costs nothing for them and a few seconds plus a Mac host build for the rest.No short run decides
render_cond,tex_flush_upload,tex_dirty_upload,core_preempt,gpu_timestamps,feedback_snap,vconv_rangeorpresent_interval, norop_mode/pad_layoutoutside the presets: they keep their defaults for a developer. A minute's boot without input counted Wonder's clears under render conditions (65,536 or more; it needs norender_cond) as readily as Tears of the Kingdom's, saw Breath of the Wild flush 32 CPU-filled textures with data (it needs notex_flush_upload) and Age of Calamity none (its movie frames come later), and the old host's Metroid Dread movie crash showed in about half the runs (now resolved by shared wake handoffs). On the iPhone,Documents/env.txtcan still setDM_OPMODE,DM_NVN_GPU_TSandDM_NVN_FB_SNAP, and the pad layout switches in game.The shader tables,
c1_full.tsvandcb_extent.tsvare written into the same folder. A second run reuses thatgame.tsv(delete it to survey again), so it can be edited by hand.--provisionalbuilds from a provisional profile even for a game that has one indm/games, to test the guesses:DM_GAMES_FIRST=1makes every tool searchDM_GAMES_DIRfirst, and the IPA gets-provisionalin its name. Tools find profiles outsidedm/gamesthroughDM_GAMES_DIR(folders separated by:, searched afterdm/games);make -C dm/hostreaches such a profile through the symlinkdm/host/gen/<id>/profile. To keep a game, move the folder intodm/games/and bring it up as below. - identity and name, the build row it found, the largest RomFS file as
-
Profile. Copy
010015100B514000/game.tsv(or the provisional profile) todm/games/<title ID>/game.tsv(the base game's title ID, as the folder name) and fill in the identity,buildrows andmodules. Leave the memory values as Mario's until the bring-up measures them (docs/agents). -
Shader tables. Run
DM_GAME=<id> shader_split.py … dm/games/<id>/shader_map.tsv dm/games/<id>/shader_facts.tsv dm/games/<id>/shader_pairs.tsv dm/games/<id>/pso_pairs.tsv. Add an emptyshader_map.tsv.conflicts.tsv,bf_want.tsvandpso_manifest.tsv(copy Mario's headers). -
Shaders.
DM_GAME=<id> [IOSOUT=…] dmsh_build.py <pkg> <cache>, with a cache directory of the game's own. A full build also writesc1_full.tsvandcb_extent.tsv(local files, not committed). The chain version needs nothing: it is the same for every game. -
Host bring-up. Run
make -C dm/host GAME=<id>, then work through the bring-up loop in docs/agents. Code that only fits one game is gated by its build ID (dm_main_build()), never left to run on every game. -
IPA. Use
GAME=<id> dm/ios/tools/build_ipa.sh, or the NSP script, which now finds the profile.