Catherine: Full Body (0100BF00112C0000)
Update 1.0.1a (v131072) over the base game, SDK 8.2.1. Atlus's GFD engine (the game's code name xrd758) on NVN, with CRI middleware. Modules main, subsdk0 (GLSLC, the run-time shader compiler; the same build as Link's Awakening's, 01006BB00C6F0000), subsdk1 (the SDK's movie library) and sdk; sdk and the movie module are the builds Super Smash Bros. Ultimate runs (01006A800016E000). Plays on the iPhone; 30 fps, as on the Switch. The README lists its IPA at about 14 GB.
Brought up on the Mac: the ATLUS logo, the title, the puzzle type choice, the Golden Playhouse intro, the anime opening (CRI movies with sound) and the first Nightmare stage with Vincent under control, no red line in a 6-minute run.
What it needs
| Setting | Where | Why |
|---|---|---|
mem_used 0x6000000 (96 MB) |
game.tsv |
the rewritten modules take 92 MB, more than the default 64 MB; see Memory |
4 GB layout: mem_reserve 0x100000000, mem_alias 0x10000000, mem_stack 0x10000000, app_pool 0xCD500000 |
game.tsv |
the block of the other games without virtual address memory: 256 MB in front + 256 MB alias region + 256 MB stack region + 3.25 GB heap region |
| Node area cut and 32 MB alignment for regions of 1 GB and more | dm/tools/sdk_patches.py, sdk build 5fd4f531… |
made for Super Smash Bros. Ultimate on the same build; this game has no virtual address memory and never makes an address region |
| The 2 shaders it compiles at boot | captured on the Mac (hle_glslc.c) |
compiled at run time by its own GLSLC from GLSL sources in main, captured on the Mac as Three Houses' are (010055D009F78000) |
Frame rate
- 30 fps, as on the Switch (present interval 2). GPU p50 4.7 ms, CPU about a fifth of a core on the Mac.
Memory
- The NPDM declares no system resource (META +0x14 = 0):
nninitStartupsets a 3.11 GB heap in the 4 GB layout (pool − used;game.tsvsays the pool − 64 MB);main's bss is 48 MB. - Resolved: the memory pool for the game's 96 MB GPU heap had no storage (
MemoryPoolInitializered line), then a null read atmain+0x2eb954. The log said[mem] SetHeapSize … + modules 0x5c00000 > app pool: the rewritten modules take 92 MB (main61 MB with its bss, GLSLC 9.5 MB, the movie library 7.6 MB,sdk13.8 MB), soSetHeapSize(pool − 64 MB)failed and the game ran on a 32 MB heap, as Age of Calamity did.mem_usedis 96 MB, the NSP script's guess after the rewrite. - On the Mac (M6) in the first stage: 0.79 GB resident, 1.9 GB of Metal allocations.
Shaders
- 3,128 GLSLC outputs (1,244 vertex, 1,872 pixel, 12 compute), all of one stage each: 3,050 loose files in
data/shaders_binary(*.vs/*.ps, read like FINAL FANTASY TACTICS' technique files), 76 inmainand 2 the game compiles at boot with its own GLSLC (captured on the Mac). Single-stage programs paired with the cover, as in 13 Sentinels. Every binding decodes with Mario's layout. - Resolved: a null read at
main+0x3316dcinnnMain, right after the window setup (the program object's interface pointers at +0xd0 / +0xd8 both null). The game compiles two built-in programs at boot with its own GLSLC, and first checks that GLSLC's GPU code version againstnvnDeviceGetIntegeritems 0x2E–0x31 (the driver's supported range,main+0x330dcc). The host reported Super Mario Odyssey's SDK 7 driver range, 1.5–1.14; this GLSLC writes 1.15, so nothing was compiled and the first uniform lookup read the empty program. The host now reports at least the version the game's ownglslcGetVersionreturns (hle_glslc.cdm_glslc_gpu_code,hle_nvn.c). - Resolved: shader type 0 in every vertex shader's SPH (word 0 =
0x0061; every other game's vertex shaders have0x0461).shader_split.pyfound no stage for them and stopped at the first captured output (no GLSLC output). Ryujinx'sShaderHeaderreads a graphics header's type 0 as a vertex shader;sph_stagedoes too. A census of the other games' packages and of everymainon hand found no other type-0 vertex shader. - Resolved: every post pass and the window drew one texel's colour (a black screen with sound). Shader type 0 is Maxwell's first vertex slot (VERTEX_CULL_BEFORE_FETCH), and this GLSLC (GPU code version 1.15) writes every vertex shader as two programs in one code area: part A (VertexA) at code +0 (the SPH above) loads the position attribute and writes only the position; part B (VertexB), at control +0x700 with a 0x30-byte header of zeros and an SPH of type 1, reads the other attributes and writes the other outputs. Control +0x748 lists both as {SPH offset, length from the SPH, ?}. The hardware runs A, then B. Translated alone, A left every varying at (0, 0, 0, 1), so the full-screen passes sampled their sources at (0, 0) and the 2D draws their atlases' first texel.
dmsh_build.py(vertex_ab) merges the two as Ryujinx'sTranslatorContext.Translate(VertexA)does: A's instructions with each unconditionalEXITturned into aBRAto B's first instruction, then B's, under B's SPH with A's flags and input / output maps ORed in and the larger local memory sizes. In all 1,244: A writes no generic output, A ends in anEXITpredicated on CC FCSM_TR (which Ryujinx never takes; left as it is), an unconditionalEXITand aBRAto itself, and neither part has an absolute or table jump, so moving B keeps every branch. c1 is what follows part B (zero padding in all of them). A type-0 shader with no part B listed is translated as it is.
Rendering
- Resolved: the cutscene subtitle band was a hard-edged dark box with a magenta stroke through it. The band is a 32x512 BC3 gradient the game copies from a staging buffer (
nvnCommandBufferCopyBufferToTexture, recorded at the start of a frame,main+0x305e64), and the GPU copy read the next texture's blocks: the source was right when recorded and when encoded, but the game's resource thread wrote the next texture into the same staging block 64 ms later, before that first submission had even been committed, because the host spent 300 ms building pipelines inside it. On the Switch the GPU runs a submission's copies as soon as the submit returns. A segment is now committed at the first pass boundary after any buffer → texture copy (g_cut_copy,sub_cutinnvn_mtl.m); theresidency setline counts these cuts (N after buffer → texture copies, a few per 300 frames in this game, only while it loads). The game reuses its staging memory about a frame after recording a copy. Found withDM_NVN_HILITEon the band's pixel shader,DM_NVN_TEXRAWof the texture (decoded: opaque black and magenta blocks, not a fade) andDM_PROBE_WATCHon the staging address beside a log of the copy's submission completing.
Movies and audio
- CRI ADX2 sound (
data/sound*/*.acb/.awb) through one 6-channel AudioOut. - CRI Sofdec2 movies (
data/movie*/*.usm) whose H.264 goes through the SDK'smovie::Decoder(the host's VideoToolbox decoder; mode 1, output format 4). - Resolved: a read far outside guest memory at
main+0x344f74on the first movie picture (Sofdec2's NV12 → RGB conversion). The player readsnv12-y-stride,nv12-y-offset,nv12-uv-strideandnv12-uv-offsetfrom the output format and checks none of the results (main+0x34e694); the host set nonv12-uv-stride, so the chroma stride was stack garbage. It is the luma stride now, as for any NV12 picture (hle_movie.cmd_format).
Services
- Resolved: abort in
sm::Initializeundernn::settings::GetAvailableLanguageCodeCount()(main+0x117590: it comparesLanguageCode::Make(0 … count − 1)withGetDesiredLanguage's code). The host returns the count of its language table, beside Paper Mario'sGetAvailableLanguageCodes(hle_sdk16.c). The other imports no hook names run insdk(theLanguageCodecomparison,ClockSnapshot's constructor and getters, fibers,NumberLineAllocator).
Files
- Everything is in five CRI CPK archives (
data/Base.cpk,Model.cpk,Puzzle.cpk,Keyfree.cpk,Patch.cpk);romfs_markerisdata/Model.cpk. - Only the merged 1.0.1a copy is extracted; the base game's own build IDs are added when needed.
Input
-
- pauses a cutscene (B resumes, Y skips); the left stick moves Vincent, A grabs blocks.
Mac routes
- From a fresh save,
DM_KEYS="5:A*25/2,60:Plus*14/4,61:Y*14/4,62:A*14/4"skips the intro scenes into the first Nightmare stage (about frame 3,300);120:LUp~0.4*40/1.5then climbs.
Found on this game
General host changes first needed here; each applies to every game:
- GLSLC GPU code version range follows the game's own GLSLC (
hle_glslc.cdm_glslc_gpu_code,hle_nvn.c; on Bayonetta 3 the reported newest version came to follow the SDK, 1.15 from SDK 8 after this game's GLSLC 8.2.1): GLSLC GPU code version against the driver's range, Newest GPU code version reported by the driver. - VertexA / VertexB merge (
shader_split.pysph_stage,dmsh_build.pyvertex_ab), explained in full under Shaders: VertexA and VertexB parts merged into one vertex shader. - Commit at the first pass boundary after a buffer → texture copy (
g_cut_copy,sub_cut; always on, even withDM_NVN_CUT=0): same section. nv12-uv-stridein the movie decoder's output format (md_formatinhle_movie.c): Output format keys (md_format).nn::settings::GetAvailableLanguageCodeCount(hle_sdk16.c): same section.
Open
game.tsvstill describes the game as "In bring-up"; the development notes say it plays on the iPhone.- The heap: the development notes say 3.11 GB (pool − used),
game.tsv"the pool − 64 MB (3.11 GB)". - The profile has no
pad_layoutrow, so the virtual gamepad starts in the default 2D layout; the README's list of games that start in 2D does not name it ("the other games in 3D").