use super::base_mapper::BaseMapper;
use super::mapper::MapperContext;
use crate::nes::cartridge::{Mapper, MapperCapabilities};
fn prg_ram_size_kb(ctx: &MapperContext) -> usize {
if ctx.prg_ram_size_specified && ctx.prg_ram_banks_8k > 0 {
ctx.prg_ram_banks_8k as usize * 8
} else {
0
}
}
pub struct SimpleFixedPrgMapper<const CHR_BANK_KB: usize, const MAPPER_NUM: u8> {
base: BaseMapper,
chr_bank_raw: u8,
}
impl<const CHR_BANK_KB: usize, const MAPPER_NUM: u8> SimpleFixedPrgMapper<CHR_BANK_KB, MAPPER_NUM> {
pub fn new(ctx: MapperContext) -> Self {
let bus_conflicts = ctx.submapper != 1;
let capabilities = MapperCapabilities {
has_chr_banking: true,
max_prg_ram_kb: prg_ram_size_kb(&ctx),
chr_bank_size_kb: CHR_BANK_KB,
..Default::default()
};
let mut base = BaseMapper::new(&ctx, capabilities);
base.configure_chr_banking(CHR_BANK_KB * 1024);
base.set_bus_conflicts(bus_conflicts);
Self {
base,
chr_bank_raw: 0,
}
}
}
impl<const CHR_BANK_KB: usize, const MAPPER_NUM: u8> Mapper
for SimpleFixedPrgMapper<CHR_BANK_KB, MAPPER_NUM>
{
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) {
let effective = if self.base.has_bus_conflicts() {
value & self.base.read_prg_rom_fixed(addr)
} else {
value
};
self.chr_bank_raw = effective;
self.base.select_chr_page(0, effective as i16);
}
}
fn registers_snapshot(&self) -> Vec<u8> {
vec![self.chr_bank_raw]
}
fn restore_registers(&mut self, data: &[u8]) {
if let Some(&value) = data.first() {
self.chr_bank_raw = value;
self.base.select_chr_page(0, value as i16);
}
}
}
pub struct SimpleBankedPrgMapper<
const PRG_BANK_KB: usize,
const MAPPER_NUM: u8,
const FIXED_LAST: bool,
> {
base: BaseMapper,
bank_select: u8,
bank_select_mask: u8,
}
impl<const PRG_BANK_KB: usize, const MAPPER_NUM: u8, const FIXED_LAST: bool>
SimpleBankedPrgMapper<PRG_BANK_KB, MAPPER_NUM, FIXED_LAST>
{
pub fn new(ctx: MapperContext) -> Self {
let prg_bank_size = PRG_BANK_KB * 1024;
let num_banks = (ctx.prg_rom.len() / prg_bank_size).max(1);
let bank_select_mask = (num_banks.next_power_of_two() - 1) as u8;
let bus_conflicts = ctx.submapper != 2;
let capabilities = MapperCapabilities {
max_prg_ram_kb: prg_ram_size_kb(&ctx),
prg_bank_size_kb: PRG_BANK_KB,
..Default::default()
};
let mut base = BaseMapper::new(&ctx, capabilities);
base.configure_prg_banking(prg_bank_size);
if FIXED_LAST {
base.select_prg_page(1, -1);
} else {
base.select_prg_page(0, 0);
}
base.set_bus_conflicts(bus_conflicts);
Self {
base,
bank_select: 0,
bank_select_mask,
}
}
}
impl<const PRG_BANK_KB: usize, const MAPPER_NUM: u8, const FIXED_LAST: bool> Mapper
for SimpleBankedPrgMapper<PRG_BANK_KB, MAPPER_NUM, FIXED_LAST>
{
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) {
let effective = self.base.apply_bus_conflict(addr, value);
self.bank_select = effective & self.bank_select_mask;
let switchable_page = if FIXED_LAST { 0 } else { 1 };
self.base
.select_prg_page(switchable_page, self.bank_select as i16);
}
}
fn registers_snapshot(&self) -> Vec<u8> {
vec![self.bank_select]
}
fn restore_registers(&mut self, data: &[u8]) {
if !data.is_empty() {
self.bank_select = data[0];
let switchable_page = if FIXED_LAST { 0 } else { 1 };
self.base
.select_prg_page(switchable_page, self.bank_select as i16);
}
}
}
pub struct DualBank32Mapper<
const PRG_MASK: u8,
const PRG_SHIFT: u8,
const CHR_MASK: u8,
const CHR_SHIFT: u8,
const BUS_CONFLICTS: bool,
const MAPPER_NUM: u8,
> {
base: BaseMapper,
prg_bank_raw: u8,
chr_bank_raw: u8,
}
impl<
const PRG_MASK: u8,
const PRG_SHIFT: u8,
const CHR_MASK: u8,
const CHR_SHIFT: u8,
const BUS_CONFLICTS: bool,
const MAPPER_NUM: u8,
> DualBank32Mapper<PRG_MASK, PRG_SHIFT, CHR_MASK, CHR_SHIFT, BUS_CONFLICTS, MAPPER_NUM>
{
pub fn new(ctx: MapperContext) -> Self {
let capabilities = MapperCapabilities {
has_chr_banking: true,
max_prg_ram_kb: prg_ram_size_kb(&ctx),
..Default::default()
};
let mut base = BaseMapper::new(&ctx, capabilities);
base.configure_prg_banking(32 * 1024); base.configure_chr_banking(8 * 1024); base.set_bus_conflicts(BUS_CONFLICTS);
Self {
base,
prg_bank_raw: 0,
chr_bank_raw: 0,
}
}
}
impl<
const PRG_MASK: u8,
const PRG_SHIFT: u8,
const CHR_MASK: u8,
const CHR_SHIFT: u8,
const BUS_CONFLICTS: bool,
const MAPPER_NUM: u8,
> Mapper for DualBank32Mapper<PRG_MASK, PRG_SHIFT, CHR_MASK, CHR_SHIFT, BUS_CONFLICTS, MAPPER_NUM>
{
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) {
let effective = self.base.apply_bus_conflict(addr, value);
self.chr_bank_raw = (effective >> CHR_SHIFT) & CHR_MASK;
self.prg_bank_raw = (effective >> PRG_SHIFT) & PRG_MASK;
self.base.select_chr_page(0, self.chr_bank_raw as i16);
self.base.select_prg_page(0, self.prg_bank_raw as i16);
}
}
fn registers_snapshot(&self) -> Vec<u8> {
vec![self.prg_bank_raw, self.chr_bank_raw]
}
fn restore_registers(&mut self, data: &[u8]) {
if let Some(&value) = data.first() {
self.prg_bank_raw = value;
self.base.select_prg_page(0, value as i16);
}
if let Some(&value) = data.get(1) {
self.chr_bank_raw = value;
self.base.select_chr_page(0, value as i16);
}
}
}
#[cfg(test)]
mod tests {
use super::super::mapper::MapperContext;
use super::*;
use crate::nes::cartridge::NametableLayout;
fn banked_data(bank_size: usize, num_banks: usize) -> Vec<u8> {
let mut data = vec![0; bank_size * num_banks];
for bank in 0..num_banks {
let start = bank * bank_size;
let end = start + bank_size;
for byte in &mut data[start..end] {
*byte = bank as u8;
}
}
data
}
mod simple_fixed_prg {
use super::*;
type TestMapper = SimpleFixedPrgMapper<8, 3>;
#[test]
fn test_fixed_prg_no_banking() {
let mut prg_rom = vec![0; 32 * 1024];
for (i, byte) in prg_rom.iter_mut().enumerate() {
*byte = (i / 1024) as u8;
}
let mapper = TestMapper::new(MapperContext::new_for_test(
3,
prg_rom,
vec![0; 32 * 1024],
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_prg(0x8000), 0);
assert_eq!(mapper.read_prg(0x9000), 4);
assert_eq!(mapper.read_prg(0xC000), 16);
assert_eq!(mapper.read_prg(0xFFFF), 31);
}
#[test]
fn test_chr_bank_switching() {
let mut chr_rom = vec![0; 32 * 1024];
for bank in 0..4 {
let start = bank * 8 * 1024;
let end = start + 8 * 1024;
for byte in &mut chr_rom[start..end] {
*byte = (bank * 10) as u8;
}
}
let mut mapper = TestMapper::new(MapperContext::new_for_test(
3,
vec![0xFF; 32 * 1024], chr_rom,
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_chr(0x0000), 0);
mapper.write_prg(0x8000, 1);
assert_eq!(mapper.read_chr(0x0000), 10);
mapper.write_prg(0x8000, 2);
assert_eq!(mapper.read_chr(0x0000), 20);
mapper.write_prg(0x8000, 3);
assert_eq!(mapper.read_chr(0x0000), 30);
}
#[test]
fn test_chr_bank_wrapping() {
let mut chr_rom = vec![0; 16 * 1024]; for bank in 0..2 {
let start = bank * 8 * 1024;
let end = start + 8 * 1024;
for byte in &mut chr_rom[start..end] {
*byte = (bank * 50) as u8;
}
}
let mut mapper = TestMapper::new(MapperContext::new_for_test(
3,
vec![0xFF; 32 * 1024], chr_rom,
NametableLayout::Vertical,
));
mapper.write_prg(0x8000, 0b0000_0001);
assert_eq!(mapper.read_chr(0x0000), 50);
mapper.write_prg(0x8000, 0b0000_0011); assert_eq!(mapper.read_chr(0x0000), 50);
}
#[test]
fn test_registers_snapshot_restore() {
let mut chr_rom = vec![0; 32 * 1024];
for bank in 0..4 {
let start = bank * 8 * 1024;
let end = start + 8 * 1024;
for byte in &mut chr_rom[start..end] {
*byte = (bank * 7) as u8;
}
}
let mut mapper = TestMapper::new(MapperContext::new_for_test(
3,
vec![0xFF; 32 * 1024], chr_rom.clone(),
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0b0000_0011);
let registers = mapper.registers_snapshot();
let mut restored = TestMapper::new(MapperContext::new_for_test(
3,
vec![0xFF; 32 * 1024], chr_rom,
NametableLayout::Horizontal,
));
restored.restore_registers(®isters);
assert_eq!(restored.read_chr(0x0000), 21);
assert_eq!(restored.read_chr(0x1FFF), 21);
}
#[test]
fn test_mapper_number() {
let mapper = TestMapper::new(MapperContext::new_for_test(
3,
vec![0; 32 * 1024],
vec![0; 32 * 1024],
NametableLayout::Horizontal,
));
assert_eq!(mapper.mapper_number(), 3);
}
}
mod simple_banked_prg {
use super::*;
type TestMapper = SimpleBankedPrgMapper<16, 2, true>;
#[test]
fn test_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 as u8;
}
}
let mut mapper = TestMapper::new(
MapperContext::new_for_test(2, prg_rom, vec![], NametableLayout::Horizontal)
.with_submapper(2),
);
assert_eq!(mapper.read_prg(0x8000), 0);
assert_eq!(mapper.read_prg(0xC000), 7);
assert_eq!(mapper.read_prg(0xFFFF), 7);
mapper.write_prg(0x8000, 3);
assert_eq!(mapper.read_prg(0x8000), 3);
assert_eq!(mapper.read_prg(0xBFFF), 3);
assert_eq!(mapper.read_prg(0xC000), 7);
}
#[test]
fn test_chr_ram() {
let mut mapper = TestMapper::new(MapperContext::new_for_test(
2,
vec![0; 128 * 1024],
vec![],
NametableLayout::Horizontal,
));
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_fixed_last_bank() {
let mut prg_rom = vec![0; 256 * 1024]; for bank in 0..16 {
let start = bank * 16 * 1024;
let end = start + 16 * 1024;
for byte in &mut prg_rom[start..end] {
*byte = (bank + 100) as u8;
}
}
let mut mapper = TestMapper::new(MapperContext::new_for_test(
2,
prg_rom,
vec![],
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_prg(0xC000), 115);
mapper.write_prg(0x8000, 0);
assert_eq!(mapper.read_prg(0xC000), 115);
mapper.write_prg(0x8000, 5);
assert_eq!(mapper.read_prg(0xC000), 115);
}
#[test]
fn test_registers_snapshot_restore() {
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 as u8;
}
}
let mut mapper = TestMapper::new(
MapperContext::new_for_test(
2,
prg_rom.clone(),
vec![],
NametableLayout::Horizontal,
)
.with_submapper(2),
);
mapper.write_prg(0x8000, 3);
mapper.write_chr(0x0000, 0x5A);
let regs = mapper.registers_snapshot();
let chr = mapper.chr_ram_snapshot();
let mut restored = TestMapper::new(
MapperContext::new_for_test(2, prg_rom, vec![], NametableLayout::Horizontal)
.with_submapper(2),
);
restored.restore_registers(®s);
restored.restore_chr_ram(&chr);
assert_eq!(restored.read_prg(0x8000), 3);
assert_eq!(restored.read_chr(0x0000), 0x5A);
}
#[test]
fn test_mapper_number() {
let mapper = TestMapper::new(MapperContext::new_for_test(
2,
vec![0; 128 * 1024],
vec![],
NametableLayout::Horizontal,
));
assert_eq!(mapper.mapper_number(), 2);
}
}
mod dual_bank32 {
use super::*;
type GxROMTestMapper = DualBank32Mapper<0b0011, 4, 0b0011, 0, false, 66>;
type WideMaskTestMapper = DualBank32Mapper<0b1111, 4, 0b1111, 0, false, 11>;
type BusConflictTestMapper = DualBank32Mapper<0b0011, 0, 0b1111, 4, true, 11>;
#[test]
fn test_gxrom_bank_selection() {
let prg_rom = banked_data(32 * 1024, 4);
let chr_rom = banked_data(8 * 1024, 4);
let mut mapper = GxROMTestMapper::new(MapperContext::new_for_test(
66,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
));
assert_eq!(mapper.read_prg(0x8000), 0);
assert_eq!(mapper.read_chr(0x0000), 0);
mapper.write_prg(0x8000, 0x12);
assert_eq!(mapper.read_prg(0x8000), 1);
assert_eq!(mapper.read_chr(0x0000), 2);
}
#[test]
fn test_wide_mask_bank_selection() {
let prg_rom = banked_data(32 * 1024, 16);
let chr_rom = banked_data(8 * 1024, 16);
let mut mapper = WideMaskTestMapper::new(MapperContext::new_for_test(
11,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0x85);
assert_eq!(mapper.read_prg(0x8000), 8);
assert_eq!(mapper.read_chr(0x0000), 5);
mapper.write_prg(0x8000, 0xFF);
assert_eq!(mapper.read_prg(0x8000), 15);
assert_eq!(mapper.read_chr(0x0000), 15);
}
#[test]
fn test_bank_wrapping() {
let prg_rom = banked_data(32 * 1024, 4); let chr_rom = banked_data(8 * 1024, 2);
let mut mapper = WideMaskTestMapper::new(MapperContext::new_for_test(
11,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0x50); assert_eq!(mapper.read_prg(0x8000), 1);
mapper.write_prg(0x8000, 0x03); assert_eq!(mapper.read_chr(0x0000), 1);
}
#[test]
fn test_registers_snapshot_restore() {
let prg_rom = banked_data(32 * 1024, 4);
let chr_rom = banked_data(8 * 1024, 4);
let mut mapper = GxROMTestMapper::new(MapperContext::new_for_test(
66,
prg_rom.clone(),
chr_rom.clone(),
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0x21);
let snapshot = mapper.registers_snapshot();
let mut restored = GxROMTestMapper::new(MapperContext::new_for_test(
66,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
));
restored.restore_registers(&snapshot);
assert_eq!(restored.read_prg(0x8000), 2);
assert_eq!(restored.read_chr(0x0000), 1);
}
#[test]
fn test_mapper_numbers() {
let prg_rom = vec![0; 128 * 1024];
let chr_rom = vec![0; 32 * 1024];
let gxrom = GxROMTestMapper::new(MapperContext::new_for_test(
66,
prg_rom.clone(),
chr_rom.clone(),
NametableLayout::Horizontal,
));
assert_eq!(gxrom.mapper_number(), 66);
let wide_mask = WideMaskTestMapper::new(MapperContext::new_for_test(
11,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
));
assert_eq!(wide_mask.mapper_number(), 11);
}
#[test]
fn test_bus_conflicts_mask_write_value() {
let mut prg_rom = vec![0u8; 4 * 32 * 1024];
for byte in &mut prg_rom[32 * 1024..2 * 32 * 1024] {
*byte = 0x01;
}
let chr_rom = banked_data(8 * 1024, 16);
let mut mapper = BusConflictTestMapper::new(MapperContext::new_for_test(
11,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0x01);
assert_eq!(
mapper.read_prg(0x8000),
0,
"bus conflict should mask the write"
);
}
#[test]
fn test_no_bus_conflicts_when_disabled() {
let prg_rom = banked_data(32 * 1024, 4);
let chr_rom = banked_data(8 * 1024, 4);
let mut mapper = GxROMTestMapper::new(MapperContext::new_for_test(
66,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
));
mapper.write_prg(0x8000, 0x10);
assert_eq!(
mapper.read_prg(0x8000),
1,
"no bus conflict, bank should switch"
);
}
}
}