Chapter 3: alttp, reading Hyrule out of 128 KiB

Chapter 2 gave us the machine's work RAM: 131,072 bytes that change sixty times a second. Somewhere in there is everything the game knows. Byte $7E0010 says which part of the game is running (the title, the overworld, a dungeon, a text box). $7E0022 and $7E0020 are Link's position in pixels. $7EF36D is his health, in eighths of a heart, because that is how the HUD draws half and quarter hearts.

This crate turns those bytes into names. Hand it the whole of work RAM and you get a State back:

let state = alttp::State::read(console.wram());
state.mode      // Mode::Overworld
state.step      // what that mode is doing, by the game's own table
state.world     // World::Light or World::Dark
state.place     // Place::Outdoors(area) or Place::Indoors(room)
state.hearts()  // 3.5
state.control   // whether the pad moves Link this very frame

It is Serialize, so the same value is what the window's State panel shows (chapter 7) and what the MCP state tool returns (chapter 8).

Aside: where do the names come from? Not from guessing. The game was taken apart long ago by the romhacking community, and snesrev/zelda3 is a C reimplementation that compiles to a bit-exact match of the real ROM. A mode's name here is the name of the routine the game's own dispatch table jumps to for that byte, and each table is cited on the type it produced. A byte with no table behind it stays Unrecognised(byte) or Numbered, carrying its value, rather than getting a plausible name.

The most careful function in here is has_control. "Link is in a walking mode" is not the same as "the player's buttons move him": four other flags (a spell animation, an immobilised flag, a menu block, and on the overworld a special-entrance trigger) can each hold him in place. The rule is copied out of the two functions in the game that enforce it, and a test sets each flag in turn to prove each one takes control away.

Mode::kind sorts every mode into what a caller has to do about it (play, an overlay, a title, a load that ends by itself, ...), and it has no wildcard arm: a new mode is a compile error until someone says which kind it is.

Try it. nix develop -c buck2 test //packages/alttp:test runs the control-gate tests, the text-box test and the opening-flags test. No ROM needed: they build work RAM by hand.

For the people who maintain it

In this folder

PathWhat
src/lib.rsState::read and its parts: Mode and Kind, Step and Interface, World, Place, Player (Link's state machine), Progress, Opening, Follower, Direction; the control gate; the tests.
BUCKThe rust_library (its only dependency is serde) and //packages/alttp:test.

The addresses and what they mean come from research/alttp-ram-map.md, which cites the disassemblies it read them from. Who reads a State: packages/panels, packages/mcp, packages/zbanks (its stall detector uses Place), and the apps.

← Previous: Chapter 2, console/ · Up: packages · Next: Chapter 4, zbanks/ →