rommage.asm$A5C0 · city.bin

rommage

Brute-force search for NES Game Genie codes. Patch a byte, run the game, compare the frames against a clean run, and keep the codes that change things without crashing.

coverrommage's clustered HTML report from a 600-candidate Super Mario Bros. search: the clean 1-1 baseline frame above the top-ranked Game Genie codes, whose frames show garbled tiles, recoloured levels and corrupted HUDs
rommage's clustered HTML report from a 600-candidate Super Mario Bros. search: the clean 1-1 baseline frame above the top-ranked Game Genie codes, whose frames show garbled tiles, recoloured levels and corrupted HUDs
README.mdrommage/index.mdx

A Game Genie code patches a single CPU read: when the game reads a given address in ROM, it gets a different byte back. Finding good ones used to mean reading disassembly and guessing. rommage tries a lot of them instead and looks at what happens on screen.

The pipeline

Each candidate is run on nes-py, with a small C++ patch that adds a cheat table to the CPU bus. Stages get more expensive as the candidate pool shrinks:

  1. Trace. Perturb each PRG byte once and run a short rollout. Addresses the game never reads during that play sequence are dropped. On Super Mario Bros. walking right through 1-1, this cuts 32,768 addresses to 1,789.
  2. Boot check (optional). Reject codes that change the title screen.
  3. Stage 1. A 60-frame rollout scored by dHash distance alone. Nulls (no change) and crashes (maximal change) are thrown out.
  4. Stage 2. A full rollout scored by dHash plus a 64-bin colour histogram, then ranked into an HTML gallery.

Each worker process keeps one emulator alive and restores a saved state, so no candidate pays for boot or the title screen. Runs can be resumed. On eight cores a sampled SMB1 search takes about 12 minutes.

Some finds

Code Patch Effect
KAAYPYSA $F701 85→84 STA becomes STY in sound init; scrambled music and SFX
VSZAPEPZ $80A1 29→D6 an AND in the NMI becomes a DEC; level terrain keeps mutating
AELUNLAL $BBBF 30→00 an object timer cap drops to zero; hammers rain after a block hit

The repo includes a long writeup that frames all this as a search problem: cascaded classifiers, ablation as dimensionality reduction, perceptual hashing, and a few fights with multiprocessing.Pool.

Limits

Mapper 0 (NROM) only for now, which covers SMB1, Donkey Kong, Excitebike, Ice Climber and similar early titles. You need to patch and rebuild nes-py first; docs/PORTING.md has the steps. Then:

cd genie_search
python3 rommage.py search ../smb1.nes --trace-live-addrs \
    --sample 2000 --workers 8 --out ./results/smb1