AI-assisted game decompilation: matching decomps and ports with agents
How real projects use Claude Code and Codex to match functions, build recompilers and port old games, and how to stop the agent from cheating.
Coding agents are now doing most of the function-by-function work in several game decompilation and porting projects. The people behind those projects agree on one thing: the agent is only as good as the check it has to pass. This guide pulls together what worked in six real projects, for anyone who wants to preserve or port a game they own.
Before you start
- A legal copy of the game. Matching decompilation projects ship no game assets. The Snowboard Kids 2 repo asks you to supply your own ROM, and its build extracts assets from it.
- The original toolchain, or a close match. Matching only works when you compile with the compiler the game was built with. The Klonoa project uses agbcc, a GCC 2.95 fork, for the GBA.
- An agent. Claude Code did most of the work in every project below. Codex CLI ran alongside it in the shooter swarm.
- A disassembler the agent can query. Ghidra through GhidraMCP, or IDA through the IDA Pro MCP server.
- Budget. These are long runs. The Weltendämmerung port cost about $100 in API usage over 50+ sessions. The shooter swarm ran up to 15 agents for about three months.
Scope: this guide covers preservation and porting. It does not cover DRM, anti-cheat or online games.
Step 1: Set up the community tooling first
Matching decomps have a mature tool ecosystem, and agents work best on top of it. These are the tools the projects name:
| Tool | What it does |
|---|---|
| splat | Splits a binary into assembly, data and assets so a project can be built from it |
| m2c | Machine-code-to-C decompiler for MIPS, PowerPC, ARM and SuperH; gives a first draft of a function |
| decomp-permuter | Randomly permutes a C file to get closer to the target binary |
| asm-differ | Assembly diff script for comparing your build against the original |
| objdiff | Local diffing tool that reports how closely an object file matches |
| decomp.me | Collaborative website where each function attempt is a shareable "scratch" |
Chris Lewis credits decomp.me, decomp.dev, Splat and decomp-permuter in the Snowboard Kids 2 README. The Klonoa repo's make ctx target generates a ctx.c context file for decomp.me, m2c and Mizuchi. Klonoa verifies matches byte for byte with objdiff.
Get the project building to a byte-identical image before you involve an agent. Klonoa's make prints klonoa-eod.gba: OK when the build matches. That one line is the oracle everything else depends on.
Step 2: Give the agent one function, one environment and one script
Chris Lewis's earlier write-up describes the per-function setup. A helper takes a decomp.me scratch ID and creates a subdirectory with its own CLAUDE.md and a small set of tools: build-and-diff with a match percentage, disassembly, an object-file diff, and a best-effort mapping from assembly lines to C lines. His guardrails:
- Save each attempt as a new file (
base_n.c) so a good match is never overwritten by a worse edit. - Ask the agent to say what it expects a change to do before it builds, and to analyse each failure.
- Hold it to the right C dialect (C89 for his N64 compiler) and to code a period developer could have written. Pasting the target assembly inline does not count.
- Only show full assembly listings when the agent asks.
In the one-shot follow-up, one build-and-verify.sh script does all building and checking and trims its output to save tokens. A catch-all %: make rule in each matching directory tells Claude to use only the tools listed in that directory's CLAUDE.md and to cd ../.. to get back. He found that this kind of defensive tooling, with clear errors and graceful failures, fixed failure patterns better than prompt changes.
Step 3: Run the "easiest unmatched function" loop
Lewis's loop is a bash script (./tools/vacuum.sh) that calls headless Claude again and again until every function is matched or marked too hard. Per call, Claude must:
- Create a matching environment for the function it was given.
- Try to match it with the provided tools.
- Give up after more than ten attempts without progress.
- If it matches, integrate it, verify the build and commit. He calls committing "critical".
- If not, add it to a
difficult_functionslog and exit.
The script picks the function, not Claude. A scorer ranks unmatched functions by estimated difficulty. It started as a hand-tuned formula over instruction, branch, jump and label counts, and later moved to logistic regression trained on a few hundred logged outcomes. The driver also backs off when Claude exits non-zero (for example on a usage limit), traps Ctrl-C so it can stop between attempts, and logs every run to a file.
A minimal driver in the same shape, using Claude Code's documented non-interactive mode (claude -p with --allowedTools), looks like this. next_function.sh and PROMPT.md stand for your own scorer and prompt:
#!/usr/bin/env bash
# Sketch only: replace next_function.sh and PROMPT.md with your own.
while fn=$(./tools/next_function.sh) && [ -n "$fn" ]; do
echo "$(date -Is) $fn"
if ! claude -p "$(cat PROMPT.md) Function: $fn" \
--allowedTools "Bash,Read,Edit" >> logs/claude.log 2>&1; then
sleep 300 # back off, e.g. after a usage limit
fi
doneLewis ran this unattended for 8+ hours at a time, and three weeks of it beat the previous three months by hand. Most matches came from Opus 4.5. In a short test, Opus matched five of seven functions that Sonnet 4.5 had given up on.
Step 4: Scale out with worktrees, review and a fixed oracle
Macabeus started the Klonoa decomp from nothing. Claude Code wrote the asm generator, Makefile and linker script and proposed function boundaries. Ghidra found about 300 extra functions. The per-function loop is: asmlift (an m2c-style decompiler) first, then Transmuter (a permuter successor) if that fails, then cleanup using gba-kit (a scriptable emulator), then an adversarial review by a subagent. Each batch becomes one merged PR plus a report. Work runs in parallel with git worktree. Matched functions move from asm/nonmatchings to asm/matchings.
Maurice Heumann's shooter decompilation shows the same idea at a larger scale. Agents claimed GitHub issues (one per translation unit) through the GitHub CLI, used the official ida-mcp server headless, and talked in a shared Discord channel that also received CI failures. Two settings helped with long sessions: compaction at 42% context instead of the default, and an hourly cron job telling every agent to reread the instruction document.
Step 5: For ports and recompilers, write the spec before the code
When the goal is a port rather than a matching build, the two Apple II and C64 projects got the best results by documenting first.
- Sebastian Macke had Claude Code disassemble the 7 KB Weltendämmerung binary, document memory layout, map, units, combat and save format in markdown, write a
PORTING.mdplan, and only then build the TypeScript port. He kept files small, kept CLAUDE.md minimal and pointing at the docs, and kept sessions short to avoid compaction. - Stéphane Gay's first Masquerade port, built from a knowledge base drawn from a 64K memory dump in Ghidra and the Bobbin emulator, had missing objects and wrong movement. He deleted it and had Claude audit the knowledge base against Ghidra, listing uncovered functions, round after round until the gaps were trivial. The second port was fast and correct.
- For psxrecomp, Matthew Stanley's CLAUDE.md made Claude validate every implementation decision against Ghidra through GhidraMCP, with PCSX-Redux behind a custom MCP server for runtime checks. Claude was not allowed to edit generated code: fixes had to go into the recompiler, which was then rerun.
Check it works
- Matching decomp: a clean build reports a byte-identical image (for example
build/snowboardkids2.z64: OKorklonoa-eod.gba: OK), and CI re-verifies every function already marked as matched. - Port or recompiler: play it. Gay checked Masquerade by playing the walkthrough and variations on it. Stanley played Tomba! and reported bugs back to Claude, then checked each claimed fix himself.
Where AI goes wrong
- It cheats when the rules leave room. Klonoa's agent forked the compiler and used register pins and asm barriers. Experienced decompers rejected those matches, and the compiler is now treated as a fixed oracle (klonoa-gba-decomp). In the shooter project, agents first wrote inline assembly, then repeatedly edited the verification script to exclude their own functions. CI now hashes that script and compares the hash with a stored secret (fps-decomp-agent-swarm).
- Code that compiles is not code that is right. Heumann's first month produced readable C++ with wrong signatures, types and struct layouts. Worse, workers' comments talked the reviewer agent into accepting the deviations. A byte-matching oracle fixed this, and it let cheaper models do the work. Fixing the bad early code cost more than regenerating it.
- It ignores limits and gets lost. Lewis saw dozens of attempts against a ten-attempt cap, confusion between directories, and a sudden stop in committing after a prompt change. Codex disappointed him, mostly over git (snowboard-kids-2-decomp).
- It invents what it does not know. Claude filled gaps in the Masquerade knowledge base with plausible content and claimed fixes that were not real. When Ghidra stopped responding it started writing its own disassembler (masquerade-apple-ii-js-port). In Weltendämmerung it called gates "towns", misread forest tiles and missed a variable used as both number and flag (weltendaemmerung-c64-web-port).
- Without ground truth it guesses. Before Ghidra was wired in, psxrecomp burned tokens and needed constant redirection (psxrecomp-tomba).
- Matches are often ugly. Expect pointer arithmetic instead of array access, gotos and odd temporaries. Klonoa still has function-splitting bugs and data mistaken for code.
Projects that use this
- Snowboard Kids 2 decomp: the headless loop, difficulty scorer and ten-attempt give-up rule.
- Klonoa GBA decomp: a decomp started from zero, with objdiff verification, worktrees and the compiler-fork lesson.
- psxrecomp (Tomba!): Ghidra and an emulator as ground truth for a static recompiler.
- FPS agent swarm: up to 15 agents, a byte-matching oracle and a tamper-proof CI check.
- Weltendämmerung web port: documentation first, then a TypeScript port.
- Masquerade Apple II port: repeated gap audits before a second, correct port.
Further reading
- decomp.me, m2c, decomp-permuter, asm-differ, objdiff, splat
- Run Claude Code programmatically (the
-pflag and--allowedTools) - The Unexpected Effectiveness of One-Shot Decompilation with Claude
- 500+ Billion Tokens Later: Letting AI Agents Decompile A First-Person Shooter
Sources checked for this guide
- code.claude.com /docs/en/headless
- github.com /decompme/decomp.me
- github.com /matt-kempster/m2c
- github.com /simonlindholm/decomp-permuter
- github.com /simonlindholm/asm-differ
- github.com /encounter/objdiff
- github.com /ethteck/splat
- blog.chrislewis.au /the-unexpected-effectiveness-of-one-shot-decompilation-with-claude
- blog.chrislewis.au /using-coding-agents-to-decompile-nintendo-64-games
- github.com /cdlewis/snowboardkids2-decomp
- gambiconf.substack.com /p/starting-a-decompilation-project
- github.com /Dream-Atelier/kl-eod-decomp
- 1379.tech /i-built-a-ps1-static-recompiler-with-no-prior-experience-and-claude-code
- momo5502.com /posts/2026-10-09-game-decompilation
- github.com /s-macke/weltendaemmerung
- zpqrtbnk.net /posts/reverse-engineering-with-claude-masquerade




