diff options
| -rw-r--r-- | src/gb.c | 63 | ||||
| -rw-r--r-- | src/gb.h | 10 |
2 files changed, 66 insertions, 7 deletions
@@ -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; } @@ -118,10 +118,20 @@ struct GB { int serial_out_fd; // if >=0, transmitted serial bytes written here (stdout) int serial_in_fd; // if >=0, received serial bytes read here (stdin), else 0xFF bool poweroff; // set by the $ED opcode: request a clean emulator exit + + // Boot ROM (BIOS). When boot_rom is non-NULL, gb_reset starts execution + // inside it (PC=0) with power-on register state; the ROM maps over low + // memory until the game writes 0xFF50 to hand control to the cartridge. + u8 *boot_rom; + int boot_rom_len; // 0x100 = DMG boot rom, 0x900 = CGB boot rom + bool boot_rom_active; // true while the boot ROM is still mapped in }; void gb_init(GB *gb); void gb_reset(GB *gb); +// Load a boot ROM (BIOS) image so the next gb_reset boots through it. Returns +// 0 on success. 256-byte images boot as DMG; 2304-byte images as CGB. +int gb_load_bootrom(GB *gb, const char *path); // Run one full CPU instruction (advancing all subsystems). Returns T-cycles. int gb_step(GB *gb); void gb_run_frame(GB *gb); |
