use crate::cartridge::Mapper;
use crate::cartridge::MapperCapabilities;
use crate::cartridge::NametableLayout;
use crate::cartridge::base_mapper::BaseMapper;
pub struct CamericaMapper {
base: BaseMapper,
bank_select: u8,
has_mirroring_control: bool,
}
impl CamericaMapper {
pub fn new(ctx: super::mapper::MapperContext) -> Self {
let has_mirroring_control = ctx.submapper == 1;
let capabilities = MapperCapabilities {
has_dynamic_mirroring: has_mirroring_control,
max_prg_ram_kb: 8,
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);
base.select_prg_page(1, -1);
if has_mirroring_control {
base.set_mirroring(NametableLayout::SingleScreenLower);
}
Self {
base,
bank_select: 0,
has_mirroring_control,
}
}
}
impl Mapper for CamericaMapper {
fn base(&self) -> &BaseMapper {
&self.base
}
fn base_mut(&mut self) -> &mut BaseMapper {
&mut self.base
}
fn write_prg(&mut self, addr: u16, value: u8) {
if self.base.try_write_prg_ram(addr, value) {
return;
}
match addr {
0x8000..=0x9FFF if self.has_mirroring_control => {
let upper = (value & 0x10) != 0;
self.base.set_mirroring(if upper {
NametableLayout::SingleScreenUpper
} else {
NametableLayout::SingleScreenLower
});
}
0xC000..=0xFFFF => {
self.bank_select = value & 0x0F;
self.base.select_prg_page(0, self.bank_select as i16);
}
_ => {}
}
}
fn registers_snapshot(&self) -> Vec<u8> {
let one_screen_upper = matches!(self.base.mirroring(), NametableLayout::SingleScreenUpper);
vec![self.bank_select, one_screen_upper as u8]
}
fn restore_registers(&mut self, data: &[u8]) {
if data.len() >= 2 {
self.bank_select = data[0];
self.base.select_prg_page(0, self.bank_select as i16);
if self.has_mirroring_control {
self.base.set_mirroring(if data[1] != 0 {
NametableLayout::SingleScreenUpper
} else {
NametableLayout::SingleScreenLower
});
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cartridge::mapper::{MapperContext, create_mapper};
#[test]
fn test_mapper_71_is_wired_in_factory() {
let prg_rom = vec![0; 128 * 1024];
let chr_rom = vec![];
let mapper = create_mapper(MapperContext::new_for_test(
71,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
));
assert!(mapper.is_ok(), "Mapper 71 should be implemented");
}
#[test]
fn test_mapper71_prg_bank_switching() {
let mut prg_rom = vec![0; 256 * 1024];
for bank in 0..16 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = bank as u8;
}
}
let mut mapper = CamericaMapper::new(MapperContext::new_for_test(
71,
prg_rom,
vec![],
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_prg(0x8000), 0);
assert_eq!(mapper.read_prg(0xC000), 15);
assert_eq!(mapper.read_prg(0xFFFF), 15);
mapper.write_prg(0xC000, 3);
assert_eq!(mapper.read_prg(0x8000), 3);
assert_eq!(mapper.read_prg(0xBFFF), 3);
assert_eq!(mapper.read_prg(0xC000), 15);
mapper.write_prg(0xD000, 10);
assert_eq!(mapper.read_prg(0x8000), 10);
assert_eq!(mapper.read_prg(0xC000), 15);
}
#[test]
fn test_mapper71_bank_register_mask() {
let mut prg_rom = vec![0; 256 * 1024];
for bank in 0..16 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = (bank * 10) as u8;
}
}
let mut mapper = CamericaMapper::new(MapperContext::new_for_test(
71,
prg_rom,
vec![],
NametableLayout::Horizontal,
));
mapper.write_prg(0xC000, 0b1111_0101); assert_eq!(mapper.read_prg(0x8000), 50);
mapper.write_prg(0xC000, 0b0000_1111); assert_eq!(mapper.read_prg(0x8000), 150);
}
#[test]
fn test_mapper71_one_screen_mirroring() {
let prg_rom = vec![0; 128 * 1024];
let mut mapper = CamericaMapper::new(
MapperContext::new_for_test(71, prg_rom, vec![], NametableLayout::Horizontal)
.with_submapper(1),
);
assert_eq!(mapper.get_mirroring(), NametableLayout::SingleScreenLower);
mapper.write_prg(0x9000, 0b0000_0000);
assert_eq!(mapper.get_mirroring(), NametableLayout::SingleScreenLower);
mapper.write_prg(0x9000, 0b0001_0000);
assert_eq!(mapper.get_mirroring(), NametableLayout::SingleScreenUpper);
mapper.write_prg(0x8000, 0b0000_0000);
assert_eq!(mapper.get_mirroring(), NametableLayout::SingleScreenLower);
mapper.write_prg(0x9000, 0b0001_1111); assert_eq!(mapper.get_mirroring(), NametableLayout::SingleScreenUpper);
}
#[test]
fn test_mapper71_submapper0_no_mirroring_control() {
let prg_rom = vec![0; 128 * 1024];
let mut mapper = CamericaMapper::new(MapperContext::new_for_test(
71,
prg_rom,
vec![],
NametableLayout::Horizontal,
));
assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
mapper.write_prg(0x9000, 0x10);
assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
}
#[test]
fn test_mapper71_chr_ram() {
let mut mapper = CamericaMapper::new(MapperContext::new_for_test(
71,
vec![0; 128 * 1024],
vec![],
NametableLayout::Horizontal,
));
mapper.write_chr(0x0000, 0xAA);
mapper.write_chr(0x1000, 0xBB);
mapper.write_chr(0x1FFF, 0xCC);
assert_eq!(mapper.read_chr(0x0000), 0xAA);
assert_eq!(mapper.read_chr(0x1000), 0xBB);
assert_eq!(mapper.read_chr(0x1FFF), 0xCC);
}
#[test]
fn test_mapper71_fixed_last_bank() {
let mut prg_rom = vec![0; 256 * 1024];
for bank in 0..16 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = (bank + 100) as u8;
}
}
let mut mapper = CamericaMapper::new(MapperContext::new_for_test(
71,
prg_rom,
vec![],
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_prg(0xC000), 115);
assert_eq!(mapper.read_prg(0xFFFF), 115);
mapper.write_prg(0xC000, 0);
assert_eq!(mapper.read_prg(0xC000), 115);
mapper.write_prg(0xC000, 5);
assert_eq!(mapper.read_prg(0xC000), 115);
mapper.write_prg(0xC000, 10);
assert_eq!(mapper.read_prg(0xC000), 115);
}
#[test]
fn test_mapper71_separate_registers() {
let mut prg_rom = vec![0; 256 * 1024];
for bank in 0..16 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = (bank + 20) as u8;
}
}
let mut mapper = CamericaMapper::new(
MapperContext::new_for_test(71, prg_rom, vec![], NametableLayout::Horizontal)
.with_submapper(1),
);
mapper.write_prg(0xC000, 5);
assert_eq!(mapper.read_prg(0x8000), 25);
assert_eq!(mapper.get_mirroring(), NametableLayout::SingleScreenLower);
mapper.write_prg(0x9000, 0x10);
assert_eq!(mapper.get_mirroring(), NametableLayout::SingleScreenUpper);
assert_eq!(mapper.read_prg(0x8000), 25);
mapper.write_prg(0xC000, 3);
assert_eq!(mapper.read_prg(0x8000), 23);
assert_eq!(mapper.get_mirroring(), NametableLayout::SingleScreenUpper);
}
#[test]
fn test_mapper71_prg_ram_support() {
let prg_rom = vec![0; 128 * 1024];
let mut mapper = CamericaMapper::new(MapperContext::new_for_test(
71,
prg_rom,
vec![],
NametableLayout::Horizontal,
));
mapper.write_prg(0x6000, 0xAA);
mapper.write_prg(0x7FFF, 0xBB);
assert_eq!(mapper.read_prg(0x6000), 0xAA);
assert_eq!(mapper.read_prg(0x7FFF), 0xBB);
}
#[test]
fn test_camerica_registers_snapshot_restores_bank_mirroring_and_chr_ram() {
let mut prg_rom = vec![0; 64 * 1024];
for bank in 0..4 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = bank as u8;
}
}
let mut mapper = CamericaMapper::new(
MapperContext::new_for_test(71, prg_rom.clone(), vec![], NametableLayout::Horizontal)
.with_submapper(1),
);
mapper.write_prg(0xC000, 2); mapper.write_prg(0x9000, 0x10); mapper.write_chr(0x0000, 0x5A);
let regs = mapper.registers_snapshot();
let chr = mapper.chr_ram_snapshot();
let mut restored = CamericaMapper::new(
MapperContext::new_for_test(71, prg_rom, vec![], NametableLayout::Horizontal)
.with_submapper(1),
);
restored.restore_registers(®s);
restored.restore_chr_ram(&chr);
assert_eq!(restored.read_prg(0x8000), 2);
assert_eq!(restored.get_mirroring(), NametableLayout::SingleScreenUpper);
assert_eq!(restored.read_chr(0x0000), 0x5A);
}
}