aboutsummaryrefslogtreecommitdiffstats
path: root/src/gb.c
diff options
context:
space:
mode:
authorgbc dev <gbc@localhost>2026-07-17 00:27:06 +0200
committergbc dev <gbc@localhost>2026-07-17 00:27:06 +0200
commit9a48db9bbc913171c62c86ff8649467474533e2d (patch)
treeaf6d600dc188e43698d17693229265d367d89e48 /src/gb.c
parentmain/gb: --keys button-script harness + serial_no_eof for OSK testing (diff)
downloadsl0pboy-9a48db9bbc913171c62c86ff8649467474533e2d.tar.gz
sl0pboy-9a48db9bbc913171c62c86ff8649467474533e2d.tar.xz
sl0pboy-9a48db9bbc913171c62c86ff8649467474533e2d.zip
gb: boot ROM (BIOS) support
Add optional boot ROM emulation. When a boot ROM image is loaded, gb_reset starts execution at PC=0 with power-on register state instead of the documented post-boot state; the ROM overlays low memory (with the CGB 0x0100-0x01FF header hole showing the cartridge through) until the game writes 0xFF50 to hand off. gb_load_bootrom() accepts 256-byte (DMG) or 2304-byte (CGB) images.
Diffstat (limited to 'src/gb.c')
-rw-r--r--src/gb.c63
1 files changed, 56 insertions, 7 deletions
diff --git a/src/gb.c b/src/gb.c
index fc19e25..2aba2a1 100644
--- a/src/gb.c
+++ b/src/gb.c
@@ -4,6 +4,7 @@
#include "cpu.h"
#include <string.h>
#include <stdio.h>
+#include <stdlib.h>
#include <unistd.h>
void gb_request_interrupt(GB *gb, u8 flag) {
@@ -47,6 +48,14 @@ static void hdma_do_block(GB *gb) {
// ---------- Bus ----------
u8 gb_read(GB *gb, u16 addr) {
+ // Boot ROM overlays low memory until the game disables it via 0xFF50.
+ // CGB layout leaves a hole at 0x0100-0x01FF where the cart header shows
+ // through (the boot logo check reads it); the ROM resumes at 0x0200.
+ if (gb->boot_rom_active) {
+ if (addr < 0x0100) return gb->boot_rom[addr];
+ if (gb->boot_rom_len > 0x0100 && addr >= 0x0200 && addr < gb->boot_rom_len)
+ return gb->boot_rom[addr];
+ }
if (addr < 0x8000) return cart_read(&gb->cart, addr);
if (addr < 0xA000) return ppu_read(gb, addr);
if (addr < 0xC000) return cart_read(&gb->cart, addr);
@@ -153,6 +162,7 @@ void gb_write(GB *gb, u16 addr, u8 val) {
}
if (addr >= 0xFF04 && addr <= 0xFF07) { timer_write(gb, addr, val); return; }
if (addr == 0xFF0F) { gb->iff = val & 0x1F; return; }
+ if (addr == 0xFF50) { if (val) gb->boot_rom_active = false; return; } // unmap BIOS
if (addr >= 0xFF10 && addr <= 0xFF3F) { gb->apu[addr - 0xFF10] = val; return; }
if (addr >= 0xFF40 && addr <= 0xFF4B) {
if (addr == 0xFF46) { oam_dma(gb, val); return; }
@@ -215,20 +225,59 @@ void gb_hdma_hblank(GB *gb) {
}
void gb_init(GB *gb) {
+ u8 *saved_boot = gb->boot_rom; // preserve a boot ROM loaded pre-init
+ int saved_len = gb->boot_rom_len;
memset(gb, 0, sizeof(*gb));
+ gb->boot_rom = saved_boot;
+ gb->boot_rom_len = saved_len;
gb_reset(gb);
}
+int gb_load_bootrom(GB *gb, const char *path) {
+ FILE *f = fopen(path, "rb");
+ if (!f) { perror("open boot rom"); return -1; }
+ fseek(f, 0, SEEK_END);
+ long n = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ if (n != 0x100 && n != 0x800 && n != 0x900) {
+ fprintf(stderr, "boot rom '%s': unexpected size %ld (want 256 for DMG "
+ "or 2304 for CGB)\n", path, n);
+ fclose(f);
+ return -1;
+ }
+ free(gb->boot_rom);
+ gb->boot_rom = malloc(n);
+ if (!gb->boot_rom || fread(gb->boot_rom, 1, n, f) != (size_t)n) {
+ fclose(f); return -1;
+ }
+ gb->boot_rom_len = (int)n;
+ fclose(f);
+ return 0;
+}
+
void gb_reset(GB *gb) {
CPU *c = &gb->cpu;
- gb->cgb_mode = gb->cart.cgb;
- // post-boot register state
- c->pc = 0x0100;
+ bool boot = (gb->boot_rom != NULL);
+ // A CGB boot ROM implies a CGB console; otherwise follow the cartridge.
+ gb->cgb_mode = boot ? (gb->boot_rom_len > 0x100) : gb->cart.cgb;
c->sp = 0xFFFE;
- if (gb->cgb_mode) {
- c->af = 0x1180; c->bc = 0x0000; c->de = 0xFF56; c->hl = 0x000D;
+ if (boot) {
+ // Power-on state: start at 0x0000 inside the boot ROM, which sets up
+ // all registers itself before jumping to the cartridge entry point.
+ gb->boot_rom_active = true;
+ c->pc = 0x0000;
+ c->af = c->bc = c->de = c->hl = 0;
+ c->sp = 0x0000;
} else {
- c->af = 0x01B0; c->bc = 0x0013; c->de = 0x00D8; c->hl = 0x014D;
+ // Skip the boot ROM: jump straight to the game with the documented
+ // post-boot register state.
+ gb->boot_rom_active = false;
+ c->pc = 0x0100;
+ if (gb->cgb_mode) {
+ c->af = 0x1180; c->bc = 0x0000; c->de = 0xFF56; c->hl = 0x000D;
+ } else {
+ c->af = 0x01B0; c->bc = 0x0013; c->de = 0x00D8; c->hl = 0x014D;
+ }
}
c->ime = false;
gb->serial_out_fd = -1;
@@ -238,5 +287,5 @@ void gb_reset(GB *gb) {
gb->iff = 0xE1;
gb->hdma_len = 0xFF;
ppu_reset(gb);
- gb->timer.div = 0x1800;
+ gb->timer.div = boot ? 0x0000 : 0x1800;
}