diff --git a/src/memory/bus.rs b/src/memory/bus.rs index f7d42de..43b7344 100644 --- a/src/memory/bus.rs +++ b/src/memory/bus.rs @@ -345,6 +345,8 @@ 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))), + 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/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..4b25fcc --- /dev/null +++ b/src/memory/mappers/ines_mapper009.rs @@ -0,0 +1,152 @@ +use std::{cell::Cell, 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, + + ///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 { + CpuRam::Persistent(GameSave::new(path)) + } else { + CpuRam::Volatile([0; 0x2000]) + }; + + Self { + prg_rom, + chr_rom, + + prg_ram, + + 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), + _ => {} + } + } +} + +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 & 0x1FFF; + data[index] + } else { + 0 + } + } + } + } + 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 } + } + } + + 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 & 0x1FFF; + data[index] = val; + } + } + } + } + + //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 { + 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, + }; + + 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 + } +} \ 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..573566e --- /dev/null +++ b/src/memory/mappers/ines_mapper010.rs @@ -0,0 +1,154 @@ +use std::{cell::Cell, 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, + + ///nesdev.org/wiki/MMC2#Registers + banks: [u8; 5], + + 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, + + prg_ram, + + mirroring, + + 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 { + 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) { + 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 { + 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, + }; + + 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 + } +} \ 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