use crate::nes::cartridge::Mapper;
use crate::nes::cartridge::MapperCapabilities;
use crate::nes::cartridge::base_mapper::BaseMapper;
pub struct NROMMapper {
base: BaseMapper,
}
impl NROMMapper {
pub fn new(ctx: crate::nes::cartridge::mapper::MapperContext) -> Self {
let capabilities = MapperCapabilities {
has_irq: false,
has_chr_banking: false,
has_dynamic_mirroring: false,
has_expansion_audio: false,
max_prg_ram_kb: if ctx.prg_ram_size_specified && ctx.prg_ram_banks_8k > 0 {
ctx.prg_ram_banks_8k as usize * 8
} else {
0
},
prg_bank_size_kb: 32,
chr_bank_size_kb: 8,
trainer_jsr: false,
..Default::default()
};
Self {
base: BaseMapper::new(&ctx, capabilities),
}
}
}
impl Mapper for NROMMapper {
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) {
self.base.try_write_prg_ram(addr, value);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nes::cartridge::NametableLayout;
use crate::nes::cartridge::mapper::MapperContext;
#[test]
fn test_nrom_32kb_prg_rom_read() {
let mut prg_rom = vec![0; 0x8000]; prg_rom[0x0000] = 0xAA; prg_rom[0x4000] = 0xBB; prg_rom[0x7FFF] = 0xCC;
let mapper = NROMMapper::new(MapperContext::new_for_test(
0,
prg_rom,
vec![0; 8192],
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_prg(0x8000), 0xAA);
assert_eq!(mapper.read_prg(0xC000), 0xBB);
assert_eq!(mapper.read_prg(0xFFFF), 0xCC);
}
#[test]
fn test_nrom_16kb_prg_rom_mirroring() {
let mut prg_rom = vec![0; 0x4000]; prg_rom[0x0000] = 0xAA; prg_rom[0x3FFF] = 0xBB;
let mapper = NROMMapper::new(MapperContext::new_for_test(
0,
prg_rom,
vec![0; 8192],
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_prg(0x8000), 0xAA);
assert_eq!(mapper.read_prg(0xBFFF), 0xBB);
assert_eq!(mapper.read_prg(0xC000), 0xAA); assert_eq!(mapper.read_prg(0xFFFF), 0xBB); }
#[test]
fn test_nrom_chr_rom_read() {
let mut chr_rom = vec![0; 8192];
chr_rom[0x0000] = 0x11;
chr_rom[0x0FFF] = 0x22;
chr_rom[0x1000] = 0x33;
chr_rom[0x1FFF] = 0x44;
let mut mapper = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 0x8000],
chr_rom,
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_chr(0x0000), 0x11);
assert_eq!(mapper.read_chr(0x0FFF), 0x22);
assert_eq!(mapper.read_chr(0x1000), 0x33);
assert_eq!(mapper.read_chr(0x1FFF), 0x44);
}
#[test]
fn test_nrom_chr_ram_write_and_read() {
let mut mapper = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![],
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_chr(0x0000), 0x00);
mapper.write_chr(0x0000, 0xAA);
mapper.write_chr(0x1000, 0xBB);
mapper.write_chr(0x1FFF, 0xCC);
assert_eq!(mapper.read_chr(0x0000), 0xAA);
assert_eq!(mapper.read_chr(0x1000), 0xBB);
assert_eq!(mapper.read_chr(0x1FFF), 0xCC);
}
#[test]
fn test_nrom_chr_rom_write_ignored() {
let chr_rom = vec![0x55; 8192];
let mut mapper = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 0x8000],
chr_rom,
NametableLayout::Horizontal,
));
mapper.write_chr(0x0000, 0xAA);
assert_eq!(mapper.read_chr(0x0000), 0x55);
}
#[test]
fn test_nrom_prg_write_ignored() {
let prg_rom = vec![0xAA; 0x8000];
let mut mapper = NROMMapper::new(MapperContext::new_for_test(
0,
prg_rom,
vec![0; 8192],
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0xBB);
assert_eq!(mapper.read_prg(0x8000), 0xAA);
}
#[test]
fn test_nrom_mirroring_modes() {
let mapper_h = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![0; 8192],
NametableLayout::Horizontal,
));
assert_eq!(mapper_h.get_mirroring(), NametableLayout::Horizontal);
let mapper_v = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![0; 8192],
NametableLayout::Vertical,
));
assert_eq!(mapper_v.get_mirroring(), NametableLayout::Vertical);
let mapper_4 = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![0; 8192],
NametableLayout::FourScreen,
));
assert_eq!(mapper_4.get_mirroring(), NametableLayout::FourScreen);
}
#[test]
fn test_nrom_ppu_address_changed_noop() {
let mut mapper = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![0; 8192],
NametableLayout::Horizontal,
));
mapper.ppu_address_changed(0x0000);
mapper.ppu_address_changed(0x1000);
mapper.ppu_address_changed(0x1FFF);
}
#[test]
fn test_nrom_chr_ram_snapshot_restores_contents() {
let mut mapper = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![],
NametableLayout::Horizontal,
));
mapper.write_chr(0x0000, 0xAA);
mapper.write_chr(0x1FFF, 0xBB);
let chr = mapper.chr_ram_snapshot();
let mut restored = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![],
NametableLayout::Horizontal,
));
restored.restore_chr_ram(&chr);
assert_eq!(restored.read_chr(0x0000), 0xAA);
assert_eq!(restored.read_chr(0x1FFF), 0xBB);
}
#[test]
fn test_nrom_open_bus() {
let mapper = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 32 * 1024],
vec![0; 8192],
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_prg_open_bus(0x0000, 0x12), 0x12);
assert_eq!(mapper.read_prg_open_bus(0x1000, 0x34), 0x34);
assert_eq!(mapper.read_prg_open_bus(0x2000, 0x56), 0x56);
assert_eq!(mapper.read_prg_open_bus(0x3000, 0x78), 0x78);
assert_eq!(mapper.read_prg_open_bus(0x4000, 0x9A), 0x9A);
assert_eq!(mapper.read_prg_open_bus(0x5000, 0xBC), 0xBC);
assert_eq!(mapper.read_prg_open_bus(0x5FFF, 0xDE), 0xDE);
}
#[test]
fn test_nrom_mapped_regions_dont_return_open_bus() {
let prg_rom = vec![0xAB; 32 * 1024];
let chr_rom = vec![0; 8 * 1024];
let mapper = NROMMapper::new(MapperContext::new_for_test(
0,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
));
let open_bus = 0x42;
let rom_result = mapper.read_prg_open_bus(0x8000, open_bus);
assert_eq!(
rom_result, 0xAB,
"PRG-ROM region should return ROM data, not open-bus"
);
let rom_result2 = mapper.read_prg_open_bus(0xC000, open_bus);
assert_eq!(
rom_result2, 0xAB,
"PRG-ROM region should return ROM data, not open-bus"
);
}
#[test]
fn test_nrom_open_bus_boundary_at_6000() {
let mapper = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0; 32 * 1024],
vec![0; 8192],
NametableLayout::Horizontal,
));
let open_bus = 0x55;
assert_eq!(
mapper.read_prg_open_bus(0x5FFF, open_bus),
open_bus,
"$5FFF should return open-bus"
);
let _ = mapper.read_prg_open_bus(0x6000, open_bus);
}
#[test]
fn test_nrom_no_prg_ram_read_returns_zero() {
let mapper = NROMMapper::new(
MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![0; 8192],
NametableLayout::Horizontal,
)
.with_prg_ram_banks(0),
);
assert_eq!(
mapper.read_prg(0x6000),
0,
"no PRG-RAM: read at $6000 should return 0"
);
assert_eq!(
mapper.read_prg(0x7FFF),
0,
"no PRG-RAM: read at $7FFF should return 0"
);
assert_eq!(
mapper.read_prg(0x6800),
0,
"no PRG-RAM: read at $6800 should return 0"
);
}
#[test]
fn test_nrom_no_prg_ram_write_ignored() {
let mut mapper = NROMMapper::new(
MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![0; 8192],
NametableLayout::Horizontal,
)
.with_prg_ram_banks(0),
);
mapper.write_prg(0x6000, 0xAB);
mapper.write_prg(0x7FFF, 0xCD);
assert_eq!(
mapper.read_prg(0x6000),
0,
"no PRG-RAM: write should be ignored"
);
assert_eq!(
mapper.read_prg(0x7FFF),
0,
"no PRG-RAM: write should be ignored"
);
}
#[test]
fn test_nrom_no_prg_ram_open_bus_returns_open_bus() {
let mapper = NROMMapper::new(
MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![0; 8192],
NametableLayout::Horizontal,
)
.with_prg_ram_banks(0),
);
let open_bus = 0x42;
assert_eq!(
mapper.read_prg_open_bus(0x6000, open_bus),
open_bus,
"no PRG-RAM: $6000 should return open-bus"
);
assert_eq!(
mapper.read_prg_open_bus(0x7FFF, open_bus),
open_bus,
"no PRG-RAM: $7FFF should return open-bus"
);
}
#[test]
fn test_nrom_with_prg_ram_read_write_works() {
let mut mapper = NROMMapper::new(
MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![0; 8192],
NametableLayout::Horizontal,
)
.with_prg_ram_banks(1),
);
mapper.write_prg(0x6000, 0x77);
mapper.write_prg(0x7FFF, 0x88);
assert_eq!(
mapper.read_prg(0x6000),
0x77,
"PRG-RAM present: write/read should work"
);
assert_eq!(
mapper.read_prg(0x7FFF),
0x88,
"PRG-RAM present: write/read should work"
);
}
}