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
+}
+