use crate::cartridge::base_mapper::BaseMapper;
use crate::cartridge::{Mapper, MapperCapabilities, NametableLayout};
pub struct IremG101Mapper {
base: BaseMapper,
prg_regs: [u8; 2],
chr_regs: [u8; 8],
prg_mode: bool,
submapper: u8,
}
impl IremG101Mapper {
const CONTROL_PRG_MODE_BIT: u8 = 0b0000_0010;
const CONTROL_MIRROR_BIT: u8 = 0b0000_0001;
const CHR_REG_SELECT_MASK: u16 = 0x0007;
pub fn new(ctx: super::mapper::MapperContext) -> Self {
let submapper = ctx.submapper;
let capabilities = MapperCapabilities {
has_chr_banking: true,
has_dynamic_mirroring: submapper != 1,
max_prg_ram_kb: 8,
prg_bank_size_kb: 8,
chr_bank_size_kb: 1,
..Default::default()
};
let mut base = BaseMapper::new(&ctx, capabilities);
if submapper == 1 {
base.set_mirroring(NametableLayout::SingleScreenUpper);
}
base.configure_prg_banking(8 * 1024);
base.configure_chr_banking(1024);
let mut mapper = Self {
base,
prg_regs: [0; 2],
chr_regs: [0; 8],
prg_mode: false,
submapper,
};
mapper.update_banks();
mapper
}
#[cfg(test)]
pub fn new_internal(prg_rom: Vec<u8>, chr_rom: Vec<u8>, mirroring: NametableLayout) -> Self {
let ctx = super::mapper::MapperContext::new_for_test(32, prg_rom, chr_rom, mirroring);
Self::new(ctx)
}
fn update_banks(&mut self) {
let r0 = self.prg_regs[0] as i16;
let r1 = self.prg_regs[1] as i16;
if !self.prg_mode {
self.base.select_prg_page(0, r0);
self.base.select_prg_page(1, r1);
self.base.select_prg_page(2, -2);
self.base.select_prg_page(3, -1);
} else {
self.base.select_prg_page(0, -2);
self.base.select_prg_page(1, r1);
self.base.select_prg_page(2, r0);
self.base.select_prg_page(3, -1);
}
for (slot, &bank) in self.chr_regs.iter().enumerate() {
self.base.select_chr_page(slot, bank as i16);
}
}
fn mirroring_to_bit(mirroring: NametableLayout) -> u8 {
if mirroring == NametableLayout::Horizontal {
1
} else {
0
}
}
}
impl Mapper for IremG101Mapper {
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;
}
match addr & 0xF000 {
0x8000 => {
self.prg_regs[0] = value & 0x1F;
self.update_banks();
}
0x9000 => {
if self.submapper != 1 {
self.prg_mode = (value & Self::CONTROL_PRG_MODE_BIT) != 0;
self.base
.set_mirroring_hv((value & Self::CONTROL_MIRROR_BIT) != 0);
self.update_banks();
}
}
0xA000 => {
self.prg_regs[1] = value & 0x1F;
self.update_banks();
}
0xB000 => {
let slot = (addr & Self::CHR_REG_SELECT_MASK) as usize;
self.chr_regs[slot] = value;
self.update_banks();
}
_ => {}
}
}
fn registers_snapshot(&self) -> Vec<u8> {
let mut snap = Vec::with_capacity(12);
snap.extend_from_slice(&self.prg_regs);
snap.extend_from_slice(&self.chr_regs);
snap.push(self.prg_mode as u8);
snap.push(Self::mirroring_to_bit(self.base.mirroring()));
snap
}
fn restore_registers(&mut self, data: &[u8]) {
if data.len() >= 12 {
self.prg_regs.copy_from_slice(&data[0..2]);
self.chr_regs.copy_from_slice(&data[2..10]);
self.prg_mode = data[10] != 0;
if self.submapper != 1 {
self.base.set_mirroring_hv(data[11] != 0);
}
self.update_banks();
}
}
}
#[cfg(test)]
mod tests {
use super::IremG101Mapper;
use crate::cartridge::NametableLayout;
use crate::cartridge::mapper::{Mapper, MapperContext, create_mapper};
use crate::cartridge::test_helpers::banked_data;
fn make_mapper(prg_banks: usize, chr_banks_1k: usize) -> Box<dyn Mapper> {
let prg_rom = banked_data(8 * 1024, prg_banks);
let chr_rom = banked_data(1024, chr_banks_1k);
create_mapper(MapperContext::new_for_test(
32,
prg_rom,
chr_rom,
NametableLayout::Vertical,
))
.expect("Mapper 32 should be implemented")
}
fn make_mapper_submapper(
prg_banks: usize,
chr_banks_1k: usize,
submapper: u8,
) -> Box<dyn Mapper> {
let prg_rom = banked_data(8 * 1024, prg_banks);
let chr_rom = banked_data(1024, chr_banks_1k);
create_mapper(
MapperContext::new_for_test(32, prg_rom, chr_rom, NametableLayout::Vertical)
.with_submapper(submapper),
)
.expect("Mapper 32 should be implemented")
}
#[test]
fn prg_mode0_reg0_selects_bank_at_8000() {
let mut mapper = make_mapper(6, 8);
mapper.write_prg(0x8000, 2); assert_eq!(mapper.read_prg(0x8000), 2);
}
#[test]
fn prg_mode0_reg1_selects_bank_at_a000() {
let mut mapper = make_mapper(6, 8);
mapper.write_prg(0xA000, 3); assert_eq!(mapper.read_prg(0xA000), 3);
}
#[test]
fn prg_mode0_c000_is_fixed_second_to_last_bank() {
let mapper = make_mapper(6, 8);
assert_eq!(mapper.read_prg(0xC000), 4);
}
#[test]
fn prg_mode0_e000_is_fixed_last_bank() {
let mapper = make_mapper(6, 8);
assert_eq!(mapper.read_prg(0xE000), 5);
}
#[test]
fn prg_mode0_reg0_does_not_affect_c000() {
let mut mapper = make_mapper(6, 8);
mapper.write_prg(0x8000, 1); assert_eq!(mapper.read_prg(0xC000), 4);
}
#[test]
fn prg_mode1_8000_is_fixed_second_to_last_bank() {
let mut mapper = make_mapper(6, 8);
mapper.write_prg(0x9000, 0b0000_0010); assert_eq!(mapper.read_prg(0x8000), 4); }
#[test]
fn prg_mode1_c000_uses_reg0() {
let mut mapper = make_mapper(6, 8);
mapper.write_prg(0x8000, 1); mapper.write_prg(0x9000, 0b0000_0010); assert_eq!(mapper.read_prg(0xC000), 1);
}
#[test]
fn prg_mode1_a000_still_uses_reg1() {
let mut mapper = make_mapper(6, 8);
mapper.write_prg(0xA000, 2); mapper.write_prg(0x9000, 0b0000_0010); assert_eq!(mapper.read_prg(0xA000), 2);
}
#[test]
fn prg_mode1_e000_is_still_fixed_last_bank() {
let mut mapper = make_mapper(6, 8);
mapper.write_prg(0x9000, 0b0000_0010); assert_eq!(mapper.read_prg(0xE000), 5); }
#[test]
fn prg_mode_can_switch_back_to_mode0() {
let mut mapper = make_mapper(6, 8);
mapper.write_prg(0x8000, 1);
mapper.write_prg(0x9000, 0b0000_0010); mapper.write_prg(0x9000, 0b0000_0000); assert_eq!(mapper.read_prg(0x8000), 1); assert_eq!(mapper.read_prg(0xC000), 4); }
#[test]
fn chr_regs_select_1kb_banks() {
let mut mapper = make_mapper(4, 11);
for i in 0u8..8 {
mapper.write_prg(0xB000 | i as u16, i); }
for i in 0u16..8 {
assert_eq!(
mapper.read_chr(i * 0x400),
i as u8,
"CHR window {} should map to bank {}",
i,
i
);
}
}
#[test]
fn chr_regs_are_independently_selectable() {
let mut mapper = make_mapper(4, 11);
mapper.write_prg(0xB002, 5); mapper.write_prg(0xB005, 9); assert_eq!(mapper.read_chr(0x0800), 5); assert_eq!(mapper.read_chr(0x1400), 9); }
#[test]
fn mirroring_bit0_0_is_vertical() {
let mut mapper = make_mapper(4, 8);
mapper.write_prg(0x9000, 0b0000_0000); assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
}
#[test]
fn mirroring_bit0_1_is_horizontal() {
let mut mapper = make_mapper(4, 8);
mapper.write_prg(0x9000, 0b0000_0001); assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
}
#[test]
fn submapper1_hardwired_1screen_upper_mirroring() {
let mapper = make_mapper_submapper(4, 8, 1);
assert_eq!(mapper.get_mirroring(), NametableLayout::SingleScreenUpper);
}
#[test]
fn submapper1_9000_write_does_not_change_mirroring() {
let mut mapper = make_mapper_submapper(4, 8, 1);
mapper.write_prg(0x9000, 0b0000_0001); assert_eq!(mapper.get_mirroring(), NametableLayout::SingleScreenUpper);
}
#[test]
fn submapper1_9000_write_does_not_enable_prg_mode1() {
let mut mapper = make_mapper_submapper(6, 8, 1);
mapper.write_prg(0x8000, 1); mapper.write_prg(0x9000, 0b0000_0010); assert_eq!(mapper.read_prg(0x8000), 1);
assert_eq!(mapper.read_prg(0xC000), 4); }
#[test]
fn registers_snapshot_and_restore() {
let prg_rom = banked_data(8 * 1024, 6);
let chr_rom = banked_data(1024, 11);
let mut mapper = IremG101Mapper::new_internal(
prg_rom.clone(),
chr_rom.clone(),
NametableLayout::Vertical,
);
mapper.write_prg(0x8000, 2);
mapper.write_prg(0xA000, 3);
mapper.write_prg(0xB003, 7);
mapper.write_prg(0x9000, 0b0000_0011);
let snap = mapper.registers_snapshot();
let mut restored =
IremG101Mapper::new_internal(prg_rom, chr_rom, NametableLayout::Vertical);
restored.restore_registers(&snap);
assert_eq!(restored.read_prg(0xC000), 2); assert_eq!(restored.read_prg(0xA000), 3);
assert_eq!(restored.read_chr(0x0C00), 7); assert_eq!(restored.get_mirroring(), NametableLayout::Horizontal);
}
}