- RE'd the full file-loader/overlay subsystem in Ghidra via MCP: CdReadRequest -> CdReadStateMachine (hand-rolled CdControl SeekL/ReadN reader, NOT PsyQ CdRead); LIST.CD RAM cache cdFileLocTable + ResourceGetCdLoc/resourceIdMap lookup; LZSS staging buffer 0x80079A70; loadDestPtrTable; ~50 symbols named (T1-T5,T8) - T6b MILESTONE: resident blob @0x800CEDF8 + location overlay @0x80128158 proven byte-identical (sha1-equal contiguous prefixes) vs live PCSX-Redux RAM; blobs imported into Ghidra (ImportOverlay.java); overlay-map table -> memory-map.md §4.3 - PCSX-Redux runtime oracle: Windows-native build, WSL reads RAM via the web API at 172.17.208.1:8081; ledger #4 resolved - live-verified player/time state: gold(/10) HP BP day hour tiredness boss-HP; save-header template (Q#5) + debug-menu dispatch (Q#9: DEBUG.BIN path is dead) - overlay map: 13 area-states across SC01/02/03, 2 duplicate pairs, location x chapter keying, free-roam vs menu-hub granularity - resolves Open Q#1/#3/#4/#5/#6/#8/#9/#12; corrects SLUS-90029 (FF8 demo, not BFM) - 2 US prototypes acquired + CRC-verified (Aug-31 1369DE07, Sep-8 5C24728E) - rules R9 (verify persistence), R10 (>=3-datapoint live verification), R11 (runtime oracle Windows-native, bridged to WSL) - bumps project version 1.2.0 -> 1.3.0
14 KiB
PhaseEnd — Phase 3: File-loader & overlay-map RE (+ PCSX-Redux harness)
Date: 2026-06-14 · Project Version: 1.3.0 · Phase Status: Complete
Build Log
Files created/changed and complete — do not recreate:
tools/ghidra_mcp_start.sh/ghidra_mcp_stop.sh/ghidra_mcp_verify.sh— oracle lifecycle: start detached headless MCP server / clean save+stop (the ONLY way headless MCP writes persist) / read-only R9 verify. Used every RE session.tools/ghidra_scripts/BfmMcpServer.java— headless MCP server + commit-on-clean-shutdown; run bystart.sh.tools/ghidra_scripts/GetSymbolAt.java— read-only symbol probe consumed byverify.sh(R9).tools/ghidra_scripts/ImportOverlay.java— headless: loads the proven resident blob (→0x800CEDF8) + one location overlay (→0x80128158) into the program (RAMblock, convertToInitialized+setBytes); idempotent. (T7)tools/ghidra_scripts/VerifyOverlay.java— read-only R9 check that the imported blob bytes persisted. (T7)tools/ghidra_scripts/ExportSymbols.java+config/symbols.us.txt— headless export of all 967 USER_DEFINED symbols (PsyQ-signature names + manual RE) to committed text — the durable, diffable, re-importable record of the RE work (the.repis gitignored); Phase-5 splat input. (phase-close)dumps/INDEX.md+dumps/*.bin— the 28-dump live RAM corpus, committed (H1 relaxed while private; static, not regenerable, off-machine backup); INDEX maps each dump → state. (phase-close).gitignore— added/tools/pcsx-redux/(AppImage),/.run/(runtime scratch); dumps committed..claude/settings.local.json— SessionStart hook that auto-starts the oracle. (T0)/tmp→ project-local migration:ghidra_mcp_start.sh/stop.sh+BfmMcpServer.javanow use~/bfm-decomp/.run/{ghidra-mcp.log,mcp-stop.req}(no/tmp); full start/stop/save/R9 cycle re-certified. (R12)docs/memory-map.md— the phase's main written deliverable. Added: the T1 "imported into Ghidra" block; the T2 (CdRead chain) / T3 (LIST.CD cache+lookup) / T4 (LZSS staging) / T5 (US load addresses) / T6b (MILESTONE proof) blocks; §4.3 overlay-map table (13 area-states + the loader model); §3.4 player-state promoted toverified(gold/HP/BP/day/hour/tiredness-raw) + refutations; §2.3saveHeaderTemplate; §5 debug-menu dispatch; Open Questions #1,#3,#4,#5,#6,#8,#9,#12 resolved; SLUS-90029 corrected (FF8 demo, not BFM).docs/SETUP.md— §3 PCSX-Redux as-built (Windows-native + WSL web-API bridge); ledger #4 RESOLVED (web server :8081, dump URL).phase-ends/CURRENT_PHASE.md— absorbed into this file and deleted (P8).phase-ends/PhaseEnd_Phase3.md— this file.
Local artifacts (gitignored / outside repo — present on this machine, do NOT recreate; a fresh clone redoes them):
~/bfm-decomp/ghidra/bfm.{gpr,rep}— the saved Ghidra project.SLUS_007.26now carries the T1 seeds plus ~50 Phase-3 symbols: loader cluster (LoaderInitFileTable,CdReadRequest,CdReadStateMachine,CdReadSectorReadyCB,CdQueueBusy,cdReq_*control block), resource loader (ResourceLoadStateMachine,ResourceGetCdLoc,resourceIdMap,resLoad_*),cdFileLocTable,listCdBuffer,lzss_sectorStagingBuf,StreamLoadStateMachine,loadDestPtrTable,GameModeDispatch+gameModeHandlerTable,DebugMenuHandler,SaveLoadRoutine,saveHeaderTemplate,debugBinPresent, player-state fields — plus 2 imported memory blocks (resident blob @0x800CEDF8, tut_forest overlay @0x80128158). R9-verified; DB saved (clean shutdown).tools/pcsx-redux/PCSX-Redux-HEAD-x86_64.AppImage— Linux AppImage (downloaded; NOT used — WSLg display + WSL controller failed; the Windows-native build is the actual oracle). Gitignored.disks/— 2 CRC-verified US prototypes (Aug-31-1998 "Musashi Demo" 1369DE07, Sep-8-1998 "Musashi Master" 5C24728E) + a "Squaresoft on PlayStation Vol.2 (FF8 Demo)" disc. Gitignored.
Tools/packages installed:
- PCSX-Redux (runtime oracle). The Linux AppImage was fetched to
tools/pcsx-redux/but is unused; the Windows-native build (distrib.app/.../dev-win-x64) is what runs — web server on :8081, reached from WSL at 172.17.208.1:8081 (NAT gateway). Ships OpenBIOS (no BIOS dump needed). - usbipd-win (Windows, installed by Drew) — attempted DualSense→WSL passthrough; abandoned (WSL2 kernel
has no
CONFIG_HID_PLAYSTATION/joydev) in favour of the Windows-native emulator.
Verification results (literal):
- MILESTONE byte-proof (live PCSX-Redux RAM): resident
MAIN.CD/FILE_010/1.1@0x800CEDF8 = 18,788-byte contiguous exact prefix, sha1dbc55765593d3c157965a22af5808990714dbae9(RAM slice == extracted, EQUAL); overlaySC01/FILE_077/0.4.dec@0x80128158 = 389,400-byte contiguous exact prefix, sha18e40afab29200f00752573bfc28f9a8676000f85EQUAL. EXE resident: RAM[0x10000:] == extractedSLUS_007.26[0x800:] (8192/8192). - R9 persistence verifies — all PASS: T1 (
CdPathTable, db.5); checkpoint#1 (StreamLoadStateMachine); #2 (loadDestPtrTable); T7 (VerifyOverlay— both blobs init=true, bytes correct); #3 (DebugMenuHandler). - PCSX-Redux web API
GET :8081/api/v1/cpu/ram/raw→ exactly 2,097,152 B. - Overlay map: 13 area-states identified by RAM-vs-extracted byte-match across SC01/SC02/SC03;
2 duplicate-overlay pairs (
SC02/000≡003,SC01/005≡006). - Player/time state live-verified (multi-datapoint / controlled diff):
gold0x80078E8C (÷10),hp_max/cur0x80078EB2/EB4,bp_max/cur0x80078EB6/EB8,dayCounter0x80078EAC,hour0x80078EB1,tiredness_raw0x80078EA4,bossHp_SteamKnight0x801E4398. Four 2-datapoint false positives caught and refuted (clock 0x8007CE82, two tiredness guesses, a dow byte) → see R10. git status: only docs + tooling/scripts tracked; all ROM-derived (ghidra/,disks/, extracted bulk, the PCSX AppImage) gitignored.
Milestone achieved: docs/memory-map.md §4.3 holds a loader/overlay table where the resident blob
(0x800CEDF8) and a location overlay (0x80128158) have US load addresses proven byte-identical (sha1-equal
contiguous prefixes) against a live PCSX-Redux RAM dump — the project's central "what loads where"
unknown, resolved.
Next: Phase 4 = PROTOTYPE RESEARCH SPIKE (Drew's directive — supersedes the roadmap's "build toolchain" as the next phase; toolchain follows). Start with a symbol/debug-info hunt in the Aug-31 "Musashi Demo". See "Notes for Future Phases" below for the full brief.
Deviations
| Item | Plan | Actual | Reason |
|---|---|---|---|
| Runtime-oracle location | PCSX-Redux under WSLg (all-in-WSL) | Windows-native PCSX-Redux; WSL fetches RAM via 172.17.208.1:8081 | WSLg wouldn't paint the window; WSL2 kernel can't do the DualSense. Hybrid bridged by the web API (→ R11) |
| Overlay-mapping scope | ≥1 overlay (milestone) | 13 area-states across SC01/02/03 + full loader model | Drew-driven live tour; high value for Gen2 overlay configs |
| Player-state RE | not in plan | gold/HP/BP/day/hour/tiredness/boss-HP verified; save-header found | Opportunistic live triangulation (Drew fed ground truth) |
| Prototypes | parking lot | 2 US protos acquired + CRC-verified; FF8 demo disc | Drew acquired them; seed the Phase-4 spike |
| Next phase | Phase 4 = build toolchain (roadmap) | Phase 4 = prototype research spike | Drew's directive — assess proto value before matching |
| T6b proof form | "sha1 of dump slice == sha1 of extracted blob" (whole blob) | sha1-equal large contiguous prefix (runtime data diverges past the code) | Runtime writes mutate the blob's data after load; the code prefix is byte-identical at the exact vaddr — conclusive for the load address (P9) |
.DA/proto context |
— | SLUS-90029 corrected to FF8 demo (not a BFM demo); no playable US BFM demo exists | Drew correction + web research |
Commit Message
feat(phase-3): file-loader & overlay-map RE + PCSX-Redux runtime oracle
- RE'd the full file-loader/overlay subsystem in Ghidra via MCP: CdReadRequest ->
CdReadStateMachine (hand-rolled CdControl SeekL/ReadN reader, NOT PsyQ CdRead);
LIST.CD RAM cache cdFileLocTable + ResourceGetCdLoc/resourceIdMap lookup; LZSS
staging buffer 0x80079A70; loadDestPtrTable; ~50 symbols named (T1-T5,T8)
- T6b MILESTONE: resident blob @0x800CEDF8 + location overlay @0x80128158 proven
byte-identical (sha1-equal contiguous prefixes) vs live PCSX-Redux RAM; blobs
imported into Ghidra (ImportOverlay.java); overlay-map table -> memory-map.md §4.3
- PCSX-Redux runtime oracle: Windows-native build, WSL reads RAM via the web API
at 172.17.208.1:8081; ledger #4 resolved
- live-verified player/time state: gold(/10) HP BP day hour tiredness boss-HP;
save-header template (Q#5) + debug-menu dispatch (Q#9: DEBUG.BIN path is dead)
- overlay map: 13 area-states across SC01/02/03, 2 duplicate pairs, location x
chapter keying, free-roam vs menu-hub granularity
- resolves Open Q#1/#3/#4/#5/#6/#8/#9/#12; corrects SLUS-90029 (FF8 demo, not BFM)
- 2 US prototypes acquired + CRC-verified (Aug-31 1369DE07, Sep-8 5C24728E)
- rules R9 (verify persistence), R10 (>=3-datapoint live verification),
R11 (runtime oracle Windows-native, bridged to WSL)
- bumps project version 1.2.0 -> 1.3.0
Rules Added This Phase
| Rule | Reason |
|---|---|
R9 — Verify Ghidra persistence after every save-shutdown. Read-only re-open (tools/ghidra_mcp_verify.sh <addr> <name>) and confirm a recent edit survived — never trust "Save succeeded" alone. Mandatory at phase end; recommended at each checkpoint. |
Headless MCP writes persist only on clean shutdown; the live server holds an open transaction. Catches a lost save before it strands work. (Candidate since this phase; formalized.) |
R10 — Multi-datapoint live-RAM verification. A RAM-address finding from PCSX-Redux is verified only with ≥3 consistent datapoints OR a controlled before/after diff (e.g. paused snapshots, sleep reset). Two-datapoint matches stay candidate (coincidence-prone). |
Four 2-datapoint false positives this phase (clock 0x8007CE82, two tiredness guesses, a day-of-week byte) were each disproven by a 3rd datapoint. Aligns G1/P9 (bytes are truth). |
R11 — Runtime oracle is Windows-native, bridged to WSL via the web API (http://172.17.208.1:8081). Build, Ghidra, splat, asm-differ, analysis all stay in WSL (R2/H2 unchanged for those); only the emulator GUI + controller live on Windows — the explicit all-in-WSL exception. |
WSLg can't render PCSX-Redux and the WSL2 kernel can't expose a DualSense; the web API is a clean, low-friction WSL↔Windows boundary that preserves the rest of the architecture. |
R12 — No /tmp; project-local data only. All project data — including transient runtime scratch (logs, sentinels, temp outputs) — lives under ~/bfm-decomp (runtime scratch → .run/, gitignored). Never /tmp. |
Drew directive 2026-06-14: /tmp is volatile (28 non-regenerable RAM dumps nearly lost on a reboot). Keeps the clone self-contained and durable. |
PhaseEnd Changelog
v1.2.0 → v1.3.0 — Phase 3 complete. The file-loader/overlay subsystem is fully reverse-engineered in
Ghidra and the central "what loads where" unknown is byte-proven against live hardware behavior: the
resident blob (0x800CEDF8) and location overlays (0x80128158) load position-locked and verbatim; the loader
is a hand-rolled CdControl async reader (not PsyQ CdRead) driven by cdFileLocTable (the parsed LIST.CD
TOC) + a two-level resource-ID map. A live overlay map of 13 area-states across SC01/SC02/SC03 was captured
(duplicate overlays, location×chapter keying, free-roam-vs-menu-hub granularity). PCSX-Redux stood up as the
runtime oracle (Windows-native, WSL web-API bridge). Bonus live-verified the player/time state block
(gold÷10, HP/BP, day, hour, tiredness) and the save-header (Q#5) + debug-menu dispatch (Q#9: DEBUG.BIN dead).
Open Questions #1/#3/#4/#5/#6/#8/#9/#12 resolved. Three rules added (R9 persistence, R10 multi-datapoint
verification, R11 Windows-native oracle). Two US prototypes acquired + CRC-verified, seeding the Phase-4 spike.
Notes for Future Phases — Phase 4 RESEARCH SPIKE brief (Drew's directive)
Analyze the two CRC-verified US prototypes (disks/) before the build/matching phases:
- Debug info? Hunt the Aug-31 "Musashi Demo" + Sep-8 "Musashi Master" for
.SYM/symbol tables, debug strings, assertion/source-path text, less-stripped data. (Hidden Palace said "no symbols" — UNVERIFIED.) - Is the demo easier to decomp, and does it help the full game? Assess optimization level vs retail. Caveat: demo ≠ retail bytes → demo matches don't transfer to SLUS-00726; value is labels + a readable reference + shared tables, not matched bytes.
- Free-label lead (already identified): the Aug-31 demo's title scene/area-select very likely shares
code/tables with the retail debug menu mapped in T8 —
GameModeDispatch(0x80010B40),gameModeHandlerTable(0x800629F4),DebugMenuHandler(0x80011144). Binary-diff to lift labels in. - Deliverable: a go/no-go on whether the protos accelerate Gen1 matching, plus any labels imported.
This is a roadmap insertion before Phase 4 (build toolchain), which then follows. Also outstanding for
later: the save-data block format (trace
SaveLoadRoutine0x8002B154, Q#5); the exact tiredness-% / minute / day-vs-dow encoding; importing more overlays as Ghidra overlay address spaces (Gen2).
🛑 Stop Here
PhaseEnd written; CURRENT_PHASE.md absorbed. Drew commits AND pushes this PhaseEnd (R6/R8). Start a
fresh session for the next phase. Keep this file forever.