#include "gb.h" #include "ppu.h" #include "timer.h" #include "cpu.h" #include #include void gb_request_interrupt(GB *gb, u8 flag) { gb->iff |= flag; } // ---------- Joypad ---------- static u8 joyp_read(GB *gb) { u8 r = 0xCF | gb->joyp_sel; // sel bit4=directions, bit5=buttons ; 0 = selected. Pressed = 0. if (!(gb->joyp_sel & 0x10)) { // directions if (gb->buttons & BTN_RIGHT) r &= ~0x01; if (gb->buttons & BTN_LEFT) r &= ~0x02; if (gb->buttons & BTN_UP) r &= ~0x04; if (gb->buttons & BTN_DOWN) r &= ~0x08; } if (!(gb->joyp_sel & 0x20)) { // buttons if (gb->buttons & BTN_A) r &= ~0x01; if (gb->buttons & BTN_B) r &= ~0x02; if (gb->buttons & BTN_SELECT) r &= ~0x04; if (gb->buttons & BTN_START) r &= ~0x08; } return r; } // ---------- OAM DMA ---------- static void oam_dma(GB *gb, u8 val) { gb->dma = val; u16 src = val << 8; for (int i = 0; i < 0xA0; i++) gb->ppu.oam[i] = gb_read(gb, src + i); } // ---------- HDMA (CGB) ---------- static void hdma_do_block(GB *gb) { for (int i = 0; i < 0x10; i++) { u8 v = gb_read(gb, gb->hdma_src++); ppu_write(gb, 0x8000 + (gb->hdma_dst++ & 0x1FFF), v); } } // ---------- Bus ---------- u8 gb_read(GB *gb, u16 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); if (addr < 0xD000) return gb->wram[addr - 0xC000]; if (addr < 0xE000) { int bank = gb->cgb_mode ? (gb->wram_bank ? gb->wram_bank : 1) : 1; return gb->wram[bank * 0x1000 + (addr - 0xD000)]; } if (addr < 0xFE00) return gb_read(gb, addr - 0x2000); // echo if (addr < 0xFEA0) return ppu_read(gb, addr); if (addr < 0xFF00) return 0xFF; // unusable // IO if (addr == 0xFF00) return joyp_read(gb); if (addr == 0xFF01) return gb->sb; if (addr == 0xFF02) return gb->sc | 0x7E; if (addr >= 0xFF04 && addr <= 0xFF07) return timer_read(gb, addr); if (addr == 0xFF0F) return gb->iff | 0xE0; if (addr >= 0xFF10 && addr <= 0xFF3F) return 0xFF; // APU (stub) if (addr >= 0xFF40 && addr <= 0xFF4B) { if (addr == 0xFF46) return gb->dma; return ppu_read_reg(gb, addr); } if (addr == 0xFF4D) return gb->cgb_mode ? (gb->key1 | 0x7E) : 0xFF; if (addr == 0xFF4F) return gb->cgb_mode ? (gb->ppu.vram_bank | 0xFE) : 0xFF; if (addr == 0xFF51 && gb->cgb_mode) return gb->hdma_src >> 8; if (addr == 0xFF52 && gb->cgb_mode) return gb->hdma_src & 0xFF; if (addr == 0xFF53 && gb->cgb_mode) return gb->hdma_dst >> 8; if (addr == 0xFF54 && gb->cgb_mode) return gb->hdma_dst & 0xFF; if (addr == 0xFF55 && gb->cgb_mode) return gb->hdma_active ? gb->hdma_len : 0xFF; if ((addr == 0xFF68 || addr == 0xFF69 || addr == 0xFF6A || addr == 0xFF6B) && gb->cgb_mode) return ppu_read_reg(gb, addr); if (addr == 0xFF70) return gb->cgb_mode ? (gb->wram_bank | 0xF8) : 0xFF; if (addr >= 0xFF80 && addr < 0xFFFF) return gb->hram[addr - 0xFF80]; if (addr == 0xFFFF) return gb->ie; return 0xFF; } void gb_write(GB *gb, u16 addr, u8 val) { if (addr < 0x8000) { cart_write(&gb->cart, addr, val); return; } if (addr < 0xA000) { ppu_write(gb, addr, val); return; } if (addr < 0xC000) { cart_write(&gb->cart, addr, val); return; } if (addr < 0xD000) { gb->wram[addr - 0xC000] = val; return; } if (addr < 0xE000) { int bank = gb->cgb_mode ? (gb->wram_bank ? gb->wram_bank : 1) : 1; gb->wram[bank * 0x1000 + (addr - 0xD000)] = val; return; } if (addr < 0xFE00) { gb_write(gb, addr - 0x2000, val); return; } if (addr < 0xFEA0) { ppu_write(gb, addr, val); return; } if (addr < 0xFF00) return; if (addr == 0xFF00) { gb->joyp_sel = val & 0x30; return; } if (addr == 0xFF01) { gb->sb = val; return; } if (addr == 0xFF02) { gb->sc = val; if ((val & 0x81) == 0x81) { // transfer with internal clock: log & complete instantly if (gb->serial_log) { fputc(gb->sb, stderr); fflush(stderr); } gb->sb = 0xFF; gb->sc &= ~0x80; gb_request_interrupt(gb, INT_SERIAL); } return; } if (addr >= 0xFF04 && addr <= 0xFF07) { timer_write(gb, addr, val); return; } if (addr == 0xFF0F) { gb->iff = val & 0x1F; return; } if (addr >= 0xFF10 && addr <= 0xFF3F) return; // APU stub if (addr >= 0xFF40 && addr <= 0xFF4B) { if (addr == 0xFF46) { oam_dma(gb, val); return; } ppu_write_reg(gb, addr, val); return; } if (addr == 0xFF4D && gb->cgb_mode) { gb->key1 = (gb->key1 & 0x80) | (val & 1); return; } if (addr == 0xFF4F && gb->cgb_mode) { gb->ppu.vram_bank = val & 1; return; } if (addr == 0xFF51 && gb->cgb_mode) { gb->hdma_src = (gb->hdma_src & 0xFF) | (val << 8); return; } if (addr == 0xFF52 && gb->cgb_mode) { gb->hdma_src = (gb->hdma_src & 0xFF00) | (val & 0xF0); return; } if (addr == 0xFF53 && gb->cgb_mode) { gb->hdma_dst = (gb->hdma_dst & 0xFF) | ((val & 0x1F) << 8); return; } if (addr == 0xFF54 && gb->cgb_mode) { gb->hdma_dst = (gb->hdma_dst & 0xFF00) | (val & 0xF0); return; } if (addr == 0xFF55 && gb->cgb_mode) { int len = ((val & 0x7F) + 1) * 0x10; if (val & 0x80) { // HDMA (mode 1): transfer during hblank gb->hdma_len = val & 0x7F; gb->hdma_active = true; } else { if (gb->hdma_active) { // stopping an active hblank dma gb->hdma_active = false; gb->hdma_len = 0xFF; } else { // GDMA: transfer all now for (int i = 0; i < len; i++) { u8 v = gb_read(gb, gb->hdma_src++); ppu_write(gb, 0x8000 + (gb->hdma_dst++ & 0x1FFF), v); } } } return; } if ((addr >= 0xFF68 && addr <= 0xFF6B) && gb->cgb_mode) { ppu_write_reg(gb, addr, val); return; } if (addr == 0xFF70 && gb->cgb_mode) { gb->wram_bank = val & 0x07; return; } if (addr >= 0xFF80 && addr < 0xFFFF) { gb->hram[addr - 0xFF80] = val; return; } if (addr == 0xFFFF) { gb->ie = val; return; } } // advance subsystems by t-cycles void gb_tick(GB *gb, int tcycles) { // in double-speed mode CPU/timer run 2x, but PPU stays at normal rate. int ppu_cycles = gb->double_speed ? tcycles / 2 : tcycles; timer_tick(gb, tcycles); ppu_tick(gb, ppu_cycles); gb->cycles += tcycles; } // HDMA one block per hblank; called by ppu when entering hblank void gb_hdma_hblank(GB *gb) { if (!gb->hdma_active) return; hdma_do_block(gb); if (gb->hdma_len == 0) { gb->hdma_active = false; gb->hdma_len = 0xFF; } else { gb->hdma_len--; } } void gb_init(GB *gb) { memset(gb, 0, sizeof(*gb)); gb_reset(gb); } void gb_reset(GB *gb) { CPU *c = &gb->cpu; gb->cgb_mode = gb->cart.cgb; // post-boot register state c->pc = 0x0100; c->sp = 0xFFFE; 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->wram_bank = 1; gb->joyp_sel = 0x30; gb->iff = 0xE1; gb->hdma_len = 0xFF; ppu_reset(gb); gb->timer.div = 0x1800; }