use crate::cartridge::base_mapper::BaseMapper;
use crate::cartridge::mapper::{Mapper, MapperCapabilities, MapperContext};
pub struct Mapper307 {
base: BaseMapper,
current_register: u8,
regs: [u8; 8],
ciram: [u8; CIRAM_SIZE],
wram: [u8; 0x2000],
}
const PRG_PAGE_SIZE: usize = 0x1000; const CIRAM_SIZE: usize = 0x800;
impl Mapper307 {
pub fn new(ctx: MapperContext) -> Self {
let capabilities = MapperCapabilities {
has_dynamic_mirroring: true,
prg_bank_size_kb: 4,
max_prg_ram_kb: 8,
..Default::default()
};
let base = BaseMapper::new(&ctx, capabilities);
Self {
base,
current_register: 0,
regs: [0; 8],
ciram: [0; CIRAM_SIZE],
wram: [0; 0x2000],
}
}
fn prg_bank_count(&self) -> usize {
(self.base.prg_rom().len() / PRG_PAGE_SIZE).max(1)
}
fn resolve_prg_bank(&self, bank: i32) -> usize {
let count = self.prg_bank_count() as i32;
let b = ((bank % count) + count) % count;
b as usize
}
fn read_prg_rom_page(&self, bank: usize, offset: usize) -> u8 {
let prg = self.base.prg_rom();
prg.get(bank * PRG_PAGE_SIZE + offset).copied().unwrap_or(0)
}
fn nt_page_for_quadrant(&self, quadrant: usize) -> usize {
let bit = match quadrant {
0 => self.regs[2] & 1,
1 => self.regs[4] & 1,
2 => self.regs[3] & 1,
3 => self.regs[5] & 1,
_ => 0,
};
bit as usize
}
}
impl Mapper for Mapper307 {
fn base(&self) -> &BaseMapper {
&self.base
}
fn base_mut(&mut self) -> &mut BaseMapper {
&mut self.base
}
fn capabilities(&self) -> MapperCapabilities {
MapperCapabilities {
has_dynamic_mirroring: true,
prg_bank_size_kb: 4,
max_prg_ram_kb: 8,
..Default::default()
}
}
fn read_prg(&self, addr: u16) -> u8 {
let count = self.prg_bank_count() as i32;
match addr {
0x6000..=0x6FFF => {
let offset = (addr - 0x6000) as usize;
self.wram.get(offset).copied().unwrap_or(0)
}
0x7000..=0x7FFF => {
let bank = self.resolve_prg_bank(15);
let offset = (addr - 0x7000) as usize;
self.read_prg_rom_page(bank, offset)
}
0x8000..=0x8FFF => {
let bank = self.resolve_prg_bank((self.regs[6] as i32) * 2);
let offset = (addr - 0x8000) as usize;
self.read_prg_rom_page(bank, offset)
}
0x9000..=0x9FFF => {
let bank = self.resolve_prg_bank((self.regs[6] as i32) * 2 + 1);
let offset = (addr - 0x9000) as usize;
self.read_prg_rom_page(bank, offset)
}
0xA000..=0xAFFF => {
let bank = self.resolve_prg_bank(count - 4);
let offset = (addr - 0xA000) as usize;
self.read_prg_rom_page(bank, offset)
}
0xB000..=0xBFFF => {
let offset = (addr - 0xB000) as usize + 0x1000;
self.wram.get(offset).copied().unwrap_or(0)
}
0xC000..=0xCFFF => {
let bank = self.resolve_prg_bank((self.regs[7] as i32) * 2);
let offset = (addr - 0xC000) as usize;
self.read_prg_rom_page(bank, offset)
}
0xD000..=0xDFFF => {
let bank = self.resolve_prg_bank((self.regs[7] as i32) * 2 + 1);
let offset = (addr - 0xD000) as usize;
self.read_prg_rom_page(bank, offset)
}
0xE000..=0xEFFF => {
let bank = self.resolve_prg_bank(count - 2);
let offset = (addr - 0xE000) as usize;
self.read_prg_rom_page(bank, offset)
}
0xF000..=0xFFFF => {
let bank = self.resolve_prg_bank(count - 1);
let offset = (addr - 0xF000) as usize;
self.read_prg_rom_page(bank, offset)
}
_ => 0,
}
}
fn read_prg_open_bus(&self, addr: u16, open_bus: u8) -> u8 {
match addr {
0x6000..=0xFFFF => self.read_prg(addr),
_ => open_bus,
}
}
fn wram_size(&self) -> usize {
self.wram.len()
}
fn wram_snapshot(&self) -> Vec<u8> {
self.wram.to_vec()
}
fn load_wram_snapshot(&mut self, data: &[u8]) {
let len = data.len().min(self.wram.len());
self.wram[..len].copy_from_slice(&data[..len]);
}
fn write_prg(&mut self, addr: u16, value: u8) {
match addr {
0x6000..=0x6FFF => {
let offset = (addr - 0x6000) as usize;
if offset < 0x1000 {
self.wram[offset] = value;
}
}
0xB000..=0xBFFF => {
let offset = (addr - 0xB000) as usize + 0x1000;
if offset < 0x2000 {
self.wram[offset] = value;
}
}
0x8000..=0x9FFF | 0xC000..=0xFFFF => match addr & 0xE001 {
0x8000 => self.current_register = value & 0x07,
0x8001 => {
self.regs[self.current_register as usize] = value;
}
_ => {}
},
_ => {}
}
}
fn read_nametable(&mut self, addr: u16) -> Option<u8> {
let addr = addr & 0x2FFF;
if !(0x2000..=0x2FFF).contains(&addr) {
return None;
}
let quadrant = ((addr - 0x2000) >> 10) as usize;
let page = self.nt_page_for_quadrant(quadrant);
let offset = (addr as usize & 0x3FF) + page * 0x400;
Some(self.ciram[offset])
}
fn write_nametable(&mut self, addr: u16, value: u8) -> bool {
let addr = addr & 0x2FFF;
if !(0x2000..=0x2FFF).contains(&addr) {
return false;
}
let quadrant = ((addr - 0x2000) >> 10) as usize;
let page = self.nt_page_for_quadrant(quadrant);
let offset = (addr as usize & 0x3FF) + page * 0x400;
self.ciram[offset] = value;
true
}
fn registers_snapshot(&self) -> Vec<u8> {
let mut snap = Vec::with_capacity(9 + CIRAM_SIZE);
snap.extend_from_slice(&self.regs);
snap.push(self.current_register);
snap.extend_from_slice(&self.ciram);
snap
}
fn restore_registers(&mut self, data: &[u8]) {
if data.len() < 9 + CIRAM_SIZE {
return;
}
self.regs.copy_from_slice(&data[0..8]);
self.current_register = data[8];
self.ciram.copy_from_slice(&data[9..9 + CIRAM_SIZE]);
}
fn reset(&mut self) {
self.current_register = 0;
self.regs = [0; 8];
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cartridge::NametableLayout;
use crate::cartridge::mapper::MapperContext;
fn make_prg_rom(num_banks: usize) -> Vec<u8> {
let mut prg = vec![0u8; num_banks * PRG_PAGE_SIZE];
for bank in 0..num_banks {
let start = bank * PRG_PAGE_SIZE;
for b in prg[start..start + PRG_PAGE_SIZE].iter_mut() {
*b = bank as u8;
}
}
prg
}
fn create_mapper(num_banks: usize) -> Mapper307 {
let prg = make_prg_rom(num_banks);
let chr = vec![0u8; 0x2000]; let ctx = MapperContext::new_for_test(307, prg, chr, NametableLayout::Horizontal);
Mapper307::new(ctx)
}
#[test]
fn test_prg_7000_fixed_to_bank_15() {
let mapper = create_mapper(32);
assert_eq!(mapper.read_prg(0x7000), 15);
assert_eq!(mapper.read_prg(0x7FFF), 15);
}
#[test]
fn test_prg_a000_fixed_to_4th_from_last() {
let mapper = create_mapper(32);
assert_eq!(mapper.read_prg(0xA000), 28);
}
#[test]
fn test_prg_e000_fixed_to_second_last_bank() {
let mapper = create_mapper(32);
assert_eq!(mapper.read_prg(0xE000), 30);
}
#[test]
fn test_prg_f000_fixed_to_last_bank() {
let mapper = create_mapper(32);
assert_eq!(mapper.read_prg(0xF000), 31);
}
#[test]
fn test_prg_8000_bank_switching_via_reg6() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x8000, 6); mapper.write_prg(0x8001, 2); assert_eq!(mapper.read_prg(0x8000), 4); assert_eq!(mapper.read_prg(0x9000), 5); }
#[test]
fn test_prg_c000_bank_switching_via_reg7() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x8000, 7); mapper.write_prg(0x8001, 3); assert_eq!(mapper.read_prg(0xC000), 6); assert_eq!(mapper.read_prg(0xD000), 7); }
#[test]
fn test_prg_8000_default_is_bank_0_and_1() {
let mapper = create_mapper(32);
assert_eq!(mapper.read_prg(0x8000), 0);
assert_eq!(mapper.read_prg(0x9000), 1);
}
#[test]
fn test_register_select_uses_low_3_bits() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x8000, 0xFF);
mapper.write_prg(0x8001, 4); assert_eq!(mapper.read_prg(0xC000), 8); }
#[test]
fn test_writes_to_a000_bfff_do_not_change_registers() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x8000, 6); mapper.write_prg(0x8001, 2); let before = mapper.read_prg(0x8000);
mapper.write_prg(0xA000, 7); mapper.write_prg(0xA001, 0); assert_eq!(
mapper.read_prg(0x8000),
before,
"$A000-$BFFF must not affect registers"
);
}
#[test]
fn test_wram_page0_readable_at_6000() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x6000, 0xAB);
assert_eq!(mapper.read_prg(0x6000), 0xAB);
}
#[test]
fn test_wram_page1_readable_at_b000() {
let mut mapper = create_mapper(32);
mapper.write_prg(0xB000, 0xCD);
assert_eq!(mapper.read_prg(0xB000), 0xCD);
}
#[test]
fn test_wram_page0_and_page1_are_independent() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x6000, 0x11);
mapper.write_prg(0xB000, 0x22);
assert_eq!(mapper.read_prg(0x6000), 0x11);
assert_eq!(mapper.read_prg(0xB000), 0x22);
}
#[test]
fn test_nametable_default_all_map_to_page_0() {
let mut mapper = create_mapper(32);
mapper.write_nametable(0x2001, 0xAA); mapper.write_nametable(0x2401, 0xBB); assert_eq!(
mapper.read_nametable(0x2001),
Some(0xBB),
"NT0 shares page 0"
);
assert_eq!(
mapper.read_nametable(0x2401),
Some(0xBB),
"NT1 shares page 0"
);
assert_eq!(
mapper.read_nametable(0x2801),
Some(0xBB),
"NT2 shares page 0"
);
assert_eq!(
mapper.read_nametable(0x2C01),
Some(0xBB),
"NT3 shares page 0"
);
}
#[test]
fn test_nametable_reg4_routes_nt1_to_page1() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x8000, 4); mapper.write_prg(0x8001, 1); mapper.write_nametable(0x2000, 0x11); mapper.write_nametable(0x2400, 0x22); assert_eq!(
mapper.read_nametable(0x2000),
Some(0x11),
"NT0 should read page 0"
);
assert_eq!(
mapper.read_nametable(0x2400),
Some(0x22),
"NT1 should read page 1"
);
}
#[test]
fn test_nametable_horizontal_mirroring_pattern() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x8000, 3);
mapper.write_prg(0x8001, 1); mapper.write_prg(0x8000, 5);
mapper.write_prg(0x8001, 1); mapper.write_nametable(0x2000, 0xAA); mapper.write_nametable(0x2800, 0xBB); assert_eq!(mapper.read_nametable(0x2000), Some(0xAA));
assert_eq!(mapper.read_nametable(0x2400), Some(0xAA)); assert_eq!(mapper.read_nametable(0x2800), Some(0xBB));
assert_eq!(mapper.read_nametable(0x2C00), Some(0xBB)); }
#[test]
fn test_nametable_vertical_mirroring_pattern() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x8000, 4);
mapper.write_prg(0x8001, 1); mapper.write_prg(0x8000, 5);
mapper.write_prg(0x8001, 1); mapper.write_nametable(0x2000, 0xAA); mapper.write_nametable(0x2400, 0xBB); assert_eq!(mapper.read_nametable(0x2000), Some(0xAA));
assert_eq!(mapper.read_nametable(0x2800), Some(0xAA));
assert_eq!(mapper.read_nametable(0x2400), Some(0xBB));
assert_eq!(mapper.read_nametable(0x2C00), Some(0xBB));
}
#[test]
fn test_registers_snapshot_round_trips() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x8000, 6); mapper.write_prg(0x8001, 5); mapper.write_prg(0x8000, 7); mapper.write_prg(0x8001, 3); mapper.write_nametable(0x2001, 0x42);
let snap = mapper.registers_snapshot();
let mut mapper2 = create_mapper(32);
mapper2.restore_registers(&snap);
assert_eq!(mapper2.read_prg(0x8000), mapper.read_prg(0x8000));
assert_eq!(mapper2.read_prg(0xC000), mapper.read_prg(0xC000));
assert_eq!(mapper2.read_nametable(0x2001), Some(0x42));
}
#[test]
fn test_wram_snapshot_round_trips() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x6000, 0xAB); mapper.write_prg(0xB000, 0xCD); let snap = mapper.wram_snapshot();
let mut mapper2 = create_mapper(32);
mapper2.load_wram_snapshot(&snap);
assert_eq!(mapper2.read_prg(0x6000), 0xAB);
assert_eq!(mapper2.read_prg(0xB000), 0xCD);
}
#[test]
fn test_reset_clears_registers() {
let mut mapper = create_mapper(32);
mapper.write_prg(0x8000, 6);
mapper.write_prg(0x8001, 5);
mapper.reset();
assert_eq!(mapper.read_prg(0x8000), 0);
assert_eq!(mapper.read_prg(0x9000), 1);
}
}