use crate::nes::cartridge::base_mapper::BaseMapper;
use crate::nes::cartridge::mapper::{Mapper, MapperCapabilities};
const MAPPER_NUMBER: u16 = 125;
const PRG_BANK_SIZE: usize = 8 * 1024;
pub struct Mapper125 {
base: BaseMapper,
prg_reg: u8,
wram: Vec<u8>,
}
impl Mapper125 {
pub fn new(ctx: crate::nes::cartridge::mapper::MapperContext) -> Self {
let capabilities = MapperCapabilities {
prg_bank_size_kb: 8,
chr_bank_size_kb: 8,
..Default::default()
};
let mut base = BaseMapper::new(&ctx, capabilities);
base.configure_prg_banking(PRG_BANK_SIZE);
base.configure_prg_6000_banking();
let mut mapper = Self {
base,
prg_reg: 0,
wram: vec![0; 8 * 1024],
};
mapper.apply_state();
mapper
}
fn apply_state(&mut self) {
self.base.select_prg_page(0, -4);
self.base.select_prg_page(1, -3);
self.base.select_prg_page(3, -1);
self.base.select_prg_6000_page(self.prg_reg as i16);
}
}
impl Mapper for Mapper125 {
fn base(&self) -> &BaseMapper {
&self.base
}
fn base_mut(&mut self) -> &mut BaseMapper {
&mut self.base
}
fn mapper_number(&self) -> u16 {
MAPPER_NUMBER
}
fn read_prg(&self, addr: u16) -> u8 {
match addr {
0x6000..=0x7FFF => self.base.try_read_prg_6000(addr).unwrap_or(0),
0xC000..=0xDFFF => self.wram[(addr - 0xC000) as usize],
0x8000..=0xBFFF | 0xE000..=0xFFFF => self.base.read_prg_rom(addr),
_ => 0,
}
}
fn write_prg(&mut self, addr: u16, value: u8) {
match addr {
0x6000 => {
self.prg_reg = value;
self.apply_state();
}
0xC000..=0xDFFF => {
self.wram[(addr - 0xC000) as usize] = value;
}
_ => {}
}
}
fn registers_snapshot(&self) -> Vec<u8> {
vec![self.prg_reg]
}
fn restore_registers(&mut self, data: &[u8]) {
if let Some(®) = data.first() {
self.prg_reg = reg;
self.apply_state();
}
}
fn initialize_ram(&mut self, mode: crate::nes::console::RamInitMode) {
self.base_mut().initialize_ram(mode);
crate::nes::console::initialize_ram(&mut self.wram, mode);
}
fn reset(&mut self) {
self.prg_reg = 0;
self.apply_state();
}
fn wram_size(&self) -> usize {
self.wram.len()
}
fn wram_snapshot(&self) -> Vec<u8> {
self.wram.clone()
}
fn load_wram_snapshot(&mut self, data: &[u8]) {
let len = data.len().min(self.wram.len());
self.wram[..len].copy_from_slice(&data[..len]);
}
}
#[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;
use crate::nes::console::RamInitMode;
const TEST_PRG_BANKS_8K: usize = 16;
fn make_mapper() -> Mapper125 {
Mapper125::new(MapperContext::new_for_test(
MAPPER_NUMBER,
banked_data(PRG_BANK_SIZE, TEST_PRG_BANKS_8K),
vec![], NametableLayout::Vertical,
))
}
#[test]
fn mapper_125_is_registered_in_factory() {
let result = create_mapper(MapperContext::new_for_test(
MAPPER_NUMBER,
banked_data(PRG_BANK_SIZE, TEST_PRG_BANKS_8K),
vec![],
NametableLayout::Vertical,
));
assert!(result.is_ok(), "Mapper 125 must be creatable via factory");
}
#[test]
fn reset_maps_fixed_prg_banks() {
let mapper = make_mapper();
assert_eq!(
mapper.read_prg(0x8000),
12,
"$8000 should read bank -4 (12)"
);
assert_eq!(
mapper.read_prg(0xA000),
13,
"$A000 should read bank -3 (13)"
);
assert_eq!(
mapper.read_prg(0xE000),
15,
"$E000 should read bank -1 (15)"
);
}
#[test]
fn reset_maps_prg_6000_to_bank_0() {
let mapper = make_mapper();
assert_eq!(
mapper.read_prg(0x6000),
0,
"$6000 should read bank 0 on reset"
);
}
#[test]
fn c000_dfff_is_writable_ram() {
let mut mapper = make_mapper();
mapper.write_prg(0xC000, 0x42);
mapper.write_prg(0xDFFF, 0xAB);
assert_eq!(
mapper.read_prg(0xC000),
0x42,
"WRAM at $C000 must be writable"
);
assert_eq!(
mapper.read_prg(0xDFFF),
0xAB,
"WRAM at $DFFF must be writable"
);
}
#[test]
fn c000_dfff_is_independent_of_prg_rom() {
let mut mapper = make_mapper();
mapper.write_prg(0xC000, 0xFF);
assert_eq!(
mapper.read_prg(0x8000),
12,
"PRG-ROM at $8000 must be unchanged"
);
}
#[test]
fn c000_dfff_defaults_to_zero() {
let mapper = make_mapper();
assert_eq!(mapper.read_prg(0xC000), 0, "WRAM should default to 0");
assert_eq!(mapper.read_prg(0xD000), 0, "WRAM should default to 0");
}
#[test]
fn write_to_6000_selects_prg_bank_at_6000() {
let mut mapper = make_mapper();
mapper.write_prg(0x6000, 5);
assert_eq!(
mapper.read_prg(0x6000),
5,
"bank 5 should be mapped to $6000"
);
mapper.write_prg(0x6000, 10);
assert_eq!(
mapper.read_prg(0x6000),
10,
"bank 10 should be mapped to $6000"
);
}
#[test]
fn write_to_6001_is_not_a_register() {
let mut mapper = make_mapper();
mapper.write_prg(0x6000, 5);
mapper.write_prg(0x6001, 10);
assert_eq!(
mapper.read_prg(0x6000),
5,
"$6001 write must not change the PRG register"
);
}
#[test]
fn register_write_does_not_affect_fixed_banks() {
let mut mapper = make_mapper();
mapper.write_prg(0x6000, 3);
assert_eq!(
mapper.read_prg(0x8000),
12,
"$8000 must remain fixed at bank -4"
);
assert_eq!(
mapper.read_prg(0xA000),
13,
"$A000 must remain fixed at bank -3"
);
assert_eq!(
mapper.read_prg(0xE000),
15,
"$E000 must remain fixed at bank -1"
);
}
#[test]
fn mirroring_is_hard_wired_from_header() {
let mapper = make_mapper();
assert_eq!(
mapper.get_mirroring(),
NametableLayout::Vertical,
"mirroring must match the header"
);
}
#[test]
fn reset_restores_prg_bank_to_0() {
let mut mapper = make_mapper();
mapper.write_prg(0x6000, 7);
assert_eq!(mapper.read_prg(0x6000), 7, "precondition: PRG bank 7");
mapper.reset();
assert_eq!(
mapper.read_prg(0x6000),
0,
"reset must restore PRG bank 0 at $6000"
);
}
#[test]
fn snapshot_restore_round_trips_prg_register() {
let mut mapper = make_mapper();
mapper.write_prg(0x6000, 9);
let snapshot = mapper.registers_snapshot();
let mut restored = make_mapper();
restored.restore_registers(&snapshot);
assert_eq!(restored.read_prg(0x6000), 9, "restored PRG bank must be 9");
}
#[test]
fn wram_snapshot_restore_round_trips() {
let mut mapper = make_mapper();
mapper.write_prg(0xC000, 0x42);
mapper.write_prg(0xC100, 0xAB);
let snapshot = mapper.wram_snapshot();
let mut restored = make_mapper();
restored.load_wram_snapshot(&snapshot);
assert_eq!(
restored.read_prg(0xC000),
0x42,
"WRAM at $C000 must be restored"
);
assert_eq!(
restored.read_prg(0xC100),
0xAB,
"WRAM at $C100 must be restored"
);
}
#[test]
fn capabilities_match_mapper_hardware() {
let mapper = make_mapper();
let caps = mapper.capabilities();
assert!(!caps.has_irq, "no IRQ on this board");
assert!(!caps.has_expansion_audio, "no expansion audio");
assert!(!caps.has_dynamic_mirroring, "mirroring is hard-wired");
assert_eq!(caps.prg_bank_size_kb, 8, "PRG bank size is 8 KB");
}
#[test]
fn wram_size_is_8kb() {
let mapper = make_mapper();
assert_eq!(mapper.wram_size(), 8 * 1024, "WRAM must be 8 KB");
}
#[test]
fn initialize_ram_zero_clears_wram() {
let mut mapper = make_mapper();
mapper.write_prg(0xC000, 0xFF);
mapper.write_prg(0xDFFF, 0xAB);
mapper.initialize_ram(RamInitMode::Zero);
assert_eq!(
mapper.read_prg(0xC000),
0x00,
"initialize_ram(Zero) must clear WRAM at $C000"
);
assert_eq!(
mapper.read_prg(0xDFFF),
0x00,
"initialize_ram(Zero) must clear WRAM at $DFFF"
);
}
}