Files
nxnative-easy/dm/games/README.md
T
seedds 41ac235637 Unity shaders: UnityFS reader, Engage shader tables and NVN layout
- New dm/gfx/tools/unityfs.py: UnityFS bundles (LZ4 / LZMA blocks),
  serialized files (formats 19..22), objects read by their type trees
  (Unity's common string table comes from the game's own main), and a
  Shader object's per-platform compressed subprograms. The player's
  own files have no type trees and borrow the bundles' Shader tree of
  the same type hash.
- shader_split.py reads Unity Shader objects in a second pass: each
  Switch subprogram is one NVN program (0x60-byte header of per-stage
  offset / control size / code size, then control + code per stage);
  a program's vertex / pixel stages form a pair. Engage: 9,269
  shaders, 4,634 pairs. Mario's tables and package are byte-identical.
- Engage profile: shader groups engine / assets, measured NVN layout
  (texture handles 0x168 + 8 r, 128 slots; separate samplers 0x568),
  regenerated tables, chain a295a8035b7b. 9,268 of 9,269 compile (one
  geometry shader; Metal has none).
- dmsh_build.py: the translator's built-in bases only matter with
  per-slot facts, so the guard checks that; c2 reads count the sampler
  table and 16-byte rows overlapping a table as fillable.
- game.py: DM_GAME_NVN_SAMP_BASE. NSP script survey note updated.
- Mario chain is now 271056163ea2 (Mac and iOS libraries rebuilt,
  16,266 of 16,268 as before; c1_full regenerated).
2026-10-14 20:29:26 +08:00

4.7 KiB

Game profiles

Each game the port knows has a folder dm/games/<id>/. Mario's is smbw. The folder holds:

  • game.tsv: the game's constants, one key<TAB>value row each, with comments on their own lines.
  • The shader tables that shader_split.py writes: shader_map.tsv (+ .conflicts.tsv), shader_facts.tsv, shader_pairs.tsv.
  • c1_full.tsv: regenerated by c1_guard.py after each shader build.
  • pso_manifest.tsv: the prewarm seed list.
  • bf_want.tsv.
  • dmsh_chain.h: the chain version, written by dmsh_chain.py --header.

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, nvn_*, 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
dm/ios/tools/build_ipa_from_nsp.py the NSPs' title ID everything above. With no matching profile it only writes a survey

All of them default to smbw. To query a profile:

  • python3 dm/games/game.py list lists the profiles.
  • python3 dm/games/game.py <id> <key> prints one key.

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)
app_name, bundle_id, ipa, app_category iOS app. app_name is only the fallback: the NSP script uses its APP_NAME setting, else the game name from the NSP. A manual build_ipa.sh run and the Mac window title use app_name
build <ver> <display version> <module>=<build ID>… One row per known version. The NSP script rejects other builds. The host reports the display version by main's build ID
package Version packed into the IPA by default
shader_versions Versions whose union the shader tables are (base game first). All of them are needed to build
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
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)
nvn_tex_base, nvn_tex_slots, nvn_samp_base, nvn_img_base, nvn_img_slots, nvn_ssbo_bases NVN driver constant segments, measured over all shaders
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)

The translator (dmsh_xlate/Program.cs) still has Mario's texture / sampler bases and the depth convention built in. It uses the bases only to look up per-slot texture / image types in shader_facts.tsv; dmsh_build.py decodes the bindings from the profile. So a profile with other bases (Engage: 0x168 / 0x568) builds as long as its facts table has no per-slot texture / image entries; otherwise dmsh_build.py stops, and the translator must take the bases from the profile first.

Adding a game

  1. Survey. Run dm/ios/tools/build_ipa_from_nsp.py on 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.
  2. Profile. Copy smbw/game.tsv to dm/games/<id>/game.tsv and fill in the identity, build rows and modules. Leave the memory and NVN values as Mario's until the bring-up measures them (docs/agents).
  3. 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. Add an empty shader_map.tsv.conflicts.tsv, bf_want.tsv and pso_manifest.tsv (copy Mario's headers).
  4. Chain.
    1. DM_GAME=<id> dmsh_chain.py --header
    2. DM_GAME=<id> [IOSOUT=…] dmsh_build.py <pkg> <cache>
    3. c1_guard.py <cache>/b-<V> dm/games/<id>/c1_full.tsv
  5. 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.
  6. IPA. Use GAME=<id> dm/ios/tools/build_ipa.sh, or the NSP script, which now finds the profile.