From 320070f56f4e4f6a61b38128d25e18b9aa177163 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 7 Jul 2026 10:35:55 -0400 Subject: [PATCH] docs: implementation plan for Ghidra+MCP native-RE workflow (u00428010) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plans/2026-07-07-ghidra-mcp-native-re.md | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-07-ghidra-mcp-native-re.md diff --git a/docs/superpowers/plans/2026-07-07-ghidra-mcp-native-re.md b/docs/superpowers/plans/2026-07-07-ghidra-mcp-native-re.md new file mode 100644 index 0000000..f736bf4 --- /dev/null +++ b/docs/superpowers/plans/2026-07-07-ghidra-mcp-native-re.md @@ -0,0 +1,188 @@ +# Ghidra + MCP Native-RE Workflow Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to run this plan with review checkpoints. This is a **runbook**, not code-TDD — steps alternate between **[you]** (setup / Ghidra GUI / launching the game) and **[me]** (MCP-driven analysis). Each task ends at a concrete gate. Steps use checkbox (`- [ ]`) syntax. + +**Goal:** Stand up a "Claude drives Ghidra over MCP" loop and prove it by reversing `u00428010` (SCJUMP's native decision→scene resolver) to a concrete verdict — recover the `0x62ccf → SCxxxx.BIN` mapping, or pinpoint exactly why it's runtime-only. + +**Architecture:** Load our existing unpacked engine dump (`build/engine-dump/range_00400000.bin`) into Ghidra 12.1.2; connect the bethington/ghidra-mcp bridge so I can call `decompile_function`/`get_xrefs_*`/`read_memory`; drive the analysis of `u00428010` and the `0x5f0ed` array; capture findings in `docs/engine-re.md`. + +**Tech Stack:** Ghidra 12.1.2 + bethington/ghidra-mcp (Java 21, Maven 3.9+, Python 3.10+, `uv`); pe-sieve v0.4.1.1 (escalation only); the game (JP locale) for a fresh dump if escalating. + +## Global Constraints + +- **Analysis only — no redistribution.** The engine dump / any reconstructed PE stay local. +- **Substitute your paths** where marked: `` = your Ghidra 12.1.2 install (README example `C:\ghidra_12.1.2_PUBLIC`); `` = your `ghidra-mcp` clone; `` = the running game's process id. +- **Run pe-sieve from PowerShell, not the Bash tool** — Git Bash mangles `/pid` `/imp` `/dir` into paths. (`& .\tools\pe-sieve32.exe /pid /imp 3 /dir `.) +- Findings live in `docs/engine-re.md` (new canonical doc); resolved op semantics also go to `vm-map/opcodes.toml` (rebuild with `opcodes_build.py --build`). One fact, one home. +- Git repo root is `age-reimpl/`; commit docs from there. Commit trailer: `Co-Authored-By: Claude Opus 4.8 (1M context) `. + +**Reference facts (verified 2026-07-07):** +- Engine dump: `build/engine-dump/range_00400000.bin` (2,490,368 bytes) is the **full module image at base 0x400000** (memory-laid-out). VA→file offset = `VA − 0x400000`, so `u00428010`'s handler region is at file offset `0x28010`, and the AGF landmark VA `0x474f1f` is at `0x74f1f`. +- Target: consumers run `lookup-array(ptr, 0x5f0ed, 0x62ccf)` then `u00428010(ptr)`. Kelebek names the op by its handler VA `0x428010` (may drift in our build → resolve via the interpreter op-dispatch table `[esi+idx*120+0x53d88]` or xrefs from the operand-fetch `call 0x41b940`). Interpreter runs from the module @0x400000 (stable base). +- `0x62ccf` decision values: 847 distinct, range 0..1693 (from `build/scjump-decisions.json`). Scene files are `SC0000..SC1690` (see `paths.scripts()` / `build/asset-sections.json`). +- pe-sieve: v0.4.1.1 (x86) at `tools/pe-sieve32.exe`; `/imp 3` = rebuild ImportTable from scratch. + +--- + +### Task 1: Bring the environment up — image loaded + MCP connected + +Deliverable: I can call a bethington MCP tool against the loaded engine image and get a real result. + +- [ ] **Step 1 [you]: Build + deploy the Ghidra MCP plugin** + +```bash +git clone https://github.com/bethington/ghidra-mcp.git +cd +python -m tools.setup ensure-prereqs --ghidra-path "" +python -m tools.setup build +python -m tools.setup deploy --ghidra-path "" +``` + +Expected: build succeeds; the extension is deployed into ``. + +- [ ] **Step 2 [you]: Load the engine image in Ghidra** + +1. Start Ghidra → new project → **File > Import File** → select + `S:\Game Hacking\Eushully\Himegari\age-reimpl\build\engine-dump\range_00400000.bin`. +2. In the import dialog: **Format = Raw Binary**, **Language = `x86:LE:32:default`** (compiler: `windows`). +3. Open it in CodeBrowser. Set the image base: **Window > Memory Map** → set the block's start to + `0x00400000` (or set `Options > Image Base = 0x400000` at import). +4. Run **Analysis > Auto Analyze** (accept defaults; ensure Decompiler + Function ID are on). + +Expected: auto-analysis completes; the Listing shows disassembled functions. + +- [ ] **Step 3 [you]: Enable + start the MCP server in Ghidra, then the bridge** + +1. **File > Configure > Configure All Plugins > GhidraMCP** → check the box. +2. **Tools > GhidraMCP > Start MCP Server** (serves `http://127.0.0.1:8089/`). +3. Verify HTTP: `curl http://127.0.0.1:8089/check_connection` → expect a success/OK response. +4. Launch the stdio bridge: + +```bash +cd +uv run bridge-mcp-ghidra +``` + +- [ ] **Step 4 [you]: Register the server in Claude Code** + +Add to the project MCP config (`.mcp.json` at the workspace root, or `claude mcp add-json ghidra …`): + +```json +{ + "mcpServers": { + "ghidra": { + "command": "uv", + "args": ["run", "--directory", "", "bridge-mcp-ghidra"] + } + } +} +``` + +Then reconnect the session so the `ghidra` MCP tools register (they arrive as deferred tools I fetch via ToolSearch). Tell me when it's connected. + +- [ ] **Step 5 [me]: Connectivity gate** + +I `ToolSearch` for the ghidra tools (e.g. `select:list_functions,decompile_function,get_function_by_address`), then call `list_functions` (or `check_connection`). +Gate: a non-empty function list comes back → the loop works, proceed. If it errors → we debug the bridge/Ghidra-server/config before continuing (fallback: you paste decompiler output; degraded but unblocked). + +--- + +### Task 2: Confirm image quality at the target; escalate to pe-sieve only if needed + +Deliverable: a confirmed-legible decompilation at the `u00428010` region (raw dump, or a pe-sieve-rebuilt PE). + +- [ ] **Step 1 [me]: Sanity-check the load + assess the target region** + +- Validate the dump: `get_function_by_address(0x474f1f)` / `read_memory` near it — confirm it reads as real code around the `BM` (`0x4D42`) check (the recorded AGF-decoder landmark). +- Navigate to `0x428010`: `get_function_by_address(0x428010)` + `decompile_function`. Assess whether the decompiled output is coherent (recognizable control flow, memory refs) or garbage/truncated. + +Gate: coherent → skip to Task 3. Garbage/incomplete (bad section boundaries, unresolved refs blocking reading) → escalate (Step 2). + +- [ ] **Step 2 [you, only if escalating]: pe-sieve a clean, import-rebuilt PE** + +With the game running (JP locale), from **PowerShell**: + +```powershell +# NOTE: do not name this $pid — that is a PowerShell automatic variable (the shell's own PID). +$agePid = (Get-Process AGE*).Id # confirm this returns exactly one id +& "S:\Game Hacking\Eushully\Himegari\age-reimpl\tools\pe-sieve32.exe" /pid $agePid /imp 3 /dmode 3 /dir "S:\Game Hacking\Eushully\Himegari\age-reimpl\build\engine-pe" +``` + +Expected: an output folder under `build/engine-pe\process_\` containing the rebuilt AGE module PE (named by base, e.g. `400000.AGE.EXE`). Validate: it contains plaintext `SYS4422` / `.BIN` / `DATA1` strings (recorded dump-sanity check) — e.g. `Select-String -Path -Pattern 'DATA1' -Encoding ascii`. + +- [ ] **Step 3 [you, only if escalating]: Re-import the rebuilt PE in Ghidra** + +Import the pe-sieve module as **Format = Portable Executable (PE)** (base 0x400000 is intact), auto-analyze, keep the MCP server pointed at this program. Tell me when ready; I re-run Step 1's assessment. + +Gate: coherent decompilation at `0x428010` → proceed to Task 3. + +--- + +### Task 3: Reverse `u00428010` → decision→scene verdict + +Deliverable: an evidence-backed answer to "what scene does decision N load?", or a precise statement of the runtime dependency + the Frida hook that would resolve it. **[me]**, MCP-driven. + +- [ ] **Step 1: Locate the real handler** + +`get_function_by_address(0x428010)`. If it lands mid-function or on non-handler code (Kelebek build drift): find the true handler via xrefs — `get_xrefs_to` around the op-dispatch table `[esi+idx*120+0x53d88]`, or from the operand-fetch path `0x41b940` — and via `list_functions` near 0x428010. + +- [ ] **Step 2: Decompile + read the handler** + +`decompile_function` on the handler. Read: how it consumes its pointer argument (the `&0x5f0ed[decision]` reference — begin/element), what struct fields it reads, what it returns, and what it calls (esp. anything that looks like a file/scene load or a table index). + +- [ ] **Step 3: Chase the data** + +Follow references from the handler (and from `0x5f0ed`) to any static table in `.data`: `get_xrefs_to(0x5f0ed)`, `list_data_items` around it, `read_memory` on the referenced table(s). Determine whether the decision index maps — directly or via `0x5f0ed` — to a scene id / filename / SYS4INI section. + +- [ ] **Step 4: Cross-check against the corpus** + +For candidate scene ids/indices the table yields, cross-check against real `SC####.BIN` names (`paths.scripts()`) and `build/asset-sections.json`. If a decision value resolves to an existing scene, that's the mapping. Spot-check 2–3 known decisions end-to-end. + +- [ ] **Step 5: Verdict** + +Write the conclusion: +- **(a) Static mapping recovered** — the `decision → scene` table/formula, with the cross-checked evidence. (This would finish SCJUMP's open boundary as a *static* win.) +- **(b) Runtime-dependent** — exactly what live state the resolution needs that the table doesn't encode, and the precise Frida hook (address + what to log) that would capture it. + +Gate: one of (a)/(b) with concrete evidence. Report it to you before writing docs. + +--- + +### Task 4: Capture findings + updates + +Deliverable: committed docs recording the result and the repeatable workflow. + +- [ ] **Step 1 [me]: Write `docs/engine-re.md`** + +Create the canonical native-RE doc with two parts: +1. **Runbook** — "How to run the Ghidra + MCP loop": the bethington setup (build/deploy, enable plugin, start server:8089, bridge, Claude Code registration), loading `build/engine-dump/range_00400000.bin` (raw, base 0x400000, `x86:LE:32`), the AGF landmark sanity check (VA 0x474f1f), and the pe-sieve escalation recipe (`/imp 3 /dmode 3`, PowerShell). Cross-link `vm-mapping-plan.md` appendix rather than duplicating the packing detail. +2. **Findings — `u00428010` (decision→scene)** — handler address (as-found), decompiled behavior, the `0x5f0ed` + table structure, and the Task 3 verdict (mapping or runtime hook). Leave a "native walls backlog" list (call-script dispatch, op 0x60, gfx command-buffer) as future sections. + +- [ ] **Step 2 [me]: Propagate the result** + +- `docs/scjump-progression.md` — update the "native decision→scene boundary" section with the verdict (mapping recovered → link the table; or the identified runtime hook). +- `vm-map/opcodes.toml` — if `u00428010` (or any op identified en route) earned real semantics, add/enrich its entry; run `py -3.11 -X utf8 tools/opcodes_build.py --build`. +- `CLAUDE.md` canonical-documents map — add: `Native-engine RE (decompiled AGE.EXE) → docs/engine-re.md` (workspace root; edit on disk, not in the repo). +- `docs/tools-reference.md` — add `pe-sieve32.exe` (dump-prep) if it was used; note its home (`tools/` or move to `bin/` per PROJECT-STRUCTURE). +- `docs/PROJECT-STRUCTURE.md` — add `docs/engine-re.md` and (if kept) `pe-sieve32.exe`. + +- [ ] **Step 3 [me]: Commit docs + update status memory** + +```bash +git add docs/engine-re.md docs/scjump-progression.md docs/tools-reference.md docs/PROJECT-STRUCTURE.md vm-map/opcodes.toml docs/opcode-reference.md +git commit -m "docs: native-engine RE — Ghidra+MCP workflow + u00428010 decision->scene findings + +Co-Authored-By: Claude Opus 4.8 (1M context) " +``` + +Update `~/.claude/…/memory/himegari-port-status.md` + `MEMORY.md`: record that the Ghidra+MCP native-RE loop is stood up (bethington/ghidra-mcp on the 0x400000 module dump) and the `u00428010` verdict; note it's the reusable path for the remaining native walls. Convert 2026-07-07 to the absolute date. (Memory is outside the repo — no commit.) + +--- + +## Notes for the implementer + +- **This is a runbook with a hard human/tool dependency** (Ghidra GUI + MCP bridge + possibly the running game). Stop at each gate; don't fabricate analysis results — every finding in Task 3/4 must come from an actual MCP call. +- **A "runtime-dependent" verdict is still success** — it validates the workflow and converts an open wall into a defined Frida task. +- **VA drift**: never trust the raw Kelebek VA blindly; confirm the handler via the dispatch table / xrefs (Task 3 Step 1). +- **If the MCP bridge can't be made to work**, fall back to pasted decompiler output for `u00428010` — slower (no automated xrefs) but the analysis and docs still land. +- The exact bethington commands/config are from the repo README (2026-07-07); if the repo has since changed, follow its current README for build/bridge/registration and keep the rest of this plan.