Local web IDE / Pokemon GBA decomp
Edit a Pokemon GBA decomp project visually or in plain English, then build and play the ROM in your browser.
Point it at a decompilation project on disk. It classifies the project, scans it into one
typed manifest, and renders that manifest as a navigable game: maps, warps, triggers,
object events, dialogue, flags and variables, encounter tables, trainers and their parties,
species, moves, learnsets, abilities, items, and the type chart. Everything runs on
127.0.0.1. Nothing is uploaded anywhere.
This repository contains no ROMs, no ROM images, no decompilation dumps, no extracted graphics, audio, text or save data, and no binary patches derived from a commercial game. The tool operates on a decompilation project and ROM files that you supply yourself, and you are responsible for supplying a legally obtained copy.
corpus/ is the drop-zone for your own files. It is git-ignored, and the
.gitignore blocks *.gba, *.gbc, *.gb,
*.nds, *.sav, *.srm, *.bps,
*.ips, *.ups and *.xdelta repo-wide.
Pokemon and related names are trademarks of Nintendo, Creatures Inc., and GAME FREAK Inc. This is an unaffiliated, non-commercial fan tool.
There are two ways to change a Pokemon GBA game, and neither of them gives you an editor.
Binary patching means finding a table in a .gba image and writing bytes. It
works until you want to add something the original ROM has no room for. Free space hunting,
repointing and one-off patches accumulate until nobody can say what the ROM now contains.
A decompilation project removes those limits: real C source that compiles back to a
byte-matching ROM. What it costs you is the editor. The game becomes thousands of files of
C, headers, .inc scripts, .json map data and PNG tilesets. Editing
a trainer's party means finding the right block in the right file and not breaking the build.
This project is the missing middle. It treats the decomp tree as the source of truth, reads it into one typed manifest, renders that manifest as something you can click through, and writes changes back into the real C source. Compiling and playing are part of the inner loop rather than a separate step you leave the tool to perform.
scan/data/struct-block.ts is 159 lines. It parses decomp files shaped as
[ID] = { .field = value } arrays and rewrites only the value span of named
fields, so macros, #if guards, comments and unmodelled fields survive
verbatim. Moves, abilities and items sit directly on that parser; learnsets and
encounters reuse its bracket matcher over a macro list and over
wild_encounters.json; species, the type chart and trainers have their own
format-specific writers built to the same discipline. Every write is
tmp-file-plus-rename.
agent/mcp-server.ts registers 71 tools against the open project, one per
module under agent/tools/ (59 of them propose-* mutations,
plus 21 colocated test files).
The claude CLI runs as a child process and streams turn events over a
WebSocket. No tool writes to disk: proposals become a diff card you approve, then a
patch applier writes them.
build/decomp-build.ts shells into devkitPro's MSYS2 login bash, prepends
$DEVKITARM/bin to PATH, runs make modern, and
overrides CPP with a CR-stripping wrapper because devkitARM's CRLF output
chokes pret's preproc. Async polled jobs, a 200,000-character log cap, a
30 minute ceiling.
parseMapSectionOrder in scan/decomp.ts reads
region_map_sections.h into the game's own MAPSEC_* ordering,
so the navigator lists Pallet, Viridian, Pewter and onward rather than Route1, Route10,
Route2.
open project
-> detect/ classify decomp | patch | hybrid
-> scan/ parse into <projectRoot>/.editor/manifest.json
-> frontend render manifest as a navigable world
edit visually: PATCH route -> domain writer -> tmp+rename into decomp source -> op-log
edit in English: WebSocket -> spawner -> claude CLI -> MCP propose_* -> patch store
-> diff card -> you approve -> patch-applier -> op-log
Build & Play: make modern job -> .gba -> mGBA-wasm in the browser
Both edit paths land in the same op-log, so undo, redo and the project timeline behave identically whether a change came from a visual editor or from an approved AI patch.
| Tier | Size | Role |
|---|---|---|
| app/shared/src | 19 files, 2,924 lines | Every type that crosses the wire, defined once |
| app/backend/src | 254 files, 70,721 lines | Fastify server, detection, scanners, atomic writers, op-log, MCP host, build runner |
| app/frontend/src | 254 files, 77,414 lines | Map editor, navigator, data editors, inspector registry, PixiJS compositor, Zustand stores |
| engine/src | 355 files, 62,412 lines | Standalone binary-ROM introspection engine (legacy path, about 29 percent of the TypeScript here) |
| tile-intel-svc/src | 45 Python files, 9,382 lines | Optional FastAPI sidecar for tileset understanding |
git clone https://github.com/csnyder256/gba-rom-hack-ide cd gba-rom-hack-ide .\start.ps1 # or: node scripts\start.mjs
engines..env is needed for the Node stack. You pick your decomp checkout in the app, and the backend derives ROM_EDITOR_PROJECT_ROOT from it for the MCP server. Copy .env.example to .env only if you run the optional Docker tier or the Python sidecar, which are the only things that read it.claude CLI on PATH.${TILE_INTEL_PG_PASSWORD:?...} and fails fast unless it is set. Set it, or run with SKIP_TILE_INTEL=1. The editor degrades gracefully without it.
A working single-operator tool, not a released product. It pivoted: it began as a binary ROM
patcher, that path failed its own audit, and the project re-founded itself on editing decomp
source and compiling with make. The binary engine is still in the tree, still
imported in places, and clearly labelled legacy.
Recorded against a real cloned project, not fixtures: 1,104 learnsets, 469 wild encounter tables, 664 trainers with editable parties, 33,585 decoded script steps across 8,821 labels, 882 flags and 167 variables. The full write-up, including the audit that triggered the pivot, is in the planning docs.