Reading the game
A mod becomes interesting the moment it knows what's happening inside the game. In Munos you find that out by reading the console's memory — the same RAM the game itself reads and writes every frame.
Memory is an address space
A retro console keeps its live state — player position, score, lives, which enemies are alive — at fixed numeric addresses in RAM. If you know that Super Mario Bros. stores Mario's on-screen X position at address 0x0086, you can read it:
var mario_x = read_u8(0x0086)read_u8 reads one unsigned byte at the given address. The result is a u8 (0–255).
Naming your addresses
Magic numbers scattered through code are hard to read. The convention is to name every address as a const at the top of the file:
const PLAYER_X: u32 = 0x0086 // Mario's screen X
const PLAYER_Y: u32 = 0x00CE // Mario's screen Y
const LIVES: u32 = 0x075A // remaining livesAddresses are u32 — wide enough to point anywhere in any console's address space. A const can't be reassigned, which is exactly what you want for a fixed hardware address.
Now the reads explain themselves:
event frame(frame_num: i32) {
var x = read_u8(PLAYER_X)
var y = read_u8(PLAYER_Y)
var lives = read_u8(LIVES)
print("Mario is at ", x, ",", y, " with ", lives, " lives")
}Where do the addresses come from?
Memory maps are game- and even region-specific. Modders find them in community RAM maps (the Data Crystal wiki is a common source), in disassemblies, or by watching memory change as they play. Each script keeps its own table of the addresses it cares about.
Reading wider values
A single byte only holds 0–255. Many values — a 16-bit X coordinate, a score — span several bytes. Munos has a read builtin for each width and signedness:
read_u8(addr) read_u16(addr) read_u32(addr) // unsigned
read_i8(addr) read_i16(addr) read_i32(addr) // signedMulti-byte reads use the console's native byte order, so you don't have to assemble bytes yourself:
const SCORE: u32 = 0x07DD
var score = read_u16(SCORE) // both bytes, correctly orderedPick the width that matches how the game stores the value, and the signedness that matches what it means (a velocity that can go negative wants read_i8).
Integer types, briefly
Every value in Munos has a fixed-width integer type:
| Type | Bits | Range |
|---|---|---|
u8 | 8 | 0 … 255 |
u16 | 16 | 0 … 65 535 |
u32 | 32 | 0 … ~4.3 billion |
i8 | 8 | −128 … 127 |
i16 | 16 | −32 768 … 32 767 |
i32 | 32 | ~−2.1 … 2.1 billion |
There are no floats — integers only. And there's a rule that trips up newcomers: Munos never converts between types automatically. You can't add a u8 to an i32 directly. When you need a value in a different type, you cast explicitly:
var raw: u8 = read_u8(PLAYER_X) // a u8
var x: i32 = i32(raw) // cast it to i32 to do i32 mathThe cast functions are named after their target type: i8(x), i16(x), i32(x), u8(x), u16(x), u32(x). You'll cast often — most reads come back as u8, but most arithmetic and most builtins want i32. That's normal Munos style, not a smell.
Writing memory, too
The same family exists for writing. Writing game memory lets a mod change the game live — a randomizer, a difficulty tweak, an infinite-lives cheat:
write_u8(LIVES, 99) // set lives to 99There's a write_* for each width: write_u8/u16/u32 and write_i8/i16/i32. Use writes with care — you're reaching into the running game's state.
Putting it together
Here's a mod that watches the player's position and prints it only when it changes, using a shared variable to remember the last value:
const PLAYER_X: u32 = 0x0086
var last_x: i32 = -1
event frame(frame_num: i32) {
var x = i32(read_u8(PLAYER_X))
if x != last_x {
print("x changed to ", x)
last_x = x
}
}Note last_x is declared at the top level but outside the event — so it persists across frames. A var inside the event body would reset every frame.
What you learned
- Game state lives at fixed addresses;
read_u8(addr)reads a byte. - Name addresses as
const … : u32at the top of the file. - There's a read/write builtin per width and sign:
read_u8…read_i32,write_u8…write_i32. - Munos is integer-only and never auto-converts; cast with
i32(x)etc. - A
varoutside an event persists; one inside resets each call.
Next
Now you can find out where the player is. Let's draw something meaningful there.