Files
BFM-decomp/docs/SETUP.md
T
2026-06-10 22:17:46 -06:00

37 KiB
Raw Blame History

SETUP.md — Environment Setup & Daily Operations Reference

This file is the EVOLVABLE reference layer. Unlike PROJECT_CONTEXT.md (permanent, never edited), this document holds volatile facts — pinned versions, URLs, commands, ports — and may be updated freely as tools move. Note each change in the active phase log (CURRENT_PHASE.md). Items marked TBD / UNVERIFIED / JP-only — re-derive for US are honest gaps: confirm before relying on them, then update this file.

Last full revision: 2026-06-10 (initial authoring, pre-Phase-1 — nothing below is installed yet except the repo itself).

Version pin summary

Component Pinned version Side Phase
JDK 21 (Temurin) Windows 1
Ghidra 12.1 PUBLIC exactly (ghidra_12.1_PUBLIC_20260513.zip) — NOT 12.1.2 Windows 1
GhidrAssistMCP v2.8.0 (ghidra_12.1_PUBLIC_20260530_GhidrAssistMCP.zip) Windows 1
ghidra_psx_ldr release 2026.06.04 (ghidra_12.1_PUBLIC_20260604_ghidra_psx_ldr.zip) Windows 1
PCSX-Redux dev-win-x64 nightly (no version pin; record build on install) Windows 3
WSL2 distro Ubuntu-24.04 (Python 3.12 ships with it) WSL2 4
splat pip splat64[mips] >=0.41.0,<1.0.0; freeze exact version once Phase 5 is green WSL2 4
Vintage compiler decompals/old-gcc release 0.17: gcc-2.7.2-psx + gcc-2.7.2-cdk WSL2 4
maspsx git submodule, mkst/maspsx (decomp.me pins commit 874855c53f65f8fa57447e1da6bde6236dbef9d5 — reasonable default pin) WSL2 4
asm-differ / m2c / decomp-permuter git submodules (URLs in §4.6) WSL2 4
binutils (mipsel) apt binutils-mipsel-linux-gnu — >=2.38 regression check required, 2.35 known-good per open-ribbon WSL2 4

§1 The hybrid environment (two machines in one box)

┌─ Windows 11 Pro 25H2 (native) ────────────────────┐   ┌─ WSL2 Ubuntu 24.04 (ext4) ─────────────────────┐
│                                                   │   │                                                │
│  Ghidra 12.1 PUBLIC  (JDK 21 Temurin)             │   │  ~/bfm-decomp          ← SECOND clone          │
│   ├─ ghidra_psx_ldr 2026.06.04 (PSX loader,       │   │   ├─ .venv/            splat64[mips] etc.      │
│   │   PsyQ signatures, .gdt type archives)        │   │   ├─ tools/maspsx, tools/asm-differ,           │
│   └─ GhidrAssistMCP v2.8.0 ── SSE 127.0.0.1:8080 ─┼─┐ │   │  tools/m2c, tools/decomp-permuter (subm.)  │
│                                                   │ │ │   ├─ bin/gcc-2.7.2-psx, bin/gcc-2.7.2-cdk      │
│  PCSX-Redux (runtime oracle: debugger, Lua,       │ │ │   │  (old-gcc 0.17, Linux x86-64 binaries)     │
│   web API RAM dumps, GDB server :3333)            │ │ │   ├─ disks/             BIN/CUE dump,          │
│                                                   │ │ │   │                     copied in ONCE         │
│  Claude Code ← .mcp.json ─────────────────────────┼─┘ │   └─ asm/ build/ expected/  (generated,        │
│   │                                               │   │                             never committed)   │
│   └─ runs builds remotely via wsl.exe ────────────┼──►│  apt: binutils/gcc-mipsel-linux-gnu, make,     │
│                                                   │   │       ninja, python3.12, ...                   │
│  Z:\Storage\git\BFM-decomp     ← FIRST clone      │   │                                                │
│   (Ghidra project .gpr/.rep, docs, configs,       │   │                                                │
│    notes, Claude Code cwd — NEVER builds here)    │   │                                                │
└───────────────────────┬───────────────────────────┘   └───────────────────────┬────────────────────────┘
                        │                                                       │
                        └────────────── git remote (the ONLY sync path) ────────┘

