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

10 KiB
Raw Permalink Blame History

Ace Attorney Investigations Collection (010005501E68C000)

Update 1.0.0.2 (v131072) over the base game, which is an XCI; its DLC NSP is a licence only. SDK 17.5.3. Unity 2022.3.17f1 IL2CPP on NVN. Modules main, subsdk0 (the movie module, NintendoSDK_movie) and sdk. Plays on the iPhone; 60 fps, as on the Switch (present interval 1). On the Mac it was brought up through the first-boot settings, the title, Investigations 1's menus and Episode 1's opening and first investigation up to the first Logic prompt (25 minutes with no red line). Much of the bring-up was worked out first by armyja in a fork (GitHub issues #17, #18, #19 and pull requests #15, #16).

What it needs

Setting Where Why
Node area cut 1 GB → 62 MB dm/tools/sdk_patches.py, sdk build 311deab1… (the AddressRegionManager constructor at sdk+0x139458; the movs at +0x139468 and +0x1394fc) without it StandardAllocator::Initialize(nullptr, pool − used) (main+0x874b6c) aborts at its first CentralHeap::Init; see Memory
5 GB guest layout: mem_reserve 0x140000000, mem_alias 0x118000000, mem_stack 0x10000000 game.tsv Engage's block: 256 MB in front + 4.375 GB alias region + 256 MB stack region + 128 MB heap region (the game never calls SetHeapSize)
app_pool 0x40000000 (1 GB) game.tsv with Engage's 2.5 GB pool the live address regions reached the end of the alias region after 140 s; see Memory
shader_groups _unitybuiltinshaders_=engine /Resources/=engine /StreamingAssets/=assets /Data/=data game.tsv engine built-ins (the unitybuiltinshaders bundle, Resources/) go to engine, the player's serialized files to data, the other Addressables bundles to assets

Memory

  • The NPDM declares 16 MB of system resource, so IsVirtualAddressMemoryEnabled is true: nninitStartup (main+0x874b00) hands application pool − used memory to StandardAllocator as virtual address memory, one 1 GB-aligned address region in the alias region, and never calls SetMemoryHeapSize. Unity's own allocator (main+0x40f1d4 → AllocateAddressRegion) reserves its pools beside it, each aligned by its size.
  • Resolved: StandardAllocator::Initialize aborted in nninitStartup. SDK 17's AddressRegionManager takes about 1 GB of the alias region as its node area first (the same node area as Engage's SDK): CentralHeap::Init(nullptr, pool − used) got no 1 GB-aligned region for 3.14 GB after it. The constructor (sdk+0x139458: limit = alias start + 0x3fe00000 at +0x28, a unit heap of 0x30-byte nodes, the area then registered as occupied) takes Engage's cut to 62 MB.
  • Resolved: then the alias region filled in minutes. With Engage's 2.5 GB pool the game ran, but probes on VammManager::AllocateAddressRegion's success path (sdk+0x1395a8: x21 size, x22 address) and FreeAddressRegion (sdk+0x139df0), with commits from the MapPhysicalMemory log, over the first five minutes (the title, Episode 1's opening, the first investigation) showed StandardAllocator reserving 2.44 GB and committing only 0.19 GB, Unity's pools holding 1.06 GB, and the live regions ending 4.37 GB into the 4.375 GB alias region after 140 s. Over ten minutes of the same route, a 1.5 GB pool (StandardAllocator 1.44 GB at +0.75 GB) let them reach +3.91 GB, a 1 GB pool +2.85 GB.
  • Current: the pool is reported as 1 GB. StandardAllocator gets 0.94 GB (five times its use) right after the node area, and running out of address space is a crash, while a pool a little smaller than what the game maps in all (1.02 GB at the peak) changes nothing: the host does not enforce it and only nninitStartup reads it. This game is why mem_map_limit is off by default (dm/games/README.md, Keys).
  • On the Mac about 1.55 GB resident and 1.25 GB of Metal allocations, flat from the first investigation on (25 minutes; address space at +2.81 GB).

Rewriter

  • Resolved: a jump table <addr> assertion on a br x18 with no table in front of it (main+0x1d271bc and +0x204ebec). Not a jump table but an indirect tail call: an IL2CPP thunk loads the method pointer with ldr x18, [x0, #0x18], moves its stack arguments with every other scratch register (ldr x16, [sp], ldp x17, x15, [sp, #8], …) and ends with br x18. The rewriter now turns it into x16 = guest x18; br x16 (tail_call / site_br_call).

Services

The C# file API and the real-time clock reach the SDK's C library (nnmusl), not nn::fs / nn::time directly.

  • Resolved: Addressables never started, then a null asset crash (main+0x31100ec). The DM_FS=1 output had no StreamingAssets/aa/settings.json at all. Unity 2022's C# file API goes File.Exists → SystemNative_Stat → stat = open + fstat + close (sdk+0x948b0). nnmusl's path check (sdk+0x904e0, then the converter sdk+0x8ef50) asks nn::fs::IsPathMounted, GetPathLengthMax, GetUtf16PathLengthMax, GetEntryNameLengthMax and GetUtf16EntryNameLengthMax before it calls nn::fs::GetEntryType / OpenFile, and the SDK's own versions read the empty mount table (IsPathMounted false; the next one aborted with Result 0x35f202). The host answers them from its mount table, with Horizon's 0x300-byte limits and no UTF-16 limit (hle_fs.c). The path .NET builds is /rom:/…; nnmusl's converter turns it back into rom:/…. armyja's fork (GitHub issue #18) patched the managed stat wrappers by address instead; the hooks need no per-build table.
  • Resolved: std::terminate (Break from sdk+0x5bfa48) at the first real-time read, right after the first-boot dialog (main+0x3f3f68 → std::chrono::system_clock::now()). clock_gettime(CLOCK_REALTIME) calls through a source pointer (sdk+0xb05010) that nntimeStartup sets once time::Initialize succeeds: 1 = nn::time::GetStandardUserSystemClockCurrentTimespec, 2 = the adjustable clock's (sdk+0x8e414). Both asked the time service; they are host take-overs now (hle_time.c), so the C library's real-time clock is the host's clock.
  • Not needed in the end: the process heap of pull request #16 (nninitStartup calls SetMemoryHeapSize / AllocateMemoryBlock only without virtual address memory, and nothing else in main calls them) and the tolerant common string table of pull request #15 (1.0.0.2's main has the table intact at main+0x46169ac, 109 entries ending at Hash128).
  • It calls GetSharedFontAddress without importing ScalableFontEngineHelper::Decode, so the host gives it the TTF itself (font_skip, see Patrick's Parabox).

Files and shaders

  • 4,951 stage shaders: 2,475 vertex / pixel pairs (4,294 in the player's serialized files, 517 built-ins, 140 in the Addressables bundles) and one geometry shader, which Metal cannot express (its build fails). Unity's player also has 11 GLSLC outputs of its own in main beside the game's shaders.
  • Unity 2022 Shader objects: the program code keeps the 0x60-byte stage header, but from Unity 2022 the blob also holds each program's parameter tables as entries of their own (the parsed form's m_ParameterBlobIndices; their bytes start PGlobals), and the player's programs are listed in m_PlayerSubPrograms[tier][].m_BlobIndex (m_SubPrograms is empty); unity_code_blobs keeps only those. Subprogram version 202012090 (from Unity 2021.2) has no local keyword list, so unityfs.subprogram reads no second keyword array (with one it read the code length as a keyword count).
  • The player's serialized files have no type trees and the Addressables bundles hold no Shader, except the unitybuiltinshaders bundle, whose tree has the same hash and is borrowed in the split's second pass. armyja's issue #17 proposed UnityPy and a reader of its own; neither was needed.
  • Its Shader type hash (ea424a5f…) is the one Brain Memory Training Academy's stripped files carry; the Unity 2022.3 layout now written out in unityfs.py (KNOWN_TREES) was checked against this game's own tree: the same 2,324 nodes with the same alignment flags, and its 40 bundle Shader objects decode identically with either. The written-out tree is given only in the second pass: taken in the first, this game's player files came before its unitybuiltinshaders bundle and the built-ins' source column and package indices moved; with the second pass its tables come out byte-identical.
  • Binding layout as Engage's (separate textures and samplers): all 4,950 translatable shaders decode every binding (shaders with a binding whose slot was not decoded 0), and 190 read c2 rows as data.

Mac routes

  • A fresh save through the first-boot notices (the privacy prompt's second button is "Next"), the title and Investigations 1's Episode 1: DM_KEYS="f600:A~0.3,f1200:A~0.3,f1800:A~0.3,f2400:A~0.3,f3000:Down~0.3,f3200:A~0.3,f3800:A~0.3,f4400:A~0.3,f5000:A~0.3,f5600:A~0.3,f6200:A~0.3,f6800:A~0.3*270/120". The first investigation in Edgeworth's office comes at about frame 18,000; at about frame 30,000 the first Logic prompt waits for L.

Found on this game

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

Open

  • The one geometry shader does not build (Metal cannot express it).
  • game.tsv has no sys_resource row, so the host reports its default, 32 MB (game.py, hle_svc.c), where the NPDM declares 16 MB.