37 KiB
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/zfrom 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. Everymake,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 against12.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 installExtensionwithGHIDRA_INSTALL_DIRset).
§2.3 GhidrAssistMCP v2.8.0
- Asset for our Ghidra:
ghidra_12.1_PUBLIC_20260530_GhidrAssistMCP.zipfromhttps://github.com/symgraph/GhidrAssistMCP/releases/tag/2.8.0(repo moved fromjtang613/GhidrAssistMCPtosymgraph/GhidrAssistMCP; GitHub redirects. The release carries TWO assets — take the12.1one, not12.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) fromhttps://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)
- Extract
SLUS_007.26from the disc image (LBA 24, 0x65000 bytes, track 1 is MODE2/2352) and import into Ghidra. The loader auto-selects "PSX Executables Loader", languagePSX: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 theCreateGteMacSegmentscript (that is only for migrating legacy non-PSX projects). - 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).
- Read the detected PsyQ version: Edit → Options for Program → Program Information → "PsyQ Version". Expected: 4.0 (our own EXE scan found 12 genuine
Psstamps: 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.0to 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.) - One-time manual
.gdtattach (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.gdtalso exists). This gives PsyQ struct/typedef types for retyping work. - Early MCP type-resolution test (run before any bulk typing): via MCP, run the
typestool withaction=setapplying a PsyQ type (e.g. apply a known PsyQ struct at some address) and confirm it resolves. UNVERIFIED whether thetypes/structtools 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=trueis 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/rawreturns 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.luaand reloads it at startup — a crashing script wedges the emulator across restarts (deletepcsx.luato 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 connectgdb-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.confchanges 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):
- Inside WSL, get the host IP via the
ip routemethod — 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}' - 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 - 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=localhostbinds 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.38generated 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.gzhttps://github.com/mkst/esa/releases/download/psyq-binaries/psyq4.1.tar.gz(containCC1PSX.EXE,ASPSX.EXE,CCPSX.EXE,PSYLINK.EXE,PSYLIB.EXE; 1–2.3 MB each)- Their
.OBJoutput 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-versionMUST 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_lioff ≥2.50;sltu_atoff ≥2.60;$gpsymbol+offset ≥2.70;$gpforla≥2.80. The observable 2.56 vs 2.67 tell: "$at forsltu < 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)
gcc-2.7.2-psxcc1 +--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).- Same cc1 +
--aspsx-version=2.67(PsyQ 4.1 assembler era). gcc-2.7.2-cdk(cygnus-2.7.2-970404, the exact CC1PSX 4.0/4.1 base) × 2.56, then × 2.67.- 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. - 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)/$varsitself before wsl.exe sees them. $LASTEXITCODEcarries 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=1before capturing build logs. --cdwith 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: runmake expectedexclusively after a build whose check passed. A staleexpected/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(orgcc2.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.
- Start Ghidra (Windows):
ghidraRun.bat. - Open the BFM project → open
SLUS_007.26in CodeBrowser. Confirm the GhidrAssistMCP control panel (Window → GhidrAssistMCP) shows the server up onlocalhost:8080. - Start/resume Claude Code in
Z:\Storage\git\BFM-decomp. Run/mcp— theghidraserver 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). - Build preflight (when the session involves building, Phase 4+):
Plus
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'git statuson the Windows clone — both trees clean or intentionally dirty before starting. - 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 |