Files
seedds 3a993fa3fe Subsystem references and a symptom index: the failure catalogue of docs/agents/03-bringup.md and the per-title material of docs/agents/04-graphics.md (section 1's container list, section 6 and its "Seen in <game>" subsections) move into docs/reference/, one heading per mechanism: rewriter.md, memory.md, threads.md, services.md, movies.md, audio.md, frame-rate.md, nvn-objects.md, rendering.md (with the Vulkan and OpenGL layers), shaders.md and game-state.md, indexed by docs/reference/README.md. Each entry gives the symptom, the cause, the fix (file and function), the games it was found on (linked to their notes in dm/games/<id>/README.md) and any check, caveat or later change; one mechanism met on several games is one entry. docs/agents/symptoms.md lists every entry's symptom (an assertion, a [RED] line, an abort or crash site, a visible result) with a link to it, 350 lines, generated from the Symptom bullets. 03 keeps the loop and reading the log, and its Failure catalogue section now points to the references, the index and where a new finding goes; 04 keeps sections 1-5 and 7 (the container list is shaders.md's Containers by engine), and section 6 says where each kind of finding is. Facts in the old sections about one game only were checked against that game's note, where they already were. Statements the code no longer matches are marked as history beside the current value (movie output slots: MD_NOUT 64, MD_NOUT_AAC 32, MD_NOUT_VIDEO 16; APP_POOL is the profile key app_pool; Engage's layout is 5 / 4.375 GB; inline uniform updates are deferred by default; scaled copies are drawn), and Tears of the Kingdom joins the node-area entry's build list (issue #70). The game notes, docs/agents/README.md, 02 and 05 link to the exact entries, and so do the comments in nvn_mtl.m (nvnTextureCompare) and hle_audio.c (the RemoteIO buffer) and the provisional-profile note in profile_guess.py. 03 goes from 29,600 to 470 words, 04 from 34,400 to 3,500. Checked: every backticked span, module address, issue / PR number and 16-digit hash of AGENTS.md, development.md, dm/games/README.md and docs/agents/01-05 at 6a06877 (4,600 items) still appears in the repository's Markdown (the same five file patterns as before appear as the full names they stood for), every relative link and heading anchor in the repository's Markdown resolves, and the tool tests pass (the four that need capstone or lz4 in the T7 venv)
2026-10-25 12:31:58 +08:00
..
patch_nso.py split into the general rewriter and per-build patch files (GitHub issue #63): every game's bring-up edited the one file, so PRs for unrelated games conflicted. dm/tools/patch_nso.py (1,711 → about 1,100 lines) keeps the general passes and the driver; the instruction encoders move unchanged to dm/tools/nso_asm.py; a game's own patches (CUTS, OBS and the frame-step stubs, now FRAME_STEP = {build ID: stub(ctx)}) move verbatim with their comments to dm/games/<id>/patches.py (16 profiles: Tears of the Kingdom, Breath of the Wild, Age of Calamity and Paper Mario: The Thousand-Year Door with their stubs), and the cuts of Nintendo's sdk module builds, 6 of the 15 shipped by 2 to 4 games, to dm/tools/sdk_patches.py. The rewriter loads every patch file, whatever the game, and applies those of a module's build ID at the points it did before (cuts before the scan, gates after the rewrite, stubs after the gates); a build ID claimed twice stops it, and its command line needs no DM_GAME. build_src.sha256 lists the rewriter's files and every patch file (the same for every module). NSP script: the rewrite step's reuse key counts nso_asm.py and only the patch files that name one of the game's builds (patch_files), profile_files leaves patches.py out (only the rewrite step reads it), and a derived profile gets none. dm/tools/tests/test_patches.py: no build claimed twice, the entries' forms, a game's file names only its own profile's non-sdk builds, sdk_patches.py only sdk builds some profile ships, and the reuse key's patch files. Checked byte-identical: every extracted module on the game drive (131 builds) rewritten with the previous and the new rewriter gives the same outputs but build_src.sha256, 28 of the 30 patched builds among them (all four frame-step mains), and 7 fail alike (rtld, and New Super Mario Bros. U Deluxe's, which no profile rewrites); the other two (Super Mario Bros. Wonder's mains and Absolum's, no game files here) by their CUTS / OBS data, equal to the previous tables. AGENTS.md, docs, dm/games/README.md, the profiles' comments and the host comments that named a game's cut in patch_nso.py now name its patches.py or sdk_patches.py
2026-10-23 23:36:21 +08:00
patch_nso.py split into the general rewriter and per-build patch files (GitHub issue #63): every game's bring-up edited the one file, so PRs for unrelated games conflicted. dm/tools/patch_nso.py (1,711 → about 1,100 lines) keeps the general passes and the driver; the instruction encoders move unchanged to dm/tools/nso_asm.py; a game's own patches (CUTS, OBS and the frame-step stubs, now FRAME_STEP = {build ID: stub(ctx)}) move verbatim with their comments to dm/games/<id>/patches.py (16 profiles: Tears of the Kingdom, Breath of the Wild, Age of Calamity and Paper Mario: The Thousand-Year Door with their stubs), and the cuts of Nintendo's sdk module builds, 6 of the 15 shipped by 2 to 4 games, to dm/tools/sdk_patches.py. The rewriter loads every patch file, whatever the game, and applies those of a module's build ID at the points it did before (cuts before the scan, gates after the rewrite, stubs after the gates); a build ID claimed twice stops it, and its command line needs no DM_GAME. build_src.sha256 lists the rewriter's files and every patch file (the same for every module). NSP script: the rewrite step's reuse key counts nso_asm.py and only the patch files that name one of the game's builds (patch_files), profile_files leaves patches.py out (only the rewrite step reads it), and a derived profile gets none. dm/tools/tests/test_patches.py: no build claimed twice, the entries' forms, a game's file names only its own profile's non-sdk builds, sdk_patches.py only sdk builds some profile ships, and the reuse key's patch files. Checked byte-identical: every extracted module on the game drive (131 builds) rewritten with the previous and the new rewriter gives the same outputs but build_src.sha256, 28 of the 30 patched builds among them (all four frame-step mains), and 7 fail alike (rtld, and New Super Mario Bros. U Deluxe's, which no profile rewrites); the other two (Super Mario Bros. Wonder's mains and Absolum's, no game files here) by their CUTS / OBS data, equal to the previous tables. AGENTS.md, docs, dm/games/README.md, the profiles' comments and the host comments that named a game's cut in patch_nso.py now name its patches.py or sdk_patches.py
2026-10-23 23:36:21 +08:00
Subsystem references and a symptom index: the failure catalogue of docs/agents/03-bringup.md and the per-title material of docs/agents/04-graphics.md (section 1's container list, section 6 and its "Seen in <game>" subsections) move into docs/reference/, one heading per mechanism: rewriter.md, memory.md, threads.md, services.md, movies.md, audio.md, frame-rate.md, nvn-objects.md, rendering.md (with the Vulkan and OpenGL layers), shaders.md and game-state.md, indexed by docs/reference/README.md. Each entry gives the symptom, the cause, the fix (file and function), the games it was found on (linked to their notes in dm/games/<id>/README.md) and any check, caveat or later change; one mechanism met on several games is one entry. docs/agents/symptoms.md lists every entry's symptom (an assertion, a [RED] line, an abort or crash site, a visible result) with a link to it, 350 lines, generated from the Symptom bullets. 03 keeps the loop and reading the log, and its Failure catalogue section now points to the references, the index and where a new finding goes; 04 keeps sections 1-5 and 7 (the container list is shaders.md's Containers by engine), and section 6 says where each kind of finding is. Facts in the old sections about one game only were checked against that game's note, where they already were. Statements the code no longer matches are marked as history beside the current value (movie output slots: MD_NOUT 64, MD_NOUT_AAC 32, MD_NOUT_VIDEO 16; APP_POOL is the profile key app_pool; Engage's layout is 5 / 4.375 GB; inline uniform updates are deferred by default; scaled copies are drawn), and Tears of the Kingdom joins the node-area entry's build list (issue #70). The game notes, docs/agents/README.md, 02 and 05 link to the exact entries, and so do the comments in nvn_mtl.m (nvnTextureCompare) and hle_audio.c (the RemoteIO buffer) and the provisional-profile note in profile_guess.py. 03 goes from 29,600 to 470 words, 04 from 34,400 to 3,500. Checked: every backticked span, module address, issue / PR number and 16-digit hash of AGENTS.md, development.md, dm/games/README.md and docs/agents/01-05 at 6a06877 (4,600 items) still appears in the repository's Markdown (the same five file patterns as before appear as the full names they stood for), every relative link and heading anchor in the repository's Markdown resolves, and the tool tests pass (the four that need capstone or lz4 in the T7 venv)
2026-10-25 12:31:58 +08:00

Absolum (0100A83022A22000)

Update 1.1.1 (v393216) over the base game (v0), SDK 18.3.2. FNA (XNA 4 reimplemented in C#) compiled with .NET NativeAOT (NativeAOT-NX; its own GC, regions enabled), SDL3, FNA3D's NVN driver (FNA3D_Driver_NVN.cpp), Wwise. Modules main, subsdk2 (the movie module, for FNA3D's video player: H.264 logos) and sdk; NVIDIA's Vulkan and OpenGL drivers (subsdk0, subsdk1) are not loaded. Epic Online Services comes as an NRO the host does not load; the game plays offline without it. Playable on an iPhone.

Status: brought up on the Mac: the logo movie, the licence agreement, the main menu, the campfire character select, the intro and the first level (the "Strike / Skill" tutorial and the cave after it), at 60 fps with real sound and saves; about 2.5 GB resident.

What it needs

Setting Where Why
malloc region cut 40 GB → 2.5 GB: main+0x28bff4 mov x0,#0xa00000000 → mov x0,#0xa0000000 patches.py, main 916f5b8d… malloc's StandardAllocator takes 40 GB of address space; 2.19 GB is used into the first level; see Memory
game_env DOTNET_GCRegionRange=60000000 game.tsv, hle_os.c the .NET GC's region range 1.5 GB instead of 5 × its hard limit (3.75 GB)
Node area cut 1 GB → 62 MB (sdk+0x13be4c / +0x13bee0) and 32 MB alignment for regions of 1 GB and more (sdk+0x13c03c) dm/tools/sdk_patches.py, sdk build f118ae1c… the 1 GB alignment lost up to 1 GB before each of the two large regions
Engage's 5 GB block: mem_reserve 0x140000000, mem_alias 0x118000000, mem_stack 0x10000000, app_pool 0xCD500000 game.tsv 256 MB in front + 4.375 GB alias region + 256 MB stack region + 128 MB heap region (never used)

No pad_layout key: the iPhone gamepad starts in the 2D layout (README).

Memory

  • The NPDM declares 16 MB of system resource (virtual address memory), and main never calls SetMemoryHeapSize: everything lives in address regions of the alias region. profile_guess.py gives every game with an NPDM system resource the 5 GB block, and this game came out right with it (Unity 6 takes its memory with no StandardAllocator and no heap).
  • Resolved: nn::diag abort at sdk+0x12dad0 in nninitStartup, called from main+0x756310. That is malloc's StandardAllocator::Initialize(nullptr, 0xa00000000): 40 GB of address space (main+0x28bff4; nninitStartup is at main+0x1b54d0, the call goes through main+0x7562d0).
  • How the needs were measured: one boot on the Mac with a 64 GB alias region (mem_reserve 0x1030000000, mem_alias 0x1008000000), probing VammManager::AllocateAddressRegion's success path (sdk+0x13bf8c: x21 size, x22 address) with DM_SVC=1's map log. The regions, from boot into the first level:
    • malloc's: 2.19 GB used, 1.75 GB of it FNA3D's GPU heap, allocated whole at FNA3D_CreateDevice (main+0xc03660 → main+0x563370); its blocks reach 332 MB from the region's start and 1.82 GB from its end, and no more after the title. The cut gives it 2.5 GB, 300 MB to spare.
    • the .NET GC's region range: 5 × its hard limit (768 MB = 75% of the 1 GB its GetPhysicalMemoryLimit, main+0x28bffc, reports), 3.75 GB. The GC reads DOTNET_GCRegionRange through getenv first; the host answers getenv from the profile key game_env (hle_os.c; every other name goes to the SDK's own getenv, an empty environment), which sets 1.5 GB. The GC then uses its range from both ends, 756 MB from the start and 748 MB from the end (718 MB mapped into the first level), where it used only the first 1.14 GB of 3.75.
    • a few small regions (bookkeeping, thread-local blocks), under 5 MB.
  • Resolved: the regions did not fit even then. SDK 18's AllocateAddressRegion (sdk+0x13bfd0) aligns a region by its size: 64 KB under 2 MB, 2 MB under 4 MB, 4 MB under 32 MB, 32 MB under 1 GB, else 1 GB in absolute address (+0x13c03c), and places it first-fit after the regions already there. The two large regions, with a 4 KB one between them, lost up to 1 GB each to alignment, depending on where mmap put the guest block (measured with a 64 GB alias region: 0.6 GB before malloc's at that run's alias base, 1 GB before the GC's). A cut on that build aligns them to 32 MB as well (what the SDK gives regions from 32 MB up; nothing in the SDK or the game reads the region's alignment), beside the node-area cut. The sdk's AddressRegionManager is at the offsets of Super Mario Galaxy's SDK 18.3.5 build.
  • Current: the regions end 4.13 GB into the 4.375 GB alias region of Engage's 5 GB block. The mapped total peaks at 2.9 GB, under the hardware pool.

Rendering

The effects (.xnb) are XNA's D3D9 effects (MOJOSHADER is linked in and parses them for the parameters). Every binding decodes with the usual layout.

  • Resolved: the whole picture upside down (the title logo and "Press Any Button" mirrored top to bottom). The game keeps NVN's lower-left window origin (handled as for Link's Awakening, 01006BB00C6F0000) and, twice a frame, sets viewport 0's swizzle with nvnCommandBufferSetViewportSwizzles to (x, −y, z, w), which negates clip-space y: how a renderer with OpenGL heritage flips its picture. The host had it as a red-reporting stub. It is recorded now (OP_VP_SWIZZLE) and applied at the draw as a viewport flipped about its centre, the same mapping. Facing is decided after the swizzle on the GPU as in Metal, so the winding stays. Other swizzles (axis exchanges, negated x / z / w) give a red line and are drawn unswizzled.
  • Resolved: the logo movie as a strip of small grey copies at the top of the screen (FNA3D's nx_video_player.cpp: type 1, output 3, mode 2). The texture held NV12 bytes: in RGBA8, 405 rows of 1920 pixels are exactly a 1920×1080 NV12 frame, rows 0–270 the luma at video black (16) and rows 270–405 the chroma (128). This game's MP4 demuxer hands the parameter sets to Configure (csd-0 / csd-1), which made the H.264 decoder there without switching it to RGBA, and the first packet then found it made. Both paths now go through md_avc_open (hle_movie.c).

Files and shaders

  • The shaders the GPU runs are 163 GLSLC outputs in rom:/shaders.nx.bin (a u32 count, then per entry a u32 hash, a u32 size and one whole output), which shader_split.py reads as a loose file no other reader takes: 162 unique shaders, 83 pixel and 79 vertex, every vertex shader paired with the single-stage cover. Groups fna and data (shader_groups).
  • romfs_marker: data/textures.

Code

  • Resolved: RuntimeError: 788 ldr x18, #0xd1eb0 in patch_nso.py. The text segment starts with 1.7 MB of data (main+0x0–+0x1b5000, high-entropy, no FDE covers it, and no adrp or relocation points into it): 62% of its words do not decode, and 6,858 of the rest read as instructions that use x18, plus three "svc" words with numbers such as #0x91e7. The linear sweep rewrote them all. dm/tools/nso_code.py finds such data blocks (a stretch of at least 4 KB between the .eh_frame FDE ranges in which more than a quarter of the words do not decode); the sweep skips them (DATA_BLOCKS, logged as data blocks left alone), and so does insn_census.py, whose counts then match the rewriter's again (11,744 x18 sites in this main). Code without unwind information is still swept: it decodes (zlib at main+0x75261c, libunwind's register restore at main+0x28dbd4).
  • Padding halfword jump tables only to their largest entry (half_count) changed one to six tables in this main, as in a few other games' (Halfword jump table landing pads sized to the largest entry).

Services

  • Resolved: SIGSEGV in vi::CreateLayer(Layer**, Display*, int, int) (sdk+0x3f9bc4, from SDL3's Switch video driver at main+0x239304): the overload with the layer's size; the host had only the two-argument one. It returns the static layer (hle_vi.c). The survey's import table had listed every vi call as unhooked: nso_syms.py host_hooks read only dm/host, and the vi hooks are in dm/gfx (it reads dm/gfx and dm/gfx/nvn now).
  • AudioIn (SDL3's audio driver lists the input devices when its audio starts): the host has no microphone. ListAudioIns reports none, both opens fail, and the calls that need an open AudioIn answer without touching the game's object (hle_audio.c).
  • Epic Online Services in an NRO (rom:/nro/EOSSDK-Switch-Shipping.nro, loaded with ro::LoadModule; main imports the EOS_* functions). Before the host loaded NROs, the imports stayed UNIMPL stubs and EOS_Platform_Create handed back a garbage handle; the game checks it ([EOS] Failed to create platform) and plays offline. Current: the host never loads an online SDK: an NRO that exports EOS_Platform_Create is left out of the pack (dm/tools/nro_collect.py ONLINE_SDKS, a refused row in nro.tsv) and ro::LoadModule refuses it with one log line (refused …: an online SDK (Epic Online Services), not loaded). Offline, local saves.
  • The modules: main imports EGL (SDL3's GL support, the same 13 EGL functions as Digimon Story Cyber Sleuth) and vkGetInstanceProcAddr (its Vulkan support), but FNA3D renders with NVN and names no GL function. NVIDIA's OpenGL and Vulkan drivers program the GPU through NvRm, which the host does not have, so they are never loaded (profile_guess.py GPU_DRIVERS); the EGL imports stay unbound stubs that nothing calls.

Mac routes

  • A fresh save reaches the first level at about frame 9,000 with DM_KEYS="f1500:A,f1900:A,f2300:A,f2700:A,f3000:A,f3300:A,f3600:A,f4000:A,f4400:A,f4800:A,f5400:A*30/150" (any button, accept the licence, New game, the character select, then A through the intro and the first dialogue); LRight walks, Y strikes and X uses the skill.

Found on this game

General host changes first needed here; each applies to every game:

Open

  • Shader counts differ: development.md says 162 GLSLC outputs in shaders.nx.bin; 04 and dm/games/README.md say 163 outputs (162 unique).
  • Loading online SDKs: always load, refuse them (as now), or decide per profile is an open question for the maintainer (Online SDKs in NROs are never loaded).