use super::cartridge::GbCartridge;
pub struct Mbc2 {
rom: Vec<u8>,
ram: [u8; 512],
rom_bank: u8,
ram_enabled: bool,
battery: bool,
}
impl Mbc2 {
pub fn new(rom: Vec<u8>, battery: bool) -> Self {
Self {
rom,
ram: [0u8; 512],
rom_bank: 0,
ram_enabled: false,
battery,
}
}
fn rom_bank_count(&self) -> usize {
(self.rom.len() / 0x4000).max(1)
}
fn effective_rom_bank(&self) -> usize {
let raw = (self.rom_bank & 0x0F) as usize;
let promoted = if raw == 0 { 1 } else { raw };
promoted & (self.rom_bank_count() - 1)
}
fn read_rom(&self, addr: u16) -> u8 {
let (bank, offset) = if addr < 0x4000 {
(0, addr as usize)
} else {
(self.effective_rom_bank(), addr as usize - 0x4000)
};
let idx = bank * 0x4000 + offset;
self.rom.get(idx).copied().unwrap_or(0xFF)
}
fn read_ram(&self, addr: u16) -> u8 {
if !self.ram_enabled {
return 0xFF;
}
let idx = (addr as usize - 0xA000) & 0x1FF;
self.ram[idx] | 0xF0
}
fn write_registers(&mut self, addr: u16, val: u8) {
if addr & 0x0100 == 0 {
self.ram_enabled = (val & 0x0F) == 0x0A;
} else {
self.rom_bank = val & 0x0F;
}
}
fn write_ram(&mut self, addr: u16, val: u8) {
if !self.ram_enabled {
return;
}
let idx = (addr as usize - 0xA000) & 0x1FF;
self.ram[idx] = val;
}
}
impl GbCartridge for Mbc2 {
fn read(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x7FFF => self.read_rom(addr),
0xA000..=0xBFFF => self.read_ram(addr),
_ => 0xFF,
}
}
fn write(&mut self, addr: u16, val: u8) {
match addr {
0x0000..=0x3FFF => self.write_registers(addr, val),
0xA000..=0xBFFF => self.write_ram(addr, val),
_ => {}
}
}
fn has_battery(&self) -> bool {
self.battery
}
fn ram_snapshot(&self) -> Vec<u8> {
self.ram.to_vec()
}
fn restore_ram(&mut self, data: &[u8]) {
let len = data.len().min(self.ram.len());
self.ram[..len].copy_from_slice(&data[..len]);
}
fn mbc_state_snapshot(&self) -> Vec<u8> {
vec![self.rom_bank, self.ram_enabled as u8]
}
fn restore_mbc_state(&mut self, data: &[u8]) {
if data.len() >= 2 {
self.rom_bank = data[0];
self.ram_enabled = data[1] != 0;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_rom(bank_count: usize) -> Vec<u8> {
let mut rom = vec![0u8; bank_count * 0x4000];
for bank in 0..bank_count {
let fill = bank as u8;
let start = bank * 0x4000;
rom[start..start + 0x4000].fill(fill);
}
rom
}
#[test]
fn test_mbc2_initial_rom_bank0_readable() {
let cart = Mbc2::new(make_rom(4), false);
assert_eq!(cart.read(0x0000), 0x00);
assert_eq!(cart.read(0x3FFF), 0x00);
}
#[test]
fn test_mbc2_initial_high_region_maps_bank1() {
let cart = Mbc2::new(make_rom(4), false);
assert_eq!(cart.read(0x4000), 0x01);
assert_eq!(cart.read(0x7FFF), 0x01);
}
#[test]
fn test_mbc2_rom_bank_switch_selects_bank() {
let mut cart = Mbc2::new(make_rom(4), false);
cart.write(0x0100, 0x02);
assert_eq!(cart.read(0x4000), 0x02);
}
#[test]
fn test_mbc2_writing_zero_bank_reg_promotes_to_bank1() {
let mut cart = Mbc2::new(make_rom(4), false);
cart.write(0x0100, 0x00);
assert_eq!(cart.read(0x4000), 0x01);
}
#[test]
fn test_mbc2_bank_reg_masked_to_4_bits() {
let mut cart = Mbc2::new(make_rom(16), false);
cart.write(0x0100, 0x1F);
assert_eq!(cart.read(0x4000), 0x0F);
}
#[test]
fn test_mbc2_bank_wraps_to_available_banks() {
let mut cart = Mbc2::new(make_rom(4), false);
cart.write(0x0100, 0x05); assert_eq!(cart.read(0x4000), 0x01);
}
#[test]
fn test_mbc2_ram_disabled_by_default_returns_0xff() {
let cart = Mbc2::new(make_rom(2), false);
assert_eq!(cart.read(0xA000), 0xFF);
}
#[test]
fn test_mbc2_ram_enable_any_lower_nibble_0xa() {
let mut cart = Mbc2::new(make_rom(2), false);
cart.write(0x0000, 0x2A);
cart.write(0xA000, 0x05);
assert_eq!(cart.read(0xA000), 0x05 | 0xF0);
}
#[test]
fn test_mbc2_ram_disable_non_0xa_lower_nibble() {
let mut cart = Mbc2::new(make_rom(2), false);
cart.write(0x0000, 0x0A); cart.write(0xA000, 0x07);
cart.write(0x0000, 0x00); assert_eq!(cart.read(0xA000), 0xFF);
}
#[test]
fn test_mbc2_ram_write_and_read_when_enabled() {
let mut cart = Mbc2::new(make_rom(2), false);
cart.write(0x0000, 0x0A);
cart.write(0xA000, 0x03);
assert_eq!(cart.read(0xA000), 0x03 | 0xF0);
}
#[test]
fn test_mbc2_ram_read_returns_upper_nibble_0xf() {
let mut cart = Mbc2::new(make_rom(2), false);
cart.write(0x0000, 0x0A);
cart.write(0xA001, 0x05);
assert_eq!(cart.read(0xA001), 0xF5);
}
#[test]
fn test_mbc2_ram_mirrors_a200_to_bfff() {
let mut cart = Mbc2::new(make_rom(2), false);
cart.write(0x0000, 0x0A);
cart.write(0xA000, 0x09);
assert_eq!(cart.read(0xA200), 0x09 | 0xF0);
}
#[test]
fn test_mbc2_writes_to_bit8_clear_addr_do_not_change_bank() {
let mut cart = Mbc2::new(make_rom(4), false);
cart.write(0x0100, 0x03); cart.write(0x0000, 0x0A); assert_eq!(cart.read(0x4000), 0x03);
}
#[test]
fn test_mbc2_writes_to_bit8_set_addr_do_not_enable_ram() {
let mut cart = Mbc2::new(make_rom(4), false);
cart.write(0x0100, 0x0A);
assert_eq!(cart.read(0xA000), 0xFF);
}
}