use crate::gb::apu::Apu;
use crate::gb::bus::GbBus;
use crate::gb::cartridge::GbCartridge;
use crate::gb::input::joypad::Joypad;
use crate::gb::ppu::Ppu;
use crate::gb::timer::Timer;
pub struct CgbBus {
cart: Box<dyn GbCartridge>,
pub ppu: Ppu,
wram: [u8; 0x2000],
hram: [u8; 0x7F],
timer: Timer,
pub joypad: Joypad,
apu: Apu,
if_reg: u8,
ie_reg: u8,
dma_active: bool,
dma_source: u8,
dma_position: u8,
dma_oam_blocked: bool,
}
impl CgbBus {
pub fn new(cart: Box<dyn GbCartridge>) -> Self {
let is_cgb = cart.is_cgb();
let mut bus = Self {
cart,
ppu: Ppu::new_cgb(),
wram: [0u8; 0x2000],
hram: [0u8; 0x7F],
timer: Timer::new(),
joypad: Joypad::new(),
apu: Apu::new(is_cgb),
if_reg: 0,
ie_reg: 0,
dma_active: false,
dma_source: 0,
dma_position: 0,
dma_oam_blocked: false,
};
bus.ppu.write_register(0xFF40, 0x00);
bus
}
pub fn tick(&mut self, m_cycles: u8) {
self.if_reg |= self.ppu.take_pending_interrupts();
for _ in 0..m_cycles {
self.timer.tick(1);
if self.timer.interrupt_pending {
self.if_reg |= 0x04;
self.timer.interrupt_pending = false;
}
if self.dma_active {
match self.dma_position {
0 => {
self.dma_position = 1;
}
1..=160 => {
self.dma_oam_blocked = true;
let byte_idx = (self.dma_position - 1) as u16;
let src = (self.dma_source as u16) << 8 | byte_idx;
self.ppu.oam[byte_idx as usize] = self.read_raw(src);
self.dma_position += 1;
}
161 => {
self.dma_active = false;
self.dma_oam_blocked = false;
}
_ => unreachable!(),
}
}
}
self.ppu.tick_dots(u32::from(m_cycles) * 4);
self.apu.tick(m_cycles);
}
fn read_raw(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x7FFF => self.cart.read(addr),
0x8000..=0x9FFF => {
let vram_addr = (addr - 0x8000) as usize;
if self.ppu.vbk & 0x01 != 0 {
self.ppu.vram_bank1[vram_addr]
} else {
self.ppu.vram[vram_addr]
}
}
0xA000..=0xBFFF => self.cart.read(addr),
0xC000..=0xDFFF => self.wram[(addr - 0xC000) as usize],
0xE000..=0xFFFF => self.wram[(addr - 0xE000) as usize],
}
}
fn do_oam_dma(&mut self, val: u8) {
let preserve_blocking = self.dma_active && self.dma_oam_blocked;
self.dma_active = true;
self.dma_source = val;
self.dma_position = 0;
self.dma_oam_blocked = preserve_blocking;
}
pub fn serial_output(&self) -> &[u8] {
&[]
}
pub fn set_joypad_button(&mut self, id: u8, pressed: bool) {
if self.joypad.set_button(id, pressed) {
self.if_reg |= 0x10;
}
}
pub fn is_frame_ready(&self) -> bool {
self.ppu.is_frame_ready()
}
pub fn clear_frame_ready(&mut self) {
self.ppu.clear_frame_ready();
}
pub fn sample_ready(&self) -> bool {
self.apu.sample_ready()
}
pub fn take_sample(&mut self) -> Option<f32> {
self.apu.take_sample()
}
pub fn set_audio_sample_rate(&mut self, rate: f32) {
self.apu.set_sample_rate(rate);
}
pub fn reset(&mut self) {
let apu_rate = self.apu.sample_rate();
self.ppu = Ppu::new_cgb();
self.ppu.write_register(0xFF40, 0x00);
self.timer = Timer::new();
self.joypad = Joypad::new();
self.apu = Apu::new(self.cart.is_cgb());
self.apu.set_sample_rate(apu_rate);
self.wram = [0u8; 0x2000];
self.hram = [0u8; 0x7F];
self.if_reg = 0;
self.ie_reg = 0;
self.dma_active = false;
self.dma_source = 0;
self.dma_position = 0;
self.dma_oam_blocked = false;
}
pub fn capture_bus_state(&self) -> crate::gb::console::save_state::BusState {
use crate::gb::console::save_state::{BusState, GbBusType};
BusState {
bus_type: GbBusType::Cgb,
ppu: self.ppu.clone(),
wram: self.wram,
hram: self.hram,
timer: self.timer.clone(),
joypad: self.joypad.clone(),
apu: self.apu.clone(),
if_reg: self.if_reg,
ie_reg: self.ie_reg,
dma_active: self.dma_active,
dma_source: self.dma_source,
dma_position: self.dma_position,
dma_oam_blocked: self.dma_oam_blocked,
boot_rom_active: None,
sb: None,
sc: None,
serial_buf: None,
serial_bits_remaining: None,
serial_master_clock: None,
model: None,
}
}
pub fn restore_bus_state(
&mut self,
state: &crate::gb::console::save_state::BusState,
) -> Result<(), String> {
use crate::gb::console::save_state::GbBusType;
if state.bus_type != GbBusType::Cgb {
return Err(format!(
"bus type mismatch: expected CGB, found {:?}",
state.bus_type
));
}
self.ppu = state.ppu.clone();
self.wram = state.wram;
self.hram = state.hram;
self.timer = state.timer.clone();
self.joypad = state.joypad.clone();
self.apu = state.apu.clone();
self.if_reg = state.if_reg;
self.ie_reg = state.ie_reg;
self.dma_active = state.dma_active;
self.dma_source = state.dma_source;
self.dma_position = state.dma_position;
self.dma_oam_blocked = state.dma_oam_blocked;
Ok(())
}
pub fn cart_ram_snapshot(&self) -> Vec<u8> {
self.cart.ram_snapshot()
}
pub fn restore_cart_ram(&mut self, data: &[u8]) {
self.cart.restore_ram(data);
}
pub fn mbc_state_snapshot(&self) -> Vec<u8> {
self.cart.mbc_state_snapshot()
}
pub fn restore_mbc_state(&mut self, data: &[u8]) {
self.cart.restore_mbc_state(data);
}
}
impl GbBus for CgbBus {
fn read(&mut self, addr: u16) -> u8 {
match addr {
0x0000..=0x7FFF => self.cart.read(addr),
0x8000..=0x9FFF => self.ppu.read_vram(addr),
0xA000..=0xBFFF => self.cart.read(addr),
0xC000..=0xDFFF => self.wram[(addr - 0xC000) as usize],
0xE000..=0xFDFF => self.wram[(addr - 0xE000) as usize],
0xFE00..=0xFE9F => {
if self.dma_oam_blocked {
return 0xFF;
}
self.ppu.read_oam(addr)
}
0xFEA0..=0xFEFF => 0xFF,
0xFF00 => self.joypad.read(),
0xFF01 => 0xFF, 0xFF02 => 0xFF, 0xFF04..=0xFF07 => self.timer.read(addr),
0xFF0F => self.if_reg | 0xE0,
0xFF10..=0xFF3F => self.apu.read_register(addr),
0xFF40..=0xFF45 | 0xFF47..=0xFF4B => self.ppu.read_register(addr),
0xFF46 => self.dma_source,
0xFF4F | 0xFF68..=0xFF6C => self.ppu.read_cgb_register(addr).unwrap_or(0xFF),
0xFF80..=0xFFFE => self.hram[(addr - 0xFF80) as usize],
0xFFFF => self.ie_reg,
_ => 0xFF,
}
}
fn write(&mut self, addr: u16, val: u8) {
match addr {
0x0000..=0x7FFF => self.cart.write(addr, val),
0x8000..=0x9FFF => self.ppu.write_vram(addr, val),
0xA000..=0xBFFF => self.cart.write(addr, val),
0xC000..=0xDFFF => self.wram[(addr - 0xC000) as usize] = val,
0xE000..=0xFDFF => self.wram[(addr - 0xE000) as usize] = val,
0xFE00..=0xFE9F => {
if !self.dma_oam_blocked {
self.ppu.write_oam(addr, val);
}
}
0xFEA0..=0xFEFF => {}
0xFF00 => self.joypad.write(val),
0xFF01 | 0xFF02 => {} 0xFF04..=0xFF07 => {
self.timer.write(addr, val);
if self.timer.fire_write_overflow_if_pending() {
self.if_reg |= 0x04;
self.timer.take_interrupt();
}
}
0xFF0F => self.if_reg = val & 0x1F,
0xFF10..=0xFF3F => self.apu.write_register(addr, val),
0xFF40..=0xFF45 | 0xFF47..=0xFF4B => {
self.ppu.write_register(addr, val);
self.if_reg |= self.ppu.take_pending_interrupts();
}
0xFF46 => self.do_oam_dma(val),
0xFF4F | 0xFF68..=0xFF6C => {
self.ppu.write_cgb_register(addr, val);
}
0xFF50 => {} 0xFF80..=0xFFFE => self.hram[(addr - 0xFF80) as usize] = val,
0xFFFF => self.ie_reg = val,
_ => {}
}
}
fn tick(&mut self, m_cycles: u8) {
CgbBus::tick(self, m_cycles);
}
fn ppu(&self) -> &Ppu {
&self.ppu
}
fn ppu_mut(&mut self) -> &mut Ppu {
&mut self.ppu
}
fn read_for_debugger(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x7FFF => self.cart.read(addr),
0x8000..=0x9FFF => self.ppu.read_vram(addr),
0xA000..=0xBFFF => self.cart.read(addr),
0xC000..=0xDFFF => self.wram[(addr - 0xC000) as usize],
0xE000..=0xFDFF => self.wram[(addr - 0xE000) as usize],
0xFE00..=0xFE9F => {
if self.dma_oam_blocked {
return 0xFF;
}
self.ppu.oam[(addr - 0xFE00) as usize]
}
0xFEA0..=0xFEFF => 0xFF,
0xFF00 => self.joypad.read(),
0xFF01 => 0xFF, 0xFF02 => 0xFF, 0xFF04..=0xFF07 => self.timer.read(addr),
0xFF0F => self.if_reg | 0xE0,
0xFF10..=0xFF3F => self.apu.read_register(addr),
0xFF40..=0xFF45 | 0xFF47..=0xFF4B => self.ppu.read_register(addr),
0xFF46 => self.dma_source,
0xFF4F | 0xFF68..=0xFF6C => self.ppu.read_cgb_register(addr).unwrap_or(0xFF),
0xFF80..=0xFFFE => self.hram[(addr - 0xFF80) as usize],
0xFFFF => self.ie_reg,
_ => 0xFF,
}
}
}