rob.asm$DAD0 · chip.bin

R.O.B. (Reverse-engineering Opcode Buddy)

A 6502 disassembler for NROM NES games that writes one hyperlinked HTML listing, plus a scripted-emulator harness for discovering what RAM bytes and routines actually mean. Developed against Capcom's 1942.

coverThe top of the generated 1942.html page: a short preamble about the machine-generated disassembly and Game Genie codes, then the iNES header box and the named NES hardware registers
The top of the generated 1942.html page: a short preamble about the machine-generated disassembly and Game Genie codes, then the iNES header box and the named NES hardware registers
README.mdrob/index.mdx

R.O.B. takes an iNES ROM and produces a single self-contained HTML page: address, bytes, label, mnemonic, operand and comment columns, in the style of the well-known Super Mario Bros. disassembly, with every jump target and data reference turned into an in-page link. The test subject is 1942 (Capcom, 1985). The ROM isn’t in the repo; a pre-rendered 1942.html is.

Disassembly

disasm.py is plain Python with no dependencies.

  • Recursive descent from the NMI, RESET and IRQ vectors, following JSR, JMP abs and branches.
  • Routines reached only through dispatch tables are recovered heuristically: the byte after every RTS/RTI/JMP/BRK is tried as an entry point, and so is every 16-bit word in unclaimed data that points into PRG. A tentative trace only counts if it runs at least six clean instructions, ends in a terminator and contains no BRK.
  • Anything not reached is emitted as .byte data. Hardware registers get names (PPUCTRL, OAMDMA, …).
  • roundtrip.py reparses the HTML and checks it rebuilds the PRG exactly: 32768 of 32768 bytes on 1942.

Limits: mapper 0 only, no bank switching, no indirect-jump tracing, no CHR decoding.

Finding out what things mean

Auto-labels like L_c2e8 only get you so far. scenarios.py boots the ROM in nes-py, plays scripted button sequences (press start, autofire, idle, die once, …), snapshots RAM at checkpoints, and joins every changed byte to the store instructions in the disassembly that write it. drift.py adds per-byte timelines tagged UP, DOWN, SAW or FLIP.

There’s Game Genie tooling too: genie.py decodes codes to an address in the listing, and genie_candidates.py ranks patchable bytes (counter decrements, comparisons, dispatch-table entries) and suggests codes to try. fiber_trace.py walks 1942’s coroutine-style entity system one fiber at a time. Results go into labels.json, which is up to 211 named addresses.

The RAM map section of 1942.html: named addresses such as Joypad1_State, Score_D4, Lives_Ones and ExtraLife_PendingFlag, each with a comment on what reads or writes it
Part of the RAM map in 1942.html, built from labels.json. Each name comes with notes on the code that touches it.
python3 disasm.py 1942.nes -o 1942.html
python3 scenarios.py press_start