use crate::nes::cartridge::base_mapper::BaseMapper;
use crate::nes::cartridge::mmc3::MMC3Mapper;
use crate::nes::cartridge::{Mapper, MapperCapabilities};
const MAPPER_NUMBER: u16 = 197;
const CHR_1K_BANK_SIZE: usize = 0x0400;
const CHR_BANK_MASK: usize = CHR_1K_BANK_SIZE - 1;
const PRG_BANK_SIZE: usize = 0x2000;
const PRG_BANK_MASK: usize = PRG_BANK_SIZE - 1;
pub struct Mapper197 {
inner: MMC3Mapper,
}
impl Mapper197 {
pub fn new(ctx: crate::nes::cartridge::mapper::MapperContext) -> Self {
Self {
inner: MMC3Mapper::new(ctx),
}
}
fn mapped_chr_bank(&self, addr: u16) -> usize {
let chr_mode = (self.inner.bank_select_reg() & 0x80) != 0;
let r0 = self.inner.chr_bank_reg(0) as usize;
let r2 = self.inner.chr_bank_reg(2) as usize;
let r3 = self.inner.chr_bank_reg(3) as usize;
let slot_1k = (addr as usize) >> 10;
if !chr_mode {
match slot_1k {
0..=3 => (r0 << 1) + slot_1k, 4..=5 => (r2 << 1) + (slot_1k - 4), 6..=7 => (r3 << 1) + (slot_1k - 6), _ => 0,
}
} else {
match slot_1k {
0..=3 => (r2 << 1) + slot_1k, 4..=7 => (r0 << 1) + (slot_1k - 4) % 2, _ => 0,
}
}
}
}
impl Mapper for Mapper197 {
fn base(&self) -> &BaseMapper {
&self.inner.base
}
fn base_mut(&mut self) -> &mut BaseMapper {
&mut self.inner.base
}
fn mmc3_delegate(&self) -> Option<&MMC3Mapper> {
Some(&self.inner)
}
fn mmc3_delegate_mut(&mut self) -> Option<&mut MMC3Mapper> {
Some(&mut self.inner)
}
fn read_prg(&self, addr: u16) -> u8 {
match addr {
0x6000..=0x7FFF => self.inner.read_prg(addr),
0x8000..=0xFFFF => {
let bank = self.inner.mapped_prg_bank(addr);
let offset = (addr as usize) & PRG_BANK_MASK;
self.inner.read_prg_at_bank(bank, offset)
}
_ => 0,
}
}
fn read_prg_open_bus(&self, addr: u16, open_bus: u8) -> u8 {
match addr {
0x6000..=0x7FFF => self.inner.read_prg_open_bus(addr, open_bus),
0x8000..=0xFFFF => self.read_prg(addr),
_ => open_bus,
}
}
fn write_prg(&mut self, addr: u16, value: u8) {
self.inner.write_prg(addr, value);
}
fn read_chr(&mut self, addr: u16) -> u8 {
let bank = self.mapped_chr_bank(addr);
let offset = (addr as usize) & CHR_BANK_MASK;
self.inner.read_chr_1k_at(bank, offset)
}
fn write_chr(&mut self, addr: u16, value: u8) {
let bank = self.mapped_chr_bank(addr);
let offset = (addr as usize) & CHR_BANK_MASK;
self.inner.write_chr_1k_at(bank, offset, value);
}
fn mapper_number(&self) -> u16 {
MAPPER_NUMBER
}
fn wram_size(&self) -> usize {
self.inner.wram_size()
}
fn wram_snapshot(&self) -> Vec<u8> {
self.inner.wram_snapshot()
}
fn load_wram_snapshot(&mut self, data: &[u8]) {
self.inner.load_wram_snapshot(data);
}
fn registers_snapshot(&self) -> Vec<u8> {
self.inner.registers_snapshot()
}
fn restore_registers(&mut self, data: &[u8]) {
self.inner.restore_registers(data);
}
fn capabilities(&self) -> MapperCapabilities {
self.inner.capabilities()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nes::cartridge::NametableLayout;
use crate::nes::cartridge::mapper::{Mapper, MapperContext, create_mapper};
use crate::nes::cartridge::test_helpers::banked_data;
const PRG_8K_BANKS: usize = 8;
const CHR_1K_BANKS: usize = 128;
fn make_mapper() -> Mapper197 {
Mapper197::new(MapperContext::new_for_test(
MAPPER_NUMBER,
banked_data(PRG_BANK_SIZE, PRG_8K_BANKS),
banked_data(CHR_1K_BANK_SIZE, CHR_1K_BANKS),
NametableLayout::Vertical,
))
}
fn set_chr_reg(mapper: &mut Mapper197, index: usize, value: u8) {
let mode_bits = mapper.inner.bank_select_reg() & 0xC0;
mapper.write_prg(0x8000, mode_bits | (index as u8 & 0x07)); mapper.write_prg(0x8001, value); }
fn set_chr_mode(mapper: &mut Mapper197, mode1: bool) {
let bs = mapper.inner.bank_select_reg();
let new_bs = if mode1 { bs | 0x80 } else { bs & !0x80 };
mapper.write_prg(0x8000, new_bs);
}
#[test]
fn mapper_197_is_registered() {
let result = create_mapper(MapperContext::new_for_test(
MAPPER_NUMBER,
banked_data(PRG_BANK_SIZE, PRG_8K_BANKS),
banked_data(CHR_1K_BANK_SIZE, CHR_1K_BANKS),
NametableLayout::Vertical,
));
assert!(
result.is_ok(),
"Mapper 197 must be registered in the factory"
);
}
#[test]
fn mode0_reg0_selects_4kb_block_at_0000() {
let mut mapper = make_mapper();
set_chr_mode(&mut mapper, false);
set_chr_reg(&mut mapper, 0, 5);
assert_eq!(mapper.read_chr(0x0000), 10, "$0000 must read 1K bank 10");
assert_eq!(mapper.read_chr(0x0400), 11, "$0400 must read 1K bank 11");
assert_eq!(mapper.read_chr(0x0800), 12, "$0800 must read 1K bank 12");
assert_eq!(mapper.read_chr(0x0C00), 13, "$0C00 must read 1K bank 13");
}
#[test]
fn mode0_reg2_selects_2kb_block_at_1000() {
let mut mapper = make_mapper();
set_chr_mode(&mut mapper, false);
set_chr_reg(&mut mapper, 2, 3);
assert_eq!(mapper.read_chr(0x1000), 6, "$1000 must read 1K bank 6");
assert_eq!(mapper.read_chr(0x1400), 7, "$1400 must read 1K bank 7");
}
#[test]
fn mode0_reg3_selects_2kb_block_at_1800() {
let mut mapper = make_mapper();
set_chr_mode(&mut mapper, false);
set_chr_reg(&mut mapper, 3, 4);
assert_eq!(mapper.read_chr(0x1800), 8, "$1800 must read 1K bank 8");
assert_eq!(mapper.read_chr(0x1C00), 9, "$1C00 must read 1K bank 9");
}
#[test]
fn mode0_reg2_does_not_affect_0000_to_0fff() {
let mut mapper = make_mapper();
set_chr_mode(&mut mapper, false);
set_chr_reg(&mut mapper, 0, 2); set_chr_reg(&mut mapper, 2, 10);
assert_eq!(mapper.read_chr(0x0000), 4, "$0000 must still follow reg[0]");
}
#[test]
fn mode1_reg2_selects_4kb_block_at_0000() {
let mut mapper = make_mapper();
set_chr_mode(&mut mapper, true);
set_chr_reg(&mut mapper, 2, 6);
assert_eq!(mapper.read_chr(0x0000), 12, "$0000 must read 1K bank 12");
assert_eq!(mapper.read_chr(0x0400), 13, "$0400 must read 1K bank 13");
assert_eq!(mapper.read_chr(0x0800), 14, "$0800 must read 1K bank 14");
assert_eq!(mapper.read_chr(0x0C00), 15, "$0C00 must read 1K bank 15");
}
#[test]
fn mode1_reg0_selects_2kb_at_1000_and_1800() {
let mut mapper = make_mapper();
set_chr_mode(&mut mapper, true);
set_chr_reg(&mut mapper, 0, 7);
assert_eq!(mapper.read_chr(0x1000), 14, "$1000 must read 1K bank 14");
assert_eq!(mapper.read_chr(0x1400), 15, "$1400 must read 1K bank 15");
assert_eq!(
mapper.read_chr(0x1800),
14,
"$1800 must mirror reg[0] block"
);
assert_eq!(
mapper.read_chr(0x1C00),
15,
"$1C00 must mirror reg[0] block"
);
}
#[test]
fn prg_e000_is_last_bank_at_power_on() {
let mapper = make_mapper();
let last = (PRG_8K_BANKS - 1) as u8;
assert_eq!(
mapper.read_prg(0xE000),
last,
"$E000 must be fixed to last 8KB bank"
);
}
fn clock_irq_a12_rising_edge(mapper: &mut Mapper197) {
mapper.ppu_address_changed(0x0000);
for _ in 0..3 {
mapper.cpu_cycle();
}
mapper.ppu_address_changed(0x1000);
}
#[test]
fn irq_not_pending_at_power_on() {
let mapper = make_mapper();
assert!(!mapper.irq_pending());
}
#[test]
fn mmc3_irq_is_clocked_through_mapper197() {
let mut mapper = make_mapper();
mapper.write_prg(0xC000, 1); mapper.write_prg(0xC001, 0); mapper.write_prg(0xE001, 0);
clock_irq_a12_rising_edge(&mut mapper);
assert!(
!mapper.irq_pending(),
"first qualified A12 rise should only reload/decrement the IRQ counter"
);
clock_irq_a12_rising_edge(&mut mapper);
assert!(
mapper.irq_pending(),
"second qualified A12 rise should assert MMC3 IRQ through Mapper197"
);
}
#[test]
fn snapshot_round_trips_chr_state() {
let mut mapper = make_mapper();
set_chr_mode(&mut mapper, false);
set_chr_reg(&mut mapper, 0, 5);
set_chr_reg(&mut mapper, 2, 3);
set_chr_reg(&mut mapper, 3, 4);
let snap = mapper.registers_snapshot();
let mut restored = make_mapper();
restored.restore_registers(&snap);
assert_eq!(
restored.read_chr(0x0000),
mapper.read_chr(0x0000),
"CHR $0000 must match"
);
assert_eq!(
restored.read_chr(0x1000),
mapper.read_chr(0x1000),
"CHR $1000 must match"
);
assert_eq!(
restored.read_chr(0x1800),
mapper.read_chr(0x1800),
"CHR $1800 must match"
);
}
}