diff --git a/dm/games/0100D680194B2000/README.md b/dm/games/0100D680194B2000/README.md new file mode 100644 index 0000000000..2a0f1c409f --- /dev/null +++ b/dm/games/0100D680194B2000/README.md @@ -0,0 +1,27 @@ +# Pikmin 2 (`0100D680194B2000`) + +Base game 1.0.0 (v0), SDK 14.3.0. Nintendo European Research & Development's (NERD) proprietary GameCube layer (**Hagi** / **VirtualFlipper**), on **Vulkan**. The PowerPC code is statically recompiled into `main` (`Statifier true`), with no JIT. Hagi emulates the GameCube hardware: Flipper GX on Vulkan with run-time SPIR-V translation, AX DSP audio streaming from `rom://pikmin2/AudioRes/Stream/`, and emulated GameCube flash memory / save cards. Modules `main`, `subsdk0` (NintendoSDK_movie) and `sdk`. Plays on the Mac at 60 fps. + +## What it needs + +| Setting | Where | Why | +|---|---|---| +| `op_mode handheld` | [`game.tsv`](game.tsv) | standard handheld layout; docked uses TV scaling | +| `pad_layout full` | `game.tsv` | the GameCube controller layout mapped to the Switch Pro / full controller | +| `modules main subsdk0 sdk` | `game.tsv` | `subsdk1`–`subsdk4` are replaced by the host's Vulkan layer | +| 9 GB block: `mem_reserve 0x240000000`, `mem_alias 0x150000000`, `mem_stack 0x10000000` | `game.tsv` | Hagi calls `AllocateAddressRegion` for 4.002 GB (`0x100200000`) to map the entire 32-bit GameCube address space; see [Memory](#memory) | +| Cut at `main+0x2ba5b4` | [`patches.py`](patches.py) | `_ZN5movie` destructor unconditionally calls `AbortImpl` during video teardown; NOP'd to allow clean cutscene transitions | +| Node area cut 1 GB → 62 MB + null heap guard | `dm/tools/sdk_patches.py`, `sdk` build `2aef2cffb104ee7c9e212fd2f4c731e9` | AddressRegionManager node area compaction and early return on uninitialized `heap_base` in `CentralHeap::Free` | + +## Memory + +- NPDM declares 8 MB of system resource. The game calls `SetHeapSize` for a 2.64 GB heap (`0xa8e00000`), so the heap region is 2.75 GB. +- **GameCube Virtual Memory mapping:** During `VirtualMachine::Initialize` (`main+0x49eb20`), `Memory::Initialize` (`main+0x2a3154`) invokes `nn::os::AllocateAddressRegion` requesting `0x100200000` bytes (4.002 GB) to map the 32-bit GameCube address space. In a default compact guest configuration, this request fails with `0x1803` (`ResultOutOfMemory`), cascading into virtual machine destruction. Sizing `mem_alias` to `0x150000000` (5.25 GB) and `mem_reserve` to `0x240000000` (9 GB) gives both the SDK AddressRegionManager node area and Hagi's GameCube region ample contiguous address space. +- `CentralHeap::Free` (`sdk+0x11de60`): early deallocation before heap initialization dereferences null `heap_base` (`x8 == 0`); patched with `cbz x8, #0x11df44`. +- Footprint on the Mac: ~1.8 GB resident, flat past Day 1 Valley of Repose. + +## Vulkan & Graphics + +98 `vk*` imports through the host's Vulkan layer (`hle_vk.m`), with Hagi generating SPIR-V shaders dynamically at run time. The host compiles pipelines directly to Apple Metal. +- Handheld mode viewport scaling preserves the 16:9 Switch presentation with letterboxing on wider Mac displays. +- Stable 60 fps pacing maintained across title sequence, pre-rendered VP9 cutscenes, and in-game 3D exploration. diff --git a/dm/games/0100D680194B2000/game.tsv b/dm/games/0100D680194B2000/game.tsv new file mode 100644 index 0000000000..23da209a2b --- /dev/null +++ b/dm/games/0100D680194B2000/game.tsv @@ -0,0 +1,44 @@ +# Pikmin 2 (0100D680194B2000, 1.0.0), SDK 14.3.0. +# NERD's Hagi Wii/GameCube layer: statically recompiled into main, Hagi stands in for +# the GameCube hardware (GX -> Vulkan with SPIR-V generated at run time). +# Renders with Vulkan: main imports 98 vk* functions; the host implements them on Metal (dm/gfx/vk/hle_vk.m). +# Only main, subsdk0 (NintendoSDK_movie) and sdk are loaded. + +# ---- Identity ---- +title_id 0100D680194B2000 +program_id 0100D680194B2000 + +# ---- App ---- +name Pikmin 2 +ipa Pikmin2.ipa +app_category public.app-category.games +pad_layout full +op_mode handheld + +# ---- Versions ---- +build v0 1.0.0 main=b23a43495191e7ad33d1d0effcbb1d84 subsdk0=c781dd63daf0ddcdb9975da50a198f93 sdk=2aef2cffb104ee7c9e212fd2f4c731e9 +package v0 +shader_versions v0 + +# ---- Graphics ---- +# main imports Vulkan, not NVN: the host's Vulkan layer (dm/gfx/vk/hle_vk.m). Hagi generates its GX shaders as SPIR-V at run time. + +# ---- Files ---- +modules main subsdk0 sdk +romfs_marker data/video_replace/Application/thp/opening1.webm +zip_store .cache:.har:.szs:.thp:.webm:.metallib + +# ---- Memory ---- +# NPDM system resource 8 MB. The game calls SetHeapSize for a 2.64 GB heap (0xa8e00000), so the heap region is 2.75 GB. +# The movie player's StandardAllocator initializes with virtual address memory in the 1.75 GB alias region +# after the SDK's node area (cut from 1 GB to 62 MB in dm/tools/sdk_patches.py for this SDK 14.3.0 build). +# NERD's Hagi maps the 32-bit GameCube address space (0x100200000 bytes = 4.002 GB) via AllocateAddressRegion, +# requiring a 5.25 GB alias region and 9 GB guest reservation. +mem_reserve 0x240000000 +mem_alias 0x150000000 +mem_stack 0x10000000 +app_pool 0xCD500000 +sys_resource 0x800000 + +# ---- Shader split ---- +shader_note Pikmin 2 has no NVN shaders (its SPIR-V shaders go through the Vulkan layer) diff --git a/dm/games/0100D680194B2000/patches.py b/dm/games/0100D680194B2000/patches.py new file mode 100644 index 0000000000..14280d24e9 --- /dev/null +++ b/dm/games/0100D680194B2000/patches.py @@ -0,0 +1,19 @@ +# Pikmin 2's own code, keyed by the NSO build ID. SDK cuts live in +# dm/tools/sdk_patches.py. +CUTS = { + # 1.0.0 (v0): _ZN5movie destructor unconditionally calls nn::diag::detail::AbortImpl + # at main+0x2ba5b4 during video teardown. NOP out the abort call. + 'b23a43495191e7ad33d1d0effcbb1d84': [ + (0x2ba5b4, 'c9010094', '1f2003d5', '_ZN5movie destructor: abort call → nop'), + ], +} + +BINDING = [ + { + 'what': '_ZN5movie destructor: abort call nop', + 'now': 'build', + 'goal': 'unreviewed', + 'purpose': 'runtime', + 'why': 'unconditional AbortImpl call during movie player teardown in Hagi video cleanup', + }, +] diff --git a/dm/games/README.md b/dm/games/README.md index 27b6d7a8b6..b44c401e50 100644 --- a/dm/games/README.md +++ b/dm/games/README.md @@ -146,6 +146,7 @@ One row per profile; the notes are each profile's `README.md`. | [Patrick's Parabox](0100E44016C22000/README.md) | `0100E44016C22000` | 1.0.1 | 13.3.7 | Unity 2020.3.30f1 IL2CPP | NVN | | [Persona 4 Golden](010062B01525C000/README.md) | `010062B01525C000` | 1.0.2 | 14.3.0 | Atlus in-house (CRI middleware) | NVN | | [Persona 5 Royal](01005CA01580E000/README.md) | `01005CA01580E000` | 1.0.2 | 13.3.2 | Atlus GFD / Needle (CRI middleware) | NVN | +| [Pikmin 2](0100D680194B2000/README.md) | `0100D680194B2000` | 1.0.0 (base) | 14.3.0 | NERD Hagi (GameCube) | Vulkan | | [Pikmin 4](0100B7C00933A000/README.md) | `0100B7C00933A000` | 1.1.1 | 10.4.8 | Unreal Engine 4.26.2 | NVN | | [Pokémon Brilliant Diamond](0100000011D90000/README.md) | `0100000011D90000` | 1.3.0 | 11.4.3 | Unity 2019.4.27f1 IL2CPP | NVN | | [Pokémon Legends: Arceus](01001F5010DFA000/README.md) | `01001F5010DFA000` | 1.1.1 | 11.4.0 | Game Freak's engine (between Sword's and Trinity) | NVN | diff --git a/dm/tools/sdk_patches.py b/dm/tools/sdk_patches.py index 52d741d4ab..6f0cce4028 100644 --- a/dm/tools/sdk_patches.py +++ b/dm/tools/sdk_patches.py @@ -11,6 +11,14 @@ (0x93e8c, 'ea030232', '0a80a052', 'VammManager occupied node area: 1 GB to 64 MB'), (0x93fe0, 'ea030232', '0a40a052', 'AllocateAddressRegion: align regions of 1 GB and more to 32 MB'), ], + # Pikmin 2 1.0.0 (v0) sdk (SDK 14.3.0, NERD Hagi): AddressRegionManager node area compaction (1 GB -> 62 MB) + # and null guard on uninitialized heap_base in CentralHeap::Free (cbz x8, #0x11df44). + # The constructor is at sdk+0x12d8a0 (+0x12d8b0 sets node area limit, +0x12d94c registers it as occupied). + '2aef2cffb104ee7c9e212fd2f4c731e9': [ + (0x12d8b0, '08fca752', '087ca052', 'AddressRegionManager constructor: node area limit mov w8,#0x3fe00000 → mov w8,#0x3e00000'), + (0x12d94c, '09fca752', '097ca052', 'AddressRegionManager constructor: length of the node area registered as an occupied region mov w9,#0x3fe00000 → mov w9,#0x3e00000'), + (0x11de60, 'bf3903d5', '280700b4', 'CentralHeap::Free: return early if heap_base is null (cbz x8, #0x11df44)'), + ], } # SDK-wide function patches (dm/tools/nso_match.py): the two fixes above that hold for any game on an iPhone's small address space, for every game's sdk of a build with no CUTS diff --git a/docs/agents/game-patches.md b/docs/agents/game-patches.md index 6f33e425a4..e599379823 100644 --- a/docs/agents/game-patches.md +++ b/docs/agents/game-patches.md @@ -21,7 +21,7 @@ A patch's binding is what its correctness is anchored to, from the loosest. A pa ## Gaps -Every patch bound tighter than it could be: the open gaps, then the blocked, kept and unreviewed ones; in each, those reaching the most games first (the games whose profiles register the builds it touches), then those farthest from their goal. +Every patch bound tighter than it could be: the open gaps, then the blocked, kept and unreviewed ones; in each, those reaching the most games first (the games whose profiles register the builds it touches), then those farthest from their goal. 1 patch has no reviewed goal yet. | Now → goal | Patch | Purpose | Status | Evidence | Games | Where | Why | |---|---|---|---|---|---|---|---| @@ -50,6 +50,7 @@ Every patch bound tighter than it could be: the open gaps, then the blocked, kep | `build` → `game` | 60 fps frame step (112 sites) | frame rate | kept: 1.0.1 (2019) is the last update; a function match would serve no other build of this game | checked: nso_fnmap of its 112 sites into those four mains (2026-11-09) | 1 | [ASTRAL CHAIN](../../dm/games/01007300020FA000/README.md) | the game's own clock and timers (a community 60 fps mod on the host's frame step). PlatinumGames' other games do not share them: none of its 15 functions has an exact match in Bayonetta, Bayonetta 2, Bayonetta 3 or NieR:Automata | | `build` → `game` | 60 fps frame step and render scale (ksys::VFR) | frame rate | kept: 44 sites in 34 functions to fingerprint for an update that may not come (1.9.0, the Switch 2 Edition release); convert when a new build appears | checked: nso_fnmap of its 44 sites into those 13 mains (2026-11-09) | 1 | [The Legend of Zelda: Breath of the Wild](../../dm/games/01007EF00011E000/README.md) | the game's own frame code; the frame-step state page is general. No engine sibling shares it: none of its 34 functions has an exact match in Tears of the Kingdom, SMO, MK8D, ACNH, Splatoon 2 / 3, Kirby, Mario Party, Ocarina, Smash, Metroid Dread or Origami King (only the widescreen stubs' sead::PerspectiveProjection functions recur) | | `build` → `game` | Hagi coroutine stack region 1 GB → 64 MB | memory | kept: 1.1.4 is the last update; a match over Hagi would serve no other build of this game | checked: the final build 1.1.4 (the only one the profile packs), 2026-11-04 | 1 | [Super Mario 3D All-Stars: Super Mario Sunshine](../../dm/games/010049900F546002/README.md) | a size inside Hagi's own code: nothing the host sees tells a 1 GB reservation it can shrink from one it cannot | +| `build` → unreviewed | _ZN5movie destructor: abort call nop | runtime | unreviewed | judgement | 1 | [Pikmin 2](../../dm/games/0100D680194B2000/README.md) | unconditional AbortImpl call during movie player teardown in Hagi video cleanup | ## Summary @@ -74,6 +75,7 @@ Every patch bound tighter than it could be: the open gaps, then the blocked, kep | [NieR:Automata The End of YoRHa Edition (0100B8E016F76000)](#nierautomata-the-end-of-yorha-edition-0100b8e016f76000) | 8 | 0 | 0 | 0 | 1 | `build` → `game` ×2 | | [Ori and the Will of the Wisps](#ori-and-the-will-of-the-wisps) | 0 | 0 | 10 | 0 | 0 | `build` → `engine` | | [Paper Mario: The Thousand-Year Door](#paper-mario-the-thousand-year-door) | 1 | 0 | 1 | 0 | 0 | `build` → `game` | +| [Pikmin 2](#pikmin-2) | 1 | 0 | 0 | 0 | 0 | `build` → unreviewed | | [Pokémon Legends: Arceus](#pokémon-legends-arceus) | 0 | 0 | 0 | 1 | 0 | `game` | | [Super Mario 3D All-Stars: Super Mario Sunshine](#super-mario-3d-all-stars-super-mario-sunshine) | 2 | 0 | 0 | 0 | 0 | `build` → `game` | | [Super Smash Bros. Ultimate](#super-smash-bros-ultimate) | 11 | 0 | 0 | 1 | 0 | `game` | @@ -451,6 +453,15 @@ Every patch bound tighter than it could be: the open gaps, then the blocked, kep - cut `+0x2e77a4`: frame step floor in 60 Hz vsyncs: mov w9,#2 → mov w9,#1 (the game steps by the vsyncs that passed, down to one) - frame step `+0x2e77f8`: frame step: the base clock steps by the host's vsyncs (state page target fps; 0 = the game's measured step) +### Pikmin 2 + +[Pikmin 2](../../dm/games/0100D680194B2000/README.md), `0100D680194B2000`, [patches.py](../../dm/games/0100D680194B2000/patches.py). + +- **`build` → unreviewed**, runtime, unreviewed, judgement: _ZN5movie destructor: abort call nop. Unconditional AbortImpl call during movie player teardown in Hagi video cleanup + +- **1.0.0 `main`** `b23a4349…` ([patches.py:6](../../dm/games/0100D680194B2000/patches.py#L6)) + - cut `+0x2ba5b4`: _ZN5movie destructor: abort call → nop + ### Pokémon Legends: Arceus [Pokémon Legends: Arceus](../../dm/games/01001F5010DFA000/README.md), `01001F5010DFA000`, [patches.py](../../dm/games/01001F5010DFA000/patches.py). @@ -616,6 +627,10 @@ Cuts of Nintendo SDK `sdk` builds ([sdk_patches.py](../../dm/tools/sdk_patches.p - cut `+0x93dfc`: VammManager node area limit: 1 GB to 64 MB - cut `+0x93e8c`: VammManager occupied node area: 1 GB to 64 MB - cut `+0x93fe0`: AllocateAddressRegion: align regions of 1 GB and more to 32 MB +- **sdk** `2aef2cff…` ([sdk_patches.py:17](../../dm/tools/sdk_patches.py#L17)): Batman: Arkham Knight 1.0.5, DRAGON BALL: Sparking! ZERO 2.0.76, Persona 4 Golden 1.0.2, Pikmin 2 1.0.0, TUNIC 1.0.3 + - cut `+0x12d8b0`: AddressRegionManager constructor: node area limit mov w8,#0x3fe00000 → mov w8,#0x3e00000 + - cut `+0x12d94c`: AddressRegionManager constructor: length of the node area registered as an occupied region mov w9,#0x3fe00000 → mov w9,#0x3e00000 + - cut `+0x11de60`: CentralHeap::Free: return early if heap_base is null (cbz x8, #0x11df44) ### SDK-wide function-matched rules