#ifndef GBC_GB_H #define GBC_GB_H #include "types.h" #include "cart.h" // Interrupt bit flags (IE/IF at 0xFFFF / 0xFF0F) enum { INT_VBLANK = 0x01, INT_LCDSTAT= 0x02, INT_TIMER = 0x04, INT_SERIAL = 0x08, INT_JOYPAD = 0x10, }; // Joypad button bits (as used by our input state; 1 = pressed) enum { BTN_A = 0x01, BTN_B = 0x02, BTN_SELECT = 0x04, BTN_START = 0x08, BTN_RIGHT = 0x10, BTN_LEFT = 0x20, BTN_UP = 0x40, BTN_DOWN = 0x80, }; #define SCREEN_W 160 #define SCREEN_H 144 typedef struct GB GB; // ---- CPU (Sharp SM83) ---- typedef struct { union { struct { u8 f, a; }; u16 af; }; union { struct { u8 c, b; }; u16 bc; }; union { struct { u8 e, d; }; u16 de; }; union { struct { u8 l, h; }; u16 hl; }; u16 sp, pc; bool ime; // interrupt master enable bool ime_pending; // EI delayed enable bool halted; bool halt_bug; bool stopped; } CPU; // ---- PPU ---- typedef struct { u8 vram[0x4000]; // 2 banks (CGB) u8 oam[0xA0]; u8 vram_bank; // CGB // registers u8 lcdc, stat, scy, scx, ly, lyc, wy, wx, bgp, obp0, obp1; u8 vbk; // vram bank select reg (bit0) // CGB palettes u8 bcps, ocps; u8 bg_pal[64]; // 8 palettes * 4 colors * 2 bytes u8 obj_pal[64]; int mode; // 0 hblank,1 vblank,2 oam,3 draw int dots; // dot counter within current line bool frame_ready; u8 wly; // internal window line counter // framebuffer: RGB888 u8 fb[SCREEN_H][SCREEN_W][3]; } PPU; // ---- Timer ---- typedef struct { u16 div; // internal 16-bit counter; DIV is upper 8 bits u8 tima, tma, tac; bool overflow_pending; int reload_delay; } Timer; struct GB { CPU cpu; PPU ppu; Timer timer; Cart cart; u8 wram[0x8000]; // 8 banks of 4KB (CGB); DMG uses 2 u8 hram[0x7F]; u8 wram_bank; // svbk (1-7) u8 ie; // 0xFFFF u8 iff; // 0xFF0F interrupt flags u8 joyp_sel; // 0xFF00 selection bits u8 buttons; // our pressed state (1=pressed) // serial u8 sb, sc; // APU registers 0xFF10-0xFF3F. We don't synthesize sound, but games read // these back (e.g. the music fade-out decrements NR50/rAUDVOL until it // reads 0), so they must behave as real storage, not a 0xFF black hole. u8 apu[0x30]; // DMA u8 dma; // CGB bool cgb_mode; // running in CGB mode u8 key1; // speed switch bool double_speed; // HDMA / GDMA (CGB) u16 hdma_src, hdma_dst; u8 hdma_len; bool hdma_active; u64 cycles; // total T-cycles elapsed bool serial_no_eof; // if true, serial-RX EOF stays pending (don't send $04) bool serial_log; // print serial output to stderr (blargg tests) 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 // Link-over-unix-socket resilience: when serial_sock_path is set, a dead // socket (EOF / EPIPE / ECONNRESET) is closed and reconnected in the // background instead of going silently dead forever. See serial_check(). const char *serial_sock_path; // --serial-sock path (NULL = stdio serial) bool serial_sock_dead; // link socket lost; reconnect pending double serial_retry_at; // CLOCK_MONOTONIC time of next reconnect try 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); // bus access u8 gb_read(GB *gb, u16 addr); void gb_write(GB *gb, u16 addr, u8 val); void gb_tick(GB *gb, int tcycles); // advance ppu/timer/dma by t-cycles void gb_request_interrupt(GB *gb, u8 flag); #endif