use crate::nes::cartridge::base_mapper::BaseMapper;
use crate::nes::cartridge::mapper::{Mapper, MapperCapabilities};
const MAPPER_NUMBER: u16 = 232;
pub struct Mapper232 {
base: BaseMapper,
prg_block: u8,
prg_page: u8,
submapper: u8,
}
impl Mapper232 {
pub fn new(ctx: crate::nes::cartridge::mapper::MapperContext) -> Self {
let submapper = ctx.submapper;
let capabilities = MapperCapabilities {
prg_bank_size_kb: 16,
chr_bank_size_kb: 8,
..Default::default()
};
let mut base = BaseMapper::new(&ctx, capabilities);
base.configure_prg_banking(16 * 1024);
let mut mapper = Self {
base,
prg_block: 0,
prg_page: 0,
submapper,
};
mapper.apply_banks();
mapper
}
fn apply_banks(&mut self) {
let lo = ((self.prg_block << 2) | self.prg_page) as i16;
let hi = ((self.prg_block << 2) | 3) as i16;
self.base.select_prg_page(0, lo);
self.base.select_prg_page(1, hi);
}
}
impl Mapper for Mapper232 {
fn base(&self) -> &BaseMapper {
&self.base
}
fn base_mut(&mut self) -> &mut BaseMapper {
&mut self.base
}
fn mapper_number(&self) -> u16 {
MAPPER_NUMBER
}
fn write_prg(&mut self, addr: u16, value: u8) {
if self.base.try_write_prg_ram(addr, value) {
return;
}
if addr >= 0xC000 {
self.prg_page = value & 0x03;
self.apply_banks();
} else if addr >= 0x8000 {
self.prg_block = if self.submapper == 1 {
((value >> 4) & 0x01) | ((value >> 2) & 0x02)
} else {
(value >> 3) & 0x03
};
self.apply_banks();
}
}
fn registers_snapshot(&self) -> Vec<u8> {
let mut snap = self.base.banking_snapshot();
snap.push(self.prg_block);
snap.push(self.prg_page);
snap
}
fn restore_registers(&mut self, data: &[u8]) {
let expected_banking_len = self.base.banking_snapshot().len();
if data.len() >= expected_banking_len + 2 {
self.base.restore_banking(&data[..expected_banking_len]);
self.prg_block = data[expected_banking_len];
self.prg_page = data[expected_banking_len + 1];
self.apply_banks();
} else {
self.base.restore_banking(data);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nes::cartridge::NametableLayout;
use crate::nes::cartridge::mapper::{MapperContext, create_mapper};
use crate::nes::cartridge::test_helpers::banked_data;
const PRG_BANKS: usize = 16;
fn make_mapper(prg_rom: Vec<u8>) -> Mapper232 {
Mapper232::new(
MapperContext::new_for_test(MAPPER_NUMBER, prg_rom, vec![], NametableLayout::Vertical)
.with_prg_ram_banks(0),
)
}
fn make_mapper_submapper1(prg_rom: Vec<u8>) -> Mapper232 {
Mapper232::new(
MapperContext::new_for_test(MAPPER_NUMBER, prg_rom, vec![], NametableLayout::Vertical)
.with_prg_ram_banks(0)
.with_submapper(1),
)
}
#[test]
fn mapper_232_is_registered_in_factory() {
let result = create_mapper(MapperContext::new_for_test(
MAPPER_NUMBER,
banked_data(16 * 1024, PRG_BANKS),
vec![],
NametableLayout::Vertical,
));
assert!(result.is_ok(), "Mapper 232 should be registered in factory");
}
#[test]
fn power_on_lower_window_at_bank_0() {
let mapper = make_mapper(banked_data(16 * 1024, PRG_BANKS));
assert_eq!(
mapper.read_prg(0x8000),
0,
"$8000 window should start at bank 0 on power-on"
);
}
#[test]
fn power_on_upper_window_at_bank_3() {
let mapper = make_mapper(banked_data(16 * 1024, PRG_BANKS));
assert_eq!(
mapper.read_prg(0xC000),
3,
"$C000 window should start at bank 3 (last page of block 0) on power-on"
);
}
#[test]
fn page_select_sets_lower_window_within_block() {
let mut mapper = make_mapper(banked_data(16 * 1024, PRG_BANKS));
mapper.write_prg(0xC000, 0x02);
assert_eq!(mapper.read_prg(0x8000), 2, "lower window should be bank 2");
assert_eq!(
mapper.read_prg(0xC000),
3,
"upper window should remain bank 3"
);
}
#[test]
fn page_select_only_uses_low_two_bits() {
let mut mapper = make_mapper(banked_data(16 * 1024, PRG_BANKS));
mapper.write_prg(0xC000, 0xFF);
assert_eq!(mapper.read_prg(0x8000), 3);
assert_eq!(mapper.read_prg(0xC000), 3);
}
#[test]
fn block_select_shifts_both_windows() {
let mut mapper = make_mapper(banked_data(16 * 1024, PRG_BANKS));
mapper.write_prg(0x8000, 0x08);
assert_eq!(mapper.read_prg(0x8000), 4, "lower window should be bank 4");
assert_eq!(
mapper.read_prg(0xC000),
7,
"upper window should be bank 7 (last of block 1)"
);
}
#[test]
fn block_select_uses_bits_4_and_3_of_value() {
let mut mapper = make_mapper(banked_data(16 * 1024, PRG_BANKS));
mapper.write_prg(0x8000, 0x10);
assert_eq!(mapper.read_prg(0x8000), 8);
assert_eq!(mapper.read_prg(0xC000), 11);
}
#[test]
fn block_select_only_uses_two_bits() {
let mut mapper = make_mapper(banked_data(16 * 1024, PRG_BANKS));
mapper.write_prg(0x8000, 0xFF);
assert_eq!(mapper.read_prg(0x8000), 12);
assert_eq!(mapper.read_prg(0xC000), 15);
}
#[test]
fn block_1_page_2_selects_bank_6() {
let mut mapper = make_mapper(banked_data(16 * 1024, PRG_BANKS));
mapper.write_prg(0x8000, 0x08); mapper.write_prg(0xC000, 0x02); assert_eq!(mapper.read_prg(0x8000), 6);
assert_eq!(mapper.read_prg(0xC000), 7);
}
#[test]
fn upper_window_always_fixed_to_last_page_of_block() {
let mut mapper = make_mapper(banked_data(16 * 1024, PRG_BANKS));
mapper.write_prg(0x8000, 0x10); mapper.write_prg(0xC000, 0x00);
assert_eq!(
mapper.read_prg(0xC000),
11,
"upper = bank 11 (page 3 of block 2)"
);
mapper.write_prg(0xC000, 0x01);
assert_eq!(mapper.read_prg(0xC000), 11);
mapper.write_prg(0xC000, 0x02);
assert_eq!(mapper.read_prg(0xC000), 11);
mapper.write_prg(0xC000, 0x03);
assert_eq!(mapper.read_prg(0xC000), 11);
}
#[test]
fn submapper1_block_select_swaps_bits() {
let mut mapper = make_mapper_submapper1(banked_data(16 * 1024, PRG_BANKS));
mapper.write_prg(0x8000, 0x10);
assert_eq!(mapper.read_prg(0x8000), 4, "Aladdin block from bit4=1");
assert_eq!(mapper.read_prg(0xC000), 7);
}
#[test]
fn submapper1_block_select_bit2_gives_block_2() {
let mut mapper = make_mapper_submapper1(banked_data(16 * 1024, PRG_BANKS));
mapper.write_prg(0x8000, 0x08);
assert_eq!(mapper.read_prg(0x8000), 8, "Aladdin block=2 from bit2=1");
assert_eq!(mapper.read_prg(0xC000), 11);
}
#[test]
fn chr_ram_is_readable_and_writable() {
let mut mapper = make_mapper(banked_data(16 * 1024, PRG_BANKS));
mapper.write_chr(0x0000, 0xAB);
mapper.write_chr(0x1FFF, 0xCD);
assert_eq!(mapper.read_chr(0x0000), 0xAB);
assert_eq!(mapper.read_chr(0x1FFF), 0xCD);
}
#[test]
fn registers_snapshot_and_restore() {
let prg = banked_data(16 * 1024, PRG_BANKS);
let mut mapper = make_mapper(prg.clone());
mapper.write_prg(0x8000, 0x10); mapper.write_prg(0xC000, 0x01);
let snap = mapper.registers_snapshot();
let mut restored = make_mapper(prg);
restored.restore_registers(&snap);
assert_eq!(restored.read_prg(0x8000), 9, "lower bank after restore");
assert_eq!(restored.read_prg(0xC000), 11, "upper bank after restore");
}
}