HareBoy API Reference

Index

Types

type apu = struct {
	ch1: square_ch,
	ch2: square_ch,
	ch3: wave_ch,
	ch4: noise_ch,
	nr50: u8,
	nr51: u8,
	nr52: u8,
	frame_seq_timer: i32,
	frame_seq_step: i32,
	sample_timer: i32,
	sample_period: i32,
	// Audio output buffer.
	audio_buf: [2048]i16,
	// stereo pairs
	audio_len: size,
	audio_dev: u32,
};
type cartridge = struct {
	mbc: mbc_type,
	rom: []u8,
	ram: []u8,
	rom_bank: uint,
	ram_bank: uint,
	ram_enabled: bool,
	// MBC1 only: 0=ROM, 1=RAM
	banking_mode: uint,
	// MBC1 only
	mbc1_low5: uint,
	// MBC1 only
	mbc1_high2: uint,
	rom_bank_mask: uint,
	ram_bank_mask: uint,
};
type cpu = struct {
	a: u8,
	f: u8,
	b: u8,
	c: u8,
	d: u8,
	e: u8,
	h: u8,
	l: u8,
	pc: u16,
	sp: u16,
	halted: bool,
	locked: bool,
	ime: bool,
	ime_scheduled: bool,
	halt_bug: bool,
};
type gameboy = struct {
	cpu: cpu,
	mmu: mmu,
	ppu: ppu,
	tmr: timer,
	joy: joypad,
	cart: cartridge,
	snd: apu,
	total_cycles: u64,
	frame_count: u64,
	leftover_cycles: i32,
};
type joy_key = enum {
	RIGHT,
	LEFT,
	UP,
	DOWN,
	A,
	B,
	START,
	SELECT,
};
type joypad = struct {
	up: bool,
	down: bool,
	left: bool,
	right: bool,
	a: bool,
	b: bool,
	start: bool,
	select: bool,
	select_buttons: bool,
	select_dpad: bool,
};
type mbc_type = enum {
	NONE,
	MBC1,
	MBC3,
	MBC5,
};
type mmu = struct {
	// Peripheral pointers are nullable because the mmu is
	// constructed inside [[gameboy]] before stable addresses
	// exist for sibling fields.  [[init]] sets them once the
	// parent struct is placed; after that they are always
	// non-null.
	cart: nullable *cartridge,
	tmr: nullable *timer,
	joy: nullable *joypad,
	snd: nullable *apu,
	// Work RAM (0xC000-0xDFFF)
	wram: [8192]u8,
	// Video RAM (0x8000-0x9FFF)
	vram: [8192]u8,
	// OAM (0xFE00-0xFE9F)
	oam: [160]u8,
	// High RAM (0xFF80-0xFFFE)
	hram: [127]u8,
	// I/O registers (0xFF00-0xFF7F)
	io: [128]u8,
	// Interrupt Enable (0xFFFF)
	ie: u8,
	// Serial output for test ROMs.
	serial_buffer: [256]u8,
	serial_len: size,
	// OAM DMA state.
	dma_active: bool,
	dma_source: u16,
	dma_index: uint,
	dma_delay: i32,
	dma_cycles: i32,
};
type noise_ch = struct {
	enabled: bool,
	dac_enabled: bool,
	length_counter: i32,
	length_enabled: bool,
	volume: i32,
	volume_init: i32,
	volume_env_add: bool,
	volume_env_per: i32,
	volume_env_timer: i32,
	clock_shift: i32,
	width_mode: bool,
	divisor_code: i32,
	freq_timer: i32,
	lfsr: u16,
};
type ppu = struct {
	framebuffer: [SCREEN_W * SCREEN_H]u8,
	bg_color_idx: [SCREEN_W * SCREEN_H]u8,
	scanline: uint,
	scanline_cycles: uint,
	window_line: uint,
	window_was_on: bool,
	lcd_enabled: bool,
	stat_line_high: bool,
};
type square_ch = struct {
	enabled: bool,
	dac_enabled: bool,
	duty: i32,
	duty_pos: i32,
	length_counter: i32,
	length_enabled: bool,
	volume: i32,
	volume_init: i32,
	volume_env_add: bool,
	volume_env_per: i32,
	volume_env_timer: i32,
	frequency: i32,
	freq_timer: i32,
	// Sweep (CH1 only)
	sweep_enabled: bool,
	sweep_period: i32,
	sweep_negate: bool,
	sweep_shift: i32,
	sweep_timer: i32,
	sweep_shadow: i32,
	sweep_calc_done: bool,
};
type timer = struct {
	// Timer counter (0xFF05)
	tima: u8,
	// Timer modulo (0xFF06)
	tma: u8,
	// Timer control (0xFF07)
	tac: u8,
	// Internal divider; DIV reads upper 8 bits
	div: u16,
	reload_delay: i32,
	reload_cycle_b: i32,
};
type wave_ch = struct {
	enabled: bool,
	dac_enabled: bool,
	length_counter: i32,
	length_enabled: bool,
	volume_code: i32,
	frequency: i32,
	freq_timer: i32,
	sample_pos: i32,
	sample_buffer: u8,
	wave_ram: [16]u8,
};

