---
title: "rommage"
url: https://manvsmidi.com/projects/rommage/
category: experiment
status: writeup
date: 2026-04-21
tags: [nes, game-genie, emulation, python]
related: [viper, rob, streetkombat, supersonic]
---

# 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.

Experiment · Write-up · 2026-04-21

- [Source](https://github.com/sadovsky/rommage)

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](https://github.com/Kautenja/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:

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

## Connected projects

- [viper](https://manvsmidi.com/projects/viper/index.md): linked
- [R.O.B. (Reverse-engineering Opcode Buddy)](https://manvsmidi.com/projects/rob/index.md): shared tags: game-genie, emulation
- [Mortal Kombat vs Street Fighter II](https://manvsmidi.com/projects/streetkombat/index.md): shared tags: emulation
- [Super Sonic Bros.](https://manvsmidi.com/projects/supersonic/index.md): shared tags: emulation
