use crate::nes::cartridge::mapper::MapperContext;
use crate::nes::cartridge::mapper_templates::DualBank32Mapper;
pub enum ColorDreamsMapper {
WithBusConflicts(DualBank32Mapper<0b0011, 0, 0b1111, 4, true, 11>),
NoBusConflicts(DualBank32Mapper<0b0011, 0, 0b1111, 4, false, 11>),
}
impl ColorDreamsMapper {
pub fn new(ctx: MapperContext) -> Self {
if ctx.submapper == 1 {
Self::NoBusConflicts(DualBank32Mapper::new(ctx))
} else {
Self::WithBusConflicts(DualBank32Mapper::new(ctx))
}
}
}
impl crate::nes::cartridge::Mapper for ColorDreamsMapper {
fn base(&self) -> &crate::nes::cartridge::base_mapper::BaseMapper {
match self {
Self::WithBusConflicts(m) => m.base(),
Self::NoBusConflicts(m) => m.base(),
}
}
fn base_mut(&mut self) -> &mut crate::nes::cartridge::base_mapper::BaseMapper {
match self {
Self::WithBusConflicts(m) => m.base_mut(),
Self::NoBusConflicts(m) => m.base_mut(),
}
}
fn write_prg(&mut self, addr: u16, value: u8) {
match self {
Self::WithBusConflicts(m) => m.write_prg(addr, value),
Self::NoBusConflicts(m) => m.write_prg(addr, value),
}
}
fn registers_snapshot(&self) -> Vec<u8> {
match self {
Self::WithBusConflicts(m) => m.registers_snapshot(),
Self::NoBusConflicts(m) => m.registers_snapshot(),
}
}
fn restore_registers(&mut self, data: &[u8]) {
match self {
Self::WithBusConflicts(m) => m.restore_registers(data),
Self::NoBusConflicts(m) => m.restore_registers(data),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nes::cartridge::NametableLayout;
use crate::nes::cartridge::mapper::{Mapper, MapperContext, create_mapper};
use crate::nes::cartridge::test_helpers::banked_data;
const BUS_CONFLICT_SAFE_ADDR: u16 = 0x8001;
fn banked_prg_with_conflict_safe_write(num_banks: usize) -> Vec<u8> {
let mut prg_rom = banked_data(32 * 1024, num_banks);
for bank in 0..num_banks {
prg_rom[bank * 32 * 1024 + 1] = 0xFF;
}
prg_rom
}
fn create_colordreams_mapper(
prg_rom: Vec<u8>,
chr_rom: Vec<u8>,
mirroring: NametableLayout,
) -> std::io::Result<Box<dyn Mapper>> {
create_mapper(
MapperContext::new_for_test(11, prg_rom, chr_rom, mirroring).with_prg_ram_banks(0),
)
}
#[test]
fn test_colordreams_prg_and_chr_bank_selected_by_single_write() {
let prg_rom = banked_prg_with_conflict_safe_write(4);
let chr_rom = banked_data(8 * 1024, 16);
let mut mapper = create_colordreams_mapper(prg_rom, chr_rom, NametableLayout::Horizontal)
.expect("ColorDreams (mapper 11) should be implemented");
assert_eq!(mapper.read_prg(0x8000), 0);
assert_eq!(mapper.read_chr(0x0000), 0);
mapper.write_prg(BUS_CONFLICT_SAFE_ADDR, 0x12);
assert_eq!(mapper.read_prg(0x8000), 2);
assert_eq!(mapper.read_prg(0xFFFF), 2);
assert_eq!(mapper.read_chr(0x0000), 1);
assert_eq!(mapper.read_chr(0x1FFF), 1);
}
#[test]
fn test_colordreams_register_layout_is_cccc_llpp() {
let prg_rom = banked_prg_with_conflict_safe_write(4); let chr_rom = banked_data(8 * 1024, 16);
let mut mapper = create_colordreams_mapper(prg_rom, chr_rom, NametableLayout::Horizontal)
.expect("ColorDreams (mapper 11) should be implemented");
mapper.write_prg(BUS_CONFLICT_SAFE_ADDR, 0xE3); assert_eq!(mapper.read_prg(0x8000), 3);
assert_eq!(mapper.read_chr(0x0000), 14);
}
#[test]
fn test_colordreams_mirroring_is_fixed_from_header() {
let prg_rom = banked_data(32 * 1024, 2);
let chr_rom = banked_data(8 * 1024, 2);
let mut mapper = create_colordreams_mapper(prg_rom, chr_rom, NametableLayout::Vertical)
.expect("ColorDreams (mapper 11) should be implemented");
assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
mapper.write_prg(0xFFFF, 0xFF);
assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
}
#[test]
fn test_colordreams_bank_wrapping() {
let prg_rom = banked_prg_with_conflict_safe_write(2); let chr_rom = banked_data(8 * 1024, 2);
let mut mapper = create_colordreams_mapper(prg_rom, chr_rom, NametableLayout::Horizontal)
.expect("ColorDreams (mapper 11) should be implemented");
mapper.write_prg(BUS_CONFLICT_SAFE_ADDR, 0x03);
assert_eq!(mapper.read_prg(0x8000), 1);
mapper.write_prg(BUS_CONFLICT_SAFE_ADDR, 0xF0);
assert_eq!(mapper.read_chr(0x0000), 1);
}
#[test]
fn test_colordreams_registers_snapshot_restores_banks() {
let prg_rom = banked_prg_with_conflict_safe_write(4);
let chr_rom = banked_data(8 * 1024, 4);
let mut mapper = create_colordreams_mapper(
prg_rom.clone(),
chr_rom.clone(),
NametableLayout::Horizontal,
)
.expect("ColorDreams (mapper 11) should be implemented");
mapper.write_prg(BUS_CONFLICT_SAFE_ADDR, 0x23);
let snapshot = mapper.registers_snapshot();
let mut restored = create_colordreams_mapper(prg_rom, chr_rom, NametableLayout::Horizontal)
.expect("ColorDreams (mapper 11) should be implemented");
restored.restore_registers(&snapshot);
assert_eq!(restored.read_prg(0x8000), 3);
assert_eq!(restored.read_chr(0x0000), 2);
}
#[test]
fn test_colordreams_banked_rom_replacement() {
use crate::nes::cartridge::common::BankedRom;
use crate::nes::cartridge::test_helpers::banked_data;
const PRG_BANK_SIZE: usize = 32 * 1024;
const CHR_BANK_SIZE: usize = 8 * 1024;
let prg_rom = banked_data(PRG_BANK_SIZE, 4);
let chr_rom = banked_data(CHR_BANK_SIZE, 4);
let prg_banked = BankedRom::new(prg_rom.clone(), PRG_BANK_SIZE);
let chr_banked = BankedRom::new(chr_rom.clone(), CHR_BANK_SIZE);
assert_eq!(prg_banked.read(0, 0), 0);
assert_eq!(prg_banked.read(1, 0), 1);
assert_eq!(prg_banked.read(2, 0), 2);
assert_eq!(prg_banked.read(3, 0), 3);
assert_eq!(chr_banked.read(0, 0), 0);
assert_eq!(chr_banked.read(1, 0), 1);
assert_eq!(chr_banked.read(2, 0), 2);
assert_eq!(chr_banked.read(3, 0), 3);
assert_eq!(prg_banked.read(4, 0), 0); assert_eq!(prg_banked.read(5, 0), 1); assert_eq!(prg_banked.read(7, 0), 3); assert_eq!(prg_banked.read(8, 0), 0); }
#[test]
fn test_colordreams_open_bus() {
let mapper = ColorDreamsMapper::new(
MapperContext::new_for_test(
11,
vec![0; 128 * 1024],
vec![0; 128 * 1024],
NametableLayout::Horizontal,
)
.with_prg_ram_banks(0),
);
assert_eq!(mapper.read_prg_open_bus(0x5000, 0x55), 0x55);
assert_eq!(mapper.read_prg_open_bus(0x5FFF, 0x66), 0x66);
assert_eq!(mapper.read_prg_open_bus(0x6000, 0x77), 0x77);
assert_eq!(mapper.read_prg_open_bus(0x7FFF, 0x88), 0x88);
}
#[test]
fn test_colordreams_reports_no_prg_ram() {
let mapper = create_colordreams_mapper(
vec![0; 128 * 1024],
vec![0; 128 * 1024],
NametableLayout::Horizontal,
)
.expect("ColorDreams (mapper 11) should be implemented");
assert_eq!(mapper.wram_size(), 0);
assert_eq!(mapper.capabilities().max_prg_ram_kb, 0);
}
#[test]
fn test_colordreams_applies_bus_conflicts() {
let mut prg_rom = vec![0; 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 = create_colordreams_mapper(prg_rom, chr_rom, NametableLayout::Horizontal)
.expect("ColorDreams (mapper 11) should be implemented");
mapper.write_prg(0x8000, 0x01);
assert_eq!(mapper.read_prg(0x8000), 0x00);
}
fn create_colordreams_submapper1_mapper(
prg_rom: Vec<u8>,
chr_rom: Vec<u8>,
mirroring: NametableLayout,
) -> std::io::Result<Box<dyn Mapper>> {
create_mapper(
MapperContext::new_for_test(11, prg_rom, chr_rom, mirroring)
.with_submapper(1)
.with_prg_ram_banks(0),
)
}
#[test]
fn test_colordreams_submapper1_does_not_have_bus_conflicts() {
let mut prg_rom = banked_data(32 * 1024, 4);
prg_rom[0] = 0x00;
let chr_rom = banked_data(8 * 1024, 16);
let mut mapper =
create_colordreams_submapper1_mapper(prg_rom, chr_rom, NametableLayout::Horizontal)
.expect("ColorDreams submapper 1 should be implemented");
mapper.write_prg(0x8000, 0x01);
assert_eq!(
mapper.read_prg(0x8000),
1,
"Submapper 1 should select bank 1 without bus conflicts"
);
}
#[test]
fn test_colordreams_submapper1_chr_banking_works_without_conflicts() {
let prg_rom = vec![0xFF; 4 * 32 * 1024]; let chr_rom = banked_data(8 * 1024, 16);
let mut mapper =
create_colordreams_submapper1_mapper(prg_rom, chr_rom, NametableLayout::Horizontal)
.expect("ColorDreams submapper 1 should be implemented");
mapper.write_prg(0x8000, 0x50);
assert_eq!(
mapper.read_chr(0x0000),
5,
"Submapper 1 should select CHR bank 5"
);
mapper.write_prg(0x8000, 0xA0);
assert_eq!(
mapper.read_chr(0x0000),
10,
"Submapper 1 should select CHR bank 10"
);
}
#[test]
fn test_colordreams_submapper1_combined_prg_and_chr() {
let prg_rom = banked_data(32 * 1024, 4);
let chr_rom = banked_data(8 * 1024, 16);
let mut mapper =
create_colordreams_submapper1_mapper(prg_rom, chr_rom, NametableLayout::Horizontal)
.expect("ColorDreams submapper 1 should be implemented");
mapper.write_prg(0x8000, 0x72);
assert_eq!(mapper.read_prg(0x8000), 2, "Should select PRG bank 2");
assert_eq!(mapper.read_chr(0x0000), 7, "Should select CHR bank 7");
}
#[test]
fn mapper_144_is_registered_in_factory() {
let prg_rom = banked_data(32 * 1024, 4);
let chr_rom = banked_data(8 * 1024, 16);
let result = create_mapper(
MapperContext::new_for_test(144, prg_rom, chr_rom, NametableLayout::Horizontal)
.with_prg_ram_banks(0),
);
assert!(result.is_ok(), "Mapper 144 must be creatable via factory");
}
#[test]
fn mapper_144_prg_and_chr_banking_works_like_mapper_11() {
let prg_rom = banked_data(32 * 1024, 4);
let chr_rom = banked_data(8 * 1024, 16);
let mut mapper = create_mapper(
MapperContext::new_for_test(144, prg_rom, chr_rom, NametableLayout::Horizontal)
.with_prg_ram_banks(0)
.with_submapper(1), )
.expect("mapper 144 must be registered");
mapper.write_prg(0x8000, 0x53);
assert_eq!(mapper.read_prg(0x8000), 3, "mapper 144 PRG bank 3");
assert_eq!(mapper.read_chr(0x0000), 5, "mapper 144 CHR bank 5");
}
}