use crate::cartridge::BaseMapper;
use crate::cartridge::Mapper;
use crate::cartridge::MapperCapabilities;
use crate::cartridge::NametableLayout;
pub struct NinaTengenMapper {
base: BaseMapper,
prg_bank_select: u8,
chr_bank_select: u8,
mirroring: NametableLayout,
}
impl NinaTengenMapper {
pub fn new(ctx: super::mapper::MapperContext) -> Self {
let mirroring = ctx.mirroring;
let capabilities = MapperCapabilities {
has_irq: false,
has_chr_banking: true,
has_dynamic_mirroring: true,
has_expansion_audio: false,
max_prg_ram_kb: 8,
prg_bank_size_kb: 16,
chr_bank_size_kb: 8,
trainer_jsr: false,
..Default::default()
};
let mut base = BaseMapper::new(&ctx, capabilities);
base.configure_prg_banking(0x4000); base.configure_chr_banking(0x2000); let mut mapper = Self {
base,
prg_bank_select: 0,
chr_bank_select: 0,
mirroring,
};
mapper.update_banks();
mapper
}
fn update_banks(&mut self) {
self.base.select_prg_page(0, self.prg_bank_select as i16);
self.base.select_prg_page(1, -1); self.base.select_chr_page(0, self.chr_bank_select as i16);
self.base.set_mirroring(self.mirroring);
}
}
impl Mapper for NinaTengenMapper {
fn base(&self) -> &BaseMapper {
&self.base
}
fn base_mut(&mut self) -> &mut BaseMapper {
&mut self.base
}
fn write_prg(&mut self, addr: u16, value: u8) {
if self.base.try_write_prg_ram(addr, value) {
return;
}
if (0x8000..=0xFFFF).contains(&addr) {
self.prg_bank_select = value & 0x07;
self.mirroring = if (value & 0x08) != 0 {
NametableLayout::Horizontal
} else {
NametableLayout::Vertical
};
self.chr_bank_select = (value >> 4) & 0x0F;
self.update_banks();
}
}
fn write_chr(&mut self, _addr: u16, _value: u8) {
}
fn registers_snapshot(&self) -> Vec<u8> {
vec![
self.prg_bank_select,
self.chr_bank_select,
self.mirroring.to_snapshot_byte(),
]
}
fn restore_registers(&mut self, data: &[u8]) {
if data.len() >= 3 {
self.prg_bank_select = data[0];
self.chr_bank_select = data[1];
self.mirroring = NametableLayout::from_snapshot_byte(data[2]);
self.update_banks();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cartridge::mapper::MapperContext;
#[test]
fn test_nina_tengen_prg_bank_switching() {
let mut prg_rom = vec![0; 128 * 1024];
for bank in 0..8 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = (bank * 10) as u8;
}
}
let mut mapper = NinaTengenMapper::new(MapperContext::new_for_test(
78,
prg_rom,
vec![0; 128 * 1024],
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_prg(0x8000), 0);
assert_eq!(mapper.read_prg(0xBFFF), 0);
assert_eq!(mapper.read_prg(0xC000), 70);
assert_eq!(mapper.read_prg(0xFFFF), 70);
mapper.write_prg(0x8000, 0b0000_0001);
assert_eq!(mapper.read_prg(0x8000), 10);
assert_eq!(mapper.read_prg(0xBFFF), 10);
mapper.write_prg(0x8000, 0b0000_0101);
assert_eq!(mapper.read_prg(0x8000), 50);
assert_eq!(mapper.read_prg(0xC000), 70);
}
#[test]
fn test_nina_tengen_chr_bank_switching() {
let mut chr_rom = vec![0; 128 * 1024];
for bank in 0..16 {
let start = bank * 8 * 1024;
let end = start + 8 * 1024;
for byte in &mut chr_rom[start..end] {
*byte = (bank * 15) as u8;
}
}
let mut mapper = NinaTengenMapper::new(MapperContext::new_for_test(
78,
vec![0; 128 * 1024],
chr_rom,
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_chr(0x0000), 0);
assert_eq!(mapper.read_chr(0x1FFF), 0);
mapper.write_prg(0x8000, 0b0001_0000);
assert_eq!(mapper.read_chr(0x0000), 15);
mapper.write_prg(0x8000, 0b0101_0000);
assert_eq!(mapper.read_chr(0x0000), 75);
mapper.write_prg(0x8000, 0b1111_0000);
assert_eq!(mapper.read_chr(0x0000), 225);
}
#[test]
fn test_nina_tengen_mirroring_control() {
let mut mapper = NinaTengenMapper::new(MapperContext::new_for_test(
78,
vec![0; 128 * 1024],
vec![0; 128 * 1024],
NametableLayout::Horizontal,
));
assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
mapper.write_prg(0x8000, 0b0000_0000);
assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
mapper.write_prg(0x8000, 0b0000_1000);
assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
mapper.write_prg(0x8000, 0b1111_0111); assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
mapper.write_prg(0x8000, 0b1111_1111); assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
}
#[test]
fn test_nina_tengen_combined_register() {
let mut prg_rom = vec![0; 128 * 1024];
let mut chr_rom = vec![0; 128 * 1024];
for bank in 0..8 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = (bank + 100) as u8;
}
}
for bank in 0..16 {
let start = bank * 8 * 1024;
let end = start + 8 * 1024;
for byte in &mut chr_rom[start..end] {
*byte = (bank + 200) as u8;
}
}
let mut mapper = NinaTengenMapper::new(MapperContext::new_for_test(
78,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0b0111_0011);
assert_eq!(mapper.read_prg(0x8000), 103); assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical); assert_eq!(mapper.read_chr(0x0000), 207);
mapper.write_prg(0x8000, 0b1010_1101);
assert_eq!(mapper.read_prg(0x8000), 105); assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal); assert_eq!(mapper.read_chr(0x0000), 210); }
#[test]
fn test_nina_tengen_prg_bank_mask() {
let mut prg_rom = vec![0; 128 * 1024];
for bank in 0..8 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = (bank * 25) as u8;
}
}
let mut mapper = NinaTengenMapper::new(MapperContext::new_for_test(
78,
prg_rom,
vec![0; 8 * 1024],
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0b1111_1111); assert_eq!(mapper.read_prg(0x8000), 175);
mapper.write_prg(0x8000, 0b1111_1000); assert_eq!(mapper.read_prg(0x8000), 0); }
#[test]
fn test_nina_tengen_chr_bank_mask() {
let mut chr_rom = vec![0; 128 * 1024];
for bank in 0..16 {
let start = bank * 8 * 1024;
let end = start + 8 * 1024;
for byte in &mut chr_rom[start..end] {
*byte = (bank * 12) as u8;
}
}
let mut mapper = NinaTengenMapper::new(MapperContext::new_for_test(
78,
vec![0; 32 * 1024],
chr_rom,
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0b1111_0000); assert_eq!(mapper.read_chr(0x0000), 180);
mapper.write_prg(0x8000, 0b0000_0000); assert_eq!(mapper.read_chr(0x0000), 0); }
#[test]
fn test_nina_tengen_fixed_last_prg_bank() {
let mut prg_rom = vec![0; 128 * 1024];
for bank in 0..8 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = (bank + 50) as u8;
}
}
let mut mapper = NinaTengenMapper::new(MapperContext::new_for_test(
78,
prg_rom,
vec![0; 8 * 1024],
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_prg(0xC000), 57);
mapper.write_prg(0x8000, 0);
assert_eq!(mapper.read_prg(0xC000), 57);
mapper.write_prg(0x8000, 3);
assert_eq!(mapper.read_prg(0xC000), 57);
mapper.write_prg(0x8000, 5);
assert_eq!(mapper.read_prg(0xC000), 57);
}
#[test]
fn test_nina_tengen_chr_rom_read_only() {
let chr_rom = vec![0xAA; 8 * 1024];
let mut mapper = NinaTengenMapper::new(MapperContext::new_for_test(
78,
vec![0; 32 * 1024],
chr_rom,
NametableLayout::Horizontal,
));
mapper.write_chr(0x0000, 0x55);
assert_eq!(mapper.read_chr(0x0000), 0xAA);
}
#[test]
fn test_nina_tengen_registers_snapshot_restores_banks_and_mirroring() {
let mut prg_rom = vec![0; 128 * 1024];
let mut chr_rom = vec![0; 128 * 1024];
for bank in 0..8 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = (bank + 10) as u8;
}
}
for bank in 0..16 {
let start = bank * 8 * 1024;
let end = start + 8 * 1024;
for byte in &mut chr_rom[start..end] {
*byte = (bank + 20) as u8;
}
}
let mut mapper = NinaTengenMapper::new(MapperContext::new_for_test(
78,
prg_rom.clone(),
chr_rom.clone(),
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0b1001_1101);
let snapshot = mapper.registers_snapshot();
let mut restored = NinaTengenMapper::new(MapperContext::new_for_test(
78,
prg_rom,
chr_rom,
NametableLayout::Vertical,
));
restored.restore_registers(&snapshot);
assert_eq!(restored.read_prg(0x8000), 15);
assert_eq!(restored.get_mirroring(), NametableLayout::Horizontal);
assert_eq!(restored.read_chr(0x0000), 29);
}
#[test]
fn test_nina_tengen_banked_rom_replacement() {
use crate::cartridge::common::BankedRom;
use crate::cartridge::test_helpers::banked_data;
const PRG_BANK_SIZE: usize = 0x4000; const CHR_BANK_SIZE: usize = 0x2000;
let prg_rom = banked_data(PRG_BANK_SIZE, 8);
let chr_rom = banked_data(CHR_BANK_SIZE, 16);
let prg_banked = BankedRom::new(prg_rom, PRG_BANK_SIZE);
let chr_banked = BankedRom::new(chr_rom, CHR_BANK_SIZE);
assert_eq!(prg_banked.read(0, 0), 0);
assert_eq!(prg_banked.read(1, 0), 1);
assert_eq!(prg_banked.read(7, 0), 7);
assert_eq!(chr_banked.read(0, 0), 0);
assert_eq!(chr_banked.read(15, 0), 15);
assert_eq!(prg_banked.read(8, 0), 0); assert_eq!(chr_banked.read(16, 0), 0); }
}