Code for the MMC2 and MMC4 boards (iNES mappers 9 + 10).
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 9enum ChrBankLatch { 10 FD, 11 FE, 12} 13 14#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 15enum Variant { 16 Mmc2, 17 Mmc4, 18} 19 20#[derive(Debug, Clone, Encode, Decode)] 21pub(crate) struct Mmc2 { 22 variant: Variant, 23 prg_bank: u8, 24 chr_0_fd_bank: u8, 25 chr_0_fe_bank: u8, 26 chr_0_latch: ChrBankLatch, 27 chr_1_fd_bank: u8, 28 chr_1_fe_bank: u8, 29 chr_1_latch: ChrBankLatch, 30 nametable_mirroring: NametableMirroring, 31} 32 33impl Mmc2 { 34 fn new(variant: Variant) -> Self { 35 Self { 36 variant, 37 prg_bank: 0, 38 chr_0_fd_bank: 0, 39 chr_0_fe_bank: 0, 40 chr_0_latch: ChrBankLatch::FD, 41 chr_1_fd_bank: 0, 42 chr_1_fe_bank: 0, 43 chr_1_latch: ChrBankLatch::FD, 44 nametable_mirroring: NametableMirroring::Vertical, 45 } 46 } 47 48 pub(crate) fn new_mmc2() -> Self { 49 Self::new(Variant::Mmc2) 50 } 51 52 pub(crate) fn new_mmc4() -> Self { 53 Self::new(Variant::Mmc4) 54 } 55} 56 57impl MapperImpl<Mmc2> { 58 fn map_cpu_address(&self, address: u16) -> CpuMapResult { 59 match (self.data.variant, address) { 60 (_, 0x0000..=0x401F) => panic!("invalid CPU map address: {address:04X}"), 61 (_, 0x4020..=0x5FFF) => CpuMapResult::None, 62 (_, 0x6000..=0x7FFF) => { 63 if !self.cartridge.prg_ram.is_empty() { 64 CpuMapResult::PrgRAM(u32::from(address & 0x1FFF)) 65 } else { 66 CpuMapResult::None 67 } 68 } 69 (Variant::Mmc2, 0x8000..=0x9FFF) => { 70 let prg_rom_addr = 71 BankSizeKb::Eight.to_absolute_address(self.data.prg_bank, address); 72 CpuMapResult::PrgROM(prg_rom_addr) 73 } 74 (Variant::Mmc2, 0xA000..=0xBFFF) => { 75 // Fixed at third-to-last PRG ROM bank 76 let prg_rom_addr = BankSizeKb::Eight.to_absolute_address_from_end( 77 3_u32, 78 self.cartridge.prg_rom.len() as u32, 79 address, 80 ); 81 CpuMapResult::PrgROM(prg_rom_addr) 82 } 83 (Variant::Mmc2, 0xC000..=0xDFFF) => { 84 // Fixed at second-to-last PRG ROM bank 85 let prg_rom_addr = BankSizeKb::Eight.to_absolute_address_from_end( 86 2_u32, 87 self.cartridge.prg_rom.len() as u32, 88 address, 89 ); 90 CpuMapResult::PrgROM(prg_rom_addr) 91 } 92 (Variant::Mmc2, 0xE000..=0xFFFF) => { 93 // Fixed at last PRG ROM bank 94 let prg_rom_addr = BankSizeKb::Eight 95 .to_absolute_address_last_bank(self.cartridge.prg_rom.len() as u32, address); 96 CpuMapResult::PrgROM(prg_rom_addr) 97 } 98 (Variant::Mmc4, 0x8000..=0xBFFF) => { 99 let prg_rom_addr = 100 BankSizeKb::Sixteen.to_absolute_address(self.data.prg_bank, address); 101 CpuMapResult::PrgROM(prg_rom_addr) 102 } 103 (Variant::Mmc4, 0xC000..=0xFFFF) => { 104 // Fixed at last PRG ROM bank 105 let prg_rom_addr = BankSizeKb::Sixteen 106 .to_absolute_address_last_bank(self.cartridge.prg_rom.len() as u32, address); 107 CpuMapResult::PrgROM(prg_rom_addr) 108 } 109 } 110 } 111 112 pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 { 113 self.map_cpu_address(address).read(&self.cartridge).unwrap_or(cpu_open_bus) 114 } 115 116 pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) { 117 match address { 118 0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"), 119 0x4020..=0x5FFF | 0x8000..=0x9FFF => {} 120 0x6000..=0x7FFF => { 121 if !self.cartridge.prg_ram.is_empty() { 122 self.cartridge.set_prg_ram(u32::from(address & 0x1FFF), value); 123 } 124 } 125 0xA000..=0xAFFF => { 126 self.data.prg_bank = value & 0x0F; 127 } 128 0xB000..=0xBFFF => { 129 self.data.chr_0_fd_bank = value & 0x1F; 130 } 131 0xC000..=0xCFFF => { 132 self.data.chr_0_fe_bank = value & 0x1F; 133 } 134 0xD000..=0xDFFF => { 135 self.data.chr_1_fd_bank = value & 0x1F; 136 } 137 0xE000..=0xEFFF => { 138 self.data.chr_1_fe_bank = value & 0x1F; 139 } 140 0xF000..=0xFFFF => { 141 self.data.nametable_mirroring = if value.bit(0) { 142 NametableMirroring::Horizontal 143 } else { 144 NametableMirroring::Vertical 145 }; 146 } 147 } 148 } 149 150 pub(crate) fn read_ppu_address(&mut self, address: u16, vram: &[u8; 2048]) -> u8 { 151 let value = match address { 152 0x0000..=0x0FFF => match self.data.chr_0_latch { 153 ChrBankLatch::FD => { 154 let chr_rom_addr = 155 BankSizeKb::Four.to_absolute_address(self.data.chr_0_fd_bank, address); 156 self.cartridge.get_chr_rom(chr_rom_addr) 157 } 158 ChrBankLatch::FE => { 159 let chr_rom_addr = 160 BankSizeKb::Four.to_absolute_address(self.data.chr_0_fe_bank, address); 161 self.cartridge.get_chr_rom(chr_rom_addr) 162 } 163 }, 164 0x1000..=0x1FFF => match self.data.chr_1_latch { 165 ChrBankLatch::FD => { 166 let chr_rom_addr = 167 BankSizeKb::Four.to_absolute_address(self.data.chr_1_fd_bank, address); 168 self.cartridge.get_chr_rom(chr_rom_addr) 169 } 170 ChrBankLatch::FE => { 171 let chr_rom_addr = 172 BankSizeKb::Four.to_absolute_address(self.data.chr_1_fe_bank, address); 173 self.cartridge.get_chr_rom(chr_rom_addr) 174 } 175 }, 176 0x2000..=0x3EFF => vram[self.data.nametable_mirroring.map_to_vram(address) as usize], 177 0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"), 178 }; 179 180 // Check for FD/FE latch updates 181 match (self.data.variant, address) { 182 (Variant::Mmc2, 0x0FD8) | (Variant::Mmc4, 0x0FD8..=0x0FDF) => { 183 self.data.chr_0_latch = ChrBankLatch::FD; 184 } 185 (Variant::Mmc2, 0x0FE8) | (Variant::Mmc4, 0x0FE8..=0x0FEF) => { 186 self.data.chr_0_latch = ChrBankLatch::FE; 187 } 188 (_, 0x1FD8..=0x1FDF) => { 189 self.data.chr_1_latch = ChrBankLatch::FD; 190 } 191 (_, 0x1FE8..=0x1FEF) => { 192 self.data.chr_1_latch = ChrBankLatch::FE; 193 } 194 _ => {} 195 } 196 197 value 198 } 199 200 pub(crate) fn write_ppu_address(&mut self, address: u16, value: u8, vram: &mut [u8; 2048]) { 201 match address { 202 0x0000..=0x1FFF => {} 203 0x2000..=0x3EFF => { 204 let vram_addr = self.data.nametable_mirroring.map_to_vram(address); 205 vram[vram_addr as usize] = value; 206 } 207 0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"), 208 } 209 } 210 211 pub(crate) fn name(&self) -> &'static str { 212 match self.data.variant { 213 Variant::Mmc2 => "MMC2", 214 Variant::Mmc4 => "MMC4", 215 } 216 } 217}