10 KiB
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
IsVirtualAddressMemoryEnabledis 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 callsSetMemoryHeapSize. Unity's own allocator (main+0x40f1d4→AllocateAddressRegion) reserves its pools beside it, each aligned by its size. - Resolved:
StandardAllocator::Initializeaborted innninitStartup. 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) andFreeAddressRegion(sdk+0x139df0), with commits from theMapPhysicalMemorylog, 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
nninitStartupreads it. This game is whymem_map_limitis 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 abr x18with no table in front of it (main+0x1d271bcand+0x204ebec). Not a jump table but an indirect tail call: an IL2CPP thunk loads the method pointer withldr x18, [x0, #0x18], moves its stack arguments with every other scratch register (ldr x16, [sp],ldp x17, x15, [sp, #8], …) and ends withbr x18. The rewriter now turns it intox16 = 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). TheDM_FS=1output had noStreamingAssets/aa/settings.jsonat all. Unity 2022's C# file API goesFile.Exists→SystemNative_Stat→stat=open+fstat+close(sdk+0x948b0). nnmusl's path check (sdk+0x904e0, then the convertersdk+0x8ef50) asksnn::fs::IsPathMounted,GetPathLengthMax,GetUtf16PathLengthMax,GetEntryNameLengthMaxandGetUtf16EntryNameLengthMaxbefore it callsnn::fs::GetEntryType/OpenFile, and the SDK's own versions read the empty mount table (IsPathMountedfalse; 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 intorom:/…. armyja's fork (GitHub issue #18) patched the managed stat wrappers by address instead; the hooks need no per-build table. - Resolved:
std::terminate(Breakfromsdk+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) thatnntimeStartupsets oncetime::Initializesucceeds: 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 (
nninitStartupcallsSetMemoryHeapSize/AllocateMemoryBlockonly without virtual address memory, and nothing else inmaincalls them) and the tolerant common string table of pull request #15 (1.0.0.2'smainhas the table intact atmain+0x46169ac, 109 entries ending atHash128). - It calls
GetSharedFontAddresswithout importingScalableFontEngineHelper::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
mainbeside the game's shaders. - Unity 2022
Shaderobjects: 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'sm_ParameterBlobIndices; their bytes startPGlobals), and the player's programs are listed inm_PlayerSubPrograms[tier][].m_BlobIndex(m_SubProgramsis empty);unity_code_blobskeeps only those. Subprogram version 202012090 (from Unity 2021.2) has no local keyword list, sounityfs.subprogramreads 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 theunitybuiltinshadersbundle, 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
Shadertype hash (ea424a5f…) is the one Brain Memory Training Academy's stripped files carry; the Unity 2022.3 layout now written out inunityfs.py(KNOWN_TREES) was checked against this game's own tree: the same 2,324 nodes with the same alignment flags, and its 40 bundleShaderobjects 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 itsunitybuiltinshadersbundle 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 readc2rows 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:
- Indirect tail calls through
x18(tail_call/site_br_call), tried only where the rewriter used to stop: Indirect tail calls through x18 in IL2CPP thunks. - The SDK 17 C library's path checks (
IsPathMountedand the length-limit queries,hle_fs.c) and the C library's real-time clock (hle_time.c): C library path checks ask the file system's limits,clock_gettime(CLOCK_REALTIME)through the time source pointer. - Node area cut for a Unity 2022 game with virtual address memory (
StandardAllocator::Initialize(nullptr, pool − used)): The SDK's address-region node area. - Unity 2022
Shaderobjects (unity_code_blobs, no local keyword list inunityfs.subprogram): Unity 2022 Shader objects with parameter blobs.
Open
- The one geometry shader does not build (Metal cannot express it).
game.tsvhas nosys_resourcerow, so the host reports its default, 32 MB (game.py,hle_svc.c), where the NPDM declares 16 MB.