Two full clones of this repository exist, one per OS, synchronized only by pushing/pulling through the git remote — never by editing one working tree from both sides. Rationale:

  • 9P filesystem penalty. Cross-OS file access (/mnt/z from Linux, \\wsl.localhost\... from Windows) goes through the Plan 9 protocol and is the slowest path WSL2 offers — Microsoft's own numbers put ext4 at 2–20× faster, and community benchmarks measured builds up to 375% faster after moving off /mnt. Every make, git status, and splat extract pays the tax if the build tree sits on NTFS.
  • inotify is dead across the boundary. Linux file-watchers get no events for Windows-side changes on /mnt/* (microsoft/WSL #4739, #5424), and Windows watchers get no events on \\wsl.localhost\ paths (#4581). asm-differ watch mode silently never fires unless source and build outputs live on ext4 and are modified from inside Linux.
  • Claude Code ghost-file bug. anthropics/claude-code #28015: Write/Edit on WSL2 drvfs mounts can hit a statx/9P cache-poisoning kernel bug producing "ghost files" (ENOENT for files that exist). Claude Code must never edit across the 9P boundary.

One-shot file copies across the boundary (e.g. the disc dump into WSL disks/) are fine — it is sustained/random IO and watchers that break, not correctness. Ghidra project files (.gpr/.rep) stay on local NTFS: Ghidra's docs recommend local-drive storage, and its ~lock files are unreliable over network redirectors like \\wsl.localhost.

Git hygiene for the two-OS split: the committed .gitattributes (* text=auto eol=lf + binary exclusions) is the CRLF firewall. On the Windows side set git config core.autocrlf false (or input); in the WSL clone core.filemode true is fine (ext4).


§2 Windows RE stack install (Phase 1)

Order matters: JDK → Ghidra → both extensions → import → MCP wiring.

§2.1 JDK 21 (Temurin)

winget install EclipseAdoptium.Temurin.21.JDK

Required by Ghidra 12.x. Verify: java -version reports 21.x.

§2.2 Ghidra 12.1 PUBLIC — pin EXACTLY

Download ghidra_12.1_PUBLIC_20260513.zip from https://github.com/NationalSecurityAgency/ghidra/releases/tag/Ghidra_12.1_build — plain zip, no installer; extract and run ghidraRun.bat.

⚠️ Extension version-lock warning. Ghidra extensions are point-version-locked via extension.properties. Both extension zips below are built against 12.1_PUBLIC; installing them on 12.1.2 (current latest, 2026-06-05) may trip the version check (red "incompatible" flag — same failure mode as LaurieWired issue #83 on 11.4.1). Whether the 12.1 zips happen to load on 12.1.2 is UNVERIFIED — pin 12.1, and only move to a point release when both extensions ship matching assets or you rebuild them from source (gradle installExtension with GHIDRA_INSTALL_DIR set).

§2.3 GhidrAssistMCP v2.8.0

  • Asset for our Ghidra: ghidra_12.1_PUBLIC_20260530_GhidrAssistMCP.zip from https://github.com/symgraph/GhidrAssistMCP/releases/tag/2.8.0 (repo moved from jtang613/GhidrAssistMCP to symgraph/GhidrAssistMCP; GitHub redirects. The release carries TWO assets — take the 12.1 one, not 12.0.)
  • Install: Ghidra → File → Install Extensions… → + → select zip → restart Ghidra.
  • Enable plugin: File → Configure → Configure Plugins → check GhidrAssistMCP.
  • Control panel: Window → GhidrAssistMCP → set Host = localhost, Port = 8080. Server exposes /sse (SSE), /message, and /mcp (streamable HTTP). No Python bridge process — Claude Code connects directly.
  • This is the server the psxrecomp proof-of-concept actually ran with Claude Code (their port was 7777, a user setting; we standardize on 8080 to match the committed .mcp.json).

Fallbacks (documented, not installed):

Server Status Why fallback only
bethington/ghidra-mcp v5.13.x active, claims Ghidra 12.1, 249 tools Unaudited, single-maintainer with day-apart releases, needs a Python bridge (plugin HTTP :8089), 249 tools swamp Claude's tool context. Consider only if a missing niche tool (mass create_enum, batch_decompile) becomes a bottleneck.
clearbluejar/pyghidra-mcp active, pip/uvx, headless multi-binary No struct-creation or apply-type-at-address tools — dead end as primary server for matching-decomp data work; fine for scripted headless batch passes.
LaurieWired/GhidraMCP 1.4 unmaintained — DO NOT INSTALL Caps at Ghidra 11.3.2 (issues #83/#131 open, no fix since 2025-06-23); ghidra_psx_ldr dropped 11.x. Older write-ups (including the 1379.tech psxrecomp blog post) credit it incorrectly.
ismaelcaraballo-afk/GhidraMCP-12 stale one-off recompile 12.0.1 only, no struct tools, not viable.

§2.4 ghidra_psx_ldr release 2026.06.04

  • Asset: ghidra_12.1_PUBLIC_20260604_ghidra_psx_ldr.zip (~9.15 MB, PsyQ signatures bundled — no separate OBJ download needed) from https://github.com/lab313ru/ghidra_psx_ldr/releases/tag/2026.06.04
  • Install via the same File → Install Extensions… path → restart.
  • Coexists with GhidrAssistMCP without conflicts (PSX loader/analyzer/SLEIGH vs HTTP-server plugin).

§2.5 PSX-EXE import flow (SLUS_007.26)

  1. Extract SLUS_007.26 from the disc image (LBA 24, 0x65000 bytes, track 1 is MODE2/2352) and import into Ghidra. The loader auto-selects "PSX Executables Loader", language PSX:LE:32:default, and builds the full PS1 memory map: RAM around the image at 0x80000000, scratchpad 0x1F800000, all IO/DMA/timer/CD/GPU/SPU register blocks, and a synthetic GTEMAC segment at 0x20000000 automatically — do NOT run the CreateGteMacSegment script (that is only for migrating legacy non-PSX projects).
  2. Run auto-analysis with the "PsyQ Signatures" analyzer enabled (auto-enabled for PSX-language programs). Analyzer options: "Only first match", "Minimal signature entropy" (default 3.0 — can skip tiny low-entropy library stubs), "PsyQ Version if not found" (manual override).
  3. Read the detected PsyQ version: Edit → Options for Program → Program Information → "PsyQ Version". Expected: 4.0 (our own EXE scan found 12 genuine Ps stamps: 9× 4.0, one 4.0.1x on libnum 16, one 4.2 on libnum 0, one 4.2.1x on libnum 12 — i.e. PsyQ 4.0 libs + 4.2 library updates; see §5.1). If detection errors with 'psyq/xx' cannot be found, append .0 to the version field. Record the detected value in the phase log. (Note: a raw track-1 scan during research reported slightly different per-libnum details — raw 2352-byte-sector scans produce false positives; the extracted-EXE scan is the ground truth, and DetectPsyQ at import is the final word.)
  4. One-time manual .gdt attach (GUI only — no MCP tool opens archives): in the CodeBrowser Data Type Manager, attach the bundled PsyQ type archive for the detected version — psyq400.gdt (psyq420.gdt also exists). This gives PsyQ struct/typedef types for retyping work.
  5. Early MCP type-resolution test (run before any bulk typing): via MCP, run the types tool with action=set applying a PsyQ type (e.g. apply a known PsyQ struct at some address) and confirm it resolves. UNVERIFIED whether the types/struct tools can reference types living in the attached archive or only types already copied into the program's own data type manager — this 5-minute test decides the typing workflow. Record the answer here when known.

§2.6 .mcp.json wiring + verification

The committed repo-root .mcp.json (already present):

{
  "mcpServers": {
    "ghidra": {
      "type": "sse",
      "url": "http://127.0.0.1:8080/sse"
    }
  }
}

Single ghidra entry only — do NOT copy psxrecomp's duplicated ghidra + ghidra_psx pair (same URL twice = every tool duplicated in context).

Verify in Claude Code with /mcp: ~38 mcp__ghidra__* tools should appear. These are the v2.8.0 names — e.g. get_binary_info, get_code (format: disassembly|decompiler), disassemble_at, analyze_function, xrefs, get_functions (paginated), struct (actions: create/modify/merge/set_field/name_gap/auto_create/rename_field/field_xrefs), types (list/get_info/set/delete), variables (list/rename/set_type/set_prototype), rename_symbol, batch_rename, create_data_var, create_function, search_bytes, patch_bytes, assemble_code, export_program. NOT the pre-2.4.0 names (get_function_info, list_data) that appear in psxrecomp's PLAN.md and older write-ups — those were renamed in v2.4.0 (upstream commit aa3ffc7d, 2026-03-14).

Operational cautions:

  • Tools operate on the program currently open in CodeBrowser and fail (sometimes silently) if none is open.
  • Do not run Ghidra auto-analysis concurrently with MCP-driven writes (renames/comments) — both mutate the program DB and can stall each other.

§2.7 Headless mode (batch passes)

For unattended batch passes, GhidrAssistMCP runs headless (supported since v2.3.0):

<ghidra>\support\analyzeHeadless.bat <projects-dir> McpHeadless ^
  -import <binary> ^
  -scriptPath "<extension-dir>/ghidra_scripts" ^
  -preScript GAMCPStartServerScript.java "host=127.0.0.1" "port=8080" "wait=true"
  • wait=true is mandatory — without it the analyzeHeadless process exits right after the prescript instead of serving MCP clients.
  • On subsequent runs use -process SLUS_007.26 (not -import) to reuse the existing project.

§3 PCSX-Redux (Windows) — the runtime oracle

Role: Ghidra is the static oracle; PCSX-Redux is the runtime oracle. It proves overlay load addresses by live RAM comparison, validates decompressed blobs byte-for-byte against what the game actually loads, and drives the debug-menu/loader RE.

  • Install: native Windows x64 nightly from https://distrib.app/pub/org/pcsx-redux/project/dev-win-x64 (no stable pin exists — record the build date in the phase log when installed). Docs: https://pcsx-redux.consoledev.net. Ships OpenBIOS (boots without a retail BIOS dump).
  • RAM dumps (primary use): enable the built-in web server, then GET http://localhost:<redux-port>/api/v1/cpu/ram/raw returns the full 2 MB RAM image — diff slices of it against our extractor's output to prove byte-identity. (<redux-port> = whatever port Redux's web server is configured to after moving it off 8080 — substitute the real value here at Phase 3 install; ledger #4.)

    ⚠️ Port collision (derived, plan around it): PCSX-Redux's web server documents localhost:8080 — the same port as GhidrAssistMCP. Keep GhidrAssistMCP on 8080 (matches the committed .mcp.json) and move the Redux web server to another port (e.g. 8081) when enabling it; the exact Redux config field is TBD — confirm in its settings UI on install and update this line + the dump URL.

  • Lua scripting: LuaJIT 2.1.0-beta3 (Lua 5.2 compat), FFI direct memory access; console + editor under the Debug menu. Gotcha: the editor autosaves to pcsx.lua and reloads it at startup — a crashing script wedges the emulator across restarts (delete pcsx.lua to recover); long-running scripts must yield via coroutines (~15 ms/frame budget).
  • Debugger: fully featured MIPS debugger, VRAM/SPU viewers. GDB server on port 3333 (Configuration → Emulation → Enable GDB server); for Ghidra-attached debugging launch Redux with -interpreter -debugger -gdb (dynarec breaks debugging) and connect gdb-multiarch -i mi2 → target remote localhost:3333.
  • PSX RAM at 0x80000000 mirrors physical 0x0 (0x800425D0 ≡ 0x000425D0) — relevant when reading dump offsets.

§4 WSL2 build environment (Phase 4)

WSL is not installed yet (verified 2026-06-10). Everything below is the install recipe.

§4.1 Install WSL2 + Ubuntu 24.04

Elevated PowerShell:

wsl --install -d Ubuntu-24.04

Reboot if prompted. Then verify the exact distro name before hardcoding it anywhere:

wsl -l -v        # must show 'Ubuntu-24.04' (capital U, hyphenated), VERSION 2

Also confirm the default Linux username (wsl.exe -d Ubuntu-24.04 -- whoami) — the --cd /home/<user>/... commands in §6 depend on it. TBD: fill in the actual username here after install.

§4.2 Networking: .wslconfig mirrored mode + 8-second rule

Create %UserProfile%\.wslconfig:

[wsl2]
networkingMode=mirrored

Host is Windows 11 Pro 25H2 (build 26200) — mirrored mode is supported (needs 22H2+). Benefit: WSL-side scripts can reach Windows services (GhidrAssistMCP, Redux web API) at plain http://127.0.0.1:<port> — same URL both sides. (IPv6 ::1 is not supported.)

8-second rule: .wslconfig/wsl.conf changes take effect only after a full VM stop: wsl --shutdown, then wait ~8 seconds before relaunching. Forgetting this makes networking experiments look broken.

Smoke-test after install (with Ghidra running on Windows): curl http://127.0.0.1:8080/ from inside WSL. Mirrored mode can misbehave with some VPN/virtualization stacks — if it does, fall back to NAT:

NAT fallback recipe (default mode; localhost only forwards Windows→WSL, never WSL→Windows):

  1. Inside WSL, get the host IP via the ip route method — do NOT scrape /etc/resolv.conf (on Win11 22H2+ dnsTunneling pins it to 10.255.255.254, which is not the host):
    ip route show | grep -i default | awk '{ print $3}'
    
  2. Allow inbound through the Windows firewall (which otherwise blocks the WSL subnet):
    New-NetFirewallRule -DisplayName 'GhidraMCP from WSL' -Direction Inbound -Protocol TCP `
      -LocalPort 8080 -InterfaceAlias 'vEthernet (WSL (Hyper-V firewall))' -Action Allow
    
  3. Note: under NAT the server must be reachable on the vEthernet interface, not just loopback. GhidrAssistMCP's control panel has a Host field — whether Host=localhost binds loopback-only (blocking NAT-mode access from WSL) is UNVERIFIED; mirrored mode sidesteps the question entirely. (This only matters if a WSL-side script ever queries Ghidra directly — Claude Code's MCP path is Windows-local and never crosses the boundary.)

§4.3 Second clone on ext4

wsl.exe -d Ubuntu-24.04 --cd ~ -- bash -lc 'git clone <remote-url> bfm-decomp'

The build clone lives at ~/bfm-decomp (ext4). Builds, splat, asm-differ run only here. In this clone: git config core.filemode true. TBD: the canonical remote URL (GitHub private repo planned; not created as of this writing).

§4.4 Copy the disc dump into WSL

One-shot copy over 9P is fine:

mkdir -p ~/bfm-decomp/disks
cp '/mnt/z/Storage/git/BFM-decomp/Brave Fencer Musashi (USA)/'*.bin \
   '/mnt/z/Storage/git/BFM-decomp/Brave Fencer Musashi (USA)/'*.cue ~/bfm-decomp/disks/

disks/ is gitignored — no ROM-derived bytes ever reach the remote (rule H1).

§4.5 apt packages

Adapted from sotn-decomp's tools/requirements-debian.txt (dropped Saturn/PSP-only items binutils-sh-elf, xfonts-utils; Rust/Go deferred until a duplicate-detector or asset tool needs them):

sudo apt-get update && sudo apt-get install -y \
  bchunk binutils-mipsel-linux-gnu bsdmainutils clang-format coreutils curl \
  gcc-mipsel-linux-gnu git libelf-dev make ninja-build p7zip-full \
  python3-pip python3-venv unzip wget

⚠️ binutils regression check (mandatory before trusting builds): open-ribbon documents that binutils-mipsel-linux-gnu >= 2.38 generated broken binaries; 2.35 is the known-good reference. Ubuntu 24.04 ships newer binutils — VERIFY on 24.04: after Phase 5's first full build, if the SHA1 check mysteriously fails with correct-looking asm, suspect the assembler first (mipsel-linux-gnu-as --version), and pin/downgrade or build binutils 2.35 if confirmed. Record the verdict here.

§4.6 Python venv + splat + submodules

cd ~/bfm-decomp
python3 -m venv .venv                    # Python >= 3.12 required (24.04 ships 3.12; older = f-string SyntaxError mid-build)
.venv/bin/pip install -U 'splat64[mips]>=0.41.0,<1.0.0'

The PyPI package is splat64 (not splat), and the [mips] extra is required for PSX (pulls spimdisasm/rabbitizer). Always invoke as .venv/bin/splat or .venv/bin/python3 -m splat — splat: command not found means you're outside the venv. Once Phase 5 builds green, freeze the exact working version in a committed tools/requirements-python.txt.

Submodules (add under tools/):

Submodule URL Pin
tools/maspsx https://github.com/mkst/maspsx.git commit 874855c53f65f8fa57447e1da6bde6236dbef9d5 (decomp.me's pin — keeps local results comparable to decomp.me scratches)
tools/asm-differ https://github.com/simonlindholm/asm-differ.git pin current HEAD at adoption
tools/m2c https://github.com/matt-kempster/m2c.git pin current HEAD at adoption
tools/decomp-permuter https://github.com/simonlindholm/decomp-permuter sotn pins b44b0622269fb4bff29e79fbbad26b9f47beda79 — sane default

Pin all four (sotn precedent: blindly updating submodules breaks tooling). Note: sotn's asm-differ --overlay flag is sotn-fork-specific, not upstream — for BFM overlay diffing use upstream's -o object mode or port their fork later.

§4.7 Vintage compilers (old-gcc 0.17)

Linux x86-64 prebuilts from decompals/old-gcc, release 0.17:

mkdir -p ~/bfm-decomp/tools/bin && cd ~/bfm-decomp/tools/bin
wget https://github.com/decompals/old-gcc/releases/download/0.17/gcc-2.7.2-psx.tar.gz
wget https://github.com/decompals/old-gcc/releases/download/0.17/gcc-2.7.2-cdk.tar.gz
sha256sum gcc-2.7.2-*.tar.gz   # record hashes in a committed tools/bin/*.sha256 on first download,
                               # then verify with `sha256sum --check` on every fresh setup (sotn pattern)
tar xzf gcc-2.7.2-psx.tar.gz ; tar xzf gcc-2.7.2-cdk.tar.gz
  • gcc-2.7.2-psx = community GCC 2.7.2 PSX build (primary candidate).
  • gcc-2.7.2-cdk = cygnus-2.7.2-970404, the exact base of PsyQ 4.0/4.1's CC1PSX (added in old-gcc 0.14).
  • TBD: the sha256 values themselves — not captured in research; record at first download.
  • These are x86-64 Linux ELF binaries — they are why the build side must be Linux/WSL2 at all.

§4.8 Optional: native PsyQ 4.0/4.1 binaries for arbitration (Windows side)

For byte-exact arbitration when maspsx output is in doubt, the real PsyQ Win32 tools run natively on Windows:

  • https://github.com/mkst/esa/releases/download/psyq-binaries/psyq4.0.tar.gz
  • https://github.com/mkst/esa/releases/download/psyq-binaries/psyq4.1.tar.gz (contain CC1PSX.EXE, ASPSX.EXE, CCPSX.EXE, PSYLINK.EXE, PSYLIB.EXE; 1–2.3 MB each)
  • Their .OBJ output converts to ELF with psyq-obj-parser (part of pcsx-redux; prebuilt Linux binary: https://github.com/decompme/compilers/releases/download/compilers/psyq-obj-parser.tar.gz).

Keep these under tools/ on the Windows side (not committed); they are a tie-breaker, not the daily pipeline.

§4.9 make check-env (Phase 4 exit milestone)

Phase 4's observable milestone: a check-env make target that asserts every §4 component (venv + splat import, cc1 binaries executable, maspsx present, mipsel-as/ld/objcopy on PATH, python >= 3.12) and exits 0 when invoked from Windows Claude Code via wsl.exe (see §6.1).


§5 Compiler candidate ladder (Phase 6 fingerprinting)

§5.1 The evidence

Locally verified on the extracted US EXE (DetectPsyQ-style masked-pattern scan): 13 pattern hits, of which 12 are genuine Ps library stamps — 9× PsyQ 4.0 (libnums 2, 3, 4, 6, 7, 8, 9, 17, 24), 1× 4.0.1x (libnum 16), 1× 4.2 (libnum 0), 1× 4.2.1x (libnum 12); the remaining hit (ver 0x0000 at vaddr 0x8005CD20) is a code false positive. A raw-track scan during research reported 16 hits with extra spurious 4.0 stamps — the extracted-EXE scan is ground truth, and ghidra_psx_ldr's detection at import is the final word (ledger #12). Library copyright string (c) 1993-1997 Sony corroborates the era. Conclusion: BFM links PsyQ 4.0 libraries with 4.2 library updates ⇒ the GCC 2.7.2/SN32-era toolchain — NOT sotn's GCC 2.6.3 (the starting-point doc's claim is corrected). PsyQ 4.2 was a library-only refresh: no 4.2 toolchain disc survives (absent from redump/arthus sets), so 4.2 stamps still mean the 4.0/4.1 toolchain.

Caveat: Ps stamps date the linked libraries, not the compiler that built game code — Square mixed cc1 builds within one EXE (see §5.5). The final triple is pinned only by Phase-6 fingerprinting.

§5.2 SDK → GCC → ASPSX mapping (verified from the actual binaries in mkst/esa psyq-binaries tarballs)

PsyQ SDK CC1PSX identifies as ASPSX old-gcc 0.17 artifact maspsx flag
3.3 GNU C 2.6.0 [AL 1.1, MM 40] 2.21 gcc-2.6.0-psx --aspsx-version=2.21
3.5 GNU C 2.6.0 (same binary) 2.34 gcc-2.6.0-psx --aspsx-version=2.34
3.6 GNU C 2.7.2.SN.1 2.34 gcc-2.7.2 (vanilla-ish) --aspsx-version=2.34
4.0 GNU C 2.7.2.SN32.3.7.0002 2.56 gcc-2.7.2-psx / gcc-2.7.2-cdk --aspsx-version=2.56
4.1 cygnus-2.7.2-970404 SN32.3.7.0004 (SonyPSX) 2.67 gcc-2.7.2-cdk (exact base) --aspsx-version=2.67
4.2 library-only release — no toolchain exists n/a use 4.0/4.1 row use 4.0/4.1 row
4.3 GNU C 2.8.0 SN32 Build 4.0.0007 (community shorthand "2.8.1" is wrong — that's 4.4) 2.77 gcc-2.8.0-psx --aspsx-version=2.77
4.4 GNU C 2.8.1 SN32 BUILD 4.0.0010 2.79 gcc-2.8.1-psx --aspsx-version=2.79
4.5 egcs-2.91.66 (egcs-1.1.2) 2.81 gcc-2.91.66-psx --aspsx-version=2.81
4.6 GNU C 2.95.2 BUILD 4.0.0030 2.86 gcc-2.95.2-psx --aspsx-version=2.86

⚠️ --aspsx-version MUST always be passed explicitly. maspsx with no flag is not "latest behavior" — the dataclass defaults approximate ASPSX ~2.3x (expand_li=True, sltu_at=True, nop_mflo_mfhi=True). Behavior thresholds: expand_li off ≥2.50; sltu_at off ≥2.60; $gp symbol+offset ≥2.70; $gp for la ≥2.80. The observable 2.56 vs 2.67 tell: "$at for sltu < 0" present at 2.56, gone at 2.67, and %hi/%lo support arrives at 2.67 — decide on functions with unsigned comparisons / li/sltu idioms.

§5.3 -G0 vs -G8

Read gp_value from the SLUS_007.26 EXE header and check for $gp-relative loads in Ghidra before fixing the flag (TBD — not yet read). Precedent: FF7 used -G 0; Xenogears used -G8 for game code. maspsx forces -G0 to GNU as by default — non-zero $gp needs -G8 passed to maspsx and a look at --dont-force-G0.

§5.4 Candidate ladder (try in this order)

  1. gcc-2.7.2-psx cc1 + --aspsx-version=2.56, flags -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker (FF7 style; swap to -G8 if §5.3 says so).
  2. Same cc1 + --aspsx-version=2.67 (PsyQ 4.1 assembler era).
  3. gcc-2.7.2-cdk (cygnus-2.7.2-970404, the exact CC1PSX 4.0/4.1 base) × 2.56, then × 2.67.
  4. Real PsyQ 4.0/4.1 CC1PSX.EXE + ASPSX.EXE (native Windows, §4.8) + psyq-obj-parser — byte-exact arbitration when maspsx emulation is in question.
  5. Only if diffs show GCC 2.8-style codegen: gcc-2.8.0-psx + 2.77 (unlikely — the JP master predates PsyQ 4.3-era adoption).

Library-code preset (for PsyQ SDK functions linked into the EXE): --aspsx-version=2.56 --expand-div, -O3 -G0 — the Xenogears precedent, consistent with our 4.0 lib stamps.

§5.5 Per-module mixing warning

Xenogears (the closest comparable: Square US, Oct 1998) mixes three cc1 builds in one EXE (gcc-2.7.2-psx, gcc-2.6.0-psx, gcc-2.7.2-cdk) with different maspsx flags per module. Expect per-module variation in BFM: fingerprint several modules independently (3–5 medium leaf functions each), and design the build config for per-file compiler/flag overrides from day one (sotn does this with //! comments in line 1–2 of a .c file; Xenogears with gears.toml presets).

Reference repos for build-config patterns: https://github.com/ladysilverberg/xenogears-decomp (gears.toml presets), https://github.com/Drahsid/ffvii (Makefile).


§6 Daily command crib

§6.1 Invoking WSL builds from Windows (Claude Code's pattern)

wsl.exe -d Ubuntu-24.04 --cd /home/<user>/bfm-decomp -- bash -lc 'make -j$(nproc) build'
if ($LASTEXITCODE -ne 0) { <# build failed — or wsl.exe launcher error; check stderr text #> }
  • Single-quote rule: the Linux command must be in single quotes — in double quotes PowerShell expands $(nproc)/$vars itself before wsl.exe sees them.
  • $LASTEXITCODE carries the Linux exit status (verified behavior for Ubuntu distros). Caveat: wsl.exe's own launcher errors (bad distro name, WSL not running) are also nonzero — distinguish by stderr text.
  • UTF-16 note: wsl.exe emits UTF-16LE output (garbles when captured/piped). Set $env:WSL_UTF8=1 before capturing build logs.
  • --cd with a leading / is an absolute Linux path; always pass it explicitly so builds never accidentally run under /mnt.

§6.2 Canonical compile pipeline (one object)

mipsel-linux-gnu-cpp -lang-c -Iinclude -undef -Wall -fno-builtin \
    -Dmips -D__GNUC__=2 -D__OPTIMIZE__ -Dpsx -D_PSYQ -D_MIPSEL -D_LANGUAGE_C src/foo.c \
  | bin/gcc-2.7.2-psx/cc1 -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker \
  | python3 tools/maspsx/maspsx.py --aspsx-version=2.56 \
  | mipsel-linux-gnu-as -Iinclude -march=r3000 -mtune=r3000 -no-pad-sections -O1 -G0 -o build/foo.o

Modern cpp preprocesses → vintage cc1 compiles to asm → maspsx emulates ASPSX quirks → modern GNU as assembles. Then mipsel-linux-gnu-ld with the splat-generated linker script, objcopy -O binary to the PS-EXE, SHA1-compare. cc1 path/flags above reflect the §5.4 first candidate — the exact flag set is pinned only after Phase-6 fingerprinting (-funsigned-char, -fpeephole, etc. are decided then; the cpp defines list is the sotn convention, adjust as evidence dictates).

§6.3 Planned make targets (Phase 5 builds these; names fixed now)

Target Does
make extract splat split per config/splat.us.*.yaml → asm/, linker scripts
make build full pipeline → build/us/SLUS_007.26, auto-runs the SHA1 check
make check standalone SHA1 manifest verification (byte-for-byte = the only "OK")
make expected snapshot build/us → expected/build/us (asm-differ baseline)
make check-env toolchain preflight, exit 0 = environment sane (§4.9)
make clean mandatory after ANY config/ change, before re-extract

§6.4 asm-differ + baseline discipline

.venv/bin/python3 tools/asm-differ/diff.py -mwo3 <function>     # -m rebuild, -w watch, -o vs object, -3 three-way
  • Watch mode works only with source and build outputs on ext4, modified from inside Linux (§1).
  • Re-snapshot expected/ only on green: run make expected exclusively after a build whose check passed. A stale expected/ makes asm-differ silently diff against the wrong baseline — the classic "phantom regression/phantom match".
  • Diff score 0 = matched; anything else is not matched, no matter how close.

§6.5 decomp.me settings for BFM

  • Platform: PlayStation; Compiler: gcc2.7.2-psx (or gcc2.7.2-cdk) + maspsx family — these images bundle old-gcc cc1 + maspsx at the same pinned commit we use.
  • Starting flags: -O2 -G0 (adjust per §5).
  • Do NOT use the SOTN preset (Castlevania: Symphony of the Night / gcc 2.6.3-psx / psyq_263_221) — wrong era, guaranteed near-miss diffs.
  • decomp.me's API is Cloudflare-challenged (403 to scripts) — scratch searches/uploads needing the API must be done manually in a browser.

§7 Session-start ritual

Order is load-bearing — MCP tools fail (sometimes silently) without an open program.

  1. Start Ghidra (Windows): ghidraRun.bat.
  2. Open the BFM project → open SLUS_007.26 in CodeBrowser. Confirm the GhidrAssistMCP control panel (Window → GhidrAssistMCP) shows the server up on localhost:8080.
  3. Start/resume Claude Code in Z:\Storage\git\BFM-decomp. Run /mcp — the ghidra server must be connected with ~38 tools. Then make one cheap verification call (e.g. get_binary_info) before any heavier work. No MCP round-trip = no RE work this session (rule G2).
  4. Build preflight (when the session involves building, Phase 4+):
    wsl -l -v                                                            # distro present, VERSION 2
    wsl.exe -d Ubuntu-24.04 --cd /home/<user>/bfm-decomp -- bash -lc 'git status --short && make check-env'
    
    Plus git status on the Windows clone — both trees clean or intentionally dirty before starting.
  5. If PCSX-Redux work is planned: launch it, confirm the web server port (§3) and that a RAM dump fetch returns 2 MB.

Shutdown note: Ghidra writes the program DB on save — save (or deliberately discard) before closing, and never kill Ghidra mid-MCP-write.


§8 Model strategy per phase

Principle: the oracles (SHA1 check, asm-differ, RAM-dump byte-compares) make correctness model-independent — a weaker model can't fake a match. Model tier therefore buys fewer dead ends in ambiguous work, not safer results. Spend the strongest available model where ambiguity is highest; let the oracle-protected grind run on cheaper tiers. (Precedent: psxrecomp's post-mortem — model capability was load-bearing exactly once, on the most ambiguous subsystem.)

Phase Reasoning demand Recommended tier
1 — Installs, EXE import Mechanical Standard (Opus-class)
2 — Extraction pipeline Well-specified coding vs byte-exact oracle Standard
3 — File-loader & overlay-map RE Highest in project — raw MIPS reading, US address derivation, RAM-dump experiment design Strongest available
4 — WSL setup Mechanical; order-flexible (nothing in 1–3 depends on it — schedule it when the strong-model window is closed or limits are exhausted) Any
5 — splat config + build skeleton Iterative debugging, loud error signals Standard; strongest if available
6 — Compiler fingerprint + first matches Second highest — ASPSX 2.56-vs-2.67 idiom discrimination is subtle. The fingerprint analysis is pure RE and can be front-run before Phase 4/5 exist if a strong-model window is closing Strongest available
7 — Matching at scale Pattern grind against hard oracle Standard; smaller tiers acceptable for bulk iteration (cost = wasted iterations, never wrong matches)

Budget notes (Max 20x plan): long autonomous RE sessions are token-hungry; prefer single-agent flow with oracle checks for in-phase grind, reserving multi-agent fan-outs for verification moments. Window note (2026-06-10): Fable 5 access expires ~2026-06-22 — priority order for that window: Phases 1→2 fast, then maximum depth on Phase 3, then Phase 6 fingerprint analysis if time remains; defer Phase 4 past the window.


Known-unverified ledger (recheck and update in place)

# Item Status
1 GhidrAssistMCP/psx_ldr 12.1 zips on Ghidra 12.1.2 UNVERIFIED — pin 12.1 (§2.2)
2 MCP types/struct resolution of attached-archive (.gdt) types UNVERIFIED — early test, §2.5 step 5
3 GhidrAssistMCP struct-tool ergonomics under matching-decomp load UNPROVEN — psxrecomp never exercised heavy struct creation
4 PCSX-Redux web-server port config field (8080 collision) TBD on install (§3)
5 WSL distro name Ubuntu-24.04 + default username on this machine TBD post-install (§4.1)
6 binutils ≥2.38 regression on Ubuntu 24.04's shipped binutils VERIFY (§4.5; 2.35 known-good per open-ribbon)
7 sha256 hashes of old-gcc 0.17 tarballs TBD record at first download (§4.7)
8 gp_value in SLUS_007.26 header → -G0 vs -G8 TBD (§5.3)
9 ASPSX tier for game code: 2.56 vs 2.67 OPEN — Phase 6 empirical (§5.2 tell)
10 Mirrored networking stability with this machine's VPN/virtualization stack TBD smoke test (§4.2)
11 Canonical git remote URL for two-clone sync TBD (§4.3)
12 Per-libnum stamp detail (raw-track scan reported 16 hits vs 12 genuine in extracted EXE — extracted-EXE scan is ground truth, see §5.1) re-confirm via DetectPsyQ at import (§2.5 step 3)
13 Overlay load addresses (resident 0x800CDF58 / location 0x80128508, EXE ptr table ~0x62620) JP-only — re-derive for US (owned by docs/memory-map.md)
14 Greenfield claim: decomp.me scratch search is script-blocked (Cloudflare) TBD — one-time manual browser check for BFM scratches