Errors

type audio_error = !void;

Constants

def SCREEN_H: size = 144;
def SCREEN_W: size = 160;

Globals

const PALETTE: [4][4]u8;

Functions

fn apu_close(a: *apu) void;
fn apu_flush(a: *apu) void;
fn apu_init_audio(a: *apu) (void | audio_error);
fn apu_read(a: *apu, addr: u16) u8;
fn apu_update(a: *apu, cycles: i32) void;
fn apu_write(a: *apu, addr: u16, value: u8) void;
fn audio_strerror(err: audio_error) str;
fn cart_free(c: *cartridge) void;
fn cart_read_ram(c: *cartridge, addr: u16) u8;
fn cart_read_rom(c: *cartridge, addr: u16) u8;
fn cart_write_control(c: *cartridge, addr: u16, val: u8) void;
fn cart_write_ram(c: *cartridge, addr: u16, val: u8) void;
fn cpu_step(c: *cpu, m: *mmu) i32;
fn finish(gb: *gameboy) void;
fn init(gb: *gameboy) void;
fn joypad_read(j: *joypad) u8;
fn joypad_set_key(j: *joypad, key: joy_key, pressed: bool) bool;
fn joypad_write(j: *joypad, value: u8) bool;
fn mmu_read(m: *mmu, addr: u16) u8;
fn mmu_tick_dma(m: *mmu, cycles: i32) void;
fn mmu_write(m: *mmu, addr: u16, value: u8) void;
fn newapu() apu;
fn newcart(rom: []u8) cartridge;
fn newgameboy(rom: []u8) gameboy;
fn newtimer() timer;
fn ppu_update(g: *ppu, m: *mmu) void;
fn run_frame(gb: *gameboy) void;
fn timer_read(t: *timer, addr: u16) u8;
fn timer_update(t: *timer, cycles: i32) bool;
fn timer_write(t: *timer, addr: u16, value: u8) void;

// Undocumented functions:
fn main() void;

Types

type apu[link]

type apu = struct {
	ch1: square_ch,
	ch2: square_ch,
	ch3: wave_ch,
	ch4: noise_ch,
	nr50: u8,
	nr51: u8,
	nr52: u8,
	frame_seq_timer: i32,
	frame_seq_step: i32,
	sample_timer: i32,
	sample_period: i32,
	// Audio output buffer.
	audio_buf: [2048]i16,
	// stereo pairs
	audio_len: size,
	audio_dev: u32,
};

Audio processing unit with four sound channels and an output sample buffer.

type cartridge[link]

type cartridge = struct {
	mbc: mbc_type,
	rom: []u8,
	ram: []u8,
	rom_bank: uint,
	ram_bank: uint,
	ram_enabled: bool,
	// MBC1 only: 0=ROM, 1=RAM
	banking_mode: uint,
	// MBC1 only
	mbc1_low5: uint,
	// MBC1 only
	mbc1_high2: uint,
	rom_bank_mask: uint,
	ram_bank_mask: uint,
};

ROM cartridge with optional MBC and external RAM.

type cpu[link]

type cpu = struct {
	a: u8,
	f: u8,
	b: u8,
	c: u8,
	d: u8,
	e: u8,
	h: u8,
	l: u8,
	pc: u16,
	sp: u16,
	halted: bool,
	locked: bool,
	ime: bool,
	ime_scheduled: bool,
	halt_bug: bool,
};

CPU register file and execution state.

type gameboy[link]

type gameboy = struct {
	cpu: cpu,
	mmu: mmu,
	ppu: ppu,
	tmr: timer,
	joy: joypad,
	cart: cartridge,
	snd: apu,
	total_cycles: u64,
	frame_count: u64,
	leftover_cycles: i32,
};

Top-level emulator state holding all subsystems.

type joy_key[link]

type joy_key = enum {
	RIGHT,
	LEFT,
	UP,
	DOWN,
	A,
	B,
	START,
	SELECT,
};

