mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
1052 lines
86 KiB
Markdown
1052 lines
86 KiB
Markdown
# Memory Map — Seed Address Ledger (SLUS-00726)
|
||
|
||
> **Evolvable reference layer.** This file may be updated freely as RE progresses; note each
|
||
> change in the active phase log (`CURRENT_PHASE.md`). It is the single canonical address
|
||
> ledger for the project. **Once an address is imported into Ghidra, Ghidra becomes the
|
||
> authoritative record for that symbol** (name, type, xrefs); this file then serves as the
|
||
> provenance record and the staging area for addresses not yet imported.
|
||
|
||
## Phase 3 T1 — Imported into Ghidra (authoritative as of 2026-06-13)
|
||
|
||
Per the rule above, Ghidra is now **authoritative** for these symbols' names/types/xrefs; the rows
|
||
in the tables below remain the provenance record. Import policy applied (G5): `verified`→named
|
||
symbols; `reported`→labels + a `confidence=reported` Ghidra comment; JP-only / overlay-state-
|
||
dependent→**not** imported as static symbols. Seeded, saved, and **R9-verified** in the `bfm`
|
||
project.
|
||
|
||
**Verified (named symbols — §2.1, §2.2, §3.1):**
|
||
- LZSS: `LzssDecodeSector` 0x80018730 (+plate), `LzssStateTable` 0x80072A30 (+plate),
|
||
`lzss_state` 0x800C7D24, `lzss_curMask` 0x800747A0, `lzss_curToken` 0x800747A4,
|
||
`lzss_outPtr` 0x800747AC, `lzss_ringIndex` 0x800747B0, `lzss_partialCode` 0x800747B4,
|
||
`lzss_ringBuffer` 0x1F800000.
|
||
- CD-path: `CdPathTable` 0x80062C24 (+plate w/ full 23-entry structure), `cdpath_MAIN_CD`
|
||
0x80062C54, `cdpath_MUSA_ID` 0x80063014, `cdpath_DEBUG_BIN` 0x80063044 (+ghost-anchor note).
|
||
|
||
**Reported (labels + `confidence=reported` comment; verify before relying — §3.3, §3.6):**
|
||
`gameMode` 0x800B99DE, `currentLocationId` 0x800B9A08, `padState_raw` 0x80078DC2,
|
||
`zoneLoadedBlock` 0x80075400.
|
||
|
||
**Deferred (not yet imported):** the player/stat block (§3.4) and game-flags block (§3.5) — all
|
||
`reported`, low Phase-3 relevance; candidates for a single Ghidra struct in a later phase (verify
|
||
live first). JP-only (§4.1) and overlay-state-dependent (§4.2) addresses are **not** imported.
|
||
|
||
## Phase 3 T2 — File-loader / CD-read chain (imported into Ghidra 2026-06-13)
|
||
|
||
Static trace of the file-loader from the two T1 anchors (`CdPathTable` 0x80062C24,
|
||
`cdpath_DEBUG_BIN` 0x80063044). Ghidra is authoritative for these names/types; confidence
|
||
`verified` (static — read directly from the local EXE's disassembly). **The game does not call
|
||
PsyQ `CdRead()`** — it rolls its own polled async reader via `CdControl`.
|
||
|
||
**Call chain:** boot 0x800101FC → `LoaderInitFileTable` 0x8001971C → { `LoaderResetReadState`
|
||
0x80019990; `CdSearchFile` ×(DEBUG.BIN + 21 `CdPathTable` entries); `CdReadRequest` reads LIST.CD
|
||
(0xE40 B) → `listCdBuffer`; builds `cdFileLocTable` over the 8 .CD files }. Game file-load API
|
||
(5 sites 0x80010CA4/0F1C/1100/1178/12F0) → `CdReadRequest` 0x80019A24 → { `CdQueueBusy`
|
||
0x80034B98 gate; `CdReadStateMachine` 0x80019AF8 }. State machine issues `CdControl`(0x0E SetMode
|
||
0xA0 → 0x15 SeekL + `CdPosToInt` → 0x06 ReadN + `CdReadyCallback` `CdReadSectorReadyCB`
|
||
0x8001A338 → 0x09 Pause + `CdSync`/`CdFlush`); sectors drained by `CdGetSector` in the callback.
|
||
|
||
**Functions named:** `LoaderInitFileTable` 0x8001971C (+plate), `LoaderResetReadState` 0x80019990,
|
||
`CdReadRequest` 0x80019A24 (+plate), `CdReadStateMachine` 0x80019AF8 (+plate), `CdQueueBusy`
|
||
0x80034B98, `CdReadSectorReadyCB` 0x8001A338 (function created).
|
||
|
||
**Data named:** read control block `cdReq_*` 0x800AE6F0–0x800AE7B0 (`cdReq_state` 6F0,
|
||
`cdReq_curSector` 708, `cdReq_sink` 728, `cdReq_dest` 72C, `cdReq_savedReadyCB` 734,
|
||
`cdReq_cdResult` 738, `cdReq_drainPhase` 74C, `cdReq_retry` 750, `cdReq_timeout` 754,
|
||
`cdReq_cdlFile` 7A4, `cdReq_size` 7A8, `cdReq_posInt` 7AC, `cdReq_result` 7B0); `cdFileLocTable`
|
||
0x800AE830 (per-sub-file CdlLOC+size, built from LIST.CD); `listCdBuffer` 0x80180000 (LIST.CD
|
||
staging, 0xE40 B); `debugBinPresent` 0x800747D0 (DEBUG.BIN CdSearchFile result);
|
||
`cdReq_sectorHdrBuf` 0x80078E40 (CdGetSector header scratch).
|
||
|
||
**Resolves:** Q#4 (CdRead chain) ✅; Q#3 (LIST.CD RAM cache = `listCdBuffer` raw + `cdFileLocTable`
|
||
parsed) located, pending live proof; Q#9 (DEBUG.BIN path is referenced, result in `debugBinPresent`)
|
||
✅ static. Feeds T3 (`cdFileLocTable` lookup) and T4 (per-sector sink `FUN_8002fd14` + the 0x80035
|
||
streaming cluster reached via `CdQueueBusy`).
|
||
|
||
## Phase 3 T3 — LIST.CD RAM cache + sub-file lookup (imported into Ghidra 2026-06-13)
|
||
|
||
Confidence `verified` (static). The in-RAM file system is two structures + a two-level lookup:
|
||
|
||
- **`listCdBuffer`** 0x80180000 — raw LIST.CD (0xE40 B), read once by `LoaderInitFileTable`.
|
||
- **`cdFileLocTable`** 0x800AE830 — parsed flat array, 8-byte entries `{CdlLOC pos; u32 size}`, one
|
||
per sub-file. `LoaderInitFileTable` builds it by walking `listCdBuffer` over the 8 .CD files
|
||
(MAIN, SC01–SC07), `CdIntToPos(cdFileBaseSector + subfileSectorOffset)` per entry. **Count
|
||
cross-check (✓):** total entries = **447** = the exact sum of the Phase-2 per-.CD sub-file counts
|
||
(MAIN 49 + SC01 86 + SC02 43 + SC03 140 + SC04 31 + SC05 30 + SC06 39 + SC07 29 = 447).
|
||
- **`resourceIdMap`** 0x80063138 — resource-ID → file map, stride 6 = {u16 field0 = cdFileLocTable
|
||
index (0xFFFF = special/non-CD); u16 field1 = loadParam; u16 field2 = procParam}.
|
||
- **Lookup:** `ResourceGetCdLoc(id)` 0x8001B788 = `cdFileLocTable[ resourceIdMap[id].field0 ].pos`.
|
||
**Driver:** `ResourceLoadStateMachine` 0x8001B3C4 (load-once cache via `resLoad_lastId` /
|
||
`resLoad_loadedFileIdx`; streams via `FUN_800363cc(field1, &cdFileLocTable[idx], 0x10)`; special
|
||
path `FUN_80036d58(field1)`; post-process `FUN_8002d4c8(field2, 0)`).
|
||
|
||
**Functions named:** `ResourceLoadStateMachine` 0x8001B3C4 (+plate), `ResourceGetCdLoc` 0x8001B788
|
||
(+plate). **Data named:** `resourceIdMap` 0x80063138 (+plate), `resLoad_curId` 0x800C6D34,
|
||
`resLoad_lastId` 0x800A654C, `resLoad_state` 0x800AE7EC, `resLoad_result` 0x800C6D10,
|
||
`resLoad_loadedFileIdx` 0x800747F8. **Resolves Q#3** (static — count cross-check ✓; byte-level
|
||
LIST.CD parse vs F4 deferred to the T6 live pass). Leads to T4: the 0x80036 streaming cluster
|
||
(`FUN_800363cc` = `StreamLoadStateMachine`, the System-B loader — see T4; NB it does **not** itself
|
||
call the LZSS decoder).
|
||
|
||
## Phase 3 T4 — LZSS staging buffer (US) + streaming call sites (imported into Ghidra 2026-06-13)
|
||
|
||
Confidence `verified` (static).
|
||
|
||
- **`lzss_sectorStagingBuf`** 0x80079A70 — US LZSS input staging buffer (0x800 B = 1 sector).
|
||
**Byte-verified** from US code (`lui 0x8008; addiu -0x6590` @ 0x8001A8FC). Upgrades the JP-only
|
||
§3.2 datapoint (JP 0x80078BD0 + 0xEA0); the +0xEA0 delta is now confirmed **for this buffer**.
|
||
- **Streaming model:** `LzssDecodeSector` (0x80018730) has exactly **one caller** —
|
||
`CdReadSectorReadyCB` (0x8001A338) @ 0x8001A90C — so overlays are **decompressed inline, one CD
|
||
sector at a time, in the read-ready callback** (no decode-after-load buffer). Raw (uncompressed)
|
||
files take a parallel path in the same callback: `CdGetSector` → `FUN_8002fc64` (memcpy 0x800 B)
|
||
→ `cdReq_sink` (0x800AE728), decrementing `cdReq_wordsRemaining` (0x800AE748).
|
||
- **Second loader (System B):** `StreamLoadStateMachine` (`FUN_800363cc`) — a separate 0x12-state CD
|
||
reader (`streamLoad_state` 0x8006AF00, own callback `LAB_800377d8`) driven by
|
||
`ResourceLoadStateMachine`. It does NOT call `LzssDecodeSector`; its decode/transfer path and role
|
||
(overlay vs real-time stream) → **T5**.
|
||
|
||
**Named:** `lzss_sectorStagingBuf` 0x80079A70 (+plate), `cdReq_wordsRemaining` 0x800AE748,
|
||
`StreamLoadStateMachine` 0x800363CC (+plate), `streamLoad_state` 0x8006AF00, `streamLoad_cbActive`
|
||
0x80076110, `streamLoad_savedReadyCB` 0x8006AEF0; +plate on `CdReadSectorReadyCB`. **Resolves Q#6.**
|
||
**Two CD-load subsystems** are now identified — A: `CdReadRequest`/`CdReadStateMachine` (inline LZSS);
|
||
B: `ResourceLoadStateMachine`/`StreamLoadStateMachine` — T5 pins which loads the resident blob +
|
||
overlays and at what US addresses.
|
||
|
||
## Phase 3 T5 — US load addresses (static derivation; HEURISTIC pending T6b)
|
||
|
||
The dest **addresses** are EXE-constant load destinations (static-verified — the literal `a1` args to
|
||
`CdReadRequest`); their **roles** + runtime layout are `heuristic` until the T6b RAM-dump proof.
|
||
Source: **`loadDestPtrTable` 0x80072C70** (5-entry destination table read by the boot loaders).
|
||
|
||
| US addr | Role (heuristic) | JP analogue | Cross-check |
|
||
|---|---|---|---|
|
||
| **0x800CEDF8** | resident blob base | 0x800CDF58 | = JP **+0xEA0** (main-block delta) ✓; literal dest of all 5 boot loads |
|
||
| **0x80128158** | location script overlay base | 0x80128508 | 0x8012xxxx; spans AP-world US patch sites 0x8013–0x8018xxxx + boss-HP 0x8018EE00..0x801F8714 ✓ |
|
||
| 0x800CAE08 / 0x800CCB1C / 0x800C7F08 | resident-region blobs (boot) | — | 0x800Cxxxx resident band |
|
||
|
||
**Boot-load mechanics:** 5 `CdReadRequest` sites (in `gameMode`-dispatched handlers via
|
||
`FUN_80010B40` / `PTR_LAB_800629F4`) stream fixed `cdFileLocTable` sub-files ([1],[3],[8],[10],[11]) to
|
||
`loadDestPtrTable[0]` = 0x800CEDF8. Entries [1]+ are runtime-indexed (no static xref); the overlay
|
||
(entry[1]) dest 0x80128158 is selected per-location. **Note:** low boss-HP (0x8012052E..0x801212CA)
|
||
sits *below* 0x80128158 ⇒ a separate per-location data/entity block — resolve at T6b/T7.
|
||
|
||
**Resolves Q#1** (static): US resident 0x800CEDF8 + overlay 0x80128158 derived; staging buffer
|
||
0x80079A70 already verified (T4). **Not** planted as static Ghidra symbols per G5 rule 4 (overlay region
|
||
is state-dependent) — recorded here, to be promoted to `verified` + imported in T6b/T7.
|
||
|
||
## Phase 3 T6b — MILESTONE ✅: US load addresses proven against live RAM (2026-06-13)
|
||
|
||
Proven against a **live PCSX-Redux RAM dump** (Windows-native emulator; WSL fetched the full 2 MB via
|
||
`http://172.17.208.1:8081/api/v1/cpu/ram/raw`; EXE verified resident, RAM[0x10000:]==extracted EXE).
|
||
Game state: standing still in the **tutorial forest zone** (`tut_forest`), freshly loaded. (The game
|
||
opens with an Allucaneet *FMV*, but actual gameplay begins in `tut_forest` — a never-revisited tutorial
|
||
area with scripted ability-assimilation events.)
|
||
|
||
| Blob | Extracted source | US load addr | Proof (RAM slice vs extracted) |
|
||
|---|---|---|---|
|
||
| **Resident blob** | `MAIN.CD/FILE_010/1.1` (type-1, 365,404 B) | **0x800CEDF8** `verified` | 18,788 B contiguous exact prefix, **sha1 `dbc55765…` EQUAL**; 92.98% total identical; divergences = runtime data writes |
|
||
| **Location overlay** | `SC01.CD/FILE_077/0.4.dec` (type-4 LZSS, 731,607 B) | **0x80128158** `verified` | 389,400 B contiguous exact prefix, **sha1 `8e40afab…` EQUAL**; 98.59% total identical; first divergence @+0x5F118 is a zero field the loader fills with live ptr 0x80180910 |
|
||
|
||
**Both T5-derived addresses CONFIRMED.** The **+0xEA0** resident-block delta (JP 0x800CDF58→US 0x800CEDF8)
|
||
is now byte-proven. **Overlays are position-locked** (loaded verbatim to a fixed vaddr — resolves Open Q#8):
|
||
the large contiguous exact-sha1 prefixes at the exact vaddrs prove no relocation; divergence is purely
|
||
runtime data init (P9). **Overlay-map datapoint:** `tut_forest` (tutorial forest, first playable zone) ⇒ `SC01.CD` FILE_077
|
||
"0.4" → 0x80128158. The method also re-validates the extractor — the live game's decompressed overlay ==
|
||
our LZSS decoder output for 389,400 contiguous bytes.
|
||
|
||
## Phase 3.5 — Prototype correspondence (2026-06-14)
|
||
|
||
Two CRC-verified US prototypes imported into the `bfm` Ghidra project and diffed against retail.
|
||
Full report + go/no-go: **`docs/proto-correspondence.md`**. Verdict: **NO-GO** as a Gen1 label/symbol
|
||
accelerator (neither proto carries symbols retail lacks; both have FEWER names than retail); **retain**
|
||
Sep-8 as a Phase-6 compiler-determinism corroborant and both as Gen2 overlay/engine assets.
|
||
|
||
| Build | Main EXE (region) | Load addr | SHA1 | Funcs | vs retail |
|
||
|---|---|---|---|---|---|
|
||
| Sep-8 "Master" | `SLUS_007.26` (PROTO) | 0x80010000 | `43006a31…` | 1726 | **99.6% byte-identical**; only 3 substantial funcs differ |
|
||
| Aug-31 "Demo" | `MUSASHI/USA_DEMO.EXE` (DEMO) | **0x80018000** | `6150b0f8…` | 1737 | 66% norm-identical; 862 1:1 pairs; **SC02 region** |
|
||
|
||
- All three builds detected **PsyQ 4.0.0** (independent corroboration for the Phase-6 fingerprint).
|
||
- **Sep-8 pre-release diff** (the ONLY 3 substantial code changes vs retail — a Phase-6 cross-check):
|
||
`DebugMenuHandler` 0x80011144, `CdReadSectorReadyCB` 0x8001a338 (424 ins), `SaveLoadRoutine` 0x8002b154.
|
||
- **DEMO dispatch anchors** (confidence `verified` vs the demo binary; **DEMO region — NOT valid for US
|
||
retail**): `GameModeDispatch` 0x80018b48, `gameModeHandlerTable` 0x8006ae8c (**18 entries**, same order +
|
||
same idx[10]==idx[15] duplicate as retail's 0x800629f4), `gameMode` 0x800c1eb6, debug-menu slot[7]
|
||
handler 0x800194cc (code diverged from retail `DebugMenuHandler`). NB demo `gameMode` 0x800c1eb6 ≠ retail
|
||
0x800b99de and is NOT the JP −0xEA0 delta — the demo has its own layout.
|
||
- Proto EXEs gitignored under `extracted/proto/`; programs live in the `bfm` Ghidra project (gitignored).
|
||
|
||
## Purpose and rules
|
||
|
||
1. **Every address carries a Region and a Source.** No exceptions.
|
||
2. **JP addresses are NEVER assumed valid for US.** The Archipelago AP-world documents
|
||
cross-region deltas (see [Region deltas](#region-deltas-jp--us)), but these are
|
||
block-specific and **heuristic only** — any US address derived by delta must be marked
|
||
`heuristic` until byte-verified against the US binary or a live US RAM dump.
|
||
3. **Confidence levels:**
|
||
- `verified` — byte-checked against the local SLUS-00726 dump in this project (or an
|
||
address referenced directly by byte-verified code).
|
||
- `reported` — published by an external source; plausible and often multi-source, but not
|
||
yet locally verified against the US binary/RAM.
|
||
- `heuristic` — derived via a cross-region delta or pattern match; must be re-derived
|
||
before any use as a symbol.
|
||
4. Overlay-region addresses (≳0x80120000) are **state-dependent**: they are only valid while
|
||
a specific location overlay is loaded. Never import them as static EXE symbols.
|
||
|
||
### Region codes
|
||
|
||
| Code | Meaning |
|
||
|---|---|
|
||
| `US` | SLUS-00726, USA retail (final build 1998-09-22 per Hidden Palace) |
|
||
| `JP` | SLPS-01490 *Brave Fencer Musashiden* (1998-07-16); re-release SLPS-02769 unexamined |
|
||
| `JPDEMO` | Japanese demo build (known only from TCRF's per-version debug GameShark code) |
|
||
| `PROTO` | US prototypes: Aug-31-1998 demo proto, Sep-8-1998 near-final master (Hidden Palace) |
|
||
| `DEMO` | **Corrected 2026-06-13 (Drew):** SLUS-90029 = the **FF8** playable demo bundled in the BFM case — **not** BFM content. **No known *playable* US BFM demo exists.** Closest: SLUS-90028 "Squaresoft on PlayStation Vol.1" carries BFM **preview FMV only** (its playable demo is Xenogears). The Aug-31-1998 proto is the actual playable pre-release BFM build. |
|
||
|
||
### Region deltas (JP ↔ US) — heuristic only
|
||
|
||
From the AP-world client (`client.py`), which supports both discs and is independently
|
||
consistent with TCRF's per-region debug codes:
|
||
|
||
| Block | Delta (US → JP) | Cross-check |
|
||
|---|---|---|
|
||
| Main state block (0x800B9xxx) | **−0xEA0** | US menu-state 0x800B99DE ↔ JP 0x800B8B3E — matches TCRF debug codes exactly |
|
||
| Zone-loaded block (0x80075400) | −0xE70 | AP-world only |
|
||
| Stats table | −0xEA4 | AP-world only (note: *not* the same as the main-block delta) |
|
||
| Code regions | varies: +0x2A8, +0xA70, +0x344, +0x2D0, +0xE8 | per-overlay, no single delta — **never apply blindly** |
|
||
|
||
**UNVERIFIED inconsistency** carried from research: AP-world notes the town-ID-check patch at
|
||
US 0x8015A7E4 with "JP equivalent 0x8015AB20, delta +0x2A8", but 0x8015AB20 − 0x8015A7E4 =
|
||
0x33C, not 0x2A8. Do not resolve by guesswork; re-check `client.py` when importing.
|
||
**Status (P33 E2, 2026-09-07): HISTORIC.** `client.py` v0.8.1 contains neither 0x8015A7E4 nor 0x8015AB20 — the town-ID
|
||
check patch was dropped; town/portal handling now writes portal-table entries (`fix_town_id`). Nothing left to resolve.
|
||
|
||
### Address-base conversion for external sources
|
||
|
||
| Source | Convention | Conversion to PSX virtual address |
|
||
|---|---|---|
|
||
| AP-world `client.py` | BizHawk MAIN_RAM offset | add 0x80000000 (e.g. 0x078EB4 → 0x80078EB4) |
|
||
| Autosplitter (Aalaizah gist) | BizHawk octoshock.dll offset | subtract 0x30DF90, add 0x80000000 (conversion cross-verified via playerLocation) |
|
||
| GameShark codes (libretro cht / gamehacking.org) | 0x80-prefixed or bare offset | already KSEG0 (8007xxxx etc.) |
|
||
|
||
---
|
||
|
||
## 1. PS-X EXE header facts (locally verified 2026-06-10)
|
||
|
||
Extracted from `SLUS_007.26` (disc LBA 24, 413,696 bytes, MODE2/2352 track 1; user data at
|
||
raw-sector offset 24).
|
||
|
||
| Field | Value | Confidence | Notes |
|
||
|---|---|---|---|
|
||
| magic | `PS-X EXE` | verified | |
|
||
| `pc0` (entry) | 0x80010000 | verified | |
|
||
| `gp0` | **0x00000000** | verified | Header carries no $gp ⇒ **−G0 CONFIRMED (Phase 5)**: the splat disasm has zero `($gp)` base accesses / `%gp_rel` relocs |
|
||
| `t_addr` | 0x80010000 | verified | |
|
||
| `t_size` | 0x64800 | verified | EXE image spans 0x80010000–0x80074800 |
|
||
| `sp` | 0x801FFFF0 | verified | Matches SYSTEM.CNF `STACK = 801ffff0` |
|
||
|
||
The EXE is tiny (~400 KB) relative to the game: the bulk of engine/script code lives in the
|
||
`.CD` overlays (see §4).
|
||
|
||
### Script / event system — RESOLVED: compiled-MIPS dispatch, NO bytecode VM (Phase 12, byte-backed)
|
||
|
||
The Gen2-roadmap "Script VM instruction set" open question is **resolved by matching the resident engine** (123/146 fns, 85.6% byte-identical from C source, Phase 12): **BFM has no bytecode/script VM.** "Scripting" is **compiled MIPS code structured as state machines dispatched through function-pointer tables indexed by game-state variables** — the same idiom at every level:
|
||
- **EXE top level:** `GameModeDispatch` (0x80010B40) → `gameModeHandlerTable[gameMode]()` (18 entries @0x800629F4; `gameMode` @0x800B99DE).
|
||
- **Resident engine (matched, the proof):** the same idiom at finer grain — `func_800CEDFC` = `if (D_800B99F0 < 0x14) D_800D3430[D_800B99F0]()`; also `D_800D3480[D_800B99F6]()` (`func_800CEE40`), the `D_800D3488[…]` cluster (`func_800CF4D4` etc.), `D_800D3490[D_800B99FC]()`, and the `func_800D2F0C/2FB0/30C8/31C0/31FC` cluster dispatching `D_800D39xx[entity->state]()`. The dispatch index is always a game-state var (game mode, `currentLocationId` 0x800B9A08, per-entity sub-state at struct +0x14/+0x15).
|
||
- **Location overlays (0x80128158):** contain **compiled MIPS** (text/font pointers materialized inline by `lui/addiu`), not bytecode — they *call* the resident engine's fns at fixed addresses.
|
||
|
||
So "matching the script VM" means matching these compiled dispatchers + handlers (done in the harvest), NOT writing a bytecode interpreter. The `VM_*` symbols are PsyQ **libsnd** (refuted as a game VM, Phase-12 T1). Basis for the gen2-roadmap **EXIT criterion #2 amendment** (T6): *"script/event system resolved (compiled-MIPS state/mode dispatch, no bytecode VM)."*
|
||
|
||
**Text→data split (Phase 5, splat).** `config/splat.us.exe.yaml` splits file `[0x800,0x531DC)` → vram
|
||
`[0x80010000,0x800629DC)` as code (`asm`) and `[0x531DC,0x65000)` → `[0x800629DC,0x80074800)` as data (the
|
||
splat `psxexeinfo` estimate; **vram = fileoff + 0x8000F800**). The true code↔data transition is a mixed
|
||
region — last func `USERFUNC_OBJ_84` @0x8006290C, then small data (`D_800629xx`), then `gameModeHandlerTable`
|
||
@0x800629F4 (the game-mode handler **pointer table**). splat's in-`asm` data detection labels it bar one
|
||
address, `D_800629D4` (referenced by the boot code), added by hand to `config/symbols.us.txt`. **Byte-identity
|
||
is robust to the exact boundary** (the disasm round-trips); a clean rodata/data segmentation is a Gen2 refinement.
|
||
|
||
---
|
||
|
||
## 2. Static EXE region — 0x80010000–0x80074800 (US)
|
||
|
||
### 2.1 LZSS decompressor (byte-verified vs AiroRom listing)
|
||
|
||
The annotated MIPS listing at
|
||
`https://raw.githubusercontent.com/wiki/pleonex/AiroRom/Brave-fencer-Musashi.md` (the GitHub
|
||
wiki UI fails to render it; fetch raw) was byte-verified in-session against the local
|
||
SLUS-00726 EXE (words checked at 0x80018730/34/90, 0x800187BC, 0x80018824, 0x80018910, and
|
||
the table at 0x80072A30). The routine is a **resumable 5-state machine** consuming input in
|
||
0x800-byte CD-sector chunks. Format details live in `docs/formats.md`.
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x80018730 | `LzssDecodeSector` (entry) | US | AiroRom wiki listing; byte-verified vs local EXE | verified | Primary Phase-1 milestone anchor (MCP round-trip target) |
|
||
| 0x80018790 | `.LzssState1_initToken` | US | AiroRom listing (state table), byte-verified code | verified | State 1: init token/ring |
|
||
| 0x800187A4 | `.LzssState2_checkBit` | US | same | verified | State 2: check token bit |
|
||
| 0x800187F8 | `.LzssState3_match` | US | same | verified | State 3: process match |
|
||
| 0x80018824 | `.LzssState0_finish` | US | same | verified | State 0: finish |
|
||
| 0x800188B4 | `.LzssState4_nextBit` | US | same | verified | State 4: next token bit |
|
||
| 0x80018910 | (return point) | US | same | verified | Routine return |
|
||
| 0x80072A30 | `LzssStateTable` | US | AiroRom listing; byte-verified vs local EXE | verified | Data: `00000000 FFFFFFFF` then 5 pointers to the state entry points above |
|
||
|
||
### 2.2 CD-path string table (locally verified 2026-06-10, exact)
|
||
|
||
Re-derived in-session by scanning the extracted EXE: a table of `\NAME;1` path strings,
|
||
**23 entries, 0x30-byte stride**, spanning 0x80062C24–0x8006307x. (An earlier session note
|
||
said "≈0x80063045"; the exact string starts, including the backslash, are below.)
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x80062C24 | `CdPathTable` (first entry: `\LIST.CD;1`) | US | local EXE scan 2026-06-10 | verified | Entry stride 0x30; order: LIST.CD, MAIN.CD, SC01–SC07.CD, then STR movies |
|
||
| 0x80062C54 | `\MAIN.CD;1` | US | local EXE scan | verified | |
|
||
| 0x80062C84 | `\SC01.CD;1` … (SC02–SC07 follow at +0x30 each) | US | local EXE scan | verified | SC07 at 0x80062DA5−1 = 0x80062DA4 |
|
||
| 0x80062DD4–0x80062FE4 | STR movie paths (ST01–ST06, SHOPS01, OPEN, END, OUT; entry starts) | US | local EXE scan | verified | ST04 and ST05 each appear **twice**; `LOGOA/LOGOB.STR` and the `.DA` audio files are **absent** from this table — referenced elsewhere (**TBD**) |
|
||
| 0x80063014 | `\MUSA.ID;1` | US | local EXE scan | verified | Disc-ID check file (9 bytes `MUSASHI\r\n` on disc) |
|
||
| 0x80063044 | `\DEBUG.BIN;1` | US | local EXE scan | verified | **`DEBUG.BIN` is absent from the retail ISO** ⇒ a surviving debug-loader code path references this string. Prime RE anchor: find xrefs to locate the debug loader |
|
||
|
||
### 2.3 Other static EXE data
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x800638FA | `BodyStatLevelTable` | US | AP-world `client.py` (`0x0638FA + 16*level`) | reported | 16-byte records indexed by level; inside EXE static data — verify in Ghidra |
|
||
| 0x80072DF0 | `saveHeaderTemplate` | US | **VERIFIED live (T6b 2026-06-14; RAM==EXE)** — **CORRECTED P31 S73: the row's extent is WRONG past +0x54.** 0x80072DF0+0x54 = 0x80072E44 is `jtbl_80072E44`, `func_8002B0B4`'s SaveLoadRoutine dispatch table and the first 12 bytes of the S72 span-B `.rodata` carve — not part of this template. The "handler code ptrs" it described are that jump table's entries. Treat this row as verified only up to +0x54. | **verified (extent corrected)** | PS1 memcard save-header template: `Hero` default name (SJIS full-width) @+0; memcard **filename** `BASLUS-00726MUSASHI` @+0xC; **title** `BRAVE FENCER MUSASHI` (SJIS) @+0x20; save/load **handler code ptrs** 0x8002B154 / 0x8002B1AC / 0x8002BEA4 @+0x54. Anchors Q#5 |
|
||
| ~EXE+0x62620 | overlay/script pointer table | **JP** | jywjyw `note.md` (JP EXE file offset) | reported (**JP-only — re-derive for US**) | EXE-side pointer table tied to the resident script blob (§4). US analogue **TBD** — Phase 3 |
|
||
|
||
---
|
||
|
||
## 3. BSS / runtime statics (0x80074800–0x800Cxxxx region + scratchpad)
|
||
|
||
The PS-X EXE header declares no separate BSS; everything above the file image
|
||
(>0x80074800) is runtime-cleared memory. Addresses 0x800747xx fall *inside* the loaded
|
||
image (late data/sbss).
|
||
|
||
### 3.1 LZSS decompressor state (code-derived from the byte-verified routine)
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x800747A0 | `lzss_curMask` | US | AiroRom listing (referenced by verified code) | verified | Current flag-bit mask (byte), 0x01→0x80 |
|
||
| 0x800747A4 | `lzss_curToken` | US | same | verified | Current token byte |
|
||
| 0x800747AC | `lzss_outPtr` | US | same | verified | Output write pointer |
|
||
| 0x800747B0 | `lzss_ringIndex` | US | same | verified | Ring-buffer write index |
|
||
| 0x800747B4 | `lzss_partialCode` | US | same | verified | Partial 16-bit match code (resume across sector boundary) |
|
||
| 0x800C7D24 | `lzss_state` | US | AiroRom listing (referenced by verified code) | verified | State variable 0–4; indexes `LzssStateTable` @ 0x80072A30. Lives beyond the EXE image (runtime BSS) |
|
||
| 0x1F800000 | `lzss_ringBuffer` | US (hardware) | AiroRom listing; byte-verified code | verified | 1024-byte ring window in PS1 **scratchpad** (D-cache). Match offsets are *absolute* ring indices with −1 bias — see `docs/formats.md` |
|
||
|
||
### 3.2 Loader buffers
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x80079A70–0x8007A270 | `lzss_sectorStagingBuf` (0x800 bytes) | **US** | **T4: byte-verified from US code** (`lui 0x8008; addiu -0x6590` @ 0x8001A8FC) | **verified** | Sector-at-a-time LZSS input staging buffer; filled by `CdGetSector`, consumed by `LzssDecodeSector` inline (see Phase 3 T4 block). JP analogue 0x80078BD0 (+0xEA0 delta — confirmed for this buffer) |
|
||
| 0x80180000 / 0x800AE830 | `listCdBuffer` (raw, 0xE40) / `cdFileLocTable` (parsed) | US | **T2/T3: located in US code** | **verified** | LIST.CD raw staging + the parsed `{CdlLOC;size}`×447 table; replaces the old `listCd_ramCache` TBD (see Phase 3 T2/T3 blocks) |
|
||
|
||
### 3.3 Pad / input state
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x80078DC2 | `padState_raw` (halfword) | US | TCRF debug GameShark condition (`D0078DC2 0200`) | reported | 0x0200 = L3. **Distinct from 0x80078DCA — do not conflate** |
|
||
| 0x80078DCA | `padState_mirror` | US | gamehacking.org #88529 / libretro cht (joker code) | reported | Separate pad mirror/field used by GameShark joker codes |
|
||
|
||
### 3.4 Player state block (0x80078E7C–0x80078F08)
|
||
|
||
Sources: gamehacking.org NTSC-U page 88529 and the libretro-database GameShark `.cht`
|
||
(35 cheats; addresses recovered via the libretro mirror because gamehacking.org blocks
|
||
scripts), AP-world `client.py`, and the LiveSplit autosplitter gist. A coherent player/stats
|
||
struct clearly lives here — good candidate for a single Ghidra struct.
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x80078E7C | `gameClock` (2 words) | US | libretro cht | reported | In-game clock |
|
||
| 0x80078E80 | `storyProgress` (u32) | US | autosplitter (octoshock conversion cross-verified) | reported | Progress/story counter |
|
||
| **0x80078E8C** | `gold` (u16, stored as **gold ÷ 10**) | US | **VERIFIED live (T6b, 4-state triangulation 2026-06-14)** | **verified** | Holds 145/155/165 for displayed gold 1450/1550/1650 (game stores gold/10 — gold is always ×10, which defeated naive value searches). Mirror copy at 0x8011F804. **Supersedes the reported 0x80078E8E** ("money", which reads 0 — refuted) |
|
||
| 0x80078EA4 | `tiredness_raw` (u16) | US | **VERIFIED live (T7): 12984→55620→0 (accumulates w/ activity, resets to 0 on sleep)** | **verified** | Raw tiredness accumulator; displayed % is derived (likely overflow-counted). **Supersedes 0x80078EA6** ("tiredness" — refuted: moved 119→160 on sleep, not →0) |
|
||
| 0x80078EAC | `dayCounter` (**u16**) | US | **VERIFIED live (T7): 1→2 on the Day1→Day2 sleep rollover**; type + writer from the decompiled source (P33 E2, 2026-09-07) | **verified** | In-game day number. Written by the shared rollover function `func_8014B084` (`src/shared/engine_core.h`, every location overlay): `day = day + 1`, wraps to **1 at 0x16E (366)**. The day-of-week is the SEPARATE byte below (the old "dow not yet separable" note is resolved; 0x80078EAF refuted as dow stands) |
|
||
| 0x80078EBA | `dayOfWeek` (u8) | US | **code-derived (P33 E2):** `func_8014B084` sets `dow = (dow + 1) % 7` on the same rollover; AP-world `client.py` v0.8.1 reads `0x078eba` as "#32 day of the week" (JP −0xEA0) | **verified** (code) | 0..6. The AP-world address is CORRECT; it is not the day counter. Read alongside 0x80078EAC by many overlay scripts (`D_80078EBA == 4` etc.) |
|
||
| 0x80078EB1 | `hour` (u8) | US | **VERIFIED live (T7): 13→14→15→18→19→3 across 6 dumps** | **verified** | In-game hour 0-23; time advances in 15-min jumps (~7.5 IRL s each). Minute field not cleanly located; old 0x8007CE82 "clock" refuted (coincidence) |
|
||
| 0x80078EB2 | `hp_max` (u16) | US | **VERIFIED live (T6b change-detection 2026-06-13)** | **verified** | =150 across two states (HP max); pairs {max,cur} with 0xEB4 |
|
||
| 0x80078EB4 | `hp_current` (u16) | US | **VERIFIED live** (146→136 tracked Drew's HP) | **verified** | **Resolves Q#12**: AP-world right — 0xEB4 = current HP, 0xEB2 = max |
|
||
| 0x80078EB6 / 0x80078EB8 | `bp_max` / `bp_current` (u16) | US | **VERIFIED live** (0xEB8 137→91 tracked Drew's BP) | **verified** | **Ledger corrected — labels were SWAPPED**: 0xEB6=max(150), 0xEB8=current |
|
||
| 0x80078EE4 / 0x80078EE8 | `bodyLevel` / `bodyExp` | US | gamehacking / cht | reported | |
|
||
| 0x80078EEC / 0x80078EF0 | `mindLevel` / `mindExp` | US | gamehacking / cht | reported | |
|
||
| 0x80078EF4 / 0x80078EF8 | `fusionLevel` / `fusionExp` | US | gamehacking / cht | reported | Fusion sword (JP らいこうまる) |
|
||
| 0x80078EFC / 0x80078F00 | `luminaLevel` / `luminaExp` | US | gamehacking / cht | reported | Lumina sword (JP Reigando); exp max 0x031B per cht |
|
||
| 0x80078F08 | `exp_quickLevel` | US | gamehacking / cht | reported | |
|
||
|
||
> Known-bad datum: the cht's "Charging Bar Always Full" code is listed as `80078EEBE`
|
||
> (9 digits — typo for 0x078EEB or 0x078EBE). **UNVERIFIED** — test in emulator before use.
|
||
|
||
### 3.5 Game flags block (0x800AE6xx)
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x800AE640 | `musicId` (byte) | US | gamehacking / cht | reported | Music modifier; track 128 is the unused TCRF tune |
|
||
| 0x800AE64A–0x800AE64C | `scrolls_legendaryArmor` (3 bytes) | US | AP-world; cht (armor 0x0AE64B–4C); autosplitter (0x800AE64B) | reported | |
|
||
| 0x800AE650 | `minkuFlags` | US | AP-world | reported | |
|
||
| 0x800AE651 | `chestFlags` (5 bytes) | US | AP-world; autosplitter uses byte 0x800AE652 within it | reported | |
|
||
| 0x800AE658 | `techsBitfield` | US | gamehacking / cht | reported | All-techniques bitfield |
|
||
| 0x800AE666 | `castleGuardState` | US | AP-world | reported | |
|
||
| 0x800AE671 | `binchoRescueFlags` (9 bytes) | US | AP-world | reported | |
|
||
|
||
### 3.6 Mode / location / world state
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x800B99DE | `gameMode` | US | TCRF debug code (`800B99DE 0007`); AP-world ("main-menu flag") | reported | **Writing 7 (while pad == 0x0200) enters the debug menu** — see §5. JP: 0x800B8B3E; JPDEMO: 0x800B894E |
|
||
| 0x800B9A08 | `currentLocationId` (s16/u16) | US | autosplitter; AP-world; gamehacking "map modifier" (3 independent sources) | reported | Map IDs: 0x1010 town, 0x2018 interiors, 0x3000+ dungeons; autosplitter start-trigger value 12293 (0x3005); ~25 room IDs enumerated in the gist |
|
||
| 0x80075400 | `zoneLoadedBlock` | US | AP-world (zone-loaded + portal-destination control block) | reported | JP delta −0xE70 |
|
||
| 0x800BA1E7 | `inventory` | US | AP-world | reported | |
|
||
| 0x800BA1F3 | `geezerFlag` | US | AP-world | reported | |
|
||
| 0x800BA1F8–0x800BA21A | `rescueCompleteFlags` | US | libretro cht | reported | |
|
||
| 0x800BA21B | `toyCollection` (43 bytes) | US | AP-world | reported | |
|
||
| 0x800BA285 | `steamwoodStatus` | US | AP-world | reported | Adjacent to: |
|
||
| 0x800BA286 | `raftLogs` | US | gamehacking / cht | reported | |
|
||
| 0x8000BA94 | `buildIdBytes` | US+JP | AP-world (region auto-detect reads MAIN_RAM 0x00BA94) | reported | Below the EXE base (kernel-area RAM) — nature **TBD**; AP uses it to distinguish US vs JP at runtime |
|
||
|
||
### 3.7 Misc reported (low priority)
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x800A63F3 | shading byte | US | gamehacking / cht | reported | |
|
||
| 0x800AFAF9/FB/FD | screen-tilt bytes | US | gamehacking (FB/FD) / cht (F9/FB/FD) | reported | |
|
||
| 0x800AFB0C | "model weirdness" | US | gamehacking / cht | reported | |
|
||
|
||
---
|
||
|
||
## 4. Overlay region (≳0x800B9480 / 0x800CDF58+)
|
||
|
||
### 4.1 JP overlay map — **JP-only, re-derive every address for US in Phase 3**
|
||
|
||
Source: jywjyw's `bravefencer-hack` `doc/note.md` (Japanese SLPS-01490; same engine).
|
||
jywjyw's IDA workflow — load the EXE, then "load additional binary" for the two blobs at the
|
||
addresses below — is directly replicable in Ghidra once US equivalents are found. Research
|
||
explicitly warns: US addresses *will* differ (e.g. US font offset 0x4CBC vs JP 0x4C4C).
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x80010000–0x80073FFF | JP EXE image | JP | jywjyw `note.md` | reported | JP file offset = vaddr − 0x8000F800; JP image slightly smaller than US (US ends 0x80074800) |
|
||
| 0x800B9480 | SQV/audio region | JP | jywjyw `note.md` | reported (**JP-only**) | |
|
||
| 0x800CDF58–0x80128504 | `residentScriptBlob` — MAIN.CD FILE_010 PAC entry "1.1" (type 1, uncompressed) | JP | jywjyw `note.md` | reported (**JP-only**); **US (T5): 0x800CEDF8** (heuristic) | Memory-resident script blob loaded at game start; pointer table in EXE near JP file offset ~0x62620. **US base 0x800CEDF8 = JP +0xEA0 — `loadDestPtrTable[0]`; pending T6b proof** |
|
||
| 0x80128508+ | `locationScriptOverlay` — current location's type-4 "0.4" blob (LZSS-decompressed, ~800 KB) | JP | jywjyw `note.md` | reported (**JP-only**); **US (T5): 0x80128158** (heuristic) | **Contains MIPS code**: text/font pointers materialized by `lui/addiu` pairs inside the blob (JP examples 0x8013928C/0x8013920C/0x801391EC; Vehek's US example at 0x8017E4DC). Third (town-dialog) script chains from inside the second. **US base 0x80128158 = `loadDestPtrTable[1]`; pending T6b proof** |
|
||
| 0x801FF800–0x801FFFFF | script runtime scratch | JP | jywjyw `note.md` | reported (**JP-only**) | Top of RAM, below initial SP 0x801FFFF0 |
|
||
|
||
### 4.2 US overlay-resident addresses (state-dependent — only valid with the right overlay loaded)
|
||
|
||
Boss HP living at 0x8012xxxx–0x801Exxxx is the key observation that overlays load high
|
||
(≳0x80120000), consistent with the JP map's 0x80128508 base. Source: LiveSplit autosplitter
|
||
(octoshock offsets, conversion cross-verified) unless noted.
|
||
|
||
| Address | Symbol/Name (proposed) | Region | Source/Provenance | Confidence | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 0x8012052E | bossHp_BenEd | US | autosplitter | reported | overlay-state-dependent |
|
||
| 0x80120316 | bossHp_Skullpion/QueenAnt/TowerOfDeath/DarkLumina2 | US | autosplitter | reported | same slot reused by 4 bosses |
|
||
| 0x801204A8 | steamwoodValveTimer | US | libretro cht | reported | |
|
||
| 0x801212CA | bossHp_Kojiro | US | autosplitter | reported | |
|
||
| 0x80126B8E | player Y-velocity area ("moon jump") | US | gamehacking / cht | reported | code/data in overlay |
|
||
| 0x80126C00 | jump/physics code | US | libretro cht | reported | code patch site |
|
||
| 0x80126D98 | midair-jump counter | US | gamehacking / cht | reported | |
|
||
| 0x8013F430 | scroll-cursor patch site | US | AP-world (code patch; JP delta +0x344) | reported; **function resolved (P33 E2)** | inside `func_8013F350` (490 ins; a shared engine body — the same bytes in every location overlay; `src/shared/engine_core.h` `DEFINE_func_8013F350`) |
|
||
| 0x8014BCF8 | max-BP calculation patch site | US | AP-world (nop'd `addu $v0,$a1`; JP delta +0x2D0) | reported; **function resolved (P33 E2)** | inside `func_8014BCEC(a0, a1)` (14 ins, shared ×every location overlay): `bp_max (0x80078EB6) += a1`, clamped to 0x662 — the nop removes the increase |
|
||
| 0x8015A7E4 | town-ID check patch site (`andi $v0,$s0,0x4000`) | US | AP-world (historic: absent from `client.py` v0.8.1; the JP-delta inconsistency in the header is moot) | reported; **function resolved (P33 E2)** | inside `func_8015A3C8` (493 ins, shared ×every location overlay) |
|
||
| 0x8018E096 | entrance-index patch site | US | AP-world `client.py` v0.8.1 (`0x18e096 + jp*0xe8`, writes one byte 1 → 0 "to prevent softlock"; JP delta +0xE8) | reported | per-overlay code (7 location overlays carry code here, 7 different functions) — a byte inside an instruction immediate, not a shared body |
|
||
| 0x80146280 | Time-Sanity hook site | US | AP-world `client.py` v0.8.1 (`time_hook`; JP 0x80146550 = +0x2D0) | reported; **function resolved (P33 E2)** | inside `func_80146128` (142 ins, shared ×every location overlay): a jump rewritten to the AP-injected `setTimec.bin` routine |
|
||
| 0x8018EE00 | bossHp_RelicKeeper | US | autosplitter | reported | |
|
||
| 0x801E4398 | bossHp_SteamKnight | US | autosplitter; **VERIFIED live (T6b 2026-06-14): 27→0 as boss defeated** | **verified** (state-dependent) | Valid only while the Allucaneet Castle overlay (`SC02/FILE_005`) is loaded; live-anchors the §4.2 boss-HP region |
|
||
| 0x801EFD28 | bossHp_FrostDragon | US | autosplitter | reported | |
|
||
| 0x801F8714 | bossHp_DarkLuminaFinale | US | autosplitter | reported | |
|
||
|
||
(Also documented by AP-world but not a RAM address: hair-color palette edited directly in
|
||
VRAM at GPURAM offset 0x86AC4.)
|
||
|
||
### 4.3 Overlay map — live-verified (Phase 3 T6b/T7, PCSX-Redux RAM dumps 2026-06-14)
|
||
|
||
Every overlay below was identified by byte-matching the live RAM at the overlay slot **0x80128158**
|
||
against our extracted `*/0.4.dec` (LZSS-decompressed) files; the resident blob stayed at **0x800CEDF8**
|
||
(`MAIN.CD/FILE_010/1.1`) throughout. Confidence `verified` (live RAM == extracted blob, large exact
|
||
prefix). Format: location/state → `SC??.CD / FILE_nnn`.
|
||
|
||
| Location / state | Overlay (`*/0.4`) | Notes |
|
||
|---|---|---|
|
||
| **Resident blob** (always) | `MAIN.CD/FILE_010/1.1` @0x800CEDF8 | type-1 uncompressed; the milestone resident blob |
|
||
| tut_forest (tutorial forest — game start, never revisited) | `SC01/FILE_077` | the **milestone** overlay (daytime) |
|
||
| Spiral Tower exterior (pre- & post-erection) | `SC01/FILE_080` | tower erection is an in-overlay state, not a reload |
|
||
| Spiral Tower lower interior | `SC01/FILE_084` | |
|
||
| Spiral Tower upper (top + head-chase down) | `SC02/FILE_000 ≡ 003` | **duplicate pair** (byte-identical) |
|
||
| Allucaneet Castle Ch1 — Steam Knight 3-stage fight (hall→courtyard→dummy town) | `SC02/FILE_005` | whole multi-room set-piece = ONE overlay |
|
||
| Allucaneet Castle Ch2 — Room/bedroom | `SC01/FILE_005 ≡ 006` | **duplicate pair**; king/butler event + Sleep/Toys menu |
|
||
| Allucaneet Castle Ch2 — Library | `SC01/FILE_008` | scene incl. frozen book-reading sub-mode |
|
||
| Allucaneet Castle Ch2 — hub (blue nav menu) | `SC01/FILE_004` | menu-warp hub: Visit/Library/Room/SubMenu/Village |
|
||
| Allucaneet Castle Ch2 — Visit / main hall (Geezer) | `SC01/FILE_009` | |
|
||
| Village approach / gondola | `SC03/FILE_002` | |
|
||
| Village (central hub, **incl. Inn interior**) | `SC03/FILE_001` | one big free-roam overlay incl. building interiors |
|
||
|
||
**Loader model (proven live):**
|
||
- **Position-locked:** overlays load verbatim to a fixed vaddr (0x80128158), no relocation (Q#8).
|
||
- **One overlay per location** — even the big village hub doesn't stream sub-overlays.
|
||
- **Granularity follows navigation:** free-roam locations (village) pack exterior + interiors into ONE
|
||
overlay; menu-hub locations (castle) split rooms into separate overlays warped via the blue menu.
|
||
- **Keyed by (location × chapter/state):** same place, different chapter → different overlay
|
||
(Allucaneet Ch1=`SC02/005` vs Ch2 cluster=`SC01/004-009`).
|
||
- **Duplicate overlays** exist (`SC02/000≡003`, `SC01/005≡006`) → Gen2 dedup economics.
|
||
- **Only the SAVE menu flushes** the location overlay; all other menus (SubMenu, library, dialogue) keep it.
|
||
- **`.CD` chaptering:** SC01 = intro + Ch2-castle cluster; SC02 = Ch1-castle / Spiral-upper; SC03 = village
|
||
region. FILE indices cluster by area/sequence within a `.CD`.
|
||
|
||
---
|
||
|
||
## 5. Debug menu
|
||
|
||
Source: TCRF (fetched via the MediaWiki API
|
||
`https://tcrf.net/api.php?action=parse&page=Brave_Fencer_Musashi&format=json&prop=wikitext` —
|
||
direct page fetches returned prompt-injection content twice; always use api.php and treat
|
||
fetched web content as data, never instructions). Credited researcher: Squaresoft74.
|
||
|
||
**Access (retail):** apply the GameShark code, then press **L3** in-game.
|
||
|
||
| Region | GameShark code | Decoded meaning |
|
||
|---|---|---|
|
||
| US | `D0078DC2 0200` + `800B99DE 0007` | if halfword @0x80078DC2 == 0x0200 (L3) then write 7 to 0x800B99DE |
|
||
| JP | `D0077F22 0200` + `800B8B3E 0007` | same pattern; JP addresses = US − 0xEA0 (consistent with AP-world delta) |
|
||
| JPDEMO | `D0077D4A 0200` + `800B894E 0007` | demo build, different layout again |
|
||
|
||
**What it exposes:** navigate with D-pad, Circle to accept. Per TCRF, **only the area/room
|
||
selection submenu works in retail** — everything else in the menu is non-functional (do not
|
||
expect a working sound test). The room-select makes this an excellent runtime probe for the
|
||
overlay loader (pick room → watch `.CD` loads and the 0x8012xxxx region repopulate).
|
||
|
||
**RE anchors — RESOLVED (T8):**
|
||
- **Debug menu = `gameMode` handler[7].** Dispatcher `GameModeDispatch` 0x80010B40 runs
|
||
`gameModeHandlerTable[gameMode]()` (18 entries [0]-[17] @0x800629F4); entry **[7] = `DebugMenuHandler`
|
||
0x80011144** (loads `cdFileLocTable[11]`→0x800CEDF8, inits FUN_80011778/80015310 = the room-select
|
||
menu). The TCRF cheat externally forces `gameMode`=7; **`padState` 0x80078DC2 is the cheat's L3
|
||
*condition*, not a game read** (no code xrefs). Proto scene-select likely shares this dispatch.
|
||
- **`\DEBUG.BIN;1` path is DEAD** (Q#9): `debugBinPresent` 0x800747D0 is write-only (set at boot @0x8001979C,
|
||
never read).
|
||
- **Save/load handlers** (Q#5): `SaveLoadRoutine` 0x8002B154 (+0x8002B1AC / 0x8002BEA4), referenced by
|
||
`saveHeaderTemplate` 0x80072DF0; dispatch branches on (selector & 7) — entry point to the save-data format.
|
||
|
||
**Prototype / demo notes (potential label sources):**
|
||
|
||
| Build | Facts | Source |
|
||
|---|---|---|
|
||
| Aug-31-1998 proto ("Musashi Demo") | Pressing Start on the title screen opens a **SCENE/AREA SELECTION menu** instead of the main menu — likely the same code behind the retail L3 menu. Fewer voice lines. **STAGED locally 2026-06-13 (`disks/`, CloneCD .img), CRC32 1369DE07 ✓ verified.** | Hidden Palace "Project Deluge" (Apr 2021) |
|
||
| Sep-8-1998 master ("Musashi Master") | Near-final US localization master ("Dual Shock Vibration" vs final "Vibration", one dialog line changed). Useful to validate diff tooling. **STAGED locally 2026-06-13 (`disks/`, CloneCD .img), CRC32 5C24728E ✓ verified.** | Hidden Palace |
|
||
| SLUS-90029 (and "Squaresoft on PlayStation Vol.2") | **Not BFM** — an **FF8** playable demo disc bundled in the BFM retail case (corrected 2026-06-13, Drew). The FF8-demo collector disc is staged in `disks/` but carries no BFM code. **No playable US BFM demo is known**; SLUS-90028 "Squaresoft on PlayStation Vol.1" (bundled with Parasite Eve) has BFM **preview FMV only** (its playable demo is Xenogears). | Drew; WebSearch (game-rave, emuparadise) |
|
||
|
||
**No debug symbols** in either prototype — **VERIFIED (Phase 3.5):** both main EXEs are stripped
|
||
identically to retail (no `.SYM`, no source paths, no symbol tables; only `\DEBUG.BIN;1` + the stock
|
||
`SetGraphDebug` format string, as in retail). The disc-wide `_dbg_RE1L@55` / `C:\TIMPACK\` strings are
|
||
in retail too (shipped data, not a debug build). The proto's scene-select shares the retail debug-menu
|
||
**architecture** (identical 18-mode dispatch table) but **not** its handler code or any labels — see
|
||
`docs/proto-correspondence.md` (Q#10 resolved).
|
||
|
||
---
|
||
|
||
## 6. Open questions (mirrors Phase 3 targets)
|
||
|
||
| # | Question | Why it matters | Suggested attack |
|
||
|---|---|---|---|
|
||
| 1 | **VERIFIED (T6b)** — resident `0x800CEDF8` (= JP +0xEA0), overlay `0x80128158` both byte-proven vs live RAM (sha1-equal prefixes); staging buffer `0x80079A70` (T4) | Required before any overlay can be imported into Ghidra at the right vaddr | Done; import at these vaddrs in T7 |
|
||
| 2 | US analogue of the JP EXE pointer table (~JP EXE offset 0x62620) | Maps which sub-file loads where — the overlay map | Search US EXE for the same pointer pattern; xref from the resident-blob loader |
|
||
| 3 | **RESOLVED (T3, static)** — LIST.CD raw → `listCdBuffer` 0x80180000 (0xE40 B); parsed → `cdFileLocTable` 0x800AE830 ({CdlLOC;size}×447, count cross-checks Phase-2 exactly); ID indirection via `resourceIdMap` 0x80063138; lookup `ResourceGetCdLoc` 0x8001B788 | The game reads sub-file TOCs from this in-RAM copy; key to the loader | Byte-level LIST.CD parse (F4) deferred to T6 live pass |
|
||
| 4 | ~~CdRead call chain~~ **RESOLVED (T2)** — `CdReadRequest` 0x80019A24 → `CdReadStateMachine` 0x80019AF8 (hand-rolled `CdControl` SeekL/ReadN, **not** PsyQ `CdRead`); LIST.CD read by `LoaderInitFileTable` 0x8001971C | Core of the file-loader RE (Phase 3 milestone) | See the "Phase 3 T2" block above |
|
||
| 5 | **ANCHORED (T6b)** — save-header template `saveHeaderTemplate` 0x80072DF0 (filename `BASLUS-00726MUSASHI`, SJIS title `BRAVE FENCER MUSASHI`); save/load handlers ~0x8002B154/1AC/BEA4. Full data-block format + checksum still TBD | No public documentation exists anywhere | Trace the 0x8002Bxxx handlers in Ghidra (T7/T8) + PsyQ memcard/`write` xrefs |
|
||
| 6 | **RESOLVED (T4)** — single caller `CdReadSectorReadyCB` 0x8001A338 @ 0x8001A90C; staging buffer `lzss_sectorStagingBuf` 0x80079A70 filled by `CdGetSector`; **inline per-sector decode** in the read-ready callback | Connects CD streaming to decompression | — |
|
||
| 7 | PAC types 6/7 semantics; meaning of PAC header u32 at +0x08 | Unknown to every prior source (CUE: "???"; jywjyw: "??") | Ghidra analysis of the PAC-header parser in the loader |
|
||
| 8 | **CONFIRMED position-locked (T6b)** — overlay loaded verbatim to fixed vaddr 0x80128158 (389,400 B contiguous exact match at the exact address; no relocation) | Determines splat segment strategy — simple fixed-address segments | Done |
|
||
| 9 | **RESOLVED (T8): the `\DEBUG.BIN;1` path is DEAD in retail.** `LoaderInitFileTable` 0x8001971C `CdSearchFile`s it at boot and stores the result to `debugBinPresent` 0x800747D0, but that flag is **write-only — zero readers** (only xref is the boot WRITE @0x8001979C). So nothing acts on it; the debug-loader path is dormant/dead | Possible dormant debug functionality | Done |
|
||
| 10 | **RESOLVED (Phase 3.5):** the demo's scene-select is the SAME architectural feature as the retail L3 debug menu — identical 18-entry `gameModeHandlerTable` (same order + same idx[10]==idx[15] duplicate), game mode #7 — BUT the slot-7 handler CODE diverged and the demo is **unnamed** ⇒ **no free labels**. Shared architecture, not shared labels. | Free labels for retail RE | Done — see `docs/proto-correspondence.md` |
|
||
| 11 | $gp usage: header `gp0` = 0 (verified) — is the build −G0? | Pins a compiler flag for matching (Phase 6) | Check for `$gp`-relative loads in Ghidra |
|
||
| 12 | **RESOLVED (T6b, change-detection)** — 0x80078EB2 = hp_max, 0x80078EB4 = hp_current (HP 146→136 tracked live); same {max,cur} pattern for BP at 0xEB6/0xEB8 | Correct struct field names | Done |
|
||
| 13 | Nature of `buildIdBytes` @ 0x8000BA94 (kernel-area RAM) | **ANSWERED (P33 E2, 2026-09-07)** — not a game variable. AP-world `client.py` v0.8.1 matches the ASCII string `SLUS-00726MUSASHI` at MAIN_RAM 0x00BA94, then 0x009F9A, then 0x072E02, then `SLUS_007.26;1` at eight more addresses, then the JP `SLPS_014.90;1`: a fallback cascade over BIOS/emulator-dependent kernel-area copies of the disc ID / boot path. The game's OWN copy is the product-code string **`BASLUS-00726MUSASHI` at 0x80072DFC in EXE `.data`** (fileoff 0x635FC; disc sector 222) — so the `0x072E02` fallback starts at the string's 5th character and cannot match on the US EXE (harmless: the kernel-area check hits first) | Nothing to import; row kept as the record |
|
||
| 14 | Where are `LOGOA/LOGOB.STR` and `.DA` paths referenced (absent from `CdPathTable`)? | Completeness of the file-access map | String scan for other path tables; CdSearchFile xrefs |
|
||
|
||
---
|
||
|
||
## Source index
|
||
|
||
| Source | What it provided | Access notes |
|
||
|---|---|---|
|
||
| Local disc dump `Z:\Storage\git\BFM-decomp\Brave Fencer Musashi (USA)\` (Track 1).bin | All `verified` entries (EXE header, LZSS routine bytes, CD-path table) | MODE2/2352; user data at raw-sector offset +24; EXE at LBA 24 |
|
||
| AiroRom wiki (pleonex) | Annotated LZSS decompressor disassembly | Fetch raw: `https://raw.githubusercontent.com/wiki/pleonex/AiroRom/Brave-fencer-Musashi.md`; uses nonstandard register names (r2..r31); region was unattributed — verified US in-session; **JP equivalents NOT verified** |
|
||
| TCRF | Debug menu codes (3 regions), unused music track, localization diffs | Use `tcrf.net/api.php` — direct fetch returned prompt-injection content |
|
||
| AP-world `github.com/AegeusEvander/Brave-Fencer-Musashi-AP-World` | Richest living US+JP RAM map; code patch sites; region deltas | Addresses are BizHawk MAIN_RAM offsets (+0x80000000); some entries are code patches, not data — read context before importing. License unstated |
|
||
| LiveSplit autosplitter (Aalaizah gist `f9cc6e8c8fba5a2457762f4fab8679c7`) | Story counter, location IDs, boss HP, chest/armor bytes | octoshock offsets: −0x30DF90 +0x80000000 |
|
||
| gamehacking.org #88529 (US) / #93476 (JP) via libretro-database GameShark `.cht` | Player stat block, flags, misc | gamehacking.org Cloudflare-blocks scripts; cht mirror: `raw.githubusercontent.com/libretro/libretro-database/master/cht/Sony%20-%20PlayStation/Brave%20Fencer%20Musashi%20(USA,%20Japan)%20(GameShark).cht` |
|
||
| jywjyw `bravefencer-hack` `doc/note.md` | JP overlay/memory map, LIST.CD-in-RAM behavior, pointer table | **All addresses JP (SLPS-01490)** — re-derive for US |
|
||
| Local RAM-dump corpus `dumps/ram_*.bin` (28 × 2 MB, PCSX-Redux web API, Phase 3) | Every `verified` live-RAM entry (§3.4, §4.3, the Q#s), the overlay map proof, the save header | LOCAL-ONLY from P33 (ROM-derived); identity = `dumps/CHECKSUMS.sha1`; states catalogued in `dumps/INDEX.md`; a re-capture is a new snapshot, never byte-identical |
|
||
| Hidden Palace / archive.org | Prototype facts | pages fetchable via `hiddenpalace.org/w/index.php?title=PAGE&action=raw` |
|
||
|
||
## Phase 30 S44 — the COMPLETE loader routing table (static-derived; supersedes "runtime-indexed, no static xref")
|
||
|
||
> **Provenance (G5):** `static-derived` — read from the EXE bytes (`extracted/retail/SLUS_007.26`,
|
||
> vram = fileoff + 0x8000F800), the resident payload bytes (`MAIN.CD.dir/FILE_010.dir/1.1`, fileoff =
|
||
> vram − 0x800CEDF8), the matched loader C (`src/800.c`, `src/resident/resident.c`), and the per-overlay
|
||
> wrapper asm — by 3 read-only exploration agents, 2026-08-06. Region: **US**. The Phase-3 T5 note
|
||
> "entries [1]+ are runtime-indexed (no static xref)" is **superseded**: the indices ARE static, they
|
||
> live in the resident and in each overlay, not in the EXE. Independently corroborated by two
|
||
> corpus-side methods (h_exact base voting at ~500:1; distinct-jal→prologue alignment voting) — and the
|
||
> resident control reproduces its known 0x800CEDF8 and ends at 0x80128154, four bytes under the overlay slot.
|
||
|
||
### loadDestPtrTable — 0x80072C70 (EXE fileoff 0x63470), 5 × u32
|
||
|
||
| slot | value | role (byte-proven) |
|
||
|---|---|---|
|
||
| [0] | **0x800CEDF8** | resident-module slot (boot loaders) |
|
||
| [1] | **0x80128158** | location-overlay slot |
|
||
| [2] | **0x800CAE08** | module slot A (small actor modules) |
|
||
| [3] | **0x800CCB1C** | module slot B (small actor modules) |
|
||
| [4] | **0x800C7F08** | PAC-type-7 fixed destination |
|
||
|
||
### Who loads what where (all statically enumerated)
|
||
|
||
| loader | index source | payloads | dest |
|
||
|---|---|---|---|
|
||
| 5 boot loaders (literal `&cdFileLocTable[k]` at 0x80010CA4 / 0x80010F1C / 0x800112F0 / 0x80011100 / 0x80011144) | k ∈ {1,3,8,10,11} | MAIN/1,3,8,**10 (=resident)**,11 | `loadDestPtrTable[0]` = 0x800CEDF8 |
|
||
| resident `func_800D02D0` | **`D_800D3764`** = 29 × {u32 cdFileLocIdx; u32 param} | MAIN/13…41 (contiguous) | `[2]` = 0x800CAE08 |
|
||
| resident `func_800D0488` | **`D_800D384C`** = 6 × {u32,u32} | MAIN/42…47 | `[3]` = 0x800CCB1C |
|
||
| resident `func_800CF94C` (`src/resident/resident.c:641`) | `&cdFileLocTable[12]` | MAIN/12 (an UNCOMPRESSED overlay) | `[1]` = 0x80128158 |
|
||
| per-overlay wrapper `func_80128CFC` (every overlay) | per-overlay `IDXTAB` (s16, −1-terminated, 37 entries, same list fleet-wide) + `*DESTPTR` (per-overlay initialized word) | resources incl. the SC0x sets | per-overlay dest (e.g. ov_SC01_000: IDXTAB 0x8017EEC8, *0x801A3234 = 0x801A58E8) |
|
||
| SC07 endgame pair | header-derived (id word + fn-ptr table; first table target − first prologue fileoff) | SC07/3 (code@0xFC), SC07/4 (code@0x158) | **0x801A00D8** (own slot, overlaps the overlay tail — disc-7 layout) |
|
||
|
||
### The arithmetic
|
||
|
||
- **Global cdFileLocTable index** = `gbase[cd] + subfile`; gbase = MAIN:0 SC01:49 SC02:135 SC03:178
|
||
SC04:318 SC05:349 SC06:379 SC07:418 (LIST.CD counts 49/86/43/140/31/30/39/29, byte-verified; LIST.CD
|
||
carries **LBA + length only**, never load addresses).
|
||
- **Slot adjacency proof:** 0x800CAE08 + 7,444 (max slot-A payload, MAIN/34) = 0x800CCB1C;
|
||
0x800CCB1C + 8,920 (max slot-B, MAIN/44) = 0x800CEDF4 → resident at 0x800CEDF8. The three regions are
|
||
back-to-back, each sized to its largest member. MAIN/46's self-calls (base+0x724/0x978/0xAB0) confirm slot B.
|
||
- **Module-ID law:** payload **word0 is a global module id** (dense 0x13…0x73 across all discs; the
|
||
resident is 0x36). 75/78 unclaimed payloads carry it; only the 3 raw uncompressed overlays
|
||
(MAIN/12, SC02/37, SC03/107) start directly with code. MAIN/9 and MAIN/39 both carry id 0x2D
|
||
(unresolved duplicate). MAIN/0 ≡ MAIN/1 byte-identical (one module stored twice).
|
||
- **PAC-type law (extends formats.md):** type **1** = uncompressed code/module payload; type **4** =
|
||
the same class LZSS-compressed. The PAC header's bytes 0x10–0x7FF are never read by the loader
|
||
(`CdGetSector(lzss_sectorStagingBuf, 4)` reads 4 words) — no address lives in the payload.
|
||
|
||
### Statically UNRESOLVED (parked for L3 — runtime confirm, R34)
|
||
|
||
The 28 SC0x script modules (SC03/73-79, SC03/132-138, SC04/24-30, SC05/23-29 = 7 modules × 4 per-disc
|
||
builds), SC02/9, MAIN/7 (raw file, not PAC), MAIN/9: dest comes through the resourceIdMap /
|
||
`StreamLoadStateMachine` descriptor path (`D_80068B60[(loadParam−0x100)*0x10]`) or per-overlay DESTPTR
|
||
values — per-disc, not EXE-static. Their jal-vote bases are LOW-CONFIDENCE (3–14 aligned jals, calls
|
||
almost entirely outward) and are NOT recorded as addresses here.
|
||
|
||
## Phase 30 S45 — the script-module slots + the L3 debug-menu tour (live-verified)
|
||
|
||
> **Provenance (G5):** `live-verified` — PCSX-Redux RAM (R11 web API), 2026-08-07 tour driven by
|
||
> Drew via the retail debug menu, watcher logs in `.run/s45/l3_tour.jsonl` + hit dumps. Region: US.
|
||
|
||
### The debug menu, operationally (extends §5)
|
||
- **Entry that actually works:** force `gameMode` (0x800B99DE, u16) = **7** with REPEATED writes
|
||
(the game rewrites it every frame — a single poke never latches; the GameShark constant-write
|
||
semantics are required). Web-API gotcha: **`offset` must be DECIMAL** — hex offsets are silently
|
||
parsed as 0 (= writes land at 0x80000000, kernel space).
|
||
- Mode 7 → `DebugMenuHandler` boot-loads **MAIN/11 = md_MAIN_011 (DISELECT)** to 0x800CEDF8 —
|
||
**byte-proven live: 24,236/24,240 B identical in RAM.** The menu's AREA/SCENE list:
|
||
`docs/debug-menu-list.txt` (Drew's transcription).
|
||
- Observed `gameMode` values: 5 (transition) · 6 (title/main menu) · 7 (debug) · 8 (post-menu) ·
|
||
10 (scene load) · 11 (field/load-screen) · 15/16 (attract rotation).
|
||
|
||
### The four script-module slots (all live byte-verified, contiguous exact prefixes)
|
||
| slot | payloads | meaning |
|
||
|---|---|---|
|
||
| **0x801EF468** | SC03/73–79 | town-interior scripts, chapter-2 period (id 0x44–0x4A) |
|
||
| **0x801E25E8** | SC03/132–138 | town-interior scripts, chapter-3/vampire period (0x4B–0x51) |
|
||
| **0x801E7B28** | SC04/24–30 | chapter-4 period (0x52–0x58) |
|
||
| **0x801ED988** | SC05/23–29 | chapter-5 period (0x59–0x5F) |
|
||
|
||
**The routing law:** the debug menu's AREA selects the chapter (`gbase[cd]`); each CITY interior
|
||
scene (4 FIGURE / 5 PAN / 6 ITEM / 7 INN / 8 RECYCLE / 9 KYOUKAI / 10 SAKABA) streams its own
|
||
script module — member k of the set ↔ interior k, one module resident per slot at a time (the
|
||
slot-A/B ping-pong pattern at chapter scale). ACTION scenes, dialogue, quest flags, area
|
||
transitions: none of them stream script modules (byte-checked negatives, ~15 probes).
|
||
|
||
### MAIN/3 = the main-menu module (DISCOVERED live, S45)
|
||
`MAIN.CD/FILE_003.dir/1.1` (121,884 B, **id 0x39** — completing the boot cluster 0x36–0x3B) runs
|
||
the main menu at **0x800CEDF8**: live byte-proof = 42,632-B contiguous exact prefix. It was
|
||
mis-bucketed `classified-data` by BOTH audit oracles (low jr-density module class). Onboarded as
|
||
`md_MAIN_003`. Note: 0x800D3200 is a **one-word data sentinel (0x00FFFFFF) inside .text**; the
|
||
function at 0x800D3204 materializes that address and reads/writes it (pinned in
|
||
`config/symbols.md_MAIN_003.txt` — splat's auto-boundary there is extract-order-sensitive).
|
||
|
||
### Still parked (6 payloads — every cheap state byte-checked negative)
|
||
**MAIN/7, MAIN/9** (not in: boot, title, attract ×4+ segments, load screen, main menu, OPENING,
|
||
ENDING-1/ZEN, game-over/continue) · **SC02/9** (not in: STEAM-WOOD ×3, GANRYUU, 2ST-BOSS,
|
||
GEKIRYU) · **SC03/53/54/56** (ids 0x40/0x41/0x43; not in: ISEKI, PLAZUMA, AREADEMO2-3,
|
||
VAMBI-KYOUKAI; SC03/55 = their DATA companion, id-word 0x42 but zero prologues/returns —
|
||
confirmed data). MAIN/9 shares id 0x2D with slot-A module MAIN/39 (an alternate build).
|
||
**Resolution tier: the CD-read tracer** (log `cdFileLocTable` index per read), not scene-guessing.
|
||
|
||
### S45 part 3 — SC02/9 CAPTURED + retro-verified; the emulator's role closes
|
||
- **SC02/9 = the Steam Knight (1ST-BOSS) event module** — loads at **0x801E4C60** when
|
||
`currentLocationId == 0x300E` (the matched gate `func_8012832C`, case 0x300E →
|
||
`func_80128998` → the streaming API with `&cdFileLocTable[144]`). **Double-proven:** live
|
||
capture 2026-08-07 AND the Phase-3 `dumps/ram_castle.bin` (2026-06-14) holds it at the SAME
|
||
address with the SAME 6,764-B exact prefix (R10 multi-datapoint). `bossHp_SteamKnight`
|
||
0x801E4398 sits inside this module's image. Onboarded as `md_SC02_009`.
|
||
- **Negatives (byte-checked):** pause/config menu, memory-box prompt (CENTER-PATH 0x3034),
|
||
new-game intro, high/low bar game (runs from SC03/79), Minku midnight spawn (slot-A actor
|
||
0x15 = md_MAIN_015, candidate naming) — none load any parked payload.
|
||
- **The remaining 5 (MAIN/7, MAIN/9, SC03/53/54/56) are STATIC-RE targets, not emulator
|
||
targets:** MAIN/7's loader (fn @0x80161E08 in ov_SC03_126/ov_SC04_021/ov_SC05_019) gates on
|
||
a global 0x800C3054 ∈ {0x3012, 0x3079, 0x3096} (writer untraced — not written by the EXE);
|
||
MAIN/9 has NO literal table reference anywhere (table-indexed loader). Homework: trace
|
||
0x800C3054's writer, identify the 3 host overlays' locations, decode the SC03/53 loader in
|
||
ov_SC03_104 (@0x80161FBC), find MAIN/9's indexed loader.
|
||
|
||
### S45 part 4 — CORRECTION (R14/R35): the "MAIN/7 & SC03-trio loader" leads were scanner artifacts
|
||
The §S45-part-3 claims that MAIN/7's loader is fn @0x80161E08 (ov_SC03_126/SC04_021/SC05_019,
|
||
"gate 0x800C3054 ∈ {0x3012,0x3079,0x3096}") and that SC03/53's loader is @0x80161FBC in
|
||
ov_SC03_104 are **REFUTED**. The quick hi/lo scanner paired `lui`/`lo16` operands **without
|
||
tracking base registers**; a proper register-tracked rescan (rabbitizer-checked disasm) shows:
|
||
- fn 0x80161E08 compares **currentLocationId (0x800B9A08)** against location ids {0x1A-guard,
|
||
0x3012, **0x3054**, 0x3079, 0x3096} — the "variable 0x800C3054" never existed (0x3054 is a
|
||
compared CONSTANT), and its stores target 0x8018E868, not the loc-table.
|
||
- **The only register-verified literal loc-table reference in the entire fleet is SC02/9's**
|
||
(the universal wrapper helper `func_80128998`, solved).
|
||
**Standing truth: MAIN/7, MAIN/9, SC03/53/54/56 have ZERO literal loc-table references anywhere
|
||
— all five load via table-INDEXED paths** (the resourceIdMap / `D_80068B60` descriptor route +
|
||
per-overlay IDXTAB/DESTPTR, per the original §S44 finding). Next-session homework: hunt the
|
||
DESCRIPTOR DATA (records carrying global indices 7, 9, 231, 232, 234) and decode
|
||
`ResourceGetCdLoc`/`StreamLoadStateMachine`'s index math — a structured-data hunt, still fully
|
||
static. Tooling lesson (cookbook §155): a hi/lo literal scanner MUST track base registers;
|
||
window-paired lui/lo16 produces convincing phantom cross-references.
|
||
|
||
### S45 part 5 — the resourceIdMap branch is REFUTED for all five; IDXTAB is per-overlay (2026-08-07)
|
||
|
||
> **Provenance (G5):** `static-derived` — decoded from the EXE bytes
|
||
> (`extracted/retail/SLUS_007.26`, vram = fileoff + 0x8000F800) and the onboarded payloads,
|
||
> using the index math taken from our OWN matched C, not from a guess. Region: **US**.
|
||
> Scripts: `.run/s45p5/{decode_resmap,decode_idxtab,scan_idxtabs}.py` (regenerable scratch).
|
||
|
||
**The index math, quoted from matched/drafted C (`src/800.c`) — this is the oracle:**
|
||
|
||
```c
|
||
ResourceGetCdLoc(resId): /* MATCHED, byte-exact */
|
||
idx = *(s16*)(resourceIdMap + resId*6); /* global cdFileLocTable index */
|
||
return *(s32*)(cdFileLocTable + idx*8);
|
||
|
||
ResourceLoadStateMachine: /* NON_MATCHING draft, logically faithful */
|
||
fileIdx = *(s16*)(resourceIdMap + id*6 + 0); /* <0 => non-CD path func_80036D58 */
|
||
streamId = *(s16*)(resourceIdMap + id*6 + 2); /* -> StreamLoadStateMachine param_1 */
|
||
postProcId = *(s16*)(resourceIdMap + id*6 + 4); /* -> func_8002D4C8 */
|
||
|
||
StreamLoadStateMachine(param_1, loc, n): /* NON_MATCHING draft */
|
||
sVar1 = *(s16*)(D_80068B60 + (param_1 - 0x100) * 0x10);
|
||
```
|
||
|
||
**`resourceIdMap` @ 0x80063138 (fileoff 0x53938), decoded:** exactly **162** 6-byte records,
|
||
ending 0x80063504; 2 records carry the documented `fileIdx < 0` non-CD sentinel; streamIds are
|
||
>= 0x100 as the descriptor math requires. The decode is self-consistent with the matched C in
|
||
every field, so the table model is confirmed, not assumed.
|
||
|
||
- **Coverage: 98 distinct global indices of 447.** Full dump: `.run/s45p5/resourceIdMap.tsv`.
|
||
- **FINDING (the point of the exercise): none of the five parked payloads appears.**
|
||
MAIN/7 (gi 7), MAIN/9 (gi 9), SC03/53 (231), SC03/54 (232), SC03/56 (234) have **no
|
||
`resourceIdMap` entry**, so they cannot reach `ResourceGetCdLoc` /
|
||
`StreamLoadStateMachine` / the `D_80068B60` descriptor at all.
|
||
**The §S44 "resourceIdMap / StreamLoadStateMachine descriptor path" branch of the parked-dest
|
||
disjunction is therefore REFUTED for all five.** The surviving branch is the per-overlay
|
||
`IDXTAB`/`DESTPTR` route (or a route not yet enumerated).
|
||
- The module payloads are *also* absent from `resourceIdMap` (checked gi 10/11/13/14/42/47) —
|
||
consistent with §S44: modules load via the resident's `D_800D3764`/`D_800D384C` tables and the
|
||
boot loaders, never the resource system. `resourceIdMap` is a distinct, per-location data-resource
|
||
route (its 98 entries are SC0x data sets).
|
||
|
||
**`loadDestPtrTable` @ 0x80072C70 re-derived independently (R34 second signal):** the five u32s
|
||
read `0x800CEDF8 / 0x80128158 / 0x800CAE08 / 0x800CCB1C / 0x800C7F08` — reproducing the §S44 table
|
||
exactly, including slot [4] = the PAC-type-7 fixed destination. Four of the five are the addresses
|
||
S45 byte-proved by onboarding, so this table is corroborated from two independent directions.
|
||
|
||
**CORRECTION (R14) to §S44's IDXTAB row:** the claim *"per-overlay IDXTAB (s16, -1-terminated,
|
||
37 entries, **same list fleet-wide**)"* is **wrong on the "same list fleet-wide" part**. The
|
||
37-entry list at 0x8017EEC8 is genuine **for ov_SC01_000 only** (decoded: 240,241,242,243, 167,168,
|
||
147,148,149, 153,154,155, 306..309, 102..122 — a coherent per-location resource set). Read at the
|
||
same vaddr across the fleet, **1 of 141 overlays matches and 140 hold unrelated bytes**: the IDXTAB
|
||
is per-overlay data at a **per-overlay address**. What is fleet-wide is the *mechanism*
|
||
(`func_80128CFC` + a per-overlay IDXTAB/DESTPTR), not the address or the contents.
|
||
|
||
**NEGATIVE TOOLING RESULT (recorded so it is not repeated):** a *shape-only* fleet scan for
|
||
"-1-terminated s16 runs of valid global indices" **cannot discriminate** and its hits are not
|
||
evidence. It passes its own R32 coverage assertion (it re-finds ov_SC01_000's table at
|
||
0x8017EEC8, 37 entries) yet still returns 664 "tables" across 212 payloads, whose parked-index
|
||
hits are transparently (offset, count) pair data — e.g. `[44, 2, 48, 7, 62, 6, 74, 7, ...]`
|
||
"contains 7". Small global indices (7, 9) are indistinguishable from ordinary small data by shape
|
||
alone. This is the §155 phantom failure mode in a new guise: **register-blind -> shape-blind**.
|
||
Do not re-run this scan expecting an answer.
|
||
|
||
**The correctly-scoped next instrument:** a **register-tracked** decode of the per-overlay wrapper
|
||
`func_80128CFC` (per §155: record `lui rt -> hi`, match only ops whose base is that `rt`,
|
||
invalidate on clobber) to extract, per overlay, the actual `IDXTAB` pointer and the `*DESTPTR`
|
||
word — then test whether any overlay's real IDXTAB references gi 7 / 9 / 231 / 232 / 234, and read
|
||
that overlay's DESTPTR for the load address. Structure-derived, not shape-guessed.
|
||
|
||
#### S45 p5 addendum — WHY the five resist static derivation (three refuted oracles + the region finding)
|
||
|
||
**MAIN/7 and MAIN/9 are IDENTIFIED (semantic, not address):** both payloads carry leftover
|
||
dev-machine path strings immediately after their id word —
|
||
`C:\TIMPACK\OPDEMO0.PAT` and `C:\TIMPACK\OPDEMO1.PAT` (MAIN/7 id 0x3A, MAIN/9 id 0x2D). These
|
||
are the **opening / attract-demo** playback modules. (`C:\TIMPACK\` is the same build-path family
|
||
found in retail AND the sep8 proto in Phase 3.5.) S45 p2 recorded "OPENING" as byte-checked
|
||
negative, so the live target is the **attract-mode demo** (idle at the title until the game
|
||
demos itself), which is a different state from the OPENING cutscene. MAIN/9 additionally shares
|
||
id 0x2D with slot-A module MAIN/39 (§S44's "alternate build" note) — consistent with a
|
||
demo-specific rebuild of an actor module.
|
||
|
||
**Three payload-side base oracles were built and ALL REFUTED by their own controls** (recorded so
|
||
they are not rebuilt; scripts in `.run/s45p5/`):
|
||
|
||
| oracle | idea | controls | why it fails |
|
||
|---|---|---|---|
|
||
| `derive_base.py` | invert the §154 TLO law: `base = min(table) − (4+4N)` | **0/4** | assumes code starts right after the fn-ptr table; MAIN/34 has a **0x208-byte header gap** between table end and code |
|
||
| `vote_base.py` | vote `jal` targets + table ptrs against `addiu $sp,-N` prologues | **4/12** | modules call almost entirely OUTWARD (§S44's low-confidence note, now explained), and MIPS **leaf functions have no prologue**, so the anchor set is both noisy and incomplete |
|
||
| `vote_base2.py` | self-consistency: internal `jal` targets must hit listed functions | **0/4** | **there are essentially no internal `jal`s** (`self-jals 0/N` on every control) — module functions are reached *indirectly through the header pointer table* (`jalr`), never by direct call |
|
||
|
||
The third result is the structural one: **a module's own bytes do not encode its base**, because it
|
||
makes no self-referential direct calls. Only the header pointer table's *value range* constrains
|
||
anything, and only for the payloads that have a table.
|
||
|
||
**The one real address constraint obtained — SC03/54:** its header holds **19 absolute pointers**
|
||
spanning `0x801EF718..0x801EFEE8`. With size 0x201C and a 0x50-byte table, the base is confined to
|
||
**[0x801EDED0 .. 0x801EF6C8]** (6,136-byte window); the module occupies roughly
|
||
`0x801EDED0..0x801EFEE8`.
|
||
|
||
**That window lies INSIDE SC02/9's occupied span** (`0x801E4C60 + 70,784 = 0x801F60E0`). SC02/9 and
|
||
the SC03 trio (ids 0x40/0x41/0x43, adjacent to SC02/9's 0x3E) are therefore **mutually-exclusive
|
||
event modules sharing one region around 0x801Exxxx — but at DIFFERENT base addresses** (SC03/54's
|
||
base cannot be 0x801E4C60; it is excluded by the window).
|
||
|
||
**Conclusion (this is why they are parked):** event-module destinations in this region are
|
||
**per-scene / runtime-determined, not a fixed slot from a static table** — which is exactly why no
|
||
static route exists, why the resourceIdMap branch is empty for them, and why the *emulator* was
|
||
what resolved SC02/9. The **CD-read tracer** (log the `cdFileLocTable` index per read during play)
|
||
remains the correct instrument; the OPDEMO identification above makes MAIN/7 and MAIN/9 a targeted
|
||
capture (attract demo) rather than a search.
|
||
|
||
#### S45 p5 — the exclusion proof needs a CONSUMER-side instrument (4th value-scan refuted)
|
||
|
||
A fourth attempt (`.run/s45p5/exclusion_proof.py`) tried the *proven* §S44 table shape
|
||
({u32 cdFileLocIdx; u32 param} pairs, as in `D_800D3764`/`D_800D384C`). Its **R32 control
|
||
FAILED — it did not re-find either known table**, so its output is void (R35).
|
||
|
||
**Standing verdict: no value- or shape-based scan can establish the exclusion.** Small global
|
||
indices (7, 9) are indistinguishable from ordinary small integers; 4/4 such scans have now been
|
||
refuted by their own controls or shown non-discriminating (§155a). Do not attempt a 5th.
|
||
|
||
**The only sound instrument is CONSUMER-side:** enumerate every code site that references
|
||
`cdFileLocTable` (0x800AE830) register-tracked (§155) across all 213 binaries — a bounded set,
|
||
since S45 p4 already showed literal references are almost nonexistent — then resolve each site's
|
||
index SOURCE and collect the full set of reachable global indices. gi 7/9/231/232/234 absent from
|
||
that set is the proof; present is the address. This is real work, not a quick scan.
|
||
|
||
**Partial result worth keeping:** the large, discriminating indices **231 (SC03/53) and 234
|
||
(SC03/56) appear in no pair-shaped table anywhere in the fleet** — weak evidence (the instrument
|
||
was unsound) but directionally consistent with the dead-code reading.
|
||
|
||
#### S45 p6 — the parked SC03 trio: a CHAPTER-GATED town-interior script hypothesis (recorded BEFORE test)
|
||
|
||
Written before the runtime test so the prediction stands on its own (P9). Two independent lines
|
||
converge on the same answer for **SC03/53, SC03/54, SC03/56**:
|
||
|
||
**1. The address.** SC03/54's base window, derived from its 19 absolute header pointers
|
||
(0x801EF718..0x801EFEE8, §S45 p5 addendum), is **[0x801EDED0 .. 0x801EF6C8]**. Of the four
|
||
byte-verified script-module slots (§S45), **exactly one falls inside that window**:
|
||
|
||
| slot | payloads | period | in SC03/54's window? |
|
||
|---|---|---|---|
|
||
| **0x801EF468** | SC03/73–79 | chapter 2 | **YES** |
|
||
| 0x801E25E8 | SC03/132–138 | chapter 3 / vampire | no |
|
||
| 0x801E7B28 | SC04/24–30 | chapter 4 | no |
|
||
| 0x801ED988 | SC05/23–29 | chapter 5 | no |
|
||
|
||
**2. The module ids** (§S44 word0 law) place the trio immediately BEFORE the chapter-2 block:
|
||
|
||
```
|
||
0x40 SC03/53 · 0x41 SC03/54 · 0x42 SC03/55 (DATA companion) · 0x43 SC03/56
|
||
0x44–0x4A SC03/73–79 <- chapter 2
|
||
0x4B–0x51 SC03/132–138 <- chapter 3
|
||
```
|
||
|
||
**Hypothesis:** the trio are **town-interior scripts for an earlier game period (chapter 1)** —
|
||
the same class as the 28 script modules captured in S45 p2, sharing the 0x801EF468 slot, selected
|
||
by CHAPTER/day state rather than by location.
|
||
|
||
**Why every previous sweep missed them:** S45 checked the right *scenes* (ISEKI, PLAZUMA,
|
||
AREADEMO2-3, VAMBI-KYOUKAI) at the wrong *game period*. At a late-game save the loader serves the
|
||
chapter-2/3 variants into that slot and the chapter-1 set is never requested. Location was never
|
||
the discriminating variable.
|
||
|
||
**FALSIFIABLE PREDICTION:** in an early-game town interior, `SC03/53` / `/54` / `/56` load at
|
||
**0x801EF468**. If they do not appear there, the slot coincidence is chance and this dies; the
|
||
follow-up question is then which state variable gates them, not which room to visit.
|
||
|
||
#### S45 p6 — RESULT: the runtime CD-load tracer, and what it settled (2026-08-07)
|
||
|
||
**The instrument (new, `tools/cdtrace.py`).** Three static oracles failed to derive the parked
|
||
payloads' load addresses this session. The runtime answer turned out to need **no breakpoints, no
|
||
Lua (so no `pcsx.lua` wedge hazard) and no GDB stub** — the loader keeps its whole request in RAM,
|
||
so both halves are already in the 2 MB dump we could already fetch:
|
||
|
||
```
|
||
cdReq_curSector 0x800AE708 the sector being read -> file index via cdFileLocTable
|
||
cdReq_dest 0x800AE72C where it is written -> the load address
|
||
cdFileLocTable 0x800AE830 8B/entry {CdlLOC; size}, GLOBAL index
|
||
```
|
||
|
||
`CdReadRequest(int *cdlFile, void *dest, …)` — our own MATCHED C — is why this works: `cdlFile`
|
||
points INTO `cdFileLocTable`, so `(ptr − 0x800AE830)/8` is the global index and `dest` is the
|
||
destination. Both are mirrored in the control block.
|
||
|
||
**VALIDATED before use (R35), then confirmed 7× against independently byte-proved ground truth:**
|
||
|
||
| observed live | confirms |
|
||
|---|---|
|
||
| MAIN/1, MAIN/3, MAIN/10 → `0x800CEDF8` | §S44 `loadDestPtrTable[0]`; MAIN/10 = the Phase-3 resident; MAIN/3 = S45-p2's `md_MAIN_003` |
|
||
| MAIN/42, SC03/0 → `0x800CCB1C` | §S44 `[3]` slot B — **and SC03/0 shows the module slots are NOT MAIN-only** |
|
||
| MAIN/12 → `0x80128158` | §S44's resident `func_800CF94C` row, exactly |
|
||
| 12 overlays → `0x80128158` | §S44 `[1]`, the location slot |
|
||
| LIST.CD (lba 227) → `0x80180000` | the `LoaderInitFileTable` bootstrap read in matched C (`src/800.c`) |
|
||
|
||
This is the **R34 second oracle for the whole §S44 routing table** — until now static-only.
|
||
|
||
**THE FINDING — the script-module slot `0x801EF468` is live and general.** Observed twice:
|
||
`SC03/76 → 0x801EF468` and **`SC03/34 → 0x801EF468`**. SC03/34 is NOT in the SC03/73–79 block, so
|
||
S45's label ("SC03/73–79, chapter-2 period") describes one *tenant*, not the slot: **it is a
|
||
general per-scene script-module slot that different SC03 scripts stream through.**
|
||
|
||
**Status of the pre-registered hypothesis (recorded above, before the test):**
|
||
- ✅ **Slot CONFIRMED** — `0x801EF468` is real, live, and the class the trio belongs to (it lies
|
||
inside SC03/54's independently-derived base window `[0x801EDED0..0x801EF6C8]`).
|
||
- ❌ **"Chapter-gated" WEAKENED** — scripts swap per SCENE, not per chapter. The id-block
|
||
adjacency (0x40/0x41/0x43 preceding chapter-2's 0x44–0x4A) remains suggestive but is not the
|
||
selector.
|
||
- ⬜ **Trio: 0 sightings** across 38 load events / 2 saves / multiple SC03 scenes.
|
||
|
||
**THE EFFICIENT NEXT STEP (static, no emulator):** we now have a concrete anchor the earlier hunts
|
||
lacked — the constant **`0x801EF468`**. Find the code that loads into it (register-tracked, §155)
|
||
and decode its SELECTOR — near-certainly a scene→script-index table. That yields the answer for all
|
||
three at once, instead of sweeping rooms. This is the correctly-scoped successor to the four
|
||
refuted value-scans.
|
||
|
||
**Also captured:** the full attract-cycle load map (`.run/attract_loadmap.jsonl`) — MAIN/7 and
|
||
MAIN/9 absent across a complete 304-second cycle (see the OPDEMO section above).
|
||
|
||
**Carried offer (Drew):** a fresh full playthrough with the tracer running would map every load in
|
||
the game in one pass — resolving the trio, MAIN/7/9, and validating the entire routing table.
|
||
High value, zero marginal effort beyond playing.
|
||
|
||
#### S45 p6 — SOLVED (static): the SC03 trio are ov_SC03_001's script modules
|
||
|
||
The static decode succeeded where four value-scans and three payload-side oracles failed, because
|
||
the live tracer supplied the missing anchor: the slot address **0x801EF468**.
|
||
|
||
**The chain, every link register-verified or byte-observed:**
|
||
|
||
| link | evidence |
|
||
|---|---|
|
||
| `ov_SC03_001` IDXTAB **@0x8018D7BC** — 5 × s16, `-1` terminated: **224, 231, 232, 234, 233** | direct read; the trio + its DATA companion (233 = SC03/55) in ONE table |
|
||
| `func_80128CFC` (the §S44 per-overlay wrapper) | register-tracked: `addiu→0x800AE830` (cdFileLocTable base), `lw [0x800AE834]` (size), `lw [0x800AE830]` (loc) |
|
||
| `*DESTPTR` **@0x801EBC68 = 0x801EF468** | the ONLY occurrence of that word in the fleet; read 8× by code, twice from inside `func_80128CFC` (0x80128CD0, 0x80128D34) |
|
||
| the slot is real and live | tracer: `SC03/76 → 0x801EF468`, `SC03/34 → 0x801EF468` (2026-08-07) |
|
||
| the slot lies inside SC03/54's own derived base window | `[0x801EDED0..0x801EF6C8]` from its 19 header pointers (§S45 p5 addendum) — independent |
|
||
|
||
**VERDICT: SC03/53, SC03/54, SC03/56 are LIVE script modules owned by `ov_SC03_001`**, loaded via
|
||
`func_80128CFC` into the shared script-module slot `0x801EF468`. Not dead code; not boss-gated;
|
||
not chapter-gated (that framing is retired — see p6 above, scripts swap PER SCENE). They never
|
||
appeared in any sweep because every SC03 scene we visited runs a DIFFERENT overlay
|
||
(ov_SC03_124/125/051); only the scene(s) backed by **ov_SC03_001** request them.
|
||
|
||
**Why every earlier hunt failed, precisely:** the trio have no literal `cdFileLocTable` reference
|
||
(§S45 p4) and no `resourceIdMap` entry (§S45 p5) because the index never appears in CODE at all —
|
||
it lives in a per-overlay DATA table, and the destination likewise (`*DESTPTR`). Both are
|
||
*per-overlay data*, invisible to any fleet-wide code scan. That is the structural reason, and it
|
||
also explains MAIN/7 / MAIN/9 (still open — their owning binary is not yet identified).
|
||
|
||
**NOT YET PROVED (do not overstate):** the trio's exact LOAD BASE. `0x801EF468` is the slot; the
|
||
three payloads differ in size (10,240 / 12,288 / 6,144 raw) so their bases within it are not
|
||
established, and none was observed loading. **The byte-gate is the arbiter**: onboard each at
|
||
0x801EF468 and let the first build decide (the §S44 module-class recipe, SETUP §6.7).
|
||
|
||
**METHOD NOTE (generalizes):** a runtime observation supplied ONE constant (the slot), and that
|
||
constant made a previously-impossible static decode trivial. Neither alone was enough: four
|
||
static scans failed without it, and the emulator alone never caught the trio loading. Pair a
|
||
runtime oracle with static RE rather than choosing between them.
|
||
|
||
#### S46 — the master IDXTAB/DESTPTR map: the route decoded fleet-wide, and TWO corrections (2026-08-07/08)
|
||
|
||
Full write-up + method: **`docs/idxtab-map.md`**. Tool: `tools/idxtab_map.py` (controls-gated).
|
||
|
||
**The blocker was our instrument, again (R35).** The S45 plan — "find `-1`-terminated s16 runs, then
|
||
require a register-verified reference to the run's address" — returns NOTHING as specified:
|
||
`0x8018D7BC` has zero register-verified references and is stored as a data word nowhere in the fleet.
|
||
The tables are read by gcc's **indexed global-array** form, which `find_addr_refs` was blind to:
|
||
|
||
```
|
||
lui $at, 0x8019 ; addu $at, $at, $a0 ; lh $v0, -0x2844($at) -> 0x8018D7BC
|
||
```
|
||
|
||
The address is split across the `lui` and the **load**, with the index added between, so killing the
|
||
`lui` register at the `addu` (it is written) threw away the only link. Carrying the hi half through the
|
||
add — still strictly register-tracked, never window-paired (§155) — recovers both byte-proved controls
|
||
(`ov_SC01_000` 0x8017EEC8 / 37 entries, `ov_SC03_001` 0x8018D7BC / 5 entries) **from the images alone**.
|
||
|
||
**Fleet result:** 213 binaries → 146 with a referenced index table (320 tables, after filtering 106
|
||
zero-runs), 141 with a DESTPTR, **67 distinct payloads** on this route. The two dominant tables are
|
||
**fleet-wide CONSTANTS** (identical in all 141 overlays): a 5-entry list
|
||
`SC03/46, SC03/53, SC03/54, SC03/56, SC03/55` and a 37-entry list (`SC03/62-65, SC02/32-33, SC02/12-14,
|
||
SC02/18-20, SC03/128-131, SC01/53-73`). The per-binary variable is the **DESTPTR** — 141 binaries,
|
||
**134 distinct destinations**. Shape: *constant list + runtime index + per-binary destination.*
|
||
|
||
**CORRECTION 1 (R14) — §S45 p6's ownership claim is refuted.** The trio are NOT "owned by
|
||
`ov_SC03_001`": the 5-entry table containing `SC03/53/54/56` is present with identical content in **all
|
||
141 overlays**. Everything §S45 p6 byte-observed still stands (they are live script modules reached via
|
||
`func_80128CFC`; `ov_SC03_001`'s destination is `0x801EF468`) — only "only `ov_SC03_001` requests them"
|
||
is wrong.
|
||
|
||
**CORRECTION 2 (P9) — this route CANNOT settle MAIN/7 + MAIN/9.** They are absent from all 320 tables,
|
||
but so are `MAIN/13, /20, /34, /42, /44`, which are **byte-proved to load** (the onboarding controls in
|
||
`.run/s45p5/derive_base.py`). MAIN payloads referenced by any IDXTAB: `0 1 2 3 4 5 6 8 16 17 24 32 33`
|
||
— 13 of ~49. So absence here means "not loaded *by this route*", nothing more; the descriptor
|
||
(`StreamLoadStateMachine`) and `resourceIdMap` paths are separate. **MAIN/7 and MAIN/9 remain open.**
|
||
The S45 checkpoint's hope that this would be "the strongest dead-code evidence obtainable" does not
|
||
survive its own control — recorded so a later session does not re-derive the false conclusion.
|
||
|
||
#### S45 p7 (P32 T2a, 2026-09-05) — the parked five get STATIC base evidence: two STRONG, two consistent, one split
|
||
|
||
> **Provenance (G5):** `static-derived` from the extracted payloads alone (`tools/payload_base_evidence.py`, controls-gated:
|
||
> the seven banked modules' byte-proven bases re-derive top-ranked from their own bytes, 7/7). Region **US**. Rows in
|
||
> `.run/P32/t2a/evidence.json`. **None of these is a load address until `tools/new_binary.sh`'s FIRST build is
|
||
> byte-identical (the §S44 module-class law, P9)** — T2b ran the probes in the order given: **all five onboarded on their
|
||
> first candidate** (fleet 213 → 218; `make audit-disc` UNCLAIMED 5 → 0). The first-build check is base-lenient (finding 1
|
||
> below), so each base is recorded as static-derived STRONG, to be byte-proven by the first internal-call C bank.
|
||
|
||
Why this works where §S45 p5's three payload-side oracles failed: those looked for a module's base in its OWN
|
||
prologue/table geometry (`derive_base`), or voted `jal` targets against prologues (`vote_base`, 4/12) and against
|
||
"listed functions" (`vote_base2`, 0/4). The recall killer was the **function-start set**: MIPS leaf functions have no
|
||
`addiu $sp` prologue, so a self-call into a leaf read as a miss. With starts = prologues ∪ the word after every
|
||
`jr $ra`+delay (the linear-partition boundary), every self-calling module pins its own base; the non-self-calling
|
||
ones fall to the bounded DESTPTR list + pointer/`lui` consistency instead of a guess.
|
||
|
||
| payload | size | id | TEXT_LO | evidence | candidate base(s), probe order |
|
||
|---|---|---|---|---|---|
|
||
| MAIN/7 (`FILE_007`, raw) | 9,600 | 0x3A | 0x34 | **STRONG**: 9/9 internal `jal`s and 14/16 absolute pointers land on its own function starts; lui 0x800C/0x800D ×68 | **0x800CEDF8** (the boot slot — md_MAIN_001/008/011's) — ONBOARDED `md_MAIN_007`, byte-identical `2ff702b6…`, A4 applied |
|
||
| MAIN/9 (`FILE_009.dir/0.1`) | 2,544 | 0x2D | 0x3C | **STRONG**: 6/6 internal `jal`s and 9/9 pointers on starts at exactly one base; lui 0x800C/0x800D ×51 | **0x800CD348** (inside slot B's region, +0x82C; then slot A 0x800CAE08 / slot B 0x800CCB1C) — ONBOARDED `md_MAIN_009`, byte-identical `d270f695…` |
|
||
| SC03/53 (`FILE_053.dir/0.1`) | 6,616 | 0x40 | 0x4 | **STRONG** (after the combined rule): 52/75 pointers inside, 3 of them + its one internal `jal` exactly on its own starts; 0 on starts at every rival; lui 0x801F ×18 | **0x801EF468** (ov_SC03_001's DESTPTR) — ONBOARDED `md_SC03_053`, byte-identical `c0848f30…` |
|
||
| SC03/54 (`FILE_054.dir/0.1`) | 8,220 | 0x41 | 0xF0 | **STRONG**: 106/115 pointers inside, 5 header fn-ptr-table entries exactly on its own starts (0 at every rival), lui 0x801F ×46, no internal `jal` | **0x801EF468** — ONBOARDED `md_SC03_054`, byte-identical `06bd73df…` |
|
||
| SC03/56 (`FILE_056.dir/0.1`) | 3,680 | 0x43 | 0x4 | the `jal` vote's 0x80178C8C was a COINCIDENCE (see below): both "internal" targets are function starts in **141 overlays** (shared engine code) and 0x80178C8C is nobody's DESTPTR; its 15/17 pointers cluster inside at ov_SC03_002's DESTPTR, and one outward call (0x8018151C) hits a function only 3 overlays have — ov_SC03_002 among them | **0x801CBB50** (ov_SC03_002's DESTPTR) — ONBOARDED `md_SC03_056`, byte-identical `bc768a6b…` (the two runners-up 0x801CBD90 / 0x801CC6E0 were never needed) |
|
||
|
||
**Two instrument findings from the probes (2026-09-05, T2b):**
|
||
1. **The all-`INCLUDE_ASM` first build is a NULL oracle for FINE base errors (R34).** Negative controls on MAIN/7: at
|
||
base+0x1000 the link FAILS (two internal `jal` targets leave the window → `undefined reference to
|
||
func_800CEEA4/func_800CF3F4`), but at base+8 the build is **byte-identical** (`.run/P32/t2b/control_{full,fine}.log`).
|
||
splat names every reference by its absolute address, so a nearby wrong base re-assembles to the same bytes. The §S44
|
||
"byte-identical on its FIRST build at the derived address" corroboration therefore rules out GROSS errors only; the
|
||
fine base rests on the static alignment (self-calls / fn-ptr-table entries exactly on the module's own function
|
||
starts) and is byte-PROVEN only by the first C bank that calls an internal sibling.
|
||
2. **A `jal`-vote base whose "internal" targets are overlay function starts is explained by OUTWARD calls.** SC03/56's
|
||
two vote targets (0x80178CBC, 0x80178D18) are function starts in 141 overlays — the shared engine — so the 4/4
|
||
alignment at 0x80178C8C was two shared functions happening to be spaced like two of its five starts. The tool now
|
||
downgrades such a candidate to OUTWARD-EXPLAINED unless it is a known slot / DESTPTR (R39 controls still 7/7).
|
||
|
||
Both OPDEMO modules (MAIN/7, MAIN/9) carry `C:\TIMPACK\OPDEMO0.PAT` / `OPDEMO1.PAT` at header offsets 0x4/0x1C
|
||
(§S45 p5 addendum) — the header format here is [id][two 24-byte path strings][code], hence TEXT_LO 0x34/0x3C.
|