diff --git a/README.md b/README.md index 8bc62b11..d32f68b3 100644 --- a/README.md +++ b/README.md @@ -208,29 +208,34 @@ Support for the following mappers is currently implemented or in development: -| # | Name | Example Games | # of Games1 | % of Games1 | -| --- | -------------------- | ----------------------------------------- | ---------------------- | ---------------------- | -| 000 | NROM | Bomberman, Donkey Kong, Super Mario Bros. | ~247 | ~10% | -| 001 | SxROM/MMC1B/C | Metroid, Legend of Zelda, Tetris | ~680 | ~28% | -| 002 | UxROM | Castlevania, Contra, Mega Man | ~270 | ~11% | -| 003 | CNROM | Arkanoid, Paperboy, Pipe Dream | ~155 | ~6% | -| 004 | TxROM/MMC3/MMC6 | Kirby's Adventure, Super Mario Bros. 2/3 | ~599 | ~24% | -| 005 | ExROM/MMC5 | Castlevania 3, Laser Invasion | ~24 | <0.01% | -| 007 | AxROM | Battletoads, Marble Madness | ~75 | ~3% | -| 009 | PxROM/MMC2 | Punch Out!! | 1 | <0.01% | -| 010 | FxROM/MMC4 | Fire Emblem Gaiden | 3 | <0.01% | -| 011 | Color Dreams | Crystal Mines, Metal Fighter | 15 | ~1% | -| 016 | Bandai FCG | Dragon Ball: Daimaou Fukkatsu | 14 | ~1% | -| 024 | VRC6a | Akumajou Densetsu | 1 | <0.01% | -| 026 | VRC6b | Madara, Esper Dream 2 | 2 | <0.01% | -| 034 | BNROM/NINA-001 | Deadly Towers, Impossible Mission II | 3 | <0.01% | -| 066 | GxROM/MxROM | Super Mario Bros. + Duck Hunt | ~17 | <0.01% | -| 071 | Camerica/Codemasters | Firehawk, Bee 52, MiG 29 - Soviet Fighter | ~15 | <0.01% | -| 153 | Bandai FCG | Famicom Jump II: Saikyou no 7-nin | 1 | <0.01% | -| 157 | Bandai FCG | SD Gundam Wars | 7 | <0.01% | -| 155 | SxROM/MMC1A | Tatakae!! Ramen Man: Sakuretsu Choujin | 2 | <0.01% | -| 159 | Bandai FCG | Dragon Ball Z: Kyoushuu! Saiya-jin | 4 | <0.01% | -| | | | ~2155 / 2447 | ~88.0% | +| # | Name | Example Games | # of Games1 | % of Games1 | +| --- | --------------------- | ------------------------------------------ | ---------------------- | ---------------------- | +| 000 | NROM | Bomberman, Donkey Kong, Super Mario Bros. | ~247 | ~10% | +| 001 | SxROM/MMC1B/C | Metroid, Legend of Zelda, Tetris | ~680 | ~28% | +| 002 | UxROM | Castlevania, Contra, Mega Man | ~270 | ~11% | +| 003 | CNROM | Arkanoid, Paperboy, Pipe Dream | ~155 | ~6% | +| 004 | TxROM/MMC3/MMC6 | Kirby's Adventure, Super Mario Bros. 2/3 | ~599 | ~24% | +| 005 | ExROM/MMC5 | Castlevania 3, Laser Invasion | ~24 | <0.01% | +| 007 | AxROM | Battletoads, Marble Madness | ~75 | ~3% | +| 009 | PxROM/MMC2 | Punch Out!! | 1 | <0.01% | +| 010 | FxROM/MMC4 | Fire Emblem Gaiden | 3 | <0.01% | +| 011 | Color Dreams | Crystal Mines, Metal Fighter | 15 | ~1% | +| 016 | Bandai FCG | Dragon Ball: Daimaou Fukkatsu | 14 | ~1% | +| 024 | VRC6a | Akumajou Densetsu | 1 | <0.01% | +| 026 | VRC6b | Madara, Esper Dream 2 | 2 | <0.01% | +| 034 | BNROM/NINA-001 | Deadly Towers, Impossible Mission II | 3 | <0.01% | +| 066 | GxROM/MxROM | Super Mario Bros. + Duck Hunt | ~17 | <0.01% | +| 071 | Camerica/Codemasters | Firehawk, Bee 52, MiG 29 - Soviet Fighter | ~15 | <0.01% | +| 076 | DxROM/Namco 108 | Megami Tensei: Digital Devil Story | 1 | <0.01% | +| 088 | DxROM/Namco 108 | Quinty, Dragon Spirit - Aratanaru Densetsu | 3 | <0.01% | +| 095 | DxROM/Namco 108 | Dragon Buster | 1 | <0.01% | +| 153 | Bandai FCG | Famicom Jump II: Saikyou no 7-nin | 1 | <0.01% | +| 154 | DxROM/Namco 108 | Devil Man | 1 | <0.01% | +| 157 | Bandai FCG/Datach | SD Gundam Wars | 7 | <0.01% | +| 155 | SxROM/MMC1A | Tatakae!! Ramen Man: Sakuretsu Choujin | 2 | <0.01% | +| 159 | Bandai FCG | Dragon Ball Z: Kyoushuu! Saiya-jin | 4 | <0.01% | +| 206 | DxROM/Namco 108 | Fantasy Zone, Gauntlet | 45 | ~2% | +| | | | ~2186 / 2447 | ~89.0% | diff --git a/tetanes-core/src/cart.rs b/tetanes-core/src/cart.rs index e821dc0b..249e80ff 100644 --- a/tetanes-core/src/cart.rs +++ b/tetanes-core/src/cart.rs @@ -5,8 +5,8 @@ use crate::{ fs, mapper::{ self, m024_m026_vrc6::Revision as Vrc6Revision, m034_nina001::Nina001, Axrom, BandaiFCG, - Bf909x, Bnrom, Cnrom, ColorDreams, Exrom, Fxrom, Gxrom, Mapper, Mmc1Revision, Nrom, Pxrom, - Sxrom, Txrom, Uxrom, Vrc6, + Bf909x, Bnrom, Cnrom, ColorDreams, Dxrom154, Dxrom206, Dxrom76, Dxrom88, Dxrom95, Exrom, + Fxrom, Gxrom, Mapper, Mmc1Revision, Nrom, Pxrom, Sxrom, Txrom, Uxrom, Vrc6, }, mem::RamState, ppu::Mirroring, @@ -219,6 +219,11 @@ impl Cart { } 66 => Gxrom::load(&mut cart)?, 71 => Bf909x::load(&mut cart)?, + 76 => Dxrom76::load(&mut cart)?, + 88 => Dxrom88::load(&mut cart)?, + 95 => Dxrom95::load(&mut cart)?, + 154 => Dxrom154::load(&mut cart)?, + 206 => Dxrom206::load(&mut cart)?, 155 => Sxrom::load(&mut cart, Mmc1Revision::A)?, _ => Mapper::none(), }; diff --git a/tetanes-core/src/mapper.rs b/tetanes-core/src/mapper.rs index e5738f1f..08549927 100644 --- a/tetanes-core/src/mapper.rs +++ b/tetanes-core/src/mapper.rs @@ -26,6 +26,11 @@ pub use m034_bnrom::Bnrom; pub use m034_nina001::Nina001; pub use m066_gxrom::Gxrom; pub use m071_bf909x::{Bf909x, Revision as Bf909Revision}; +pub use m076_dxrom::Dxrom as Dxrom76; +pub use m088_dxrom::Dxrom as Dxrom88; +pub use m095_dxrom::Dxrom as Dxrom95; +pub use m154_dxrom::Dxrom as Dxrom154; +pub use m206_dxrom::Dxrom as Dxrom206; pub mod bandai_fcg; pub mod m000_nrom; @@ -43,6 +48,11 @@ pub mod m034_bnrom; pub mod m034_nina001; pub mod m066_gxrom; pub mod m071_bf909x; +pub mod m076_dxrom; +pub mod m088_dxrom; +pub mod m095_dxrom; +pub mod m154_dxrom; +pub mod m206_dxrom; pub mod vrc_irq; #[derive(thiserror::Error, Debug)] @@ -100,6 +110,11 @@ pub enum Mapper { Nina001, Gxrom, Bf909x, + Dxrom76, + Dxrom88, + Dxrom95, + Dxrom154, + Dxrom206, } impl Mapper { diff --git a/tetanes-core/src/mapper/bandai_fcg.rs b/tetanes-core/src/mapper/bandai_fcg.rs index 0c12d7ce..8cf96125 100644 --- a/tetanes-core/src/mapper/bandai_fcg.rs +++ b/tetanes-core/src/mapper/bandai_fcg.rs @@ -1,4 +1,4 @@ -//! `Bandai FCG` (Mapper 016) +//! `Bandai FCG` (Mappers 016, 153, 157, and 159) //! //! diff --git a/tetanes-core/src/mapper/m000_nrom.rs b/tetanes-core/src/mapper/m000_nrom.rs index e62a28c4..f150004b 100644 --- a/tetanes-core/src/mapper/m000_nrom.rs +++ b/tetanes-core/src/mapper/m000_nrom.rs @@ -37,6 +37,16 @@ impl Nrom { } } +impl Mapped for Nrom { + fn mirroring(&self) -> Mirroring { + self.mirroring + } + + fn set_mirroring(&mut self, mirroring: Mirroring) { + self.mirroring = mirroring; + } +} + impl MemMap for Nrom { // PPU $0000..=$1FFF 8K Fixed CHR-ROM Bank // CPU $6000..=$7FFF 2K or 4K PRG-RAM Family Basic only. 8K is provided by default. @@ -65,17 +75,7 @@ impl MemMap for Nrom { } } -impl Mapped for Nrom { - fn mirroring(&self) -> Mirroring { - self.mirroring - } - - fn set_mirroring(&mut self, mirroring: Mirroring) { - self.mirroring = mirroring; - } -} - +impl Reset for Nrom {} impl Clock for Nrom {} impl Regional for Nrom {} -impl Reset for Nrom {} impl Sram for Nrom {} diff --git a/tetanes-core/src/mapper/m001_sxrom.rs b/tetanes-core/src/mapper/m001_sxrom.rs index 2cbd6d8b..c6742cae 100644 --- a/tetanes-core/src/mapper/m001_sxrom.rs +++ b/tetanes-core/src/mapper/m001_sxrom.rs @@ -275,15 +275,6 @@ impl MemMap for Sxrom { } } -impl Clock for Sxrom { - fn clock(&mut self) -> usize { - if self.regs.write_just_occurred > 0 { - self.regs.write_just_occurred -= 1; - } - 1 - } -} - impl Reset for Sxrom { fn reset(&mut self, kind: ResetKind) { self.regs.shift_register = Self::DEFAULT_SHIFT_REGISTER; @@ -296,6 +287,15 @@ impl Reset for Sxrom { } } +impl Clock for Sxrom { + fn clock(&mut self) -> usize { + if self.regs.write_just_occurred > 0 { + self.regs.write_just_occurred -= 1; + } + 1 + } +} + impl Regional for Sxrom {} impl Sram for Sxrom {} diff --git a/tetanes-core/src/mapper/m002_uxrom.rs b/tetanes-core/src/mapper/m002_uxrom.rs index abd99d81..80ddf788 100644 --- a/tetanes-core/src/mapper/m002_uxrom.rs +++ b/tetanes-core/src/mapper/m002_uxrom.rs @@ -70,7 +70,7 @@ impl Mapped for Uxrom { } } +impl Reset for Uxrom {} impl Clock for Uxrom {} impl Regional for Uxrom {} -impl Reset for Uxrom {} impl Sram for Uxrom {} diff --git a/tetanes-core/src/mapper/m003_cnrom.rs b/tetanes-core/src/mapper/m003_cnrom.rs index 8c919eb8..19540302 100644 --- a/tetanes-core/src/mapper/m003_cnrom.rs +++ b/tetanes-core/src/mapper/m003_cnrom.rs @@ -68,7 +68,7 @@ impl Mapped for Cnrom { } } +impl Reset for Cnrom {} impl Clock for Cnrom {} impl Regional for Cnrom {} -impl Reset for Cnrom {} impl Sram for Cnrom {} diff --git a/tetanes-core/src/mapper/m004_txrom.rs b/tetanes-core/src/mapper/m004_txrom.rs index d5fc7871..63c43941 100644 --- a/tetanes-core/src/mapper/m004_txrom.rs +++ b/tetanes-core/src/mapper/m004_txrom.rs @@ -59,6 +59,8 @@ pub struct Regs { pub struct Txrom { pub regs: Regs, pub mirroring: Mirroring, + pub mapper_num: u16, + pub submapper_num: u8, pub revision: Revision, pub chr_banks: Banks, pub prg_ram_banks: Banks, @@ -67,7 +69,7 @@ pub struct Txrom { impl Txrom { const PRG_WINDOW: usize = 8 * 1024; - const CHR_WINDOW: usize = 1024; + pub(super) const CHR_WINDOW: usize = 1024; const FOUR_SCREEN_RAM_SIZE: usize = 4 * 1024; const PRG_RAM_SIZE: usize = 8 * 1024; @@ -76,7 +78,7 @@ impl Txrom { const PRG_MODE_MASK: u8 = 0x40; // Bit 6 of bank select const CHR_INVERSION_MASK: u8 = 0x80; // Bit 7 of bank select - pub fn load(cart: &mut Cart) -> Result { + pub fn new(cart: &mut Cart, chr_window: usize) -> Result { cart.add_prg_ram(Self::PRG_RAM_SIZE); if cart.mirroring() == Mirroring::FourScreen { cart.add_exram(Self::FOUR_SCREEN_RAM_SIZE); @@ -92,22 +94,32 @@ impl Txrom { let mut txrom = Self { regs: Regs::default(), mirroring: cart.mirroring(), + mapper_num: cart.mapper_num(), + submapper_num: cart.submapper_num(), revision: Revision::BC, // TODO compare to known games - chr_banks: Banks::new(0x0000, 0x1FFF, chr_len, Self::CHR_WINDOW)?, + chr_banks: Banks::new(0x0000, 0x1FFF, chr_len, chr_window)?, prg_ram_banks: Banks::new(0x6000, 0x7FFF, cart.prg_ram.len(), Self::PRG_WINDOW)?, prg_rom_banks: Banks::new(0x8000, 0xFFFF, cart.prg_rom.len(), Self::PRG_WINDOW)?, }; let last_bank = txrom.prg_rom_banks.last(); txrom.prg_rom_banks.set(2, last_bank - 1); txrom.prg_rom_banks.set(3, last_bank); - Ok(txrom.into()) + Ok(txrom) + } + + pub fn load(cart: &mut Cart) -> Result { + Ok(Self::new(cart, Self::CHR_WINDOW)?.into()) + } + + pub const fn bank_register(&self, index: usize) -> u8 { + self.regs.bank_values[index] } pub fn set_revision(&mut self, rev: Revision) { self.revision = rev; } - pub fn update_banks(&mut self) { + pub fn update_prg_banks(&mut self) { let prg_last = self.prg_rom_banks.last(); let prg_lo = self.regs.bank_values[6] as usize; let prg_hi = self.regs.bank_values[7] as usize; @@ -121,7 +133,13 @@ impl Txrom { self.prg_rom_banks.set(2, prg_last - 1); } self.prg_rom_banks.set(3, prg_last); + } + pub fn set_chr_banks(&mut self, f: impl Fn(&mut Banks, &mut [u8])) { + f(&mut self.chr_banks, &mut self.regs.bank_values) + } + + pub fn update_chr_banks(&mut self) { // 1: two 2K banks at $1000-$1FFF, four 1 KB banks at $0000-$0FFF // 0: two 2K banks at $0000-$0FFF, four 1 KB banks at $1000-$1FFF let chr = self.regs.bank_values; @@ -142,6 +160,14 @@ impl Txrom { } } + pub fn update_banks(&mut self) { + self.update_prg_banks(); + // Allow mappers to override chr banks with `set_chr_banks` + if !matches!(self.mapper_num, 76 | 88) { + self.update_chr_banks(); + }; + } + pub fn clock_irq(&mut self, addr: u16) { if addr < 0x2000 { let next_clock = (addr >> 12) & 1; @@ -249,6 +275,7 @@ impl MemMap for Txrom { // 1: two 2K banks at $1000-$1FFF, // four 1K banks at $0000-$0FFF) // + // Match only $8000/1, $A000/1, $C000/1, and $E000/1 match addr & 0xE001 { 0x8000 => { @@ -262,11 +289,11 @@ impl MemMap for Txrom { } 0xA000 => { if self.mirroring != Mirroring::FourScreen { - self.mirroring = match val & 0x01 { + self.set_mirroring(match val & 0x01 { 0 => Mirroring::Vertical, 1 => Mirroring::Horizontal, _ => unreachable!("impossible mirroring"), - }; + }); self.update_banks(); } } diff --git a/tetanes-core/src/mapper/m005_exrom.rs b/tetanes-core/src/mapper/m005_exrom.rs index d2ec41b0..0c5dcc5a 100644 --- a/tetanes-core/src/mapper/m005_exrom.rs +++ b/tetanes-core/src/mapper/m005_exrom.rs @@ -554,16 +554,6 @@ impl Mapped for Exrom { } } -impl Regional for Exrom { - fn region(&self) -> NesRegion { - self.dmc.region() - } - - fn set_region(&mut self, region: NesRegion) { - self.dmc.set_region(region); - } -} - impl MemMap for Exrom { // CHR mode 0 // PPU $0000..=$1FFF 8K switchable CHR bank @@ -1008,14 +998,10 @@ impl MemMap for Exrom { } } -impl Sample for Exrom { - #[must_use] - fn output(&self) -> f32 { - let pulse1 = self.pulse1.output(); - let pulse2 = self.pulse2.output(); - let pulse = PULSE_TABLE[(pulse1 + pulse2) as usize]; - let dmc = TND_TABLE[self.dmc.output() as usize]; - -(pulse + dmc) +impl Reset for Exrom { + fn reset(&mut self, _kind: ResetKind) { + self.regs.prg_mode = PrgMode::Bank8k; + self.regs.chr_mode = ChrMode::Bank1k; } } @@ -1052,15 +1038,29 @@ impl Clock for Exrom { } } -impl Reset for Exrom { - fn reset(&mut self, _kind: ResetKind) { - self.regs.prg_mode = PrgMode::Bank8k; - self.regs.chr_mode = ChrMode::Bank1k; +impl Regional for Exrom { + fn region(&self) -> NesRegion { + self.dmc.region() + } + + fn set_region(&mut self, region: NesRegion) { + self.dmc.set_region(region); } } impl Sram for Exrom {} +impl Sample for Exrom { + #[must_use] + fn output(&self) -> f32 { + let pulse1 = self.pulse1.output(); + let pulse2 = self.pulse2.output(); + let pulse = PULSE_TABLE[(pulse1 + pulse2) as usize]; + let dmc = TND_TABLE[self.dmc.output() as usize]; + -(pulse + dmc) + } +} + impl std::fmt::Debug for Exrom { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Exrom") diff --git a/tetanes-core/src/mapper/m007_axrom.rs b/tetanes-core/src/mapper/m007_axrom.rs index 3e836990..f0988699 100644 --- a/tetanes-core/src/mapper/m007_axrom.rs +++ b/tetanes-core/src/mapper/m007_axrom.rs @@ -74,7 +74,7 @@ impl MemMap for Axrom { } } +impl Reset for Axrom {} impl Clock for Axrom {} impl Regional for Axrom {} -impl Reset for Axrom {} impl Sram for Axrom {} diff --git a/tetanes-core/src/mapper/m011_color_dreams.rs b/tetanes-core/src/mapper/m011_color_dreams.rs index 45b4f201..15024c2c 100644 --- a/tetanes-core/src/mapper/m011_color_dreams.rs +++ b/tetanes-core/src/mapper/m011_color_dreams.rs @@ -68,7 +68,7 @@ impl MemMap for ColorDreams { } } +impl Reset for ColorDreams {} impl Clock for ColorDreams {} impl Regional for ColorDreams {} -impl Reset for ColorDreams {} impl Sram for ColorDreams {} diff --git a/tetanes-core/src/mapper/m024_m026_vrc6.rs b/tetanes-core/src/mapper/m024_m026_vrc6.rs index 4bd735a3..b020b9d6 100644 --- a/tetanes-core/src/mapper/m024_m026_vrc6.rs +++ b/tetanes-core/src/mapper/m024_m026_vrc6.rs @@ -323,10 +323,10 @@ impl MemMap for Vrc6 { } } -impl Sample for Vrc6 { - #[must_use] - fn output(&self) -> f32 { - self.audio.output() +impl Reset for Vrc6 { + fn reset(&mut self, kind: ResetKind) { + self.irq.reset(kind); + self.audio.reset(kind); } } @@ -338,16 +338,16 @@ impl Clock for Vrc6 { } } -impl Reset for Vrc6 { - fn reset(&mut self, kind: ResetKind) { - self.irq.reset(kind); - self.audio.reset(kind); - } -} - impl Regional for Vrc6 {} impl Sram for Vrc6 {} +impl Sample for Vrc6 { + #[must_use] + fn output(&self) -> f32 { + self.audio.output() + } +} + #[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[must_use] pub struct Vrc6Audio { diff --git a/tetanes-core/src/mapper/m034_bnrom.rs b/tetanes-core/src/mapper/m034_bnrom.rs index 18956431..cdecd386 100644 --- a/tetanes-core/src/mapper/m034_bnrom.rs +++ b/tetanes-core/src/mapper/m034_bnrom.rs @@ -65,7 +65,7 @@ impl MemMap for Bnrom { } } +impl Reset for Bnrom {} impl Clock for Bnrom {} impl Regional for Bnrom {} -impl Reset for Bnrom {} impl Sram for Bnrom {} diff --git a/tetanes-core/src/mapper/m034_nina001.rs b/tetanes-core/src/mapper/m034_nina001.rs index c4a18d22..cb091a7b 100644 --- a/tetanes-core/src/mapper/m034_nina001.rs +++ b/tetanes-core/src/mapper/m034_nina001.rs @@ -74,7 +74,7 @@ impl MemMap for Nina001 { } } +impl Reset for Nina001 {} impl Clock for Nina001 {} impl Regional for Nina001 {} -impl Reset for Nina001 {} impl Sram for Nina001 {} diff --git a/tetanes-core/src/mapper/m066_gxrom.rs b/tetanes-core/src/mapper/m066_gxrom.rs index d7900c63..8a0349a2 100644 --- a/tetanes-core/src/mapper/m066_gxrom.rs +++ b/tetanes-core/src/mapper/m066_gxrom.rs @@ -68,7 +68,7 @@ impl Mapped for Gxrom { } } +impl Reset for Gxrom {} impl Clock for Gxrom {} impl Regional for Gxrom {} -impl Reset for Gxrom {} impl Sram for Gxrom {} diff --git a/tetanes-core/src/mapper/m071_bf909x.rs b/tetanes-core/src/mapper/m071_bf909x.rs index 96c3c362..b8f97648 100644 --- a/tetanes-core/src/mapper/m071_bf909x.rs +++ b/tetanes-core/src/mapper/m071_bf909x.rs @@ -102,7 +102,7 @@ impl MemMap for Bf909x { } } +impl Reset for Bf909x {} impl Clock for Bf909x {} impl Regional for Bf909x {} -impl Reset for Bf909x {} impl Sram for Bf909x {} diff --git a/tetanes-core/src/mapper/m076_dxrom.rs b/tetanes-core/src/mapper/m076_dxrom.rs new file mode 100644 index 00000000..2ea2bc0d --- /dev/null +++ b/tetanes-core/src/mapper/m076_dxrom.rs @@ -0,0 +1,114 @@ +//! `DxROM`/`NAMCOT-3446` (Mapper 076) +//! +//! +//! + +use crate::{ + cart::Cart, + common::{Clock, NesRegion, Regional, Reset, ResetKind, Sram}, + fs, + mapper::{self, Dxrom206, Mapped, MappedRead, MappedWrite, Mapper, MemMap}, + ppu::Mirroring, +}; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[must_use] +pub struct Dxrom { + pub inner: Dxrom206, +} + +impl Dxrom { + const CHR_WINDOW: usize = 2048; + + pub fn load(cart: &mut Cart) -> Result { + let dxrom = Self { + inner: Dxrom206::new(cart, Self::CHR_WINDOW)?, + }; + Ok(dxrom.into()) + } + + pub fn update_chr_banks(&mut self) { + self.inner.set_chr_banks(|banks, regs| { + banks.set(0, regs[2] as usize); + banks.set(1, regs[3] as usize); + banks.set(2, regs[4] as usize); + banks.set(3, regs[5] as usize); + }); + } +} + +impl Mapped for Dxrom { + fn mirroring(&self) -> Mirroring { + self.inner.mirroring() + } + + fn set_mirroring(&mut self, mirroring: Mirroring) { + self.inner.set_mirroring(mirroring); + } + + fn ppu_bus_read(&mut self, addr: u16) { + self.inner.ppu_bus_read(addr) + } + + fn ppu_bus_write(&mut self, addr: u16, val: u8) { + self.inner.ppu_bus_write(addr, val) + } + + fn cpu_bus_read(&mut self, addr: u16) { + self.inner.cpu_bus_read(addr) + } + + fn cpu_bus_write(&mut self, addr: u16, val: u8) { + self.inner.cpu_bus_write(addr, val) + } +} + +impl MemMap for Dxrom { + fn map_read(&mut self, addr: u16) -> MappedRead { + self.inner.map_read(addr) + } + + fn map_peek(&self, addr: u16) -> MappedRead { + self.inner.map_peek(addr) + } + + fn map_write(&mut self, addr: u16, val: u8) -> MappedWrite { + let write = self.inner.map_write(addr, val); + if matches!(addr, 0x8000..=0x8001) { + self.update_chr_banks(); + } + write + } +} + +impl Reset for Dxrom { + fn reset(&mut self, kind: ResetKind) { + self.inner.reset(kind); + self.update_chr_banks(); + } +} +impl Clock for Dxrom { + fn clock(&mut self) -> usize { + self.inner.clock() + } +} +impl Regional for Dxrom { + fn region(&self) -> NesRegion { + self.inner.region() + } + + fn set_region(&mut self, region: NesRegion) { + self.inner.set_region(region) + } +} +impl Sram for Dxrom { + fn save(&self, path: impl AsRef) -> fs::Result<()> { + self.inner.save(path) + } + + fn load(&mut self, path: impl AsRef) -> fs::Result<()> { + self.inner.load(path) + } +} diff --git a/tetanes-core/src/mapper/m088_dxrom.rs b/tetanes-core/src/mapper/m088_dxrom.rs new file mode 100644 index 00000000..d0f6b54a --- /dev/null +++ b/tetanes-core/src/mapper/m088_dxrom.rs @@ -0,0 +1,120 @@ +//! `DxROM`/`Namco 108` (Mapper 088) +//! +//! +//! + +use crate::{ + cart::Cart, + common::{Clock, NesRegion, Regional, Reset, ResetKind, Sram}, + fs, + mapper::{self, Dxrom206, Mapped, MappedRead, MappedWrite, Mapper, MemMap}, + ppu::Mirroring, +}; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[must_use] +pub struct Dxrom { + pub inner: Dxrom206, +} + +impl Dxrom { + pub const CHR_WINDOW: usize = Dxrom206::CHR_WINDOW; + + pub fn new(cart: &mut Cart, chr_window: usize) -> Result { + Ok(Self { + inner: Dxrom206::new(cart, chr_window)?, + }) + } + + pub fn load(cart: &mut Cart) -> Result { + Ok(Self::new(cart, Self::CHR_WINDOW)?.into()) + } + + pub fn update_chr_banks(&mut self) { + self.inner.set_chr_banks(|_, regs| { + regs[0] &= 0x3F; + regs[1] &= 0x3F; + regs[2] |= 0x40; + regs[3] |= 0x40; + regs[4] |= 0x40; + regs[5] |= 0x40; + }); + self.inner.update_chr_banks(); + } +} + +impl Mapped for Dxrom { + fn mirroring(&self) -> Mirroring { + self.inner.mirroring() + } + + fn set_mirroring(&mut self, mirroring: Mirroring) { + self.inner.set_mirroring(mirroring); + } + + fn ppu_bus_read(&mut self, addr: u16) { + self.inner.ppu_bus_read(addr) + } + + fn ppu_bus_write(&mut self, addr: u16, val: u8) { + self.inner.ppu_bus_write(addr, val) + } + + fn cpu_bus_read(&mut self, addr: u16) { + self.inner.cpu_bus_read(addr) + } + + fn cpu_bus_write(&mut self, addr: u16, val: u8) { + self.inner.cpu_bus_write(addr, val) + } +} + +impl MemMap for Dxrom { + fn map_read(&mut self, addr: u16) -> MappedRead { + self.inner.map_read(addr) + } + + fn map_peek(&self, addr: u16) -> MappedRead { + self.inner.map_peek(addr) + } + + fn map_write(&mut self, addr: u16, val: u8) -> MappedWrite { + let write = self.inner.map_write(addr, val); + if matches!(addr, 0x8000..=0x8001) { + self.update_chr_banks(); + } + write + } +} + +impl Reset for Dxrom { + fn reset(&mut self, kind: ResetKind) { + self.inner.reset(kind); + self.update_chr_banks(); + } +} +impl Clock for Dxrom { + fn clock(&mut self) -> usize { + self.inner.clock() + } +} +impl Regional for Dxrom { + fn region(&self) -> NesRegion { + self.inner.region() + } + + fn set_region(&mut self, region: NesRegion) { + self.inner.set_region(region) + } +} +impl Sram for Dxrom { + fn save(&self, path: impl AsRef) -> fs::Result<()> { + self.inner.save(path) + } + + fn load(&mut self, path: impl AsRef) -> fs::Result<()> { + self.inner.load(path) + } +} diff --git a/tetanes-core/src/mapper/m095_dxrom.rs b/tetanes-core/src/mapper/m095_dxrom.rs new file mode 100644 index 00000000..4148d03a --- /dev/null +++ b/tetanes-core/src/mapper/m095_dxrom.rs @@ -0,0 +1,105 @@ +//! `DxROM`/`NAMCOT-3425` (Mapper 095) +//! +//! +//! + +use crate::{ + cart::Cart, + common::{Clock, NesRegion, Regional, Reset, ResetKind, Sram}, + fs, + mapper::{self, Dxrom206, Mapped, MappedRead, MappedWrite, Mapper, MemMap}, + ppu::Mirroring, +}; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[must_use] +pub struct Dxrom { + pub inner: Dxrom206, +} + +impl Dxrom { + pub fn load(cart: &mut Cart) -> Result { + Ok(Dxrom206::new(cart, Dxrom206::CHR_WINDOW)?.into()) + } +} + +impl Mapped for Dxrom { + fn mirroring(&self) -> Mirroring { + self.inner.mirroring() + } + + fn set_mirroring(&mut self, mirroring: Mirroring) { + self.inner.set_mirroring(mirroring); + } + + fn ppu_bus_read(&mut self, addr: u16) { + self.inner.ppu_bus_read(addr) + } + + fn ppu_bus_write(&mut self, addr: u16, val: u8) { + self.inner.ppu_bus_write(addr, val) + } + + fn cpu_bus_read(&mut self, addr: u16) { + self.inner.cpu_bus_read(addr) + } + + fn cpu_bus_write(&mut self, addr: u16, val: u8) { + self.inner.cpu_bus_write(addr, val) + } +} + +impl MemMap for Dxrom { + fn map_read(&mut self, addr: u16) -> MappedRead { + self.inner.map_read(addr) + } + + fn map_peek(&self, addr: u16) -> MappedRead { + self.inner.map_peek(addr) + } + + fn map_write(&mut self, addr: u16, val: u8) -> MappedWrite { + let write = self.inner.map_write(addr, val); + if addr & 0x01 == 0x01 { + let nametable1 = (self.inner.bank_register(0) >> 5) & 0x01; + let nametable2 = (self.inner.bank_register(1) >> 5) & 0x01; + self.set_mirroring(match (nametable1, nametable2) { + (0, 0) => Mirroring::SingleScreenA, + (1, 1) => Mirroring::SingleScreenB, + _ => Mirroring::Horizontal, + }); + } + write + } +} + +impl Reset for Dxrom { + fn reset(&mut self, kind: ResetKind) { + self.inner.reset(kind); + } +} +impl Clock for Dxrom { + fn clock(&mut self) -> usize { + self.inner.clock() + } +} +impl Regional for Dxrom { + fn region(&self) -> NesRegion { + self.inner.region() + } + + fn set_region(&mut self, region: NesRegion) { + self.inner.set_region(region) + } +} +impl Sram for Dxrom { + fn save(&self, path: impl AsRef) -> fs::Result<()> { + self.inner.save(path) + } + + fn load(&mut self, path: impl AsRef) -> fs::Result<()> { + self.inner.load(path) + } +} diff --git a/tetanes-core/src/mapper/m154_dxrom.rs b/tetanes-core/src/mapper/m154_dxrom.rs new file mode 100644 index 00000000..e865e2a1 --- /dev/null +++ b/tetanes-core/src/mapper/m154_dxrom.rs @@ -0,0 +1,100 @@ +//! `DxROM`/`NAMCOT-3453` (Mapper 154) +//! +//! +//! + +use crate::{ + cart::Cart, + common::{Clock, NesRegion, Regional, Reset, ResetKind, Sram}, + fs, + mapper::{self, Dxrom88, Mapped, MappedRead, MappedWrite, Mapper, MemMap}, + ppu::Mirroring, +}; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[must_use] +pub struct Dxrom { + pub inner: Dxrom88, +} + +impl Dxrom { + pub fn load(cart: &mut Cart) -> Result { + Ok(Dxrom88::new(cart, Dxrom88::CHR_WINDOW)?.into()) + } +} + +impl Mapped for Dxrom { + fn mirroring(&self) -> Mirroring { + self.inner.mirroring() + } + + fn set_mirroring(&mut self, mirroring: Mirroring) { + self.inner.set_mirroring(mirroring); + } + + fn ppu_bus_read(&mut self, addr: u16) { + self.inner.ppu_bus_read(addr) + } + + fn ppu_bus_write(&mut self, addr: u16, val: u8) { + self.inner.ppu_bus_write(addr, val) + } + + fn cpu_bus_read(&mut self, addr: u16) { + self.inner.cpu_bus_read(addr) + } + + fn cpu_bus_write(&mut self, addr: u16, val: u8) { + self.inner.cpu_bus_write(addr, val) + } +} + +impl MemMap for Dxrom { + fn map_read(&mut self, addr: u16) -> MappedRead { + self.inner.map_read(addr) + } + + fn map_peek(&self, addr: u16) -> MappedRead { + self.inner.map_peek(addr) + } + + fn map_write(&mut self, addr: u16, val: u8) -> MappedWrite { + self.set_mirroring(if val & 0x40 == 0x40 { + Mirroring::SingleScreenB + } else { + Mirroring::SingleScreenA + }); + self.inner.map_write(addr, val) + } +} + +impl Reset for Dxrom { + fn reset(&mut self, kind: ResetKind) { + self.inner.reset(kind); + } +} +impl Clock for Dxrom { + fn clock(&mut self) -> usize { + self.inner.clock() + } +} +impl Regional for Dxrom { + fn region(&self) -> NesRegion { + self.inner.region() + } + + fn set_region(&mut self, region: NesRegion) { + self.inner.set_region(region) + } +} +impl Sram for Dxrom { + fn save(&self, path: impl AsRef) -> fs::Result<()> { + self.inner.save(path) + } + + fn load(&mut self, path: impl AsRef) -> fs::Result<()> { + self.inner.load(path) + } +} diff --git a/tetanes-core/src/mapper/m206_dxrom.rs b/tetanes-core/src/mapper/m206_dxrom.rs new file mode 100644 index 00000000..2441374f --- /dev/null +++ b/tetanes-core/src/mapper/m206_dxrom.rs @@ -0,0 +1,124 @@ +//! `DxROM`/`Namco 108` (Mapper 206) +//! +//! +//! + +use crate::{ + cart::Cart, + common::{Clock, NesRegion, Regional, Reset, ResetKind, Sram}, + fs, + mapper::{self, Mapped, MappedRead, MappedWrite, Mapper, MemMap, Txrom}, + mem::Banks, + ppu::Mirroring, +}; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[must_use] +pub struct Dxrom { + pub inner: Txrom, +} + +impl Dxrom { + pub const CHR_WINDOW: usize = Txrom::CHR_WINDOW; + + pub fn new(cart: &mut Cart, chr_window: usize) -> Result { + Ok(Self { + inner: Txrom::new(cart, chr_window)?, + }) + } + + pub fn load(cart: &mut Cart) -> Result { + Ok(Self::new(cart, Self::CHR_WINDOW)?.into()) + } + + pub const fn bank_register(&self, index: usize) -> u8 { + self.inner.bank_register(index) + } + + pub fn set_chr_banks(&mut self, f: impl Fn(&mut Banks, &mut [u8])) { + self.inner.set_chr_banks(f); + } + + pub fn update_chr_banks(&mut self) { + self.inner.update_chr_banks(); + } +} + +impl Mapped for Dxrom { + fn mirroring(&self) -> Mirroring { + self.inner.mirroring() + } + + fn set_mirroring(&mut self, _mirroring: Mirroring) { + // Mirroring is hardwired + } + + fn ppu_bus_read(&mut self, addr: u16) { + self.inner.ppu_bus_read(addr) + } + + fn ppu_bus_write(&mut self, addr: u16, val: u8) { + self.inner.ppu_bus_write(addr, val) + } + + fn cpu_bus_read(&mut self, addr: u16) { + self.inner.cpu_bus_read(addr) + } + + fn cpu_bus_write(&mut self, addr: u16, val: u8) { + self.inner.cpu_bus_write(addr, val) + } +} + +impl MemMap for Dxrom { + fn map_read(&mut self, addr: u16) -> MappedRead { + self.inner.map_read(addr) + } + + fn map_peek(&self, addr: u16) -> MappedRead { + self.inner.map_peek(addr) + } + + fn map_write(&mut self, mut addr: u16, mut val: u8) -> MappedWrite { + // Redirect all 0x8000..=0xFFFF writes to 0x8000..=0x8001 + if matches!(addr, 0x8000..=0xFFFF) { + addr &= 0x8001; + if addr == 0x8000 { + // Disable CHR mode 1 and Prg mode 1 + val &= 0x3F; + } + } + self.inner.map_write(addr, val) + } +} + +impl Reset for Dxrom { + fn reset(&mut self, kind: ResetKind) { + self.inner.reset(kind); + } +} +impl Clock for Dxrom { + fn clock(&mut self) -> usize { + self.inner.clock() + } +} +impl Regional for Dxrom { + fn region(&self) -> NesRegion { + self.inner.region() + } + + fn set_region(&mut self, region: NesRegion) { + self.inner.set_region(region) + } +} +impl Sram for Dxrom { + fn save(&self, path: impl AsRef) -> fs::Result<()> { + self.inner.save(path) + } + + fn load(&mut self, path: impl AsRef) -> fs::Result<()> { + self.inner.load(path) + } +} diff --git a/tetanes-core/src/mapper/mapper.template.rs b/tetanes-core/src/mapper/mapper.template.rs new file mode 100644 index 00000000..ec2b8573 --- /dev/null +++ b/tetanes-core/src/mapper/mapper.template.rs @@ -0,0 +1,64 @@ +//! `Mapper Name` (Mapper NN) +//! +//! + +use crate::{ + cart::Cart, + common::{Clock, Regional, Reset, Sram}, + mapper::{self, Mapped, Mapper, MemMap}, + mem::Banks, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[must_use] +pub struct MapperName { + pub chr_banks: Banks, + pub prg_ram_banks: Banks, + pub prg_rom_banks: Banks, +} + +impl MapperName { + const PRG_WINDOW: usize = /* PRG ROM/RAM bank window */ 0; + const CHR_WINDOW: usize = /* CHR ROM/RAM bank window */ 0; + + pub fn load(cart: &mut Cart) -> Result { + // Add CHR, PRG, or EX RAM based on mapper + let mut mapper_name = Self { + // Registers, Mirroring, etc + chr_banks: Banks::new(0x0000, 0x1FFF, cart.chr_rom.len(), Self::CHR_WINDOW)?, + /// Optional PRG RAM + prg_ram_banks: Banks::new(0x6000, 0x7FFF, cart.prg_ram.len(), Self::PRG_WINDOW)?, + prg_rom_banks: Banks::new(0x8000, 0xFFFF, cart.prg_rom.len(), Self::PRG_WINDOW)?, + }; + // Set default ROM banks + Ok(mapper_name.into()) + } + + // Methods to modify banks, clock mapper, etc +} + +impl Mapped for MapperName { + // Implement Mapped methods +} + +impl MemMap for MapperName { + /// Implement MemMap methods +} + +impl Reset for MapperName { + /// Optional, Reset methods +} +impl Clock for MapperName { + /// Optional, Clock methods +} +impl Regional for MapperName { + /// Optional, Regional methods +} +impl Sram for MapperName { + /// Optional, Sram methods +} +impl Sample for MapperName { + /// Optional, Sample methods +} +