Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions dm/games/0100D680194B2000/README.md
Original file line number Diff line number Diff line change
@@ -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.
44 changes: 44 additions & 0 deletions dm/games/0100D680194B2000/game.tsv
Original file line number Diff line number Diff line change
@@ -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)
19 changes: 19 additions & 0 deletions dm/games/0100D680194B2000/patches.py
Original file line number Diff line number Diff line change
@@ -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',
},
]
1 change: 1 addition & 0 deletions dm/games/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
8 changes: 8 additions & 0 deletions dm/tools/sdk_patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion docs/agents/game-patches.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|---|---|---|---|---|---|---|---|
Expand Down Expand Up @@ -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

Expand All @@ -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` |
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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

Expand Down
Loading