From 0dd67d8d8341894a5755c79fe2ccee41efc48caf Mon Sep 17 00:00:00 2001 From: Polaro4 Date: Wed, 10 Jun 2026 14:17:14 -0300 Subject: [PATCH 1/3] basic logic for both mappers --- src/memory/mapper_base.rs | 7 +++ src/memory/mappers/ines_mapper009.rs | 90 ++++++++++++++++++++++++++++ src/memory/mappers/ines_mapper010.rs | 60 +++++++++++++++++++ src/memory/mappers/mod.rs | 4 ++ 4 files changed, 161 insertions(+) create mode 100644 src/memory/mappers/ines_mapper009.rs create mode 100644 src/memory/mappers/ines_mapper010.rs diff --git a/src/memory/mapper_base.rs b/src/memory/mapper_base.rs index 59bcbe6..209324d 100644 --- a/src/memory/mapper_base.rs +++ b/src/memory/mapper_base.rs @@ -1,3 +1,5 @@ +use crate::memory::game_save::GameSave; + #[derive(Clone, Copy, Debug)] pub enum Mirroring { Vertical, @@ -6,6 +8,11 @@ pub enum Mirroring { SingleScreenUpper } +pub enum CpuRam { + Volatile([u8; 0x2000]), + Persistent(GameSave), +} + /// A memory mapper that abstracts over different NES cartridge board configurations. /// /// NES cartridges use various mapper chips to extend the addressable memory beyond diff --git a/src/memory/mappers/ines_mapper009.rs b/src/memory/mappers/ines_mapper009.rs new file mode 100644 index 0000000..23cfaac --- /dev/null +++ b/src/memory/mappers/ines_mapper009.rs @@ -0,0 +1,90 @@ +use std::path::Path; + +use crate::memory::{game_save::GameSave, mapper_base::{CpuRam, Mapper, Mirroring}}; + +pub struct InesMapper009 { + prg_rom: Box<[u8]>, + chr_rom: Box<[u8]>, + + prg_ram: CpuRam, + + mirroring: Mirroring, + + banks: [u8; 5], +} impl InesMapper009 { + pub fn new>(prg_rom: Box<[u8]>, chr_rom: Box<[u8]>, mirroring: Mirroring, has_save: Option

) -> Self { + let prg_ram = if let Some(path) = has_save { + CpuRam::Persistent(GameSave::new(path)) + } else { + CpuRam::Volatile([0; 0x2000]) + }; + + Self { + prg_rom, + chr_rom, + + prg_ram, + + mirroring, + + banks: [0u8; 5], + } + } +} + +impl Mapper for InesMapper009 { + fn read(&self, addr: u16) -> u8 { + match addr { + 0x6000..=0x7FFF => { + match &self.prg_ram { + CpuRam::Persistent(save) => { + save.read(addr) + } + CpuRam::Volatile(data) => { + if data.is_empty() { + let index = (addr - 0x6000) as usize & 0x07FF; + data[index] + } else { + 0 + } + } + } + } + _ => { 0 } + } + } + + fn write(&mut self, addr: u16, val: u8) { + match addr { + 0x6000..=0x7FFF => { + match &mut self.prg_ram { + CpuRam::Persistent(save) => { + save.write(addr, val) + } + CpuRam::Volatile(data) => { + if !data.is_empty() { + let index = (addr - 0x6000) as usize & 0x07FF; + data[index] = val; + } + } + } + } + 0x8000..=0xFFFF => { + + } + _ => { } + } + } + + fn read_chr(&self, addr: u16) -> u8 { + todo!() + } + + fn write_chr(&mut self, addr: u16, val: u8) { + todo!() + } + + fn mirroring(&self) -> Mirroring { + todo!() + } +} \ No newline at end of file diff --git a/src/memory/mappers/ines_mapper010.rs b/src/memory/mappers/ines_mapper010.rs new file mode 100644 index 0000000..8765c4c --- /dev/null +++ b/src/memory/mappers/ines_mapper010.rs @@ -0,0 +1,60 @@ +use std::path::Path; + +use crate::memory::{ + game_save::GameSave, + mapper_base::{CpuRam, Mapper, Mirroring} +}; + +pub struct InesMapper010 { + prg_rom: Box<[u8]>, + chr_rom: Box<[u8]>, + + prg_ram: CpuRam, + + mirroring: Mirroring, + + bank_select: u8, +} +impl InesMapper010 { + pub fn new>(prg_rom: Box<[u8]>, chr_rom: Box<[u8]>, mirroring: Mirroring, has_save: Option

) -> Self { + + let prg_ram = if let Some(path) = has_save { + CpuRam::Persistent(GameSave::new(path)) + } else { + CpuRam::Volatile([0; 0x2000]) + }; + + Self { + prg_rom, + chr_rom, + + prg_ram, + + mirroring, + + bank_select: 0, + + } + } +} +impl Mapper for InesMapper010 { + fn read(&self, addr: u16) -> u8 { + todo!() + } + + fn write(&mut self, addr: u16, val: u8) { + todo!() + } + + fn read_chr(&self, addr: u16) -> u8 { + todo!() + } + + fn write_chr(&mut self, addr: u16, val: u8) { + todo!() + } + + fn mirroring(&self) -> Mirroring { + self.mirroring + } +} \ No newline at end of file diff --git a/src/memory/mappers/mod.rs b/src/memory/mappers/mod.rs index b2f2051..4c260e9 100644 --- a/src/memory/mappers/mod.rs +++ b/src/memory/mappers/mod.rs @@ -3,6 +3,8 @@ pub mod ines_mapper001; pub mod ines_mapper002; pub mod ines_mapper003; pub mod ines_mapper004; +pub mod ines_mapper009; +pub mod ines_mapper010; pub mod ines_mapper163; pub mod dummy_mapper; @@ -12,4 +14,6 @@ pub use self::ines_mapper001::InesMapper001; pub use self::ines_mapper002::InesMapper002; pub use self::ines_mapper003::InesMapper003; pub use self::ines_mapper004::InesMapper004; +pub use self::ines_mapper009::InesMapper009; +pub use self::ines_mapper010::InesMapper010; pub use self::ines_mapper163::InesMapper163; \ No newline at end of file From cf88b1dabab9feeb610e586e4092a2551a5c9d13 Mon Sep 17 00:00:00 2001 From: Polaro4 Date: Wed, 10 Jun 2026 17:19:06 -0300 Subject: [PATCH 2/3] Update ines_mapper009 --- src/memory/bus.rs | 1 + src/memory/mappers/ines_mapper009.rs | 84 ++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/src/memory/bus.rs b/src/memory/bus.rs index f7d42de..5b5f67e 100644 --- a/src/memory/bus.rs +++ b/src/memory/bus.rs @@ -345,6 +345,7 @@ pub fn load_rom_from_file(path: &Path) -> Result>, Box Ok(wrap_in_pointers(mappers::InesMapper002::new(prg_rom_data, mirroring_type, GameSave::new(path)))), 3 => Ok(wrap_in_pointers(mappers::InesMapper003::new(prg_rom_data, chr_rom_data, mirroring_type, mapper_save_path))), 4 => Ok(wrap_in_pointers(mappers::InesMapper004::new(prg_rom_data, chr_rom_data, mirroring_type, GameSave::new(path)))), + 9 => Ok(wrap_in_pointers(mappers::InesMapper009::new(prg_rom_data, chr_rom_data, mirroring_type, mapper_save_path))), 163 => Ok(wrap_in_pointers(mappers::InesMapper163::new(prg_rom_data, chr_rom_data, mirroring_type, GameSave::new(path)))), _ => Err(format!("Mapper {} is not supported yet", mapper_match).into()) diff --git a/src/memory/mappers/ines_mapper009.rs b/src/memory/mappers/ines_mapper009.rs index 23cfaac..4b25fcc 100644 --- a/src/memory/mappers/ines_mapper009.rs +++ b/src/memory/mappers/ines_mapper009.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::{cell::Cell, path::Path}; use crate::memory::{game_save::GameSave, mapper_base::{CpuRam, Mapper, Mirroring}}; @@ -10,7 +10,11 @@ pub struct InesMapper009 { mirroring: Mirroring, + ///nesdev.org/wiki/MMC2#Registers banks: [u8; 5], + + latch_0: Cell, + latch_1: Cell, } impl InesMapper009 { pub fn new>(prg_rom: Box<[u8]>, chr_rom: Box<[u8]>, mirroring: Mirroring, has_save: Option

) -> Self { let prg_ram = if let Some(path) = has_save { @@ -28,6 +32,18 @@ pub struct InesMapper009 { mirroring, banks: [0u8; 5], + + latch_0: Cell::new(0xFD), + latch_1: Cell::new(0xFD), + } + } + fn update_latch(&self, addr: u16) { + match addr { + 0x0FD8 => self.latch_0.set(0xFD), + 0x0FE8 => self.latch_0.set(0xFE), + 0x1FD8..=0x1FDF => self.latch_1.set(0xFD), + 0x1FE8..=0x1FEF => self.latch_1.set(0xFE), + _ => {} } } } @@ -41,8 +57,8 @@ impl Mapper for InesMapper009 { save.read(addr) } CpuRam::Volatile(data) => { - if data.is_empty() { - let index = (addr - 0x6000) as usize & 0x07FF; + if !data.is_empty() { + let index = (addr - 0x6000) as usize & 0x1FFF; data[index] } else { 0 @@ -50,6 +66,18 @@ impl Mapper for InesMapper009 { } } } + 0x8000..=0x9FFF => { + //switchable prg rom bank + let bank_offset = (self.banks[0] as usize) * 0x2000; + let rom_offset = (addr - 0x8000) as usize; + self.prg_rom[bank_offset + rom_offset] + } + 0xA000..=0xFFFF => { + //24kb fixed to the last 3 banks + let start_of_last_24kb = self.prg_rom.len() - (3 * 0x2000); + let rom_offset = (addr - 0xA000) as usize; + self.prg_rom[start_of_last_24kb + rom_offset] + } _ => { 0 } } } @@ -63,28 +91,62 @@ impl Mapper for InesMapper009 { } CpuRam::Volatile(data) => { if !data.is_empty() { - let index = (addr - 0x6000) as usize & 0x07FF; + let index = (addr - 0x6000) as usize & 0x1FFF; data[index] = val; } } } } - 0x8000..=0xFFFF => { - + + //PRG ROM bank select ($A000-$AFFF) + 0xA000..=0xAFFF => { + self.banks[0] = val & 0b0000_1111 + } + + 0xB000..=0xBFFF => self.banks[1] = val & 0x1F, // Select 4 KB CHR ROM bank for PPU $0000-$0FFF (Latch 0 = $FD) + 0xC000..=0xCFFF => self.banks[2] = val & 0x1F, // Select 4 KB CHR ROM bank for PPU $0000-$0FFF (Latch 0 = $FE) + 0xD000..=0xDFFF => self.banks[3] = val & 0x1F, // Select 4 KB CHR ROM bank for PPU $1000-$1FFF (Latch 1 = $FD) + 0xE000..=0xEFFF => self.banks[4] = val & 0x1F, // Select 4 KB CHR ROM bank for PPU $1000-$1FFF (Latch 1 = $FE) + + //Mirroring ($F000-$FFFF) + 0xF000..=0xFFFF => { + self.mirroring = if val & 0x01 == 0 { Mirroring::Vertical } else { Mirroring::Horizontal } } _ => { } } } fn read_chr(&self, addr: u16) -> u8 { - todo!() - } + self.update_latch(addr); + + + // PPU $0000-$0FFF: Two 4 KB switchable CHR ROM banks + // PPU $1000-$1FFF: Two 4 KB switchable CHR ROM banks + let bank_index = match addr { + 0x0000..=0x0FFF => { + if self.latch_0.get() == 0xFD { + self.banks[1] + } else { + self.banks[2] + } + } + 0x1000..=0x1FFF => { + if self.latch_1.get() == 0xFD { + self.banks[3] + } else { + self.banks[4] + } + } + _ => 0, + }; - fn write_chr(&mut self, addr: u16, val: u8) { - todo!() + let chr_offset = (bank_index as usize * 4096) + (addr as usize % 4096); + self.chr_rom[chr_offset] } + fn write_chr(&mut self, _addr: u16, _val: u8) { } + fn mirroring(&self) -> Mirroring { - todo!() + self.mirroring } } \ No newline at end of file From 4636355801538bc63161cdbd6aa5e2fe08ef7486 Mon Sep 17 00:00:00 2001 From: Polaro4 Date: Wed, 10 Jun 2026 17:19:36 -0300 Subject: [PATCH 3/3] Update MMC4 seems to be functional now --- src/memory/bus.rs | 1 + src/memory/mappers/ines_mapper010.rs | 130 +++++++++++++++++++++++---- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/src/memory/bus.rs b/src/memory/bus.rs index 5b5f67e..43b7344 100644 --- a/src/memory/bus.rs +++ b/src/memory/bus.rs @@ -346,6 +346,7 @@ pub fn load_rom_from_file(path: &Path) -> Result>, Box Ok(wrap_in_pointers(mappers::InesMapper003::new(prg_rom_data, chr_rom_data, mirroring_type, mapper_save_path))), 4 => Ok(wrap_in_pointers(mappers::InesMapper004::new(prg_rom_data, chr_rom_data, mirroring_type, GameSave::new(path)))), 9 => Ok(wrap_in_pointers(mappers::InesMapper009::new(prg_rom_data, chr_rom_data, mirroring_type, mapper_save_path))), + 10 => Ok(wrap_in_pointers(mappers::InesMapper010::new(prg_rom_data, chr_rom_data, mirroring_type, mapper_save_path))), 163 => Ok(wrap_in_pointers(mappers::InesMapper163::new(prg_rom_data, chr_rom_data, mirroring_type, GameSave::new(path)))), _ => Err(format!("Mapper {} is not supported yet", mapper_match).into()) diff --git a/src/memory/mappers/ines_mapper010.rs b/src/memory/mappers/ines_mapper010.rs index 8765c4c..573566e 100644 --- a/src/memory/mappers/ines_mapper010.rs +++ b/src/memory/mappers/ines_mapper010.rs @@ -1,9 +1,6 @@ -use std::path::Path; +use std::{cell::Cell, path::Path}; -use crate::memory::{ - game_save::GameSave, - mapper_base::{CpuRam, Mapper, Mirroring} -}; +use crate::memory::{game_save::GameSave, mapper_base::{CpuRam, Mapper, Mirroring}}; pub struct InesMapper010 { prg_rom: Box<[u8]>, @@ -13,17 +10,19 @@ pub struct InesMapper010 { mirroring: Mirroring, - bank_select: u8, -} -impl InesMapper010 { - pub fn new>(prg_rom: Box<[u8]>, chr_rom: Box<[u8]>, mirroring: Mirroring, has_save: Option

) -> Self { + ///nesdev.org/wiki/MMC2#Registers + banks: [u8; 5], - let prg_ram = if let Some(path) = has_save { + latch_0: Cell, + latch_1: Cell, +} impl InesMapper010 { + pub fn new>(prg_rom: Box<[u8]>, chr_rom: Box<[u8]>, mirroring: Mirroring, has_save: Option

) -> Self { + let prg_ram = if let Some(path) = has_save { CpuRam::Persistent(GameSave::new(path)) } else { CpuRam::Volatile([0; 0x2000]) }; - + Self { prg_rom, chr_rom, @@ -32,28 +31,123 @@ impl InesMapper010 { mirroring, - bank_select: 0, + banks: [0u8; 5], + latch_0: Cell::new(0xFD), + latch_1: Cell::new(0xFD), + } + } + fn _update_latch(&self, addr: u16) { + match addr { + 0x0FDC => self.latch_0.set(0xFD), + 0x0FEC => self.latch_0.set(0xFE), + 0x1FDC..=0x1FDF => self.latch_1.set(0xFD), + 0x1FEC..=0x1FEF => self.latch_1.set(0xFE), + _ => {} } } } + impl Mapper for InesMapper010 { fn read(&self, addr: u16) -> u8 { - todo!() + match addr { + 0x6000..=0x7FFF => { + match &self.prg_ram { + CpuRam::Persistent(save) => { + save.read(addr) + } + CpuRam::Volatile(data) => { + if !data.is_empty() { + let index = (addr - 0x6000) as usize & 0x1FFF; + data[index] + } else { + 0 + } + } + } + } + 0x8000..=0xBFFF => { + //switchable 16kb prg rom bank + let bank_offset = (self.banks[0] as usize) * 0x4000; + let rom_offset = (addr - 0x8000) as usize; + self.prg_rom[bank_offset + rom_offset] + } + 0xC000..=0xFFFF => { + //16kb fixed to the last 3 banks + let start_of_last_16kb = self.prg_rom.len() - 0x4000; + let rom_offset = (addr - 0xC000) as usize; + self.prg_rom[start_of_last_16kb + rom_offset] + } + _ => { 0 } + } } fn write(&mut self, addr: u16, val: u8) { - todo!() + match addr { + 0x6000..=0x7FFF => { + match &mut self.prg_ram { + CpuRam::Persistent(save) => { + save.write(addr, val) + } + CpuRam::Volatile(data) => { + if !data.is_empty() { + let index = (addr - 0x6000) as usize & 0x1FFF; + data[index] = val; + } + } + } + } + + //PRG ROM bank select ($A000-$AFFF) + 0xA000..=0xAFFF => { + self.banks[0] = val & 0b0001_1111 + } + + // 3F mask instead of the mmc2's 1F mask, which makes the maximum possible bank count 64 instead of 32 + 0xB000..=0xBFFF => self.banks[1] = val & 0x3F, // Select 4 KB CHR ROM bank for PPU $0000-$0FFF (Latch 0 = $FD) + 0xC000..=0xCFFF => self.banks[2] = val & 0x3F, // Select 4 KB CHR ROM bank for PPU $0000-$0FFF (Latch 0 = $FE) + 0xD000..=0xDFFF => self.banks[3] = val & 0x3F, // Select 4 KB CHR ROM bank for PPU $1000-$1FFF (Latch 1 = $FD) + 0xE000..=0xEFFF => self.banks[4] = val & 0x3F, // Select 4 KB CHR ROM bank for PPU $1000-$1FFF (Latch 1 = $FE) + + //Mirroring ($F000-$FFFF) + 0xF000..=0xFFFF => { + //opposite of the MMC2 + self.mirroring = if val & 0x01 == 0 { Mirroring::Horizontal } else { Mirroring::Vertical } + } + _ => { } + } } fn read_chr(&self, addr: u16) -> u8 { - todo!() - } + self._update_latch(addr); - fn write_chr(&mut self, addr: u16, val: u8) { - todo!() + + // PPU $0000-$0FFF: Two 4 KB switchable CHR ROM banks + // PPU $1000-$1FFF: Two 4 KB switchable CHR ROM banks + let bank_index = match addr { + 0x0000..=0x0FFF => { + if self.latch_0.get() == 0xFD { + self.banks[1] + } else { + self.banks[2] + } + } + 0x1000..=0x1FFF => { + if self.latch_1.get() == 0xFD { + self.banks[3] + } else { + self.banks[4] + } + } + _ => 0, + }; + + let chr_offset = (bank_index as usize * 4096) + (addr as usize % 4096); + self.chr_rom[chr_offset] } + fn write_chr(&mut self, _addr: u16, _val: u8) { } + fn mirroring(&self) -> Mirroring { self.mirroring }