Code for the MMC1 board (iNES mapper 1).
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 11pub(crate) enum PrgBankingMode { 12 Switch32Kb, 13 Switch16KbFirstBankFixed, 14 Switch16KbLastBankFixed, 15} 16 17#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 18pub(crate) enum ChrBankingMode { 19 Single8KbBank, 20 Two4KbBanks, 21} 22 23#[derive(Debug, Clone, Encode, Decode)] 24pub(crate) struct Mmc1 { 25 chr_type: ChrType, 26 shift_register: u8, 27 shift_register_len: u8, 28 written_this_cycle: bool, 29 written_last_cycle: bool, 30 nametable_mirroring: NametableMirroring, 31 prg_banking_mode: PrgBankingMode, 32 chr_banking_mode: ChrBankingMode, 33 chr_bank_0: u8, 34 chr_bank_1: u8, 35 prg_bank: u8, 36 prg_bank_upper_bit: u8, 37} 38 39impl Mmc1 { 40 pub(crate) fn new(chr_type: ChrType) -> Self { 41 Self { 42 chr_type, 43 shift_register: 0, 44 shift_register_len: 0, 45 written_this_cycle: false, 46 written_last_cycle: false, 47 nametable_mirroring: NametableMirroring::Horizontal, 48 prg_banking_mode: PrgBankingMode::Switch16KbLastBankFixed, 49 chr_banking_mode: ChrBankingMode::Single8KbBank, 50 chr_bank_0: 0, 51 chr_bank_1: 0, 52 prg_bank: 0, 53 prg_bank_upper_bit: 0, 54 } 55 } 56 57 fn full_prg_bank(&self) -> u8 { 58 self.prg_bank_upper_bit | self.prg_bank 59 } 60} 61 62impl MapperImpl<Mmc1> { 63 fn map_cpu_address(&self, address: u16) -> CpuMapResult { 64 match address { 65 0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"), 66 0x4020..=0x5FFF => CpuMapResult::None, 67 0x6000..=0x7FFF => { 68 if !self.cartridge.prg_ram.is_empty() { 69 CpuMapResult::PrgRAM(u32::from(address & 0x1FFF)) 70 } else { 71 CpuMapResult::None 72 } 73 } 74 0x8000..=0xFFFF => match self.data.prg_banking_mode { 75 PrgBankingMode::Switch32Kb => { 76 // In 32KB mode, treat the bank number as a 16KB bank number but ignore the lowest bit 77 let prg_rom_addr = BankSizeKb::ThirtyTwo 78 .to_absolute_address(self.data.full_prg_bank() >> 1, address); 79 CpuMapResult::PrgROM(prg_rom_addr) 80 } 81 PrgBankingMode::Switch16KbFirstBankFixed => match address { 82 0x8000..=0xBFFF => { 83 // If PRG ROM is 512KB, the upper bit affects the fixed bank 84 let rom_addr = u32::from(address & BankSizeKb::Sixteen.address_mask()) 85 | (u32::from(self.data.prg_bank_upper_bit) << 18); 86 CpuMapResult::PrgROM(rom_addr) 87 } 88 0xC000..=0xFFFF => { 89 let prg_rom_addr = BankSizeKb::Sixteen 90 .to_absolute_address(self.data.full_prg_bank(), address); 91 CpuMapResult::PrgROM(prg_rom_addr) 92 } 93 _ => unreachable!("match arm should be unreachable"), 94 }, 95 PrgBankingMode::Switch16KbLastBankFixed => match address { 96 0x8000..=0xBFFF => { 97 let prg_rom_addr = BankSizeKb::Sixteen 98 .to_absolute_address(self.data.full_prg_bank(), address); 99 CpuMapResult::PrgROM(prg_rom_addr) 100 } 101 0xC000..=0xFFFF => { 102 // If PRG ROM is 512KB, the upper bit affects the fixed bank 103 let last_bank = self.data.prg_bank_upper_bit | 0xF; 104 let prg_rom_addr = 105 BankSizeKb::Sixteen.to_absolute_address(last_bank, address); 106 CpuMapResult::PrgROM(prg_rom_addr) 107 } 108 _ => unreachable!("match arm should be unreachable"), 109 }, 110 }, 111 } 112 } 113 114 pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 { 115 self.map_cpu_address(address).read(&self.cartridge).unwrap_or(cpu_open_bus) 116 } 117 118 pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) { 119 match address { 120 0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"), 121 0x4020..=0x5FFF => {} 122 0x6000..=0x7FFF => { 123 if !self.cartridge.prg_ram.is_empty() { 124 self.cartridge.set_prg_ram(u32::from(address & 0x1FFF), value); 125 } 126 } 127 0x8000..=0xFFFF => { 128 self.data.written_this_cycle = true; 129 130 if value.bit(7) { 131 self.data.shift_register = 0; 132 self.data.shift_register_len = 0; 133 self.data.prg_banking_mode = PrgBankingMode::Switch16KbLastBankFixed; 134 return; 135 } 136 137 if self.data.written_last_cycle { 138 return; 139 } 140 141 self.data.shift_register = (self.data.shift_register >> 1) | ((value & 0x01) << 4); 142 self.data.shift_register_len += 1; 143 144 if self.data.shift_register_len == 5 { 145 let shift_register = self.data.shift_register; 146 147 self.data.shift_register = 0; 148 self.data.shift_register_len = 0; 149 150 match address { 151 0x8000..=0x9FFF => { 152 self.data.nametable_mirroring = match shift_register & 0x03 { 153 0x00 => NametableMirroring::SingleScreenBank0, 154 0x01 => NametableMirroring::SingleScreenBank1, 155 0x02 => NametableMirroring::Vertical, 156 0x03 => NametableMirroring::Horizontal, 157 _ => unreachable!( 158 "{shift_register} & 0x03 was not 0x00/0x01/0x02/0x03", 159 ), 160 }; 161 162 self.data.prg_banking_mode = match shift_register & 0x0C { 163 0x00 | 0x04 => PrgBankingMode::Switch32Kb, 164 0x08 => PrgBankingMode::Switch16KbFirstBankFixed, 165 0x0C => PrgBankingMode::Switch16KbLastBankFixed, 166 _ => unreachable!( 167 "{shift_register} & 0x0C was not 0x00/0x04/0x08/0x0C" 168 ), 169 }; 170 171 self.data.chr_banking_mode = if shift_register.bit(4) { 172 ChrBankingMode::Two4KbBanks 173 } else { 174 ChrBankingMode::Single8KbBank 175 }; 176 } 177 0xA000..=0xBFFF => { 178 self.data.chr_bank_0 = shift_register; 179 180 // Some MMC1 variants use bit 4 of CHR bank 0 as PRG ROM A18 181 // Dragon Warrior 3 & 4 depend on this 182 if self.cartridge.prg_rom.len() >= 512 * 1024 { 183 self.data.prg_bank_upper_bit = shift_register & 0x10; 184 } 185 } 186 0xC000..=0xDFFF => { 187 self.data.chr_bank_1 = shift_register; 188 189 // Some MMC1 variants use bit 4 of CHR bank 1 as PRG ROM A18, but only 190 // when not in 8KB mode 191 if self.cartridge.prg_rom.len() >= 512 * 1024 192 && self.data.chr_banking_mode != ChrBankingMode::Single8KbBank 193 { 194 self.data.prg_bank_upper_bit = shift_register & 0x10; 195 } 196 } 197 0xE000..=0xFFFF => { 198 self.data.prg_bank = shift_register & 0x0F; 199 } 200 _ => unreachable!("match arm should be unreachable"), 201 } 202 } 203 } 204 } 205 } 206 207 pub(crate) fn tick_cpu(&mut self) { 208 self.data.written_last_cycle = self.data.written_this_cycle; 209 self.data.written_this_cycle = false; 210 } 211} 212 213impl HasBasicPpuMapping for MapperImpl<Mmc1> { 214 fn map_ppu_address(&self, address: u16) -> PpuMapResult { 215 match address { 216 0x0000..=0x1FFF => match self.data.chr_banking_mode { 217 ChrBankingMode::Two4KbBanks => { 218 let bank_number = 219 if address < 0x1000 { self.data.chr_bank_0 } else { self.data.chr_bank_1 }; 220 let chr_addr = BankSizeKb::Four.to_absolute_address(bank_number, address); 221 self.data.chr_type.to_map_result(chr_addr) 222 } 223 ChrBankingMode::Single8KbBank => { 224 // In 8KB mode, use CHR bank 0 and treat it as a 4KB bank number while ignoring 225 // the lowest bit 226 let chr_addr = 227 BankSizeKb::Eight.to_absolute_address(self.data.chr_bank_0 >> 1, address); 228 self.data.chr_type.to_map_result(chr_addr) 229 } 230 }, 231 0x2000..=0x3EFF => { 232 PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address)) 233 } 234 _ => panic!("invalid PPU map address: 0x{address:04X}"), 235 } 236 } 237}