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

53 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Cuphead (`0100A5C00D162000`)
Update 1.3.7 (v655360) over the base game; its DLC (The Delicious Last Course) is mounted as add-on content 1. SDK 5.5.0, NVN API 53.313. Unity 2017.4.9f1 IL2CPP on NVN. Modules `main`, `subsdk0` (the movie module, `NintendoSDK_movie`) and `sdk`. Plays on the iPhone. On the Mac it was brought up with no host change: the title, the save slots, the opening storybook, Elder Kettle's house, Inkwell Isle One and Forest Follies at 60 fps with no red line. Unity 2017.4's serialized and `Shader` formats (`unityfs.py`, `shader_split.py`) and the rewriter's jump-table form (`patch_nso.py`) were found by armyja on this game (GitHub pull requests #1, #2).
## What it needs
| Setting | Where | Why |
|---|---|---|
| `mem_used 0x5400000` (84 MB) | [`game.tsv`](game.tsv) | the rewritten modules take 80 MB (`main` 59.9, `sdk` 12.7, `subsdk0` 7.3); with the default 64 MB the heap request plus the modules exceeds the pool, `SetHeapSize` fails and the game aborts at its first allocation (a 0.03 GB heap) |
| 4 GB layout: `mem_reserve 0x100000000`, `mem_alias 0x10000000`, `mem_stack 0x10000000`, `app_pool 0xCD500000` | `game.tsv` | no system resource in the NPDM, so no virtual address memory: 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region |
| ZCull storage size never 0 | host, `hle_nvn.c` `zcull_size` (every game) | Unity's NVN pool allocator breaks on a zero-size block; see [Rendering](#rendering) |
| `shader_groups /Data/=data` | `game.tsv` | |
## Memory
- No system resource, so no virtual address memory: Unity sets a heap of the application pool − used memory (3.12 GB) in the 4 GB block and allocates from it.
- On the Mac about 3.2 GB resident and 2.7 GB of Metal allocations, flat over the house and the map.
## Rendering
- **Resolved: sprites broke into dark slivers or went missing in about half the runs** (a log in Forest Follies splintered for a whole attempt), **and on the iPhone the picture flickered every fourth frame.** The host reported a ZCull storage size of 0. Unity's NVN pool allocator allocates each depth target's ZCull save area from its pool; asked for a zero-size block, it gave out the offset of a live block, lost that block from its map of allocations, and later freed it with a garbage size. Buffers allocated afterwards overlapped live ones, so copies read staging bytes another copy had overwritten (every fourth frame is the upload ring's period); whether a run broke depended on the allocation order. The size is now never 0 (`zcull_size`).
- **How it was traced:** the pass dump (`DM_NVN_PASSDUMP_ALL=1`, the `Y copy_b2b` / `Y update_ub` lines) listed in command order every copy and inline update of the frame. A copy whose source an earlier GPU write of the same frame covers reads that write, not the bytes the CPU staged: the broken runs had dozens of those a frame, the clean runs none. The game's allocations overlapped, so the allocator was tracked from observation gates (`OBS` in its `patches.py`) on its free-list push and after its map insert, checking at every event that each block allocated so far was still in the game's map.
## Files and shaders
- 3,118 shaders (1,558 pixel, 1,560 vertex) from the player's serialized files and the AssetBundles of `StreamingAssets`, plus 4 GLSLC outputs in `main`. Every binding decodes.
- Its Unity 2017.4 layout is the one AI: THE SOMNIUM FILES ([010089B00D09C000](../010089B00D09C000/README.md)) reads with. Before Unity 2019.3 a `Shader` keeps one segment per platform, the form Cuphead's trees gave `shader_programs`.
- Its 2 `ComputeShader` objects are only in files without type trees, of hashes no layout covers; the split prints a `[RED]` line and leaves their kernels out.
## Saves
- The game writes a slot when it reaches the map, not when the slot is picked.
## Mac routes
- A fresh save through the title (L+R), a new slot, the storybook and the house to the map (at about 160 s): `DM_KEYS="20:L+R,26:A,32:A,38:A,44:A,50:A,56:A*20/3,120:LRight~4,125:A,127:LRight~3,131:A*10/2,152:LLeft~3,155.5:A"`.
- From that save, across the bridge (its entry is narrow between the posts) into Forest Follies (play from about 68 s; Y shoots): `DM_KEYS="20:L+R,26:A,32:A,35:A,40:LRight~1.5,42:LDown~1.5,45:LRight~3,49:LUp~1,51:LRight~3,54.5:LLeft~0.5,55.2:LUp~0.25,55.6:LRight~2,58:LUp~0.2,58.4:LRight~0.25,58.8:LUp~0.2,59.2:LRight~0.25,59.6:LUp~0.2,60:LRight~0.25,60.4:LUp~0.2,61:A,63:A"`.
## Found on this game
General host changes first needed here; each applies to every game:
- **A size the hardware never reports as 0 is never 0 on the host** (`zcull_size`): [Sizes the hardware never reports as 0 (ZCull save area)](../../../docs/reference/nvn-objects.md#sizes-the-hardware-never-reports-as-0-zcull-save-area).
- **Overlapping copies in the pass dump** as the sign of a broken game allocator: [05, Intermittent rendering glitches](../../../docs/agents/05-debugging.md#intermittent-rendering-glitches).
- **`mem_used`** for rewritten modules over 64 MB, which the NSP script now sets after the rewrite: [dm/games/README.md, Keys](../README.md#keys) and [Adding a game](../README.md#adding-a-game).
- **Unity 2017.4 serialized and `Shader` formats** (`unityfs.py`, `shader_split.py`) and the rewriter's jump-table form (`patch_nso.py`), by armyja: [shaders.md, Containers by engine](../../../docs/reference/shaders.md#containers-by-engine), [Offset jump tables with the bounds check on another register](../../../docs/reference/rewriter.md#offset-jump-tables-with-the-bounds-check-on-another-register).
## Open
- "SDK 5.5.0 (the oldest so far)" in `game.tsv` and docs/development.md is stale: DARK SOULS: REMASTERED (4.5.0) and Mario Kart 8 Deluxe (4.4.0) are older.
- The tracing text in [nvn-objects.md](../../../docs/reference/nvn-objects.md#sizes-the-hardware-never-reports-as-0-zcull-save-area) names observation gates "in its `patches.py`"; the profile has no `patches.py` now.
- `game.tsv` has no `sys_resource` row, so the host reports its default, 32 MB (`game.py`, `hle_svc.c`), although the NPDM declares none and the notes describe the game as having no virtual address memory. Not checked against the game's behaviour.