Skip to content

Memory

Read and write the emulator's address space. Client-side — the ROM lives on the client; calling these on the server has no game to address.

Addresses are u32. Out-of-range addresses trap at runtime. The set of valid ranges (RAM, registers, CHR-ROM, …) is console-specific.

Reading

munos
read_u8(addr: u32): u8
read_u16(addr: u32): u16
read_u32(addr: u32): u32
read_i8(addr: u32): i8
read_i16(addr: u32): i16
read_i32(addr: u32): i32

One builtin per width and signedness. Multi-byte reads use the console's native byte order, so you get the assembled value directly.

munos
const PLAYER_X: u32 = 0x0086
const SCORE:    u32 = 0x07DD

var x     = read_u8(PLAYER_X)    // u8
var score = read_u16(SCORE)      // u16, native byte order

Pick the width that matches how the game stores the value, and the sign that matches its meaning (a value that can go negative — a velocity — wants read_i8).

Writing

munos
write_u8(addr: u32, value: u8)
write_u16(addr: u32, value: u16)
write_u32(addr: u32, value: u32)
write_i8(addr: u32, value: i8)
write_i16(addr: u32, value: i16)
write_i32(addr: u32, value: i32)

Writing mutates the running game's state — the basis of randomizers, difficulty mods, and cheats.

munos
const LIVES: u32 = 0x075A
write_u8(LIVES, 99)

The value argument must match the builtin's width type; cast if needed (write_u8(addr, u8(n))).

write_u8 is the live machine

A write does exactly what the console's CPU does when it stores to that address — full bus, all side effects. Plain RAM takes the byte, but the same call also reaches hardware: video/scroll and sound registers, and the mapper/MBC bank-switch window ($0000–$7FFF on Game Boy, $8000–$FFFF on NES). That range is not the cartridge's bytes — writing there switches banks and toggles cart-RAM, exactly as on hardware. There is no restricted region: write_u8 gives you the whole address space.

The flip side of that power: a stray write to a hardware register or the mapper can wedge or crash the running game. Poke registers deliberately.

To edit the cartridge image itself — NOP a branch, swap an opcode permanently — use write_rom, which addresses a physical bank/offset rather than a live CPU address. Rule of thumb: write_u8 is the live machine, write_rom is the cartridge.

Casting reads

Reads come back in their natural width (read_u8u8), but most arithmetic and most other builtins want i32. Casting at the read site is idiomatic:

munos
var x = i32(read_u8(PLAYER_X))   // ready for i32 math

See Types → Conversions.

Identifying the ROM

These builtins read the mapped CPU address space, not the cartridge file itself. To identify which game is loaded, use local_rom_hash().

Part of the MultiNostalgia project.