Joypad key identifiers for joypad_set_key.

type joypad[link]

type joypad = struct {
	up: bool,
	down: bool,
	left: bool,
	right: bool,
	a: bool,
	b: bool,
	start: bool,
	select: bool,
	select_buttons: bool,
	select_dpad: bool,
};

Joypad button and selection state.

type mbc_type[link]

type mbc_type = enum {
	NONE,
	MBC1,
	MBC3,
	MBC5,
};

Memory bank controller type.

type mmu[link]

type mmu = struct {
	// Peripheral pointers are nullable because the mmu is
	// constructed inside [[gameboy]] before stable addresses
	// exist for sibling fields.  [[init]] sets them once the
	// parent struct is placed; after that they are always
	// non-null.
	cart: nullable *cartridge,
	tmr: nullable *timer,
	joy: nullable *joypad,
	snd: nullable *apu,
	// Work RAM (0xC000-0xDFFF)
	wram: [8192]u8,
	// Video RAM (0x8000-0x9FFF)
	vram: [8192]u8,
	// OAM (0xFE00-0xFE9F)
	oam: [160]u8,
	// High RAM (0xFF80-0xFFFE)
	hram: [127]u8,
	// I/O registers (0xFF00-0xFF7F)
	io: [128]u8,
	// Interrupt Enable (0xFFFF)
	ie: u8,
	// Serial output for test ROMs.
	serial_buffer: [256]u8,
	serial_len: size,
	// OAM DMA state.
	dma_active: bool,
	dma_source: u16,
	dma_index: uint,
	dma_delay: i32,
	dma_cycles: i32,
};

Memory management unit. Routes bus reads and writes to the cartridge, timer, joypad, and apu via internal pointers set up by init.

type noise_ch[link]

type noise_ch = struct {
	enabled: bool,
	dac_enabled: bool,
	length_counter: i32,
	length_enabled: bool,
	volume: i32,
	volume_init: i32,
	volume_env_add: bool,
	volume_env_per: i32,
	volume_env_timer: i32,
	clock_shift: i32,
	width_mode: bool,
	divisor_code: i32,
	freq_timer: i32,
	lfsr: u16,
};

Noise channel state (CH4).

type ppu[link]

type ppu = struct {
	framebuffer: [SCREEN_W * SCREEN_H]u8,
	bg_color_idx: [SCREEN_W * SCREEN_H]u8,
	scanline: uint,
	scanline_cycles: uint,
	window_line: uint,
	window_was_on: bool,
	lcd_enabled: bool,
	stat_line_high: bool,
};

PPU state including framebuffer, scanline counters, and window tracking.

type square_ch[link]

type square_ch = struct {
	enabled: bool,
	dac_enabled: bool,
	duty: i32,
	duty_pos: i32,
	length_counter: i32,
	length_enabled: bool,
	volume: i32,
	volume_init: i32,
	volume_env_add: bool,
	volume_env_per: i32,
	volume_env_timer: i32,
	frequency: i32,
	freq_timer: i32,
	// Sweep (CH1 only)
	sweep_enabled: bool,
	sweep_period: i32,
	sweep_negate: bool,
	sweep_shift: i32,
	sweep_timer: i32,
	sweep_shadow: i32,
	sweep_calc_done: bool,
};

Square wave channel state (used by CH1 and CH2).

type timer[link]

type timer = struct {
	// Timer counter (0xFF05)
	tima: u8,
	// Timer modulo (0xFF06)
	tma: u8,
	// Timer control (0xFF07)
	tac: u8,
	// Internal divider; DIV reads upper 8 bits
	div: u16,
	reload_delay: i32,
	reload_cycle_b: i32,
};

Timer subsystem state.

type wave_ch[link]

type wave_ch = struct {
	enabled: bool,
	dac_enabled: bool,
	length_counter: i32,
	length_enabled: bool,
	volume_code: i32,
	frequency: i32,
	freq_timer: i32,
	sample_pos: i32,
	sample_buffer: u8,
	wave_ram: [16]u8,
};

Wave channel state (CH3).

Errors

type audio_error[link]

type audio_error = !void;

Returned when apu_init_audio cannot open the SDL audio device.

Constants

def SCREEN_H[link]

def SCREEN_H: size = 144;

Screen height in pixels.

def SCREEN_W[link]

def SCREEN_W: size = 160;

Screen width in pixels.

Globals

let PALETTE[link]

const PALETTE: [4][4]u8;

DMG shade palette: index 0-3 -> RGBA (greenish Game Boy colors).

Functions

fn apu_close[link]

