Skip to content

Answer the bus with Game Genie codes, from a Hacks menu or --genie - #38

Merged
dimiro1 merged 1 commit into
masterfrom
dimiro1/game-genie-codes
Aug 20, 2026
Merged

Answer the bus with Game Genie codes, from a Hacks menu or --genie#38
dimiro1 merged 1 commit into
masterfrom
dimiro1/game-genie-codes

Conversation

@dimiro1

@dimiro1 dimiro1 commented Aug 20, 2026

Copy link
Copy Markdown
Owner

A Game Genie is not a patcher — it sat between the cartridge and the console and drove its own byte onto the data pins in the cartridge's place — so Hacks > Game Genie..., --genie CODE and genie/ungenie/genie clear in the REPL rewrite nothing at all.

The seam is three lines in MMU: mapper.prgRead has exactly two callers in the tree, which makes the hook provably total, and it goes in busRead rather than read so an OAM transfer out of a PRG page and a DMC sample fetch see it too, with the substituted byte assigned to dataBus because it is the Genie driving the pins; the field is null when there are no codes, which keeps a machine nobody is cheating on down to a null check and keeps SaveStateCompletenessTests out of the device, since a field holding null is stepped over where one holding an object would have every array inside it scrambled.

One address holds one code and a code can only reach $8000–$FFFF, both because the hardware says so, and there is deliberately no encode — bit 3 of the third letter is read by nothing, so GOSSIP would come back as GOISIP.

run.genie matters more than the rest of the comparability list: a patched run has its own cart.sha256 and a cheated one cannot, because the cartridge really is untouched, so it is the only thing in the report that tells the two apart and a save state taken with codes in will load into a machine with them out without complaining.

GameGenieRunTests makes the same edit two ways — once as the vendored .ips and once as ten eight-letter codes at the equivalent CPU addresses — and asserts the two draw byte-identical pictures, both different from plain; mvn -B clean test is green at 1674 tests and the distribution smoke test passes, SXIOPO turns DEC $075A into LDA $075A on a real Super Mario Bros., and the dialog is screenshotted and built under test but has not been clicked through by hand.

🤖 Generated with Claude Code

A Game Genie is not a patcher, and that difference is the whole of the design.
A patch rewrites the image before `Cart.load` sees it. A code rewrites nothing:
the device plugged into the cartridge slot and the cartridge plugged into it, so
it watched the CPU's address bus and drove its own byte onto the data pins in
the cartridge's place. **Hacks > Game Genie...** and `--genie CODE` do the same.

The seam is three lines in `MMU`. `mapper.prgRead` has exactly two callers in
the tree -- `busRead` and `peek` -- which is what makes the hook provably total.
It goes in `busRead` rather than in `read` so that an OAM transfer out of a PRG
page and a DMC sample fetch see it too: the device sits on /ROMSEL and has no
idea which unit inside the 2A03 is driving the address. The substituted byte is
the one assigned to `dataBus`, because it is the Genie driving the pins and open
bus keeps what was last put out. `peek` substitutes as well, so a disassembly is
a listing of the instructions that actually run rather than of ones that never
do -- at the price that the cartridge underneath is no longer readable through
the bus, which `Cart.prgROM` still has.

The field on `MMU` is null when there are no codes, and that is load-bearing
twice. It keeps a machine nobody is cheating on down to a null check on the
hottest line in the emulator, the same argument `writeListener` makes for the
write side. And it is what keeps `SaveStateCompletenessTests` out of the device:
a field holding null is recorded and stepped over, where one holding an object
would be walked into and every array inside it scrambled. `GameGenie` puts the
hook down as its first code arrives and takes it up as its last one goes, the
way `Debugger` does with its watchpoints, so nothing outside has to remember to.

Two things the hardware decides that the code had to follow. One address holds
one code -- the device answers the bus in the time it has and does not get a
second look -- so `add` replaces rather than stacking, deliberately unlike
`IPSPatch.applyTo`, where two records may overlap and the later one wins. And a
code can only reach $8000-$FFFF, because fifteen address bits and /ROMSEL is all
the cartridge port carries; there is no code for cartridge RAM or for anything
below it, and that falls out of the $8000 in the decode rather than needing a
check.

There is no `encode`. Six letters is 24 bits carrying a 15 bit address and an 8
bit value, and eight is 32 carrying those plus a compare, so bit 3 of the third
letter is spare either way and is read by nothing -- `GOSSIP` would come back as
`GOISIP`. Nothing needed the reverse direction, so it is omitted rather than
shipped with a caveat, and a test pins the loss.

`run.genie` is an array rather than a key per code, since the set is open ended
and `run.hacks`'s key-for-key comparison cannot be had. It matters more than the
other three things in that list: a patched run has its own `cart.sha256` and a
cheated one cannot, because the cartridge really is untouched -- so `run.genie`
is the only thing in the whole report that tells the two apart, and a save state
taken with codes in will load into a machine with them out without complaining.
Read back off the machine, so a `genie` part way through an interactive session
is reported as the run ended rather than as it started.

In the window the codes are session-only and per-cartridge. They survive a power
cycle, which is exactly when somebody wants to watch one take effect from the
reset vector, and go when another cartridge does, since a code written for one
game is an arbitrary byte written over the next. The dialog holds the window's
own list rather than reading the device, which the emulation thread is reading
on every instruction; the whole list goes over on each change and is replayed.

`GameGenieRunTests` is the worked example, and it is why the hello-world
cartridge earns its keep twice. It makes **the same edit two ways** -- once as
the `.ips` vendored beside it and once as ten eight-letter codes at the
equivalent CPU addresses -- and asserts the two draw byte-identical pictures,
both different from the plain run. That works because the cartridge is 32KB of
PRG and so unmirrored, which makes file offset n exactly CPU $8000 + (n-16).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dimiro1
dimiro1 merged commit e78906a into master Aug 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant