use crate::nes::cartridge::base_mapper::BaseMapper;
use crate::nes::cartridge::mapper::{Mapper, MapperContext};
use crate::nes::cartridge::nintendo::mmc3::MMC3Mapper;
const MAPPER_NUMBER: u16 = 198;
const CHR_RAM_SIZE: usize = 8 * 1024;
const CHR_1K_SIZE: usize = 1024;
const EXTRA_WRAM_SIZE: usize = 4 * 1024;
pub struct Mapper198 {
inner: MMC3Mapper,
chr_ram: [u8; CHR_RAM_SIZE],
extra_wram: [u8; EXTRA_WRAM_SIZE],
}
impl Mapper198 {
pub fn new(ctx: MapperContext) -> Self {
let prg_rom = ctx.prg_rom;
let mirroring = ctx.mirroring;
let inner = MMC3Mapper::new_with_irq_mode_and_prg_ram_banks(
prg_rom,
vec![], mirroring,
false,
1, );
Self {
inner,
chr_ram: [0; CHR_RAM_SIZE],
extra_wram: [0; EXTRA_WRAM_SIZE],
}
}
}
impl Mapper for Mapper198 {
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 mapper_number(&self) -> u16 {
MAPPER_NUMBER
}
fn read_prg(&self, addr: u16) -> u8 {
if (0x5000..=0x5FFF).contains(&addr) {
return self.extra_wram[(addr - 0x5000) as usize];
}
self.inner.read_prg(addr)
}
fn write_prg(&mut self, addr: u16, value: u8) {
if (0x5000..=0x5FFF).contains(&addr) {
self.extra_wram[(addr - 0x5000) as usize] = value;
return;
}
self.inner.write_prg(addr, value);
}
fn read_chr(&mut self, addr: u16) -> u8 {
let bank = self.inner.raw_chr_1k_bank(addr) % (CHR_RAM_SIZE / CHR_1K_SIZE);
let offset = (addr as usize) & (CHR_1K_SIZE - 1);
self.chr_ram[bank * CHR_1K_SIZE + offset]
}
fn write_chr(&mut self, addr: u16, value: u8) {
let bank = self.inner.raw_chr_1k_bank(addr) % (CHR_RAM_SIZE / CHR_1K_SIZE);
let offset = (addr as usize) & (CHR_1K_SIZE - 1);
self.chr_ram[bank * CHR_1K_SIZE + offset] = value;
}
fn registers_snapshot(&self) -> Vec<u8> {
let mut snap = self.inner.registers_snapshot();
snap.extend_from_slice(&self.extra_wram);
snap
}
fn restore_registers(&mut self, data: &[u8]) {
let inner_len = self.inner.registers_snapshot().len();
if data.len() >= inner_len {
self.inner.restore_registers(&data[..inner_len]);
}
if data.len() >= inner_len + EXTRA_WRAM_SIZE {
self.extra_wram
.copy_from_slice(&data[inner_len..inner_len + EXTRA_WRAM_SIZE]);
}
}
fn initialize_chr_ram(&mut self, mode: crate::nes::console::RamInitMode) {
crate::nes::console::initialize_ram(&mut self.chr_ram, mode);
crate::nes::console::initialize_ram(&mut self.extra_wram, mode);
}
fn initialize_ram(&mut self, mode: crate::nes::console::RamInitMode) {
self.inner.initialize_ram(mode);
crate::nes::console::initialize_ram(&mut self.chr_ram, mode);
crate::nes::console::initialize_ram(&mut self.extra_wram, mode);
}
fn chr_ram_snapshot(&self) -> Vec<u8> {
self.chr_ram.to_vec()
}
fn restore_chr_ram(&mut self, data: &[u8]) {
if data.len() == CHR_RAM_SIZE {
self.chr_ram.copy_from_slice(data);
}
}
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 reset(&mut self) {
self.inner.reset();
self.extra_wram = [0; EXTRA_WRAM_SIZE];
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nes::cartridge::NametableLayout;
use crate::nes::cartridge::mapper::create_mapper;
use crate::nes::cartridge::test_helpers::banked_data;
fn make_mapper() -> Mapper198 {
Mapper198::new(MapperContext::new_for_test(
MAPPER_NUMBER,
banked_data(8 * 1024, 80), vec![],
NametableLayout::Vertical,
))
}
#[test]
fn mapper_198_is_registered_in_factory() {
let result = create_mapper(MapperContext::new_for_test(
MAPPER_NUMBER,
banked_data(8 * 1024, 80),
vec![],
NametableLayout::Vertical,
));
assert!(result.is_ok(), "Mapper 198 must be creatable via factory");
}
#[test]
fn extra_wram_at_5000_is_readable_and_writable() {
let mut m = make_mapper();
m.write_prg(0x5000, 0xAB);
assert_eq!(m.read_prg(0x5000), 0xAB);
m.write_prg(0x5FFF, 0xCD);
assert_eq!(m.read_prg(0x5FFF), 0xCD);
}
#[test]
fn extra_wram_is_4kb() {
let mut m = make_mapper();
m.write_prg(0x5000, 0x11);
m.write_prg(0x5FFF, 0x22);
assert_eq!(m.read_prg(0x5000), 0x11);
assert_eq!(m.read_prg(0x5FFF), 0x22);
}
#[test]
fn chr_ram_read_write_roundtrip() {
let mut m = make_mapper();
m.write_chr(0x0000, 0x55);
assert_eq!(m.read_chr(0x0000), 0x55);
m.write_chr(0x1FFF, 0xAA);
assert_eq!(m.read_chr(0x1FFF), 0xAA);
}
#[test]
fn fixed_prg_banks_are_last_two_of_640kb_rom() {
let m = make_mapper();
let bank_c000 = m.inner.mapped_prg_bank(0xC000);
let bank_e000 = m.inner.mapped_prg_bank(0xE000);
assert_eq!(bank_c000, 78, "Fixed $C000 must be bank 78 ($4E)");
assert_eq!(bank_e000, 79, "Fixed $E000 must be bank 79 ($4F)");
}
#[test]
fn switchable_prg_bank_at_8000() {
let mut m = make_mapper();
m.write_prg(0x8000, 0x06); m.write_prg(0x8001, 10); let bank = m.inner.mapped_prg_bank(0x8000);
assert_eq!(bank, 10);
}
#[test]
fn chr_bank_register_affects_chr_read() {
let mut m = make_mapper();
m.write_prg(0x8000, 0x82); m.write_prg(0x8001, 5); m.write_chr(0x1000, 0x77);
assert_eq!(m.read_chr(0x1000), 0x77);
}
#[test]
fn reset_clears_extra_wram() {
let mut m = make_mapper();
m.write_prg(0x5100, 0xFF);
m.reset();
assert_eq!(
m.read_prg(0x5100),
0x00,
"Extra WRAM must be cleared on reset"
);
}
#[test]
fn snapshot_restore_round_trips_mmc3_state() {
let mut m = make_mapper();
m.write_prg(0x8000, 0x06);
m.write_prg(0x8001, 0x10);
let snap = m.registers_snapshot();
let mut m2 = make_mapper();
m2.restore_registers(&snap);
assert_eq!(m2.inner.mapped_prg_bank(0x8000), 0x10);
}
#[test]
fn snapshot_restore_preserves_extra_wram() {
let mut m = make_mapper();
m.write_prg(0x5000, 0xAA);
m.write_prg(0x5FFF, 0x55);
let snap = m.registers_snapshot();
let mut m2 = make_mapper();
m2.restore_registers(&snap);
assert_eq!(
m2.read_prg(0x5000),
0xAA,
"extra_wram[0] must survive snapshot"
);
assert_eq!(
m2.read_prg(0x5FFF),
0x55,
"extra_wram[last] must survive snapshot"
);
}
#[test]
fn chr_ram_snapshot_restore_round_trips() {
let mut m = make_mapper();
m.write_chr(0x0100, 0xDE);
m.write_chr(0x1F00, 0xAD);
let snap = m.chr_ram_snapshot();
let mut m2 = make_mapper();
m2.restore_chr_ram(&snap);
assert_eq!(m2.read_chr(0x0100), 0xDE);
assert_eq!(m2.read_chr(0x1F00), 0xAD);
}
#[test]
fn wram_size_matches_inner_mmc3_prg_ram() {
let m = make_mapper();
assert_eq!(m.wram_size(), 8 * 1024);
}
}