fn apu_close(a: *apu) void;

Closes the audio device.

fn apu_flush[link]

fn apu_flush(a: *apu) void;

Flushes buffered audio samples to SDL via apu_init_audio's device.

fn apu_init_audio[link]

fn apu_init_audio(a: *apu) (void | audio_error);

Opens the SDL audio device for playback.

fn apu_read[link]

fn apu_read(a: *apu, addr: u16) u8;

Reads an apu register (0xFF10-0xFF3F).

fn apu_update[link]

fn apu_update(a: *apu, cycles: i32) void;

Advances the apu by the given number of T-cycles.

fn apu_write[link]

fn apu_write(a: *apu, addr: u16, value: u8) void;

Writes to an apu register (0xFF10-0xFF3F).

fn audio_strerror[link]

fn audio_strerror(err: audio_error) str;

Returns a human-readable message for an audio_error.

fn cart_free[link]

fn cart_free(c: *cartridge) void;

Frees the RAM allocated by cart_init_ram. Must be called when the cartridge is no longer needed.

fn cart_read_ram[link]

fn cart_read_ram(c: *cartridge, addr: u16) u8;

Reads from external RAM.

fn cart_read_rom[link]

fn cart_read_rom(c: *cartridge, addr: u16) u8;

Reads from ROM with bank switching.

fn cart_write_control[link]

fn cart_write_control(c: *cartridge, addr: u16, val: u8) void;

Handles MBC register writes (0x0000-0x7FFF).

fn cart_write_ram[link]

fn cart_write_ram(c: *cartridge, addr: u16, val: u8) void;

Writes to external RAM.

fn cpu_step[link]

fn cpu_step(c: *cpu, m: *mmu) i32;

Execute one instruction. Reads from mmu_read and writes via mmu_write. Returns T-cycles consumed.

fn finish[link]

fn finish(gb: *gameboy) void;

Frees resources owned by the gameboy.

fn init[link]

fn init(gb: *gameboy) void;

Links the mmu bus to its peripheral components. Must be called once after newgameboy, before calling run_frame.

fn joypad_read[link]

fn joypad_read(j: *joypad) u8;

Returns the JOYP register value based on current button state.

fn joypad_set_key[link]

fn joypad_set_key(j: *joypad, key: joy_key, pressed: bool) bool;

Updates one joypad key and returns true when this change should request INT_JOYPAD.

fn joypad_write[link]

fn joypad_write(j: *joypad, value: u8) bool;

Sets the JOYP selection bits from a write to 0xFF00.

fn mmu_read[link]

fn mmu_read(m: *mmu, addr: u16) u8;

Reads a byte from the memory bus. Routes to cartridge, timer, joypad, or apu as appropriate for the given address.

fn mmu_tick_dma[link]

fn mmu_tick_dma(m: *mmu, cycles: i32) void;

Advances an active OAM DMA transfer by the given number of T-cycles.

fn mmu_write[link]

fn mmu_write(m: *mmu, addr: u16, value: u8) void;

Writes a byte to the memory bus. Routes to cartridge, timer, joypad, or apu as appropriate for the given address.

fn newapu[link]

fn newapu() apu;

Creates an apu initialized to DMG post-boot state.

fn newcart[link]

fn newcart(rom: []u8) cartridge;

Creates a cartridge from ROM data. The caller retains ownership of rom.

fn newgameboy[link]

fn newgameboy(rom: []u8) gameboy;

Creates a new gameboy initialized to DMG post-boot state. The caller must call init before run_frame.

fn newtimer[link]

fn newtimer() timer;

Creates a timer initialized to DMG post-boot state.

fn ppu_update[link]

fn ppu_update(g: *ppu, m: *mmu) void;

Advances the ppu by one T-cycle. Updates STAT, LY, and triggers scanline rendering and INT_VBLANK / INT_STAT interrupts as needed.

fn run_frame[link]

fn run_frame(gb: *gameboy) void;

Runs one frame (70224 T-cycles) of emulation. Carries over excess cycles from the previous frame so the PPU stays aligned with presentation and never overwrites the framebuffer with next-frame content.

fn timer_read[link]

fn timer_read(t: *timer, addr: u16) u8;

Returns the value of a timer register.

fn timer_update[link]

fn timer_update(t: *timer, cycles: i32) bool;

Advances the timer by n T-cycles. Returns true if an INT_TIMER interrupt should fire.

fn timer_write[link]

fn timer_write(t: *timer, addr: u16, value: u8) void;

Sets a timer register value.

fn main[link]

Show undocumented member
fn main() void;