use crate::nes::cartridge::NametableLayout;
use crate::nes::cartridge::hardware_type::HardwareType;
use crate::nes::cartridge::ines::ParsedRom;
use crate::nes::cartridge::rom_db::VsHardwareType;
use std::io;
use super::nintendo::axrom::AxROMMapper;
use super::nintendo::bnrom_nina::BnromNinaMapper;
use super::nintendo::cnrom::CNROMMapper;
use super::nintendo::cnrom_security::CnromSecurityMapper;
use super::nintendo::cprom::CpromMapper;
use super::nintendo::fds::FdsMapper;
use super::nintendo::gxrom::GxROMMapper;
use super::nintendo::mapper99::Mapper99;
use super::nintendo::mapper100::Mapper100;
use super::nintendo::mmc1::MMC1Mapper;
use super::nintendo::mmc2::MMC2Mapper;
use super::nintendo::mmc3::MMC3Mapper;
use super::nintendo::mmc4::MMC4Mapper;
use super::nintendo::mmc5::MMC5Mapper;
use super::nintendo::nes_event::NesEventMapper;
use super::nintendo::nrom::NROMMapper;
use super::nintendo::tqrom::TqromMapper;
use super::nintendo::txsrom::TxsromMapper;
use super::nintendo::un1rom::Un1romMapper;
use super::nintendo::uxrom::UxROMMapper;
use super::nintendo::uxrom_inverted::UxromInvertedMapper;
use super::konami::mapper151::Mapper151;
use super::konami::vrc1::Vrc1Mapper;
use super::konami::vrc2_vrc4::Vrc2Vrc4Mapper;
use super::konami::vrc3::Vrc3Mapper;
use super::konami::vrc6::VRC6Mapper;
use super::konami::vrc7::VRC7Mapper;
use super::namco::namco118::Namco118Mapper;
use super::namco::namco163::Namco163Mapper;
use super::namco::namcot_3425::Namcot3425Mapper;
use super::namco::namcot_3443::Namcot3443Mapper;
use super::namco::namcot_3446::Namcot3446Mapper;
use super::namco::namcot_3453::Namcot3453Mapper;
use super::bandai::bandai_fcg::BandaiFcgMapper;
use super::bandai::mapper70::Mapper70;
use super::bandai::mapper96::Mapper96;
use super::bandai::mapper152::Mapper152;
use super::bandai::mapper153::Mapper153;
use super::bandai::mapper157::Mapper157;
use super::bandai::mapper159::Mapper159;
use super::sunsoft::sunsoft_1::Sunsoft1Mapper;
use super::sunsoft::sunsoft_2::Sunsoft2Mapper;
use super::sunsoft::sunsoft_3::Sunsoft3Mapper;
use super::sunsoft::sunsoft_4::Sunsoft4Mapper;
use super::sunsoft::sunsoft_early::SunsoftEarlyMapper;
use super::sunsoft::sunsoft_fme7::SunsoftFme7Mapper;
use super::taito::taito_tc0190::TaitoTc0190Mapper;
use super::taito::taito_tc0350::TaitoTc0350Mapper;
use super::taito::taito_x1005::TaitoX1005Mapper;
use super::taito::taito_x1005_207::TaitoX1005_207Mapper;
use super::taito::taito_x1017::TaitoX1017Mapper;
use super::jaleco::jaleco_jf10::JalecoJf10Mapper;
use super::jaleco::jaleco_jf11::JalecoJf11Mapper;
use super::jaleco::jaleco_jf13::JalecoJf13Mapper;
use super::jaleco::jaleco_jf17::JalecoJf17Mapper;
use super::jaleco::jaleco_jf19::JalecoJf19Mapper;
use super::jaleco::jaleco_ss88006::JalecoSs88006Mapper;
use super::jaleco::mapper87::Mapper87;
use super::irem::irem_g101::IremG101Mapper;
use super::irem::irem_h3001::IremH3001Mapper;
use super::irem::irem_lrog017::IremLrog017Mapper;
use super::irem::irem_tam_s1::IremTamS1Mapper;
use super::irem::nina_tengen::NinaTengenMapper;
use super::camerica::camerica::CamericaMapper;
use super::tengen::mapper158::Mapper158;
use super::tengen::tengen_rambo1::TengenRambo1Mapper;
use super::sachen::mapper36::Mapper36;
use super::sachen::mapper132::Mapper132;
use super::sachen::mapper133::Mapper133;
use super::sachen::mapper136::Mapper136;
use super::sachen::mapper143::Mapper143;
use super::sachen::mapper145::Mapper145;
use super::sachen::mapper147::Mapper147;
use super::sachen::mapper148::Mapper148;
use super::sachen::mapper149::Mapper149;
use super::sachen::mapper150::Mapper150;
use super::sachen::mapper173::Mapper173;
use super::sachen::mapper243::Mapper243;
use super::sachen::sachen8259::Sachen8259;
#[cfg(test)]
use super::rom_db;
use super::unlicensed::action53::Action53Mapper;
use super::unlicensed::colordreams::ColorDreamsMapper;
use super::unlicensed::jy_company::JyCompanyMapper;
use super::unlicensed::mapper12::Mapper12;
use super::unlicensed::mapper14::Mapper14;
use super::unlicensed::mapper29::Mapper29;
use super::unlicensed::mapper31::Mapper31;
use super::unlicensed::mapper35::Mapper35;
use super::unlicensed::mapper37::Mapper37;
use super::unlicensed::mapper38::Mapper38;
use super::unlicensed::mapper39::Mapper39;
use super::unlicensed::mapper41::Mapper41;
use super::unlicensed::mapper42::Mapper42;
use super::unlicensed::mapper43::Mapper43;
use super::unlicensed::mapper44::Mapper44;
use super::unlicensed::mapper45::Mapper45;
use super::unlicensed::mapper46::Mapper46;
use super::unlicensed::mapper47::Mapper47;
use super::unlicensed::mapper49::Mapper49;
use super::unlicensed::mapper50::Mapper50;
use super::unlicensed::mapper51::Mapper51;
use super::unlicensed::mapper52::Mapper52;
use super::unlicensed::mapper53::Mapper53;
use super::unlicensed::mapper54::Mapper54;
use super::unlicensed::mapper55::Mapper55;
use super::unlicensed::mapper56::Mapper56;
use super::unlicensed::mapper57::Mapper57;
use super::unlicensed::mapper58::Mapper58;
use super::unlicensed::mapper59::Mapper59;
use super::unlicensed::mapper60::Mapper60;
use super::unlicensed::mapper61::Mapper61;
use super::unlicensed::mapper62::Mapper62;
use super::unlicensed::mapper63::Mapper63;
use super::unlicensed::mapper74::Mapper74;
use super::unlicensed::mapper79::Mapper79;
use super::unlicensed::mapper81::Mapper81;
use super::unlicensed::mapper83::Mapper83;
use super::unlicensed::mapper91::Mapper91;
use super::unlicensed::mapper103::Mapper103;
use super::unlicensed::mapper104::Mapper104;
use super::unlicensed::mapper106::Mapper106;
use super::unlicensed::mapper107::Mapper107;
use super::unlicensed::mapper108::Mapper108;
use super::unlicensed::mapper110::Mapper110;
use super::unlicensed::mapper111::GtromMapper;
use super::unlicensed::mapper112::Mapper112;
use super::unlicensed::mapper113::Mapper113;
use super::unlicensed::mapper114::Mapper114;
use super::unlicensed::mapper115::Mapper115;
use super::unlicensed::mapper116::Mapper116;
use super::unlicensed::mapper117::Mapper117;
use super::unlicensed::mapper120::Mapper120;
use super::unlicensed::mapper121::Mapper121;
use super::unlicensed::mapper122::Mapper122;
use super::unlicensed::mapper123::Mapper123;
use super::unlicensed::mapper124::Mapper124;
use super::unlicensed::mapper125::Mapper125;
use super::unlicensed::mapper126::Mapper126;
use super::unlicensed::mapper128::Mapper128;
use super::unlicensed::mapper134::Mapper134;
use super::unlicensed::mapper156::Mapper156;
use super::unlicensed::mapper162::Mapper162;
use super::unlicensed::mapper163::Mapper163;
use super::unlicensed::mapper164::Mapper164;
use super::unlicensed::mapper165::Mapper165;
use super::unlicensed::mapper166::Mapper166;
use super::unlicensed::mapper167::Mapper167;
use super::unlicensed::mapper168::Mapper168;
use super::unlicensed::mapper170::Mapper170;
use super::unlicensed::mapper171::Mapper171;
use super::unlicensed::mapper172::Mapper172;
use super::unlicensed::mapper174::Mapper174;
use super::unlicensed::mapper175::Mapper175;
use super::unlicensed::mapper176::Mapper176;
use super::unlicensed::mapper177::Mapper177;
use super::unlicensed::mapper178::Mapper178;
use super::unlicensed::mapper183::Mapper183;
use super::unlicensed::mapper187::Mapper187;
use super::unlicensed::mapper188::Mapper188;
use super::unlicensed::mapper189::Mapper189;
use super::unlicensed::mapper190::Mapper190;
use super::unlicensed::mapper191::Mapper191;
use super::unlicensed::mapper192::Mapper192;
use super::unlicensed::mapper193::Mapper193;
use super::unlicensed::mapper194::Mapper194;
use super::unlicensed::mapper195::Mapper195;
use super::unlicensed::mapper196::Mapper196;
use super::unlicensed::mapper197::Mapper197;
use super::unlicensed::mapper198::Mapper198;
use super::unlicensed::mapper199::Mapper199;
use super::unlicensed::mapper200::Mapper200;
use super::unlicensed::mapper201::Mapper201;
use super::unlicensed::mapper202::Mapper202;
use super::unlicensed::mapper203::Mapper203;
use super::unlicensed::mapper204::Mapper204;
use super::unlicensed::mapper205::Mapper205;
use super::unlicensed::mapper208::Mapper208;
use super::unlicensed::mapper210::Mapper210;
use super::unlicensed::mapper211::Mapper211;
use super::unlicensed::mapper212::Mapper212;
use super::unlicensed::mapper213::Mapper213;
use super::unlicensed::mapper214::Mapper214;
use super::unlicensed::mapper215::Mapper215;
use super::unlicensed::mapper216::Mapper216;
use super::unlicensed::mapper217::Mapper217;
use super::unlicensed::mapper218::Mapper218;
use super::unlicensed::mapper219::Mapper219;
use super::unlicensed::mapper221::Mapper221;
use super::unlicensed::mapper222::Mapper222;
use super::unlicensed::mapper223::Mapper223;
use super::unlicensed::mapper224::Mapper224;
use super::unlicensed::mapper225::Mapper225;
use super::unlicensed::mapper226::Mapper226;
use super::unlicensed::mapper227::Mapper227;
use super::unlicensed::mapper228::Mapper228;
use super::unlicensed::mapper229::Mapper229;
use super::unlicensed::mapper230::Mapper230;
use super::unlicensed::mapper231::Mapper231;
use super::unlicensed::mapper232::Mapper232;
use super::unlicensed::mapper233::Mapper233;
use super::unlicensed::mapper234::Mapper234;
use super::unlicensed::mapper235::Mapper235;
use super::unlicensed::mapper236::Mapper236;
use super::unlicensed::mapper237::Mapper237;
use super::unlicensed::mapper238::Mapper238;
use super::unlicensed::mapper240::Mapper240;
use super::unlicensed::mapper241::Mapper241;
use super::unlicensed::mapper242::Mapper242;
use super::unlicensed::mapper244::Mapper244;
use super::unlicensed::mapper245::Mapper245;
use super::unlicensed::mapper246::Mapper246;
use super::unlicensed::mapper249::Mapper249;
use super::unlicensed::mapper250::Mapper250;
use super::unlicensed::mapper251::Mapper251;
use super::unlicensed::mapper252::Mapper252;
use super::unlicensed::mapper253::Mapper253;
use super::unlicensed::mapper254::Mapper254;
use super::unlicensed::mapper255::Mapper255;
use super::unlicensed::mapper256::Mapper256;
use super::unlicensed::mapper257::Mapper257;
use super::unlicensed::mapper259::Mapper259;
use super::unlicensed::mapper260::Mapper260;
use super::unlicensed::mapper261::Mapper261;
use super::unlicensed::mapper262::Mapper262;
use super::unlicensed::mapper263::Mapper263;
use super::unlicensed::mapper264::Mapper264;
use super::unlicensed::mapper265::Mapper265;
use super::unlicensed::mapper266::Mapper266;
use super::unlicensed::mapper267::Mapper267;
use super::unlicensed::mapper268::Mapper268;
use super::unlicensed::mapper269::Mapper269;
use super::unlicensed::mapper270::Mapper270;
use super::unlicensed::mapper271::Mapper271;
use super::unlicensed::mapper274::Mapper274;
use super::unlicensed::mapper281::Mapper281;
use super::unlicensed::mapper282::Mapper282;
use super::unlicensed::mapper283::Mapper283;
use super::unlicensed::mapper284::Mapper284;
use super::unlicensed::mapper285::Mapper285;
use super::unlicensed::mapper286::Mapper286;
use super::unlicensed::mapper287::Mapper287;
use super::unlicensed::mapper288::Mapper288;
use super::unlicensed::mapper289::Mapper289;
use super::unlicensed::mapper290::Mapper290;
use super::unlicensed::mapper291::Mapper291;
use super::unlicensed::mapper292::Mapper292;
use super::unlicensed::mapper293::Mapper293;
use super::unlicensed::mapper294::Mapper294;
use super::unlicensed::mapper295::Mapper295;
use super::unlicensed::mapper296::Mapper296;
use super::unlicensed::mapper297::Mapper297;
use super::unlicensed::mapper298::Mapper298;
use super::unlicensed::mapper299::Mapper299;
use super::unlicensed::mapper300::Mapper300;
use super::unlicensed::mapper301::Mapper301;
use super::unlicensed::mapper302::Mapper302;
use super::unlicensed::mapper303::Mapper303;
use super::unlicensed::mapper304::Mapper304;
use super::unlicensed::mapper305::Mapper305;
use super::unlicensed::mapper306::Mapper306;
use super::unlicensed::mapper307::Mapper307;
use super::unlicensed::mapper308::Mapper308;
use super::unlicensed::mapper309::Mapper309;
use super::unlicensed::mapper310::Mapper310;
use super::unlicensed::mapper311::Mapper311;
use super::unlicensed::mapper312::Mapper312;
use super::unlicensed::mapper313::Mapper313;
use super::unlicensed::mapper314::Mapper314;
use super::unlicensed::mapper315::Mapper315;
use super::unlicensed::mapper319::Mapper319;
use super::unlicensed::mapper320::Mapper320;
use super::unlicensed::mapper321::Mapper321;
use super::unlicensed::mapper322::Mapper322;
use super::unlicensed::mapper323::Mapper323;
use super::unlicensed::mapper324::Mapper324;
use super::unlicensed::mapper325::Mapper325;
use super::unlicensed::mapper326::Mapper326;
use super::unlicensed::mapper327::Mapper327;
use super::unlicensed::mapper328::Mapper328;
use super::unlicensed::mapper329::Mapper329;
use super::unlicensed::mapper330::Mapper330;
use super::unlicensed::mapper331::Mapper331;
use super::unlicensed::mapper332::Mapper332;
use super::unlicensed::mapper333::Mapper333;
use super::unlicensed::mapper334::Mapper334;
use super::unlicensed::mapper335::Mapper335;
use super::unlicensed::mapper336::Mapper336;
use super::unlicensed::mapper337::Mapper337;
use super::unlicensed::mapper338::Mapper338;
use super::unlicensed::mapper339::Mapper339;
use super::unlicensed::mapper340::Mapper340;
use super::unlicensed::mapper341::Mapper341;
use super::unlicensed::mapper342::Mapper342;
use super::unlicensed::mapper344::Mapper344;
use super::unlicensed::mapper345::Mapper345;
use super::unlicensed::mapper346::Mapper346;
use super::unlicensed::mapper347::Mapper347;
use super::unlicensed::mapper348::Mapper348;
use super::unlicensed::mapper349::Mapper349;
use super::unlicensed::mapper350::Mapper350;
use super::unlicensed::multicart_15::Multicart15Mapper;
use super::unlicensed::ntdec_2722::Ntdec2722Mapper;
use super::unlicensed::super_magic_card::SuperMagicCardMapper;
use super::unlicensed::unrom512::Unrom512Mapper;
#[derive(Debug)]
#[allow(dead_code)]
pub struct MapperContext {
pub mapper: u16,
pub submapper: u8,
pub mirroring: NametableLayout,
pub hardware_type: HardwareType,
pub prg_rom: Vec<u8>,
pub chr_rom: Vec<u8>,
pub prg_ram_banks_8k: u8,
pub prg_ram_size_specified: bool,
pub battery_backed_prg_ram: bool,
pub chr_ram_size_bytes: Option<usize>,
pub crc32: u32,
pub vs_hardware_type: Option<VsHardwareType>,
}
const PRG_RAM_BANK_SIZE: usize = 8 * 1024;
const DEFAULT_PRG_RAM_BANKS_8K: u8 = 1;
impl MapperContext {
pub fn from_parsed_rom(parsed: &ParsedRom) -> Self {
let info = &parsed.header;
Self {
mapper: info.mapper,
submapper: info.submapper,
mirroring: info.mirroring,
hardware_type: HardwareType::from_console_type_and_timing(
info.console_type,
info.timing_mode,
),
prg_rom: parsed.prg_rom.clone(),
chr_rom: parsed.chr_rom.clone(),
prg_ram_banks_8k: Self::prg_ram_banks_8k_total(
info.prg_ram_size_bytes,
info.prg_nvram_size_bytes,
),
prg_ram_size_specified: info.prg_ram_size_bytes.is_some()
|| info.prg_nvram_size_bytes.is_some(),
battery_backed_prg_ram: info.battery_backed_prg_ram,
chr_ram_size_bytes: match (info.chr_ram_size_bytes, info.chr_nvram_size_bytes) {
(Some(a), Some(b)) => Some(a.max(b)),
(Some(a), None) | (None, Some(a)) => Some(a),
(None, None) => None,
},
crc32: parsed.crc32,
vs_hardware_type: parsed.header.vs_hardware_type.map(VsHardwareType::from_raw),
}
}
fn prg_ram_banks_8k_total(
prg_ram_size_bytes: Option<usize>,
prg_nvram_size_bytes: Option<usize>,
) -> u8 {
if prg_ram_size_bytes.is_none() && prg_nvram_size_bytes.is_none() {
return DEFAULT_PRG_RAM_BANKS_8K;
}
let bytes = prg_ram_size_bytes
.unwrap_or(0)
.max(prg_nvram_size_bytes.unwrap_or(0));
bytes.div_ceil(PRG_RAM_BANK_SIZE).min(u8::MAX as usize) as u8
}
#[cfg(test)]
pub fn new_for_test(
mapper: u16,
prg_rom: Vec<u8>,
chr_rom: Vec<u8>,
mirroring: NametableLayout,
) -> Self {
let crc32 = rom_db::calculate_rom_crc32(&prg_rom, &chr_rom);
Self {
mapper,
submapper: 0,
mirroring,
hardware_type: HardwareType::NesNtsc,
prg_rom,
chr_rom,
prg_ram_banks_8k: 1,
prg_ram_size_specified: true,
battery_backed_prg_ram: false,
chr_ram_size_bytes: None,
crc32,
vs_hardware_type: None,
}
}
#[cfg(test)]
pub fn with_submapper(mut self, submapper: u8) -> Self {
self.submapper = submapper;
self
}
#[cfg(test)]
pub fn with_prg_ram_banks(mut self, prg_ram_banks_8k: u8) -> Self {
self.prg_ram_banks_8k = prg_ram_banks_8k;
self
}
#[cfg(test)]
pub fn with_unspecified_prg_ram_size(mut self) -> Self {
self.prg_ram_size_specified = false;
self
}
#[cfg(test)]
pub fn with_vs_hardware_type(mut self, hw_type: VsHardwareType) -> Self {
self.vs_hardware_type = Some(hw_type);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)]
pub struct MapperCapabilities {
pub has_irq: bool,
pub has_chr_banking: bool,
pub has_dynamic_mirroring: bool,
pub has_expansion_audio: bool,
pub max_prg_ram_kb: usize,
pub prg_bank_size_kb: usize,
pub chr_bank_size_kb: usize,
pub trainer_jsr: bool,
pub trainer_load_address: u16,
}
impl Default for MapperCapabilities {
fn default() -> Self {
Self {
has_irq: false,
has_chr_banking: false,
has_dynamic_mirroring: false,
has_expansion_audio: false,
max_prg_ram_kb: 0,
prg_bank_size_kb: 32,
chr_bank_size_kb: 8,
trainer_jsr: false,
trainer_load_address: 0x7000,
}
}
}
pub trait Mapper {
fn base(&self) -> &super::base_mapper::BaseMapper;
fn base_mut(&mut self) -> &mut super::base_mapper::BaseMapper;
fn mmc3_delegate(&self) -> Option<&MMC3Mapper> {
None
}
fn mmc3_delegate_mut(&mut self) -> Option<&mut MMC3Mapper> {
None
}
fn read_prg(&self, addr: u16) -> u8 {
if let Some(value) = self.base().try_read_prg_6000(addr) {
return value;
}
if let Some(value) = self.base().try_read_prg_ram(addr) {
return value;
}
match addr {
0x8000..=0xFFFF => self.base().read_prg_rom(addr),
_ => 0,
}
}
fn read_prg_open_bus(&self, addr: u16, open_bus: u8) -> u8 {
self.base()
.read_prg_open_bus(addr, open_bus, |a| self.read_prg(a))
}
fn write_prg(&mut self, addr: u16, value: u8);
fn read_chr(&mut self, addr: u16) -> u8 {
self.base().read_chr(addr)
}
fn write_chr(&mut self, addr: u16, value: u8) {
self.base_mut().write_chr(addr, value);
}
fn ppu_address_changed(&mut self, addr: u16) {
if let Some(mmc3) = self.mmc3_delegate_mut() {
mmc3.ppu_address_changed(addr);
}
}
fn ppu_set_chr_fetch_is_sprite(&mut self, _is_sprite: bool) {}
fn ppu_set_chr_fetch_is_ppudata(&mut self) {}
fn ppu_write_ctrl(&mut self, _value: u8) {}
fn ppu_write_mask(&mut self, _value: u8) {}
fn on_oam_dma(&mut self) {}
fn on_controller_port_write(&mut self, _addr: u16, _value: u8) {}
fn on_irq_vector_read(&mut self, _addr: u16) {}
fn ppu_scanline(&mut self, _scanline: u16, _rendering_enabled: bool) {}
fn ppu_end_frame(&mut self) {}
fn read_nametable(&mut self, _addr: u16) -> Option<u8> {
None
}
fn write_nametable(&mut self, _addr: u16, _value: u8) -> bool {
false
}
fn cpu_cycle(&mut self) {
if let Some(mmc3) = self.mmc3_delegate_mut() {
mmc3.cpu_cycle();
}
}
fn reset(&mut self) {}
fn initialize_ram(&mut self, mode: crate::nes::console::RamInitMode) {
if let Some(mmc3) = self.mmc3_delegate_mut() {
mmc3.initialize_ram(mode);
} else {
self.base_mut().initialize_ram(mode);
}
}
fn initialize_chr_ram(&mut self, mode: crate::nes::console::RamInitMode) {
self.base_mut().initialize_chr_ram(mode);
}
fn irq_pending(&self) -> bool {
if let Some(mmc3) = self.mmc3_delegate() {
mmc3.irq_pending()
} else {
false
}
}
fn expansion_audio_sample(&self) -> f32 {
0.0
}
fn get_mirroring(&self) -> NametableLayout {
self.base().mirroring()
}
#[allow(dead_code)]
fn wram_size(&self) -> usize {
self.base().wram_size()
}
fn wram_snapshot(&self) -> Vec<u8> {
self.base().wram_snapshot()
}
fn load_wram_snapshot(&mut self, data: &[u8]) {
self.base_mut().load_wram_snapshot(data);
}
fn mapper_number(&self) -> u16 {
self.base().mapper_number()
}
fn prg_ram_snapshot(&self) -> Vec<u8> {
self.wram_snapshot()
}
fn chr_ram_snapshot(&self) -> Vec<u8> {
if let Some(mmc3) = self.mmc3_delegate() {
mmc3.chr_ram_snapshot()
} else {
self.base().chr_ram_snapshot()
}
}
fn registers_snapshot(&self) -> Vec<u8> {
Vec::new()
}
fn restore_prg_ram(&mut self, data: &[u8]) {
self.load_wram_snapshot(data);
}
fn restore_chr_ram(&mut self, data: &[u8]) {
if let Some(mmc3) = self.mmc3_delegate_mut() {
mmc3.restore_chr_ram(data);
} else {
self.base_mut().restore_chr_ram(data);
}
}
fn restore_registers(&mut self, _data: &[u8]) {}
#[allow(dead_code)]
fn capabilities(&self) -> MapperCapabilities {
self.base().capabilities()
}
}
macro_rules! mapper_registry {
($($id:expr => $ctor:path),+ $(,)?) => {
fn create_registry_mapper(
metadata: MapperContext,
) -> Option<Box<dyn Mapper>> {
match metadata.mapper {
$(
$id => {
Some(Box::new($ctor(metadata)))
}
)+
_ => None,
}
}
};
}
mapper_registry! {
0 => NROMMapper::new,
1 => MMC1Mapper::new,
2 => UxROMMapper::new,
3 => CNROMMapper::new,
4 => MMC3Mapper::new,
5 => MMC5Mapper::new,
6 => SuperMagicCardMapper::new,
7 => AxROMMapper::new,
8 => SuperMagicCardMapper::new,
9 => MMC2Mapper::new,
10 => MMC4Mapper::new,
11 => ColorDreamsMapper::new,
12 => Mapper12::new,
13 => CpromMapper::new,
14 => Mapper14::new,
15 => Multicart15Mapper::new,
16 => BandaiFcgMapper::new,
17 => SuperMagicCardMapper::new,
18 => JalecoSs88006Mapper::new,
19 => Namco163Mapper::new,
20 => FdsMapper::new,
21 => Vrc2Vrc4Mapper::new,
22 => Vrc2Vrc4Mapper::new,
23 => Vrc2Vrc4Mapper::new,
24 => VRC6Mapper::new,
25 => Vrc2Vrc4Mapper::new,
26 => VRC6Mapper::new,
27 => Vrc2Vrc4Mapper::new,
28 => Action53Mapper::new,
29 => Mapper29::new,
30 => Unrom512Mapper::new,
31 => Mapper31::new,
32 => IremG101Mapper::new,
33 => TaitoTc0190Mapper::new,
34 => BnromNinaMapper::new,
35 => Mapper35::new,
36 => Mapper36::new,
37 => Mapper37::new,
38 => Mapper38::new,
39 => Mapper39::new,
40 => Ntdec2722Mapper::new,
41 => Mapper41::new,
42 => Mapper42::new,
43 => Mapper43::new,
44 => Mapper44::new,
45 => Mapper45::new,
46 => Mapper46::new,
47 => Mapper47::new,
48 => TaitoTc0350Mapper::new,
49 => Mapper49::new,
50 => Mapper50::new,
51 => Mapper51::new,
52 => Mapper52::new,
53 => Mapper53::new,
54 => Mapper54::new,
55 => Mapper55::new,
56 => Mapper56::new,
57 => Mapper57::new,
58 => Mapper58::new,
59 => Mapper59::new,
60 => Mapper60::new,
61 => Mapper61::new,
62 => Mapper62::new,
63 => Mapper63::new,
64 => TengenRambo1Mapper::new,
65 => IremH3001Mapper::new,
66 => GxROMMapper::new,
67 => Sunsoft3Mapper::new,
68 => Sunsoft4Mapper::new,
69 => SunsoftFme7Mapper::new,
70 => Mapper70::new,
71 => CamericaMapper::new,
72 => JalecoJf17Mapper::new,
73 => Vrc3Mapper::new,
74 => Mapper74::new,
75 => Vrc1Mapper::new,
76 => Namcot3446Mapper::new,
77 => IremLrog017Mapper::new,
78 => NinaTengenMapper::new,
79 => Mapper79::new,
80 => TaitoX1005Mapper::new,
81 => Mapper81::new,
82 => TaitoX1017Mapper::new,
83 => Mapper83::new,
84 => Ntdec2722Mapper::new,
85 => VRC7Mapper::new,
86 => JalecoJf13Mapper::new,
87 => Mapper87::new,
88 => Namcot3443Mapper::new,
89 => SunsoftEarlyMapper::new,
90 => JyCompanyMapper::new,
91 => Mapper91::new,
92 => JalecoJf19Mapper::new,
93 => Sunsoft2Mapper::new,
94 => Un1romMapper::new,
95 => Namcot3425Mapper::new,
96 => Mapper96::new,
97 => IremTamS1Mapper::new,
99 => Mapper99::new,
100 => Mapper100::new,
101 => JalecoJf10Mapper::new,
102 => NROMMapper::new,
103 => Mapper103::new,
104 => Mapper104::new,
105 => NesEventMapper::new,
106 => Mapper106::new,
107 => Mapper107::new,
108 => Mapper108::new,
110 => Mapper110::new,
111 => GtromMapper::new,
112 => Mapper112::new,
113 => Mapper113::new,
114 => Mapper114::new,
115 => Mapper115::new,
116 => Mapper116::new,
117 => Mapper117::new,
118 => TxsromMapper::new,
119 => TqromMapper::new,
120 => Mapper120::new,
121 => Mapper121::new,
122 => Mapper122::new,
123 => Mapper123::new,
124 => Mapper124::new,
125 => Mapper125::new,
126 => Mapper126::new,
128 => Mapper128::new,
129 => Mapper58::new,
130 => Mapper331::new,
131 => Mapper205::new,
132 => Mapper132::new,
133 => Mapper133::new,
134 => Mapper134::new,
135 => Sachen8259::new,
136 => Mapper136::new,
137 => Sachen8259::new,
138 => Sachen8259::new,
139 => Sachen8259::new,
140 => JalecoJf11Mapper::new,
141 => Sachen8259::new,
142 => Mapper56::new,
143 => Mapper143::new,
144 => ColorDreamsMapper::new,
145 => Mapper145::new,
146 => Mapper79::new,
147 => Mapper147::new,
148 => Mapper148::new,
149 => Mapper149::new,
150 => Mapper150::new,
151 => Mapper151::new,
152 => Mapper152::new,
153 => Mapper153::new,
154 => Namcot3453Mapper::new,
155 => MMC1Mapper::new,
156 => Mapper156::new,
157 => Mapper157::new,
158 => Mapper158::new,
159 => Mapper159::new,
160 => JyCompanyMapper::new,
161 => MMC1Mapper::new,
162 => Mapper162::new,
163 => Mapper163::new,
164 => Mapper164::new,
165 => Mapper165::new,
166 => Mapper166::new,
167 => Mapper167::new,
168 => Mapper168::new,
169 => Multicart15Mapper::new,
170 => Mapper170::new,
171 => Mapper171::new,
172 => Mapper172::new,
173 => Mapper173::new,
174 => Mapper174::new,
175 => Mapper175::new,
176 => Mapper176::new,
177 => Mapper177::new,
178 => Mapper178::new,
179 => Mapper176::new,
180 => UxromInvertedMapper::new,
182 => Mapper114::new,
183 => Mapper183::new,
184 => Sunsoft1Mapper::new,
185 => CnromSecurityMapper::new,
187 => Mapper187::new,
188 => Mapper188::new,
189 => Mapper189::new,
190 => Mapper190::new,
191 => Mapper191::new,
192 => Mapper192::new,
193 => Mapper193::new,
194 => Mapper194::new,
195 => Mapper195::new,
196 => Mapper196::new,
197 => Mapper197::new,
198 => Mapper198::new,
199 => Mapper199::new,
200 => Mapper200::new,
201 => Mapper201::new,
202 => Mapper202::new,
203 => Mapper203::new,
204 => Mapper204::new,
205 => Mapper205::new,
206 => Namco118Mapper::new,
207 => TaitoX1005_207Mapper::new,
208 => Mapper208::new,
209 => JyCompanyMapper::new_standard,
210 => Mapper210::new,
211 => Mapper211::new,
212 => Mapper212::new,
213 => Mapper213::new,
214 => Mapper214::new,
215 => Mapper215::new,
216 => Mapper216::new,
217 => Mapper217::new,
218 => Mapper218::new,
219 => Mapper219::new,
221 => Mapper221::new,
222 => Mapper222::new,
223 => Mapper223::new,
224 => Mapper224::new,
225 => Mapper225::new,
226 => Mapper226::new,
227 => Mapper227::new,
228 => Mapper228::new,
229 => Mapper229::new,
230 => Mapper230::new,
231 => Mapper231::new,
232 => Mapper232::new,
233 => Mapper233::new,
234 => Mapper234::new,
235 => Mapper235::new,
236 => Mapper236::new,
237 => Mapper237::new,
238 => Mapper238::new,
240 => Mapper240::new,
241 => Mapper241::new,
242 => Mapper242::new,
243 => Mapper243::new,
244 => Mapper244::new,
245 => Mapper245::new,
246 => Mapper246::new,
248 => Mapper115::new,
249 => Mapper249::new,
250 => Mapper250::new,
251 => Mapper251::new,
252 => Mapper252::new,
253 => Mapper253::new,
254 => Mapper254::new,
255 => Mapper255::new,
256 => Mapper256::new,
257 => Mapper257::new,
259 => Mapper259::new,
260 => Mapper260::new,
261 => Mapper261::new,
262 => Mapper262::new,
263 => Mapper263::new,
264 => Mapper264::new,
265 => Mapper265::new,
266 => Mapper266::new,
267 => Mapper267::new,
268 => Mapper268::new,
269 => Mapper269::new,
270 => Mapper270::new,
271 => Mapper271::new,
274 => Mapper274::new,
281 => Mapper281::new,
282 => Mapper282::new,
283 => Mapper283::new,
284 => Mapper284::new,
285 => Mapper285::new,
286 => Mapper286::new,
287 => Mapper287::new,
288 => Mapper288::new,
289 => Mapper289::new,
290 => Mapper290::new,
291 => Mapper291::new,
292 => Mapper292::new,
293 => Mapper293::new,
294 => Mapper294::new,
295 => Mapper295::new,
296 => Mapper296::new,
297 => Mapper297::new,
298 => Mapper298::new,
299 => Mapper299::new,
300 => Mapper300::new,
301 => Mapper301::new,
302 => Mapper302::new,
303 => Mapper303::new,
304 => Mapper304::new,
305 => Mapper305::new,
306 => Mapper306::new,
307 => Mapper307::new,
308 => Mapper308::new,
309 => Mapper309::new,
310 => Mapper310::new,
311 => Mapper311::new,
312 => Mapper312::new,
313 => Mapper313::new,
314 => Mapper314::new,
315 => Mapper315::new,
319 => Mapper319::new,
320 => Mapper320::new,
321 => Mapper321::new,
322 => Mapper322::new,
323 => Mapper323::new,
324 => Mapper324::new,
325 => Mapper325::new,
326 => Mapper326::new,
327 => Mapper327::new,
328 => Mapper328::new,
329 => Mapper329::new,
330 => Mapper330::new,
331 => Mapper331::new,
332 => Mapper332::new,
333 => Mapper333::new,
334 => Mapper334::new,
335 => Mapper335::new,
336 => Mapper336::new,
337 => Mapper337::new,
338 => Mapper338::new,
339 => Mapper339::new,
340 => Mapper340::new,
341 => Mapper341::new,
342 => Mapper342::new,
343 => Mapper60::new,
344 => Mapper344::new,
345 => Mapper345::new,
346 => Mapper346::new,
347 => Mapper347::new,
348 => Mapper348::new,
349 => Mapper349::new,
350 => Mapper350::new,
}
#[allow(clippy::style)]
#[cfg(test)]
const SUPPORTED_MAPPERS: &[u16] = &[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
100, 101, 102, 103, 104, 106, 108, 110, 114, 115, 117, 118, 120, 121, 122, 123, 124, 125, 126,
128, 129, 132, 133, 140, 141, 142, 143, 144, 145, 146, 147, 149, 154, 155, 156, 160, 161, 165,
169, 173, 177, 180, 182, 184, 185, 193, 205, 206, 207, 214, 216, 217, 218, 219, 222, 225, 226,
227, 228, 229, 230, 248, 231, 232, 233, 234, 236, 237, 238, 241, 242, 243, 244, 245, 246, 249,
250, 251, 253, 254, 255, 256, 257, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270,
271, 274, 281, 282, 283, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 299, 300,
302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 319, 320, 322, 323, 324,
326, 327, 328, 329, 330, 331, 332, 335, 337, 338, 339, 340, 342, 343, 344, 345, 346, 347, 348,
349, 350,
];
#[cfg(test)]
pub fn supported_mappers() -> &'static [u16] {
SUPPORTED_MAPPERS
}
pub fn create_mapper(metadata: MapperContext) -> io::Result<Box<dyn Mapper>> {
let mapper_number = metadata.mapper;
create_registry_mapper(metadata).ok_or_else(|| {
io::Error::new(
io::ErrorKind::Unsupported,
format!("Mapper {} not implemented", mapper_number),
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nes::cartridge::NametableLayout;
#[test]
fn test_supported_mappers_contains_common_ids() {
let supported = supported_mappers();
assert!(supported.contains(&0));
assert!(supported.contains(&1));
assert!(supported.contains(&2));
assert!(supported.contains(&3));
assert!(supported.contains(&4));
assert!(supported.contains(&5));
assert!(supported.contains(&7));
assert!(supported.contains(&8));
assert!(supported.contains(&95));
}
#[test]
fn create_mapper_accepts_mapper_95() {
let prg_rom = vec![0u8; 8 * 1024 * 8];
let chr_rom = vec![0u8; 1024 * 16];
let metadata = MapperContext::new_for_test(95, prg_rom, chr_rom, NametableLayout::Vertical);
let mapper = create_mapper(metadata).expect("Mapper 95 should be implemented");
assert!(!mapper.capabilities().has_irq);
assert!(mapper.capabilities().has_chr_banking);
}
#[test]
fn create_mapper_uses_metadata_for_mmc5_prg_ram_size() {
let prg_rom = vec![0u8; 8 * 1024 * 4];
let chr_rom = vec![0u8; 8 * 1024];
let metadata = MapperContext {
prg_ram_banks_8k: 2,
..MapperContext::new_for_test(5, prg_rom, chr_rom, NametableLayout::Horizontal)
};
let mapper = create_mapper(metadata).expect("MMC5 mapper should be created");
assert_eq!(mapper.wram_size(), 16 * 1024);
}
#[test]
fn create_mapper_accepts_mapper_8_as_mapper_6_alias() {
let prg_rom = (0u8..16)
.flat_map(|bank| std::iter::repeat_n(bank, 16 * 1024))
.collect();
let chr_rom = vec![0u8; 8 * 1024];
let metadata =
MapperContext::new_for_test(8, prg_rom, chr_rom, NametableLayout::Horizontal);
let mut mapper =
create_mapper(metadata).expect("Mapper 8 should be created as mapper 6 alias");
mapper.write_prg(0x8000, 0x10);
assert_eq!(mapper.read_prg(0x8000), 2);
assert_eq!(mapper.read_prg(0xC000), 3);
let caps = mapper.capabilities();
assert!(caps.has_irq);
assert!(caps.has_chr_banking);
assert!(caps.trainer_jsr);
}
#[test]
fn mapper_8_reads_chr_rom_banks_in_mode_4() {
let prg_rom = vec![0u8; 32 * 1024];
let chr_rom = (0u8..4)
.flat_map(|bank| std::iter::repeat_n(0x10 + bank, 8 * 1024))
.collect();
let metadata =
MapperContext::new_for_test(8, prg_rom, chr_rom, NametableLayout::Horizontal);
let mut mapper =
create_mapper(metadata).expect("Mapper 8 should be created as mapper 6 alias");
mapper.write_prg(0x8000, 0x01);
assert_eq!(mapper.read_chr(0x0000), 0x11);
}
#[test]
fn create_mapper_accepts_mapper_129_as_mapper_58_alias() {
let prg_rom = (0u8..8)
.flat_map(|bank| std::iter::repeat_n(bank, 16 * 1024))
.collect();
let chr_rom = (0u8..8)
.flat_map(|bank| std::iter::repeat_n(0x10 + bank, 8 * 1024))
.collect();
let metadata =
MapperContext::new_for_test(129, prg_rom, chr_rom, NametableLayout::Vertical);
let mut mapper = create_mapper(metadata)
.expect("Mapper 129 should be created as mapper 58-compatible alias");
mapper.write_prg(0x8080, 0);
assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
}
fn write_mmc1_serial_register(mapper: &mut dyn Mapper, register_addr: u16, register_value: u8) {
for shift in 0..5 {
mapper.write_prg(register_addr, (register_value >> shift) & 1);
mapper.cpu_cycle();
mapper.cpu_cycle();
}
}
#[test]
fn create_mapper_accepts_mapper_155_as_mmc1a_alias() {
let prg_rom = vec![0u8; 256 * 1024];
let chr_rom = vec![0u8; 8 * 1024];
let metadata =
MapperContext::new_for_test(155, prg_rom, chr_rom, NametableLayout::Horizontal);
let mut mapper = create_mapper(metadata)
.expect("Mapper 155 should be created as MMC1A-compatible alias");
mapper.write_prg(0x6000, 0x12);
assert_eq!(mapper.read_prg(0x6000), 0x12);
write_mmc1_serial_register(mapper.as_mut(), 0xE000, 0b1_0000);
mapper.write_prg(0x6000, 0x34);
assert_eq!(mapper.read_prg(0x6000), 0x34);
}
#[test]
fn create_mapper_accepts_mapper_100_as_mmc3_compatible() {
let prg_rom = vec![0u8; 8 * 1024 * 48];
let chr_rom = vec![0u8; 1024 * 96];
let metadata =
MapperContext::new_for_test(100, prg_rom, chr_rom, NametableLayout::Horizontal);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 100 should be created");
}
#[test]
fn mapper_100_matches_mmc3_bank_mirroring_and_irq_capabilities() {
let prg_rom = (0u8..48)
.flat_map(|bank| std::iter::repeat_n(bank, 8 * 1024))
.collect();
let chr_rom = (0u8..96)
.flat_map(|bank| std::iter::repeat_n(bank, 1024))
.collect();
let mut mapper = create_mapper(MapperContext::new_for_test(
100,
prg_rom,
chr_rom,
NametableLayout::Horizontal,
))
.expect("Mapper 100 should be created");
mapper.write_prg(0x8000, 0x06);
mapper.write_prg(0x8001, 5);
assert_eq!(mapper.read_prg(0x8000), 5);
mapper.write_prg(0xA000, 0);
assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
mapper.write_prg(0xA000, 1);
assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
let caps = mapper.capabilities();
assert!(caps.has_irq);
assert!(!caps.has_expansion_audio);
}
#[test]
fn supported_mappers_includes_mapper_100() {
assert!(supported_mappers().contains(&100));
}
#[test]
fn create_mapper_accepts_mapper_106() {
let prg_rom = vec![0u8; 8 * 1024 * 48];
let chr_rom = vec![0u8; 1024 * 64];
let metadata =
MapperContext::new_for_test(106, prg_rom, chr_rom, NametableLayout::Horizontal);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 106 should be created");
}
#[test]
fn mapper_106_switches_prg_chr_and_reports_irq_without_expansion_audio() {
let prg_rom = (0u8..48)
.flat_map(|bank| std::iter::repeat_n(bank, 8 * 1024))
.collect();
let chr_rom = (0u8..64)
.flat_map(|bank| std::iter::repeat_n(bank, 1024))
.collect();
let mut mapper = create_mapper(MapperContext::new_for_test(
106,
prg_rom,
chr_rom,
NametableLayout::Vertical,
))
.expect("Mapper 106 should be created");
mapper.write_prg(0x8008, 0x03);
mapper.write_prg(0x8009, 0x05);
assert_eq!(mapper.read_prg(0x8000), 0x13);
assert_eq!(mapper.read_prg(0xA000), 0x05);
mapper.write_prg(0x8000, 0x02);
mapper.write_prg(0x8001, 0x04);
assert_eq!(mapper.read_chr(0x0000), 0x02);
assert_eq!(mapper.read_chr(0x0400), 0x05);
let caps = mapper.capabilities();
assert!(caps.has_irq);
assert!(!caps.has_expansion_audio);
}
#[test]
fn supported_mappers_includes_mapper_106() {
assert!(supported_mappers().contains(&106));
}
#[test]
fn create_mapper_accepts_mapper_102() {
let prg_rom = vec![0u8; 32 * 1024];
let chr_rom = vec![0u8; 8 * 1024];
let metadata =
MapperContext::new_for_test(102, prg_rom, chr_rom, NametableLayout::Horizontal);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 102 should be created");
}
#[test]
fn supported_mappers_includes_mapper_102() {
assert!(supported_mappers().contains(&102));
}
#[test]
fn create_mapper_accepts_mapper_122() {
let metadata = MapperContext::new_for_test(
122,
vec![0u8; 32 * 1024],
vec![0u8; 8 * 1024 * 4],
NametableLayout::Horizontal,
);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 122 should be created");
}
#[test]
fn supported_mappers_includes_mapper_122() {
assert!(supported_mappers().contains(&122));
}
#[test]
fn create_mapper_accepts_mapper_124() {
let metadata = MapperContext::new_for_test(
124,
vec![0u8; 64 * 1024],
vec![0u8; 8 * 1024],
NametableLayout::Horizontal,
);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 124 should be created");
}
#[test]
fn supported_mappers_includes_mapper_124() {
assert!(supported_mappers().contains(&124));
}
#[test]
fn create_mapper_accepts_mapper_218() {
let metadata = MapperContext::new_for_test(
218,
vec![0u8; 32 * 1024],
vec![0u8; 8 * 1024],
NametableLayout::Vertical,
);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 218 should be created");
}
#[test]
fn supported_mappers_includes_mapper_218() {
assert!(supported_mappers().contains(&218));
}
#[test]
fn mapper_102_behaves_as_nrom() {
let mut prg_rom = vec![0; 16 * 1024];
prg_rom[0x0000] = 0x12;
prg_rom[0x3FFF] = 0x34;
let mut mapper = create_mapper(MapperContext::new_for_test(
102,
prg_rom,
vec![0x5A; 8 * 1024],
NametableLayout::Vertical,
))
.expect("Mapper 102 should be created");
mapper.write_prg(0x8000, 0xFF);
assert_eq!(mapper.read_prg(0x8000), 0x12);
assert_eq!(mapper.read_prg(0xBFFF), 0x34);
assert_eq!(mapper.read_prg(0xC000), 0x12);
assert_eq!(mapper.read_prg(0xFFFF), 0x34);
assert_eq!(mapper.read_chr(0x0000), 0x5A);
}
#[test]
fn create_mapper_accepts_mapper_105_nes_event() {
let prg_rom = vec![0u8; 16 * 1024 * 16];
let chr_rom = vec![0u8; 8 * 1024];
let metadata =
MapperContext::new_for_test(105, prg_rom, chr_rom, NametableLayout::Horizontal);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 105 should be created");
}
#[test]
fn create_mapper_accepts_mapper_344_gn26() {
let metadata = MapperContext::new_for_test(
344,
vec![0u8; 256 * 1024],
vec![0u8; 128 * 1024],
NametableLayout::Vertical,
);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 344 (GN-26) should be created");
}
#[test]
fn create_mapper_accepts_mapper_340_k3036() {
let metadata = MapperContext::new_for_test(
340,
vec![0u8; 16 * 1024 * 32],
vec![0u8; 8 * 1024],
NametableLayout::Vertical,
);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 340 (BMC-K-3036) should be created");
}
#[test]
fn create_mapper_accepts_mapper_339_k3006() {
let metadata = MapperContext::new_for_test(
339,
vec![0u8; 16 * 1024 * 32],
vec![0u8; 8 * 1024],
NametableLayout::Vertical,
);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 339 (BMC-K-3006) should be created");
}
#[test]
fn supported_mappers_includes_mapper_339() {
let supported = supported_mappers();
assert!(supported.contains(&339));
}
#[test]
fn mapper_340_supports_unrom_and_nrom128_modes_and_address_mirroring_latch() {
let prg_rom = (0u8..32)
.flat_map(|bank| std::iter::repeat_n(bank, 16 * 1024))
.collect();
let metadata = MapperContext::new_for_test(340, prg_rom, vec![], NametableLayout::Vertical);
let mut mapper = create_mapper(metadata).expect("Mapper 340 should be created");
mapper.write_prg(0x8000, 0x02);
assert_eq!(mapper.read_prg(0x8000), 2);
assert_eq!(mapper.read_prg(0xC000), 7);
assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
mapper.write_prg(0x8025, 0x00);
assert_eq!(mapper.read_prg(0x8000), 5);
assert_eq!(mapper.read_prg(0xC000), 5);
assert_eq!(mapper.get_mirroring(), NametableLayout::Horizontal);
mapper.write_prg(0x8040, 0x00);
assert_eq!(mapper.read_prg(0x8000), 0);
assert_eq!(mapper.read_prg(0xC000), 7);
assert_eq!(mapper.get_mirroring(), NametableLayout::Vertical);
}
#[test]
fn create_mapper_accepts_mapper_341_tj03() {
let metadata = MapperContext::new_for_test(
341,
vec![0u8; 16 * 1024 * 8],
vec![0u8; 8 * 1024 * 8],
NametableLayout::Vertical,
);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 341 (BMC-TJ-03) should be created");
}
#[test]
fn mapper_341_reports_no_irq_and_no_expansion_audio() {
let metadata = MapperContext::new_for_test(
341,
vec![0u8; 16 * 1024 * 8],
vec![0u8; 8 * 1024 * 8],
NametableLayout::Vertical,
);
let mapper = create_mapper(metadata).expect("Mapper 341 should be created");
let caps = mapper.capabilities();
assert!(!caps.has_irq);
assert!(!caps.has_expansion_audio);
assert_eq!(caps.prg_bank_size_kb, 16);
assert_eq!(caps.chr_bank_size_kb, 8);
}
#[test]
fn create_mapper_accepts_mapper_342_coolgirl() {
let metadata = MapperContext::new_for_test(
342,
vec![0u8; 256 * 1024],
vec![0u8; 8 * 1024],
NametableLayout::Vertical,
);
let result = create_mapper(metadata);
assert!(result.is_ok(), "Mapper 342 (COOLGIRL) should be created");
}
#[test]
fn test_ppu_address_changed_default_is_noop() {
let prg_rom = vec![0u8; 32 * 1024];
let chr_rom = vec![0u8; 8 * 1024];
let metadata =
MapperContext::new_for_test(0, prg_rom, chr_rom, NametableLayout::Horizontal);
let mut mapper = create_mapper(metadata).expect("NROM mapper should be created");
mapper.ppu_address_changed(0x0000);
mapper.ppu_address_changed(0x1000);
mapper.ppu_address_changed(0x1FFF);
}
#[test]
fn mapper_130_is_alias_for_mapper_331() {
let m = make_mapper(130);
assert_eq!(m.mapper_number(), 331);
}
#[test]
fn mapper_131_is_alias_for_mapper_205() {
let m = make_mapper(131);
assert_eq!(m.mapper_number(), 205);
}
#[test]
fn create_mapper_accepts_mapper_160_as_mapper_90_alias() {
let m = make_mapper(160);
assert_eq!(m.mapper_number(), 160);
}
#[test]
fn create_mapper_accepts_mapper_161_as_mmc1_alias() {
let m = make_mapper(161);
assert_eq!(m.mapper_number(), 161);
}
#[test]
fn create_mapper_accepts_mapper_169_as_mapper_15_alias() {
let m = make_mapper(169);
assert_eq!(m.mapper_number(), 169);
}
#[test]
fn create_mapper_accepts_mapper_182_as_mapper_114_alias() {
let m = make_mapper(182);
assert_eq!(m.mapper_number(), 114);
}
#[test]
fn create_mapper_accepts_mapper_248_as_mapper_115_alias() {
let m = make_mapper(248);
assert_eq!(m.mapper_number(), 115);
}
fn make_mapper(id: u16) -> Box<dyn Mapper> {
let prg_size = 32 * 1024; let prg_rom = vec![0u8; prg_size];
let chr_rom = vec![0u8; 8 * 1024];
let metadata =
MapperContext::new_for_test(id, prg_rom, chr_rom, NametableLayout::Horizontal);
create_mapper(metadata).unwrap_or_else(|_| panic!("Mapper {} should be created", id))
}
#[test]
fn nrom_reports_no_irq() {
assert!(!make_mapper(0).capabilities().has_irq);
}
#[test]
fn mmc3_reports_irq_capability() {
assert!(make_mapper(4).capabilities().has_irq);
}
#[test]
fn mmc5_reports_irq_capability() {
assert!(make_mapper(5).capabilities().has_irq);
}
#[test]
fn sunsoft_fme7_reports_irq_capability() {
assert!(make_mapper(69).capabilities().has_irq);
}
#[test]
fn nrom_reports_no_chr_banking() {
assert!(!make_mapper(0).capabilities().has_chr_banking);
}
#[test]
fn mmc1_reports_chr_banking() {
assert!(make_mapper(1).capabilities().has_chr_banking);
}
#[test]
fn mmc3_reports_chr_banking() {
assert!(make_mapper(4).capabilities().has_chr_banking);
}
#[test]
fn nrom_reports_no_dynamic_mirroring() {
assert!(!make_mapper(0).capabilities().has_dynamic_mirroring);
}
#[test]
fn mmc1_reports_dynamic_mirroring() {
assert!(make_mapper(1).capabilities().has_dynamic_mirroring);
}
#[test]
fn axrom_reports_dynamic_mirroring() {
assert!(make_mapper(7).capabilities().has_dynamic_mirroring);
}
#[test]
fn nrom_reports_no_expansion_audio() {
assert!(!make_mapper(0).capabilities().has_expansion_audio);
}
#[test]
fn mmc5_reports_expansion_audio() {
assert!(make_mapper(5).capabilities().has_expansion_audio);
}
#[test]
fn vrc6_reports_expansion_audio() {
assert!(make_mapper(24).capabilities().has_expansion_audio);
}
#[test]
fn namco163_reports_expansion_audio() {
assert!(make_mapper(19).capabilities().has_expansion_audio);
}
#[test]
fn bandai_fcg_reports_zero_prg_ram() {
assert_eq!(make_mapper(16).capabilities().max_prg_ram_kb, 8);
}
#[test]
fn mmc1_reports_8kb_prg_ram() {
assert_eq!(make_mapper(1).capabilities().max_prg_ram_kb, 8);
}
#[test]
fn mmc5_reports_64kb_prg_ram() {
assert_eq!(make_mapper(5).capabilities().max_prg_ram_kb, 64);
}
#[test]
fn nrom_reports_32kb_prg_bank_size() {
assert_eq!(make_mapper(0).capabilities().prg_bank_size_kb, 32);
}
#[test]
fn mmc3_reports_8kb_prg_bank_size() {
assert_eq!(make_mapper(4).capabilities().prg_bank_size_kb, 8);
}
#[test]
fn uxrom_reports_16kb_prg_bank_size() {
assert_eq!(make_mapper(2).capabilities().prg_bank_size_kb, 16);
}
#[test]
fn nrom_reports_8kb_chr_bank_size() {
assert_eq!(make_mapper(0).capabilities().chr_bank_size_kb, 8);
}
#[test]
fn mmc3_reports_1kb_chr_bank_size() {
assert_eq!(make_mapper(4).capabilities().chr_bank_size_kb, 1);
}
#[test]
fn mmc1_reports_4kb_chr_bank_size() {
assert_eq!(make_mapper(1).capabilities().chr_bank_size_kb, 4);
}
#[test]
fn all_supported_mappers_return_capabilities() {
for &id in supported_mappers() {
let mapper = make_mapper(id);
let caps = mapper.capabilities();
let _ = format!("{:?}", caps);
}
}
#[test]
fn irq_capable_mappers_report_irq_pending_false_initially() {
for &id in supported_mappers() {
let mapper = make_mapper(id);
if mapper.capabilities().has_irq {
assert!(
!mapper.irq_pending(),
"Mapper {} should not have IRQ pending initially",
id
);
}
}
}
#[test]
fn expansion_audio_mappers_return_silent_initially() {
for &id in supported_mappers() {
let mapper = make_mapper(id);
if mapper.capabilities().has_expansion_audio {
assert_eq!(
mapper.expansion_audio_sample(),
0.0,
"Mapper {} with expansion audio should be silent initially",
id
);
}
}
}
#[test]
fn prg_ram_capable_mappers_report_nonzero_wram_size() {
for &id in supported_mappers() {
let mapper = make_mapper(id);
let caps = mapper.capabilities();
if caps.max_prg_ram_kb > 0 {
assert!(
mapper.wram_size() > 0,
"Mapper {} reports {}KB PRG-RAM capability but wram_size() is 0",
id,
caps.max_prg_ram_kb
);
}
}
}
fn assert_core_contract<T: Mapper>(mapper: &mut T) {
let _ = mapper.read_prg(0x8000);
mapper.write_prg(0x8000, 0x12);
let _ = mapper.read_chr(0x0000);
mapper.write_chr(0x0000, 0x34);
let _ = mapper.get_mirroring();
}
fn assert_irq_contract<T: Mapper>(mapper: &mut T) {
mapper.cpu_cycle();
let _ = mapper.irq_pending();
}
fn assert_ppu_extension_contract<T: Mapper>(mapper: &mut T) {
mapper.ppu_address_changed(0x1000);
mapper.ppu_scanline(42, true);
}
fn assert_audio_contract<T: Mapper>(mapper: &mut T) {
let _ = mapper.expansion_audio_sample();
}
fn assert_state_contract<T: Mapper>(mapper: &mut T) {
let wram = mapper.wram_snapshot();
mapper.load_wram_snapshot(&wram);
let prg = mapper.prg_ram_snapshot();
mapper.restore_prg_ram(&prg);
let chr = mapper.chr_ram_snapshot();
mapper.restore_chr_ram(&chr);
let registers = mapper.registers_snapshot();
mapper.restore_registers(®isters);
}
fn assert_composable_contract<T: Mapper>(mapper: &mut T) {
let _ = mapper.wram_size();
let _ = mapper.prg_ram_snapshot();
}
#[test]
fn nrom_satisfies_core_and_state_traits() {
let mut mapper = NROMMapper::new(MapperContext::new_for_test(
0,
vec![0u8; 32 * 1024],
vec![0u8; 8 * 1024],
NametableLayout::Horizontal,
));
assert_core_contract(&mut mapper);
assert_state_contract(&mut mapper);
assert_composable_contract(&mut mapper);
}
#[test]
fn mmc3_satisfies_core_irq_ppu_and_state_traits() {
let mut mapper = MMC3Mapper::new(MapperContext::new_for_test(
4,
vec![0u8; 32 * 1024],
vec![0u8; 8 * 1024],
NametableLayout::Horizontal,
));
assert_core_contract(&mut mapper);
assert_irq_contract(&mut mapper);
assert_ppu_extension_contract(&mut mapper);
assert_state_contract(&mut mapper);
}
#[test]
fn vrc6_satisfies_core_irq_audio_and_state_traits() {
let mut mapper = VRC6Mapper::new(MapperContext::new_for_test(
24,
vec![0u8; 32 * 1024],
vec![0u8; 8 * 1024],
NametableLayout::Horizontal,
));
assert_core_contract(&mut mapper);
assert_irq_contract(&mut mapper);
assert_audio_contract(&mut mapper);
assert_state_contract(&mut mapper);
}
#[test]
fn from_parsed_rom_allocates_prg_ram_from_nvram_when_volatile_absent() {
use crate::nes::cartridge::ines::{ConsoleType, InesHeader, ParsedRom, TimingMode};
let header = InesHeader {
mapper: 69,
submapper: 0,
console_type: ConsoleType::NesFamicom,
mirroring: NametableLayout::Horizontal,
has_trainer: false,
header_version: "2.0",
battery_backed_prg_ram: true,
prg_rom_size_bytes: 128 * 1024,
chr_rom_size_bytes: 64 * 1024,
prg_ram_size_bytes: None, prg_nvram_size_bytes: Some(8192), chr_ram_size_bytes: None,
chr_nvram_size_bytes: None,
timing_mode: TimingMode::Ntsc,
vs_ppu_type: None,
vs_hardware_type: None,
misc_roms: 0,
default_expansion_device: 0,
};
let parsed = ParsedRom {
header,
prg_rom: vec![0u8; 128 * 1024],
chr_rom: vec![0u8; 64 * 1024],
trainer: None,
crc32: 0,
payload_crc32: 0,
};
let ctx = MapperContext::from_parsed_rom(&parsed);
assert_eq!(
ctx.prg_ram_banks_8k, 1,
"NVRAM should count as PRG-RAM banks when volatile RAM is absent"
);
assert!(
ctx.prg_ram_size_specified,
"prg_ram_size_specified should be true when NVRAM is present"
);
}
#[test]
fn from_parsed_rom_uses_larger_of_volatile_and_nvram_when_both_present() {
use crate::nes::cartridge::ines::{ConsoleType, InesHeader, ParsedRom, TimingMode};
let header = InesHeader {
mapper: 69,
submapper: 0,
console_type: ConsoleType::NesFamicom,
mirroring: NametableLayout::Horizontal,
has_trainer: false,
header_version: "2.0",
battery_backed_prg_ram: true,
prg_rom_size_bytes: 128 * 1024,
chr_rom_size_bytes: 64 * 1024,
prg_ram_size_bytes: Some(8192), prg_nvram_size_bytes: Some(16384), chr_ram_size_bytes: None,
chr_nvram_size_bytes: None,
timing_mode: TimingMode::Ntsc,
vs_ppu_type: None,
vs_hardware_type: None,
misc_roms: 0,
default_expansion_device: 0,
};
let parsed = ParsedRom {
header,
prg_rom: vec![0u8; 128 * 1024],
chr_rom: vec![0u8; 64 * 1024],
trainer: None,
crc32: 0,
payload_crc32: 0,
};
let ctx = MapperContext::from_parsed_rom(&parsed);
assert_eq!(
ctx.prg_ram_banks_8k, 2,
"Should allocate banks for max(volatile, nvram)"
);
assert!(
ctx.prg_ram_size_specified,
"prg_ram_size_specified should be true when NVRAM is present"
);
}
#[test]
fn from_parsed_rom_propagates_vs_hardware_type() {
use crate::nes::cartridge::ines::{ConsoleType, InesHeader, ParsedRom, TimingMode};
use crate::nes::cartridge::rom_db::VsHardwareType;
let header = InesHeader {
mapper: 99,
submapper: 0,
console_type: ConsoleType::VsSystem,
mirroring: NametableLayout::Horizontal,
has_trainer: false,
header_version: "2.0",
battery_backed_prg_ram: false,
prg_rom_size_bytes: 32 * 1024,
chr_rom_size_bytes: 8 * 1024,
prg_ram_size_bytes: None,
prg_nvram_size_bytes: None,
chr_ram_size_bytes: None,
chr_nvram_size_bytes: None,
timing_mode: TimingMode::Ntsc,
vs_ppu_type: Some(0),
vs_hardware_type: Some(1),
misc_roms: 0,
default_expansion_device: 0,
};
let parsed = ParsedRom {
header,
prg_rom: vec![0u8; 32 * 1024],
chr_rom: vec![0u8; 8 * 1024],
trainer: None,
crc32: 0,
payload_crc32: 0,
};
let ctx = MapperContext::from_parsed_rom(&parsed);
assert_eq!(ctx.vs_hardware_type, Some(VsHardwareType::RbiBaseball));
}
#[test]
fn from_parsed_rom_non_vs_rom_has_no_vs_hardware_type() {
use crate::nes::cartridge::ines::{ConsoleType, InesHeader, ParsedRom, TimingMode};
let header = InesHeader {
mapper: 0,
submapper: 0,
console_type: ConsoleType::NesFamicom,
mirroring: NametableLayout::Horizontal,
has_trainer: false,
header_version: "1.0",
battery_backed_prg_ram: false,
prg_rom_size_bytes: 32 * 1024,
chr_rom_size_bytes: 8 * 1024,
prg_ram_size_bytes: None,
prg_nvram_size_bytes: None,
chr_ram_size_bytes: None,
chr_nvram_size_bytes: None,
timing_mode: TimingMode::Ntsc,
vs_ppu_type: None,
vs_hardware_type: None,
misc_roms: 0,
default_expansion_device: 0,
};
let parsed = ParsedRom {
header,
prg_rom: vec![0u8; 32 * 1024],
chr_rom: vec![0u8; 8 * 1024],
trainer: None,
crc32: 0,
payload_crc32: 0,
};
let ctx = MapperContext::from_parsed_rom(&parsed);
assert_eq!(ctx.vs_hardware_type, None);
}
}