use crate::cartridge::BaseMapper;
use crate::cartridge::NametableLayout;
use crate::cartridge::mapper::{Mapper, MapperCapabilities};
pub struct Mapper56 {
base: BaseMapper,
prg_reg: [u8; 3], prg_a17: [u8; 4], chr_regs: [u8; 8], bank_select: u8, irq_latch: u16,
irq_counter: u16,
irq_enabled: bool,
irq_after_ack: bool, irq_pending: bool,
}
impl Mapper56 {
pub fn new(ctx: super::mapper::MapperContext) -> Self {
let capabilities = MapperCapabilities {
has_irq: true,
has_chr_banking: true,
has_dynamic_mirroring: true,
max_prg_ram_kb: 8,
prg_bank_size_kb: 8,
chr_bank_size_kb: 1,
..Default::default()
};
let mut base = BaseMapper::new(&ctx, capabilities);
base.configure_prg_banking(0x2000);
base.configure_chr_banking(0x0400);
base.set_mirroring(NametableLayout::Vertical);
let mut mapper = Self {
base,
prg_reg: [0; 3],
prg_a17: [1; 4], chr_regs: [0; 8],
bank_select: 0,
irq_latch: 0,
irq_counter: 0,
irq_enabled: false,
irq_after_ack: false,
irq_pending: false,
};
mapper.update_banks();
mapper
}
fn update_banks(&mut self) {
for i in 0..3 {
let a17 = (self.prg_a17[i] as i16) & 1;
let reg = (self.prg_reg[i] as i16) & 0x0F;
self.base.select_prg_page(i, (a17 << 4) | reg);
}
let a17 = (self.prg_a17[3] as usize) & 1;
let block_last = (a17 << 4) | 0x0F;
let count = self.base.prg_bank_count();
let bank = if count == 0 {
0
} else {
block_last.min(count - 1)
};
self.base.select_prg_page(3, bank as i16);
for i in 0..8 {
self.base.select_chr_page(i, self.chr_regs[i] as i16);
}
}
}
impl Mapper for Mapper56 {
fn base(&self) -> &BaseMapper {
&self.base
}
fn base_mut(&mut self) -> &mut BaseMapper {
&mut self.base
}
fn read_prg(&self, addr: u16) -> u8 {
match addr {
0x6000..=0x7FFF => self.base.try_read_prg_ram(addr).unwrap_or(0),
0x8000..=0xFFFF => self.base.read_prg_banked(addr),
_ => 0,
}
}
fn write_prg(&mut self, addr: u16, value: u8) {
match addr {
0x6000..=0x7FFF => {
self.base.try_write_prg_ram(addr, value);
}
0x8000..=0x8FFF => {
self.irq_latch = (self.irq_latch & 0xFFF0) | ((value as u16) & 0x0F);
}
0x9000..=0x9FFF => {
self.irq_latch = (self.irq_latch & 0xFF0F) | (((value as u16) & 0x0F) << 4);
}
0xA000..=0xAFFF => {
self.irq_latch = (self.irq_latch & 0xF0FF) | (((value as u16) & 0x0F) << 8);
}
0xB000..=0xBFFF => {
self.irq_latch = (self.irq_latch & 0x0FFF) | (((value as u16) & 0x0F) << 12);
}
0xC000..=0xCFFF => {
self.irq_after_ack = (value & 0x01) != 0;
self.irq_enabled = (value & 0x02) != 0;
if !self.irq_enabled {
self.irq_pending = false;
}
}
0xD000..=0xDFFF => {
self.irq_pending = false;
self.irq_counter = self.irq_latch;
self.irq_enabled = self.irq_after_ack;
}
0xE000..=0xEFFF => {
self.bank_select = value & 0x07;
}
0xF000..=0xFFFF => {
match self.bank_select {
1 => self.prg_reg[0] = value & 0x0F,
2 => self.prg_reg[1] = value & 0x0F,
3 => self.prg_reg[2] = value & 0x0F,
_ => {}
}
if (addr & 0xFC00) == 0xF000 {
let slot = (addr & 0x0003) as usize;
if slot < 4 {
self.prg_a17[slot] = (value >> 3) & 0x01;
}
}
if (addr & 0xFC00) == 0xF800 {
self.base.set_mirroring_hv((value & 0x01) == 0);
}
if (addr & 0xFC00) == 0xFC00 {
let slot = (addr & 0x0007) as usize;
self.chr_regs[slot] = value & 0x7F;
}
self.update_banks();
}
_ => {}
}
}
fn irq_pending(&self) -> bool {
self.irq_pending
}
fn cpu_cycle(&mut self) {
if !self.irq_enabled || self.irq_counter == 0 {
return;
}
self.irq_counter -= 1;
if self.irq_counter == 0 {
self.irq_pending = true;
self.irq_enabled = false; }
}
fn registers_snapshot(&self) -> Vec<u8> {
let mirror_byte = match self.base.mirroring() {
NametableLayout::Vertical => 1u8,
_ => 0u8,
};
let irq_flags = (self.irq_enabled as u8)
| ((self.irq_pending as u8) << 1)
| ((self.irq_after_ack as u8) << 2);
let mut v = vec![
self.prg_reg[0],
self.prg_reg[1],
self.prg_reg[2],
self.prg_a17[0],
self.prg_a17[1],
self.prg_a17[2],
self.prg_a17[3],
mirror_byte,
self.bank_select,
irq_flags,
(self.irq_latch & 0xFF) as u8,
(self.irq_latch >> 8) as u8,
(self.irq_counter & 0xFF) as u8,
(self.irq_counter >> 8) as u8,
];
v.extend_from_slice(&self.chr_regs);
v
}
fn restore_registers(&mut self, data: &[u8]) {
if data.len() < 22 {
return;
}
self.prg_reg[0] = data[0];
self.prg_reg[1] = data[1];
self.prg_reg[2] = data[2];
self.prg_a17[0] = data[3];
self.prg_a17[1] = data[4];
self.prg_a17[2] = data[5];
self.prg_a17[3] = data[6];
self.base.set_mirroring_hv(data[7] == 0);
self.bank_select = data[8];
self.irq_enabled = (data[9] & 1) != 0;
self.irq_pending = (data[9] & 2) != 0;
self.irq_after_ack = (data[9] & 4) != 0;
self.irq_latch = (data[10] as u16) | ((data[11] as u16) << 8);
self.irq_counter = (data[12] as u16) | ((data[13] as u16) << 8);
self.chr_regs.copy_from_slice(&data[14..22]);
self.update_banks();
}
fn reset(&mut self) {
self.prg_reg = [0; 3];
self.prg_a17 = [1; 4];
self.chr_regs = [0; 8];
self.base.set_mirroring(NametableLayout::Vertical);
self.bank_select = 0;
self.irq_latch = 0;
self.irq_counter = 0;
self.irq_enabled = false;
self.irq_after_ack = false;
self.irq_pending = false;
self.update_banks();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cartridge::mapper::{MapperContext, create_mapper};
use crate::cartridge::test_helpers::banked_data;
const PRG_BANKS: usize = 16; const CHR_BANKS: usize = 64;
fn make_mapper() -> Mapper56 {
let prg = banked_data(8 * 1024, PRG_BANKS);
let chr = banked_data(1024, CHR_BANKS);
Mapper56::new(MapperContext::new_for_test(
56,
prg,
chr,
NametableLayout::Horizontal,
))
}
#[test]
fn mapper_56_is_registered() {
let result = create_mapper(MapperContext::new_for_test(
56,
banked_data(8 * 1024, PRG_BANKS),
banked_data(1024, CHR_BANKS),
NametableLayout::Vertical,
));
assert!(result.is_ok(), "Mapper 56 must be registered");
}
#[test]
fn prg_bank_select_via_e000_and_f000() {
let mut mapper = make_mapper();
mapper.write_prg(0xE000, 1); mapper.write_prg(0xF000, 5); assert_eq!(mapper.prg_reg[0], 5 & 0x0F, "PRG reg0 must be 5");
assert_eq!(mapper.read_prg(0x8000), 5, "PRG $8000 must map to bank 5");
}
#[test]
fn prg_a17_set_by_superimposed_f000_write() {
let mut mapper = make_mapper();
mapper.write_prg(0xE000, 1);
mapper.write_prg(0xF000, 0x08); assert_eq!(mapper.prg_a17[0], 1, "PRG A17 bit must be 1");
assert_eq!(
mapper.read_prg(0x8000),
24 % PRG_BANKS as u8,
"PRG bank includes A17"
);
}
#[test]
fn prg_e000_fixed_last_bank() {
let mapper = make_mapper();
assert_eq!(
mapper.read_prg(0xE000),
(PRG_BANKS - 1) as u8,
"$E000-$FFFF must be fixed to last bank"
);
}
#[test]
fn chr_banks_via_fc00() {
let mut mapper = make_mapper();
for slot in 0..8u16 {
mapper.write_prg(0xFC00 + slot, (slot * 7) as u8 & 0x3F);
}
for slot in 0..8u16 {
let bank = (slot * 7) as usize & 0x3F;
let expected = (bank % CHR_BANKS) as u8;
assert_eq!(
mapper.read_chr(slot * 1024),
expected,
"CHR slot {slot} wrong bank"
);
}
}
#[test]
fn mirroring_horizontal_via_f800() {
let mut mapper = make_mapper();
mapper.write_prg(0xF800, 0x00); assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
}
#[test]
fn mirroring_vertical_via_f800() {
let mut mapper = make_mapper();
mapper.write_prg(0xF800, 0x01); assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
}
#[test]
fn irq_not_pending_by_default() {
let mapper = make_mapper();
assert!(!mapper.irq_pending());
}
#[test]
fn irq_latch_nibbles() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 0x01); mapper.write_prg(0x9000, 0x02); mapper.write_prg(0xA000, 0x03); mapper.write_prg(0xB000, 0x04); assert_eq!(mapper.irq_latch, 0x4321, "IRQ latch must be 0x4321");
}
#[test]
fn irq_fires_after_n_cycles() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 3); mapper.write_prg(0x9000, 0);
mapper.write_prg(0xA000, 0);
mapper.write_prg(0xB000, 0);
mapper.write_prg(0xC000, 0x03);
mapper.write_prg(0xD000, 0x00); for _ in 0..2 {
assert!(!mapper.irq_pending());
mapper.cpu_cycle();
}
mapper.cpu_cycle(); assert!(mapper.irq_pending(), "IRQ must fire after 3 cycles");
}
#[test]
fn irq_acknowledge_clears_pending() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 1); mapper.write_prg(0xC000, 0x03);
mapper.write_prg(0xD000, 0); mapper.cpu_cycle(); assert!(mapper.irq_pending());
mapper.write_prg(0xD000, 0); assert!(!mapper.irq_pending());
}
#[test]
fn prg_ram_readable_and_writable_at_6000() {
let mut mapper = make_mapper();
mapper.write_prg(0x6000, 0xAB);
assert_eq!(mapper.read_prg(0x6000), 0xAB);
mapper.write_prg(0x7FFF, 0xCD);
assert_eq!(mapper.read_prg(0x7FFF), 0xCD);
}
#[test]
fn wram_snapshot_round_trip() {
let mut mapper = make_mapper();
mapper.write_prg(0x6000, 0x11);
mapper.write_prg(0x6100, 0x22);
mapper.write_prg(0x7FFF, 0x33);
let snapshot = mapper.wram_snapshot();
assert_eq!(snapshot.len(), 8192);
assert_eq!(snapshot[0x0000], 0x11);
assert_eq!(snapshot[0x0100], 0x22);
assert_eq!(snapshot[0x1FFF], 0x33);
let mut mapper2 = make_mapper();
mapper2.load_wram_snapshot(&snapshot);
assert_eq!(mapper2.read_prg(0x6000), 0x11);
assert_eq!(mapper2.read_prg(0x6100), 0x22);
assert_eq!(mapper2.read_prg(0x7FFF), 0x33);
}
}