use super::addressing::{
dma_addr_uses_gamepak, gamepak_nonseq_wait_is_slowest, gamepak_second_access_fast,
open_bus_no_cart_byte, open_bus_no_cart_halfword, open_bus_no_cart_word, timer_control_index,
vram_offset,
};
use super::gba_bus::{GbaBus, emit_gba_bus_trace_line};
use super::memory::{
EWRAM_SIZE, IWRAM_SIZE, OAM_SIZE, PRAM_SIZE, read_le_u16, read_le_u32, write_le_u16,
write_le_u32,
};
use super::waitstates::WidthClass;
use crate::gba::cpu::bus::Bus;
impl Bus for GbaBus {
fn read32(&mut self, addr: u32) -> u32 {
let aligned = addr & !0x3;
let val = match (aligned >> 24) & 0xF {
0x0 | 0x1 => self.read_bios_u32(aligned).unwrap_or_else(|| {
if Self::is_bios_addr(aligned) {
self.protected_bios_word()
} else {
self.unused_open_bus_word(aligned)
}
}),
0x2 => read_le_u32(&self.ewram, aligned as usize),
0x3 => read_le_u32(&self.iwram, aligned as usize),
0x4 => {
if let Some(value) = self.try_read_mgba_debug16(aligned) {
value as u32 | ((value as u32) << 16)
} else if Self::is_apu_open_bus_read(aligned) {
self.io_open_bus_word()
} else if (0x0400_0060..=0x0400_00A6).contains(&aligned) {
let lo = self.apu.read16(aligned) as u32;
let hi = if aligned + 2 <= 0x0400_00A6 {
self.apu.read16(aligned + 2) as u32
} else {
0
};
lo | (hi << 16)
} else {
self.io
.try_read32(
aligned,
&self.ic,
&self.timers,
&self.dma,
&self.ppu,
&self.keypad,
)
.unwrap_or_else(|| {
if (0x0400_0000..0x0400_0400).contains(&aligned) {
self.io_open_bus_word()
} else {
self.open_bus_word()
}
})
}
}
0x5 => read_le_u32(&self.pram, aligned as usize),
0x6 => {
let off = vram_offset(aligned);
read_le_u32(&self.vram, off)
}
0x7 => read_le_u32(&self.oam, aligned as usize),
0x8..=0xD => {
let off = (aligned & 0x01FF_FFFF) as usize;
self.rom_u32(off)
.unwrap_or_else(|| open_bus_no_cart_word(aligned))
}
0xE | 0xF => {
let b = self.cart_read8(addr);
u32::from_le_bytes([b, b, b, b])
}
_ => self.unused_open_bus_word(aligned),
};
self.last_bus_value = val;
val
}
fn read16(&mut self, addr: u32) -> u16 {
let aligned = addr & !0x1;
let val = match (aligned >> 24) & 0xF {
0x0 | 0x1 => self.read_bios_u16(aligned).unwrap_or_else(|| {
if Self::is_bios_addr(aligned) {
self.protected_bios_halfword(aligned)
} else {
self.unused_open_bus_halfword(aligned)
}
}),
0x2 => read_le_u16(&self.ewram, aligned as usize),
0x3 => read_le_u16(&self.iwram, aligned as usize),
0x4 => {
if let Some(value) = self.try_read_mgba_debug16(aligned) {
value
} else if Self::is_apu_open_bus_read(aligned) {
self.io_open_bus_halfword()
} else if (0x0400_0060..=0x0400_00A6).contains(&aligned) {
self.apu.read16(aligned)
} else if aligned == 0x0400_0128 {
self.sio.read_siocnt()
} else {
if matches!(
aligned,
0x0400_0100 | 0x0400_0104 | 0x0400_0108 | 0x0400_010C
) {
let timer = ((aligned - 0x0400_0100) / 4) as usize;
return self.read_timer_count_for_cpu(timer);
}
let raw = self
.io
.try_read16(
aligned,
&self.ic,
&self.timers,
&self.dma,
&self.ppu,
&self.keypad,
)
.unwrap_or_else(|| self.io_open_bus_halfword());
if aligned == 0x0400_0204 {
raw & 0x5FFF
} else {
raw
}
}
}
0x5 => read_le_u16(&self.pram, aligned as usize),
0x6 => {
let off = vram_offset(aligned);
read_le_u16(&self.vram, off)
}
0x7 => read_le_u16(&self.oam, aligned as usize),
0x8..=0xD => {
let off = (aligned & 0x01FF_FFFF) as usize;
self.rom_u16(off)
.unwrap_or_else(|| open_bus_no_cart_halfword(aligned))
}
0xE | 0xF => {
let b = self.cart_read8(addr);
u16::from_le_bytes([b, b])
}
_ => self.unused_open_bus_halfword(aligned),
};
let shift = if aligned & 0x2 == 0 { 0 } else { 16 };
self.last_bus_value =
(self.last_bus_value & !(0xFFFFu32 << shift)) | ((val as u32) << shift);
val
}
fn read8(&mut self, addr: u32) -> u8 {
let val = match (addr >> 24) & 0xF {
0x0 | 0x1 => self.read_bios_byte(addr).unwrap_or_else(|| {
if Self::is_bios_addr(addr) {
self.protected_bios_byte(addr)
} else {
self.unused_open_bus_byte(addr)
}
}),
0x2 => self.ewram[(addr as usize) % EWRAM_SIZE],
0x3 => self.iwram[(addr as usize) % IWRAM_SIZE],
0x4 => {
if let Some(value) = self.try_read_mgba_debug16(addr & !1) {
if addr & 1 == 0 {
value as u8
} else {
(value >> 8) as u8
}
} else if addr == 0x0400_0410 {
self.undoc_0x410
} else {
let aligned_hw = addr & !0x1;
if Self::is_apu_open_bus_read(aligned_hw) {
self.io_open_bus_byte(addr)
} else if (0x0400_0060..=0x0400_00A6).contains(&aligned_hw) {
let hw = self.apu.read16(aligned_hw);
if addr & 1 == 0 {
hw as u8
} else {
(hw >> 8) as u8
}
} else {
self.io
.try_read8(
addr,
&self.ic,
&self.timers,
&self.dma,
&self.ppu,
&self.keypad,
)
.unwrap_or_else(|| self.io_open_bus_byte(addr))
}
}
}
0x5 => self.pram[(addr as usize) % PRAM_SIZE],
0x6 => self.vram[vram_offset(addr)],
0x7 => self.oam[(addr as usize) % OAM_SIZE],
0x8..=0xD => {
let off = (addr & 0x01FF_FFFF) as usize;
self.rom_byte(off).unwrap_or(open_bus_no_cart_byte(addr))
}
0xE | 0xF => self.cart_read8(addr),
_ => self.unused_open_bus_byte(addr),
};
let shift = (addr & 3) * 8;
self.last_bus_value = (self.last_bus_value & !(0xFFu32 << shift)) | ((val as u32) << shift);
val
}
fn fetch32(&mut self, addr: u32) -> u32 {
let aligned = addr & !0x3;
let value = if Self::is_bios_addr(aligned) {
self.raw_bios_u32(aligned)
.unwrap_or_else(|| self.protected_bios_word())
} else {
self.executing_bios = false;
self.bios_locked = true;
self.read32(aligned)
};
if Self::is_bios_addr(aligned) {
self.executing_bios = true;
self.bios_open_bus_value = value;
self.last_bus_value = value;
} else if (0x0800_0000..=0x0DFF_FFFF).contains(&aligned) {
self.gamepak_prefetch_open_bus_value = value;
self.gamepak_prefetch_open_bus_valid = true;
}
value
}
fn fetch16(&mut self, addr: u32) -> u16 {
let aligned = addr & !0x1;
let value = if Self::is_bios_addr(aligned) {
self.raw_bios_u16(aligned)
.unwrap_or_else(|| self.protected_bios_halfword(aligned))
} else {
self.executing_bios = false;
self.bios_locked = true;
self.read16(aligned)
};
if Self::is_bios_addr(aligned) {
self.executing_bios = true;
let shift = if aligned & 0x2 == 0 { 0 } else { 16 };
self.bios_open_bus_value =
(self.bios_open_bus_value & !(0xFFFFu32 << shift)) | ((value as u32) << shift);
self.last_bus_value =
(self.last_bus_value & !(0xFFFFu32 << shift)) | ((value as u32) << shift);
} else if (0x0800_0000..=0x0DFF_FFFF).contains(&aligned) {
self.gamepak_prefetch_open_bus_value = (value as u32) | ((value as u32) << 16);
self.gamepak_prefetch_open_bus_valid = true;
}
value
}
fn write32(&mut self, addr: u32, value: u32) {
let aligned = addr & !0x3;
let region = (aligned >> 24) & 0xF;
if region != 0x4 {
self.last_bus_value = value;
}
let touches_io = region == 0x4;
match region {
0x0 | 0x1 => { }
0x2 => write_le_u32(&mut self.ewram, aligned as usize, value),
0x3 => write_le_u32(&mut self.iwram, aligned as usize, value),
0x4 => {
if self.write_mgba_debug32(aligned, value) {
return;
}
if aligned == 0x0400_00A0 {
self.apu.write_fifo_a_word(value);
} else if aligned == 0x0400_00A4 {
self.apu.write_fifo_b_word(value);
} else if (0x0400_0060..=0x0400_00A6).contains(&aligned) {
self.apu.write16(aligned, value as u16);
if aligned + 2 <= 0x0400_00A6 {
self.apu.write16(aligned + 2, (value >> 16) as u16);
}
} else {
if aligned == 0x0400_0300 {
let haltcnt_byte = ((value >> 8) & 0xFF) as u8;
if haltcnt_byte & 0x80 == 0 {
self.halt_requested = true;
}
}
let high = (value >> 16) as u16;
let timer_enable_phase = self.timer_enable_phase_for_write16(aligned + 2, high);
self.defer_active_timer_reload_write_cycle(aligned);
self.prestep_timer_disable_for_write16(aligned + 2, high);
self.mark_timer_start_delay_for_write16(aligned + 2, high);
self.mark_dma_start_delay_for_write16(aligned + 2, (value >> 16) as u16);
self.io.write32(
aligned,
value,
&mut self.ic,
&mut self.timers,
&mut self.dma,
&mut self.ppu,
&mut self.keypad,
);
if let Some((timer, phase)) = timer_enable_phase {
self.timers.align_prescaler_phase(timer, phase);
}
if aligned == 0x0400_0204 {
self.waitstates.recalculate(value as u16);
}
if aligned == 0x0400_0128 {
self.write_siocnt(value as u16);
}
if aligned == 0x0400_0134 {
self.sio.write_rcnt(value as u16);
}
}
}
0x5 => write_le_u32(&mut self.pram, aligned as usize, value),
0x6 => {
let off = vram_offset(aligned);
write_le_u32(&mut self.vram, off, value);
}
0x7 => write_le_u32(&mut self.oam, aligned as usize, value),
0x8..=0xD => { }
0xE | 0xF => {
let shift = (addr & 0x3) * 8;
let byte = ((value >> shift) & 0xFF) as u8;
self.cart_write8(addr, byte);
}
_ => {}
}
if touches_io && self.dma.any_pending() && self.dma_start_delay_cycles == 0 {
self.run_pending_dma();
}
}
fn write16(&mut self, addr: u32, value: u16) {
let aligned = addr & !0x1;
let region = (aligned >> 24) & 0xF;
if region != 0x4 {
let shift = if aligned & 0x2 == 0 { 0 } else { 16 };
self.last_bus_value =
(self.last_bus_value & !(0xFFFFu32 << shift)) | ((value as u32) << shift);
}
let touches_io = region == 0x4;
match region {
0x0 | 0x1 => {}
0x2 => write_le_u16(&mut self.ewram, aligned as usize, value),
0x3 => write_le_u16(&mut self.iwram, aligned as usize, value),
0x4 => {
if self.write_mgba_debug16(aligned, value) {
return;
}
if (0x0400_0060..=0x0400_00A6).contains(&aligned) {
self.apu.write16(aligned, value);
} else {
if aligned == 0x0400_0300 {
let haltcnt_byte = (value >> 8) as u8;
if haltcnt_byte & 0x80 == 0 {
self.halt_requested = true;
}
}
let timer_enable_phase = self.timer_enable_phase_for_write16(aligned, value);
self.defer_active_timer_reload_write_cycle(aligned);
self.prestep_timer_disable_for_write16(aligned, value);
self.mark_timer_start_delay_for_write16(aligned, value);
self.mark_dma_start_delay_for_write16(aligned, value);
self.io.write16(
aligned,
value,
&mut self.ic,
&mut self.timers,
&mut self.dma,
&mut self.ppu,
&mut self.keypad,
);
if let Some((timer, phase)) = timer_enable_phase {
self.timers.align_prescaler_phase(timer, phase);
}
self.trace_dma_cnt_h_write(aligned, value);
if aligned == 0x0400_0204 {
self.waitstates.recalculate(value);
}
if aligned == 0x0400_0128 {
self.write_siocnt(value);
}
if aligned == 0x0400_0134 {
self.sio.write_rcnt(value);
}
}
}
0x5 => write_le_u16(&mut self.pram, aligned as usize, value),
0x6 => {
let off = vram_offset(aligned);
write_le_u16(&mut self.vram, off, value);
}
0x7 => write_le_u16(&mut self.oam, aligned as usize, value),
0x8..=0xD => {}
0xE | 0xF => {
let shift = (addr & 0x1) * 8;
let byte = ((value as u32 >> shift) & 0xFF) as u8;
self.cart_write8(addr, byte);
}
_ => {}
}
if touches_io && self.dma.any_pending() && self.dma_start_delay_cycles == 0 {
self.run_pending_dma();
}
}
fn write8(&mut self, addr: u32, value: u8) {
if self.trace_config.bus > 0 {
emit_gba_bus_trace_line(format!("[GBA BUS] W8 {addr:08X}={value:02X}"));
}
let region = (addr >> 24) & 0xF;
if region != 0x4 {
let shift = (addr & 3) * 8;
self.last_bus_value =
(self.last_bus_value & !(0xFFu32 << shift)) | ((value as u32) << shift);
}
let touches_io = region == 0x4;
match region {
0x0 | 0x1 => {}
0x2 => self.ewram[(addr as usize) % EWRAM_SIZE] = value,
0x3 => self.iwram[(addr as usize) % IWRAM_SIZE] = value,
0x4 => {
if self.write_mgba_debug8(addr, value) {
return;
}
if addr == 0x0400_0410 {
self.undoc_0x410 = value;
} else if addr == 0x0400_0301 {
if value & 0x80 == 0 {
self.halt_requested = true;
}
} else if (0x0400_0060..=0x0400_00A7).contains(&addr) {
self.apu.write8(addr, value);
} else {
let aligned = addr & !1;
let old =
self.timers.channels[timer_control_index(aligned).unwrap_or(0)].control;
let shift = (addr & 1) * 8;
let merged = (old & !(0xFFu16 << shift)) | ((value as u16) << shift);
let timer_enable_phase = self.timer_enable_phase_for_write16(aligned, merged);
self.prestep_timer_disable_for_write16(aligned, merged);
self.mark_timer_start_delay_for_write8(addr, value);
self.mark_dma_start_delay_for_write8(addr, value);
self.io.write8(
addr,
value,
&mut self.ic,
&mut self.timers,
&mut self.dma,
&mut self.ppu,
&mut self.keypad,
);
if let Some((timer, phase)) = timer_enable_phase {
self.timers.align_prescaler_phase(timer, phase);
}
if aligned == 0x0400_0204 {
self.waitstates
.recalculate(self.io.backing_u16(0x0400_0204));
}
if aligned == 0x0400_0128 {
let merged = self.io.backing_u16(0x0400_0128);
self.write_siocnt(merged);
}
if aligned == 0x0400_0134 {
let merged = self.io.backing_u16(0x0400_0134);
self.sio.write_rcnt(merged);
}
}
}
0x5 => {
let off = (addr as usize & !1) % PRAM_SIZE;
self.pram[off] = value;
self.pram[off + 1] = value;
}
0x6 => {
let off = vram_offset(addr);
if off < 0x10000 {
let aligned = off & !1;
self.vram[aligned] = value;
self.vram[aligned + 1] = value;
}
}
0x7 => { }
0x8..=0xD => {}
0xE | 0xF => self.cart_write8(addr, value),
_ => {}
}
if touches_io && self.dma.any_pending() && self.dma_start_delay_cycles == 0 {
self.run_pending_dma();
}
}
fn n_cycles(&self, addr: u32, width: WidthClass) -> u32 {
self.n_cycles_width(addr, width)
}
fn s_cycles(&self, addr: u32, width: WidthClass) -> u32 {
self.s_cycles_width(addr, width)
}
fn gamepak_prefetch_enabled(&self) -> bool {
self.waitstates.prefetch_enabled
}
fn immediate_gamepak_dma_prefetch_penalty(&self, code_width: WidthClass) -> bool {
if self.dma_start_delay_cycles == 0 {
return false;
}
let Some((src, dst)) = self.dma.pending_immediate_src_dst() else {
return false;
};
let src_gamepak = dma_addr_uses_gamepak(src);
let dst_gamepak = dma_addr_uses_gamepak(dst);
let src_second_fast = gamepak_second_access_fast(self.waitstates.waitcnt, src);
let dst_second_fast = gamepak_second_access_fast(self.waitstates.waitcnt, dst);
(src_gamepak && code_width == WidthClass::Word && src_second_fast)
|| (!src_gamepak && dst_gamepak && !dst_second_fast)
}
fn embedded_bios_hle_enabled(&self) -> bool {
self.embedded_bios_loaded
}
fn embedded_bios_hle_entry_penalty(&self, addr: u32, width: WidthClass) -> u32 {
match (addr >> 24) & 0xF {
0x2 => self.n_cycles_width(addr, width) + 2 * self.s_cycles_width(addr, width) - 1,
0x8..=0xD => {
let n = self.n_cycles_width(addr, width);
let s = self.s_cycles_width(addr, width);
n + 2 * s
+ u32::from(gamepak_nonseq_wait_is_slowest(
self.waitstates.waitcnt,
addr,
))
}
_ => 0,
}
}
}