Code for the MMC3 and MMC6 boards (iNES mapper 4).
This module also contains code for some other boards that are extremely similar to MMC3:
- Namco 108 (iNES mapper 206)
- Namco 108 with 128KB CHR ROM (iNES mapper 88)
- NAMCOT-3446 (iNES mapper 76)
- NAMCOT-3453 (iNES mapper 154)
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 15enum PrgMode { 16 Mode0, 17 Mode1, 18} 19 20#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 21enum ChrMode { 22 Mode0, 23 Mode1, 24} 25 26#[derive(Debug, Clone, Encode, Decode)] 27struct BankMapping { 28 prg_mode: PrgMode, 29 chr_mode: ChrMode, 30 prg_rom_len: u32, 31 chr_len: u32, 32 prg_bank_0: u8, 33 prg_bank_1: u8, 34 chr_banks: [u8; 6], 35} 36 37impl BankMapping { 38 fn new(prg_rom_len: u32, chr_len: u32) -> Self { 39 Self { 40 prg_mode: PrgMode::Mode0, 41 chr_mode: ChrMode::Mode0, 42 prg_rom_len, 43 chr_len, 44 prg_bank_0: 0, 45 prg_bank_1: 1, 46 chr_banks: [0; 6], 47 } 48 } 49 50 fn map_prg_rom_address(&self, address: u16) -> u32 { 51 match (self.prg_mode, address) { 52 (_, 0x0000..=0x7FFF) => panic!("invalid MMC3 PRG ROM address: 0x{address:04X}"), 53 (PrgMode::Mode0, 0x8000..=0x9FFF) | (PrgMode::Mode1, 0xC000..=0xDFFF) => { 54 BankSizeKb::Eight.to_absolute_address(self.prg_bank_0, address) 55 } 56 (_, 0xA000..=0xBFFF) => BankSizeKb::Eight.to_absolute_address(self.prg_bank_1, address), 57 (PrgMode::Mode0, 0xC000..=0xDFFF) | (PrgMode::Mode1, 0x8000..=0x9FFF) => { 58 // Fixed at second-to-last bank 59 BankSizeKb::Eight.to_absolute_address_from_end(2_u32, self.prg_rom_len, address) 60 } 61 (_, 0xE000..=0xFFFF) => { 62 // Fixed at last bank 63 BankSizeKb::Eight.to_absolute_address_last_bank(self.prg_rom_len, address) 64 } 65 } 66 } 67 68 fn map_pattern_table_address(&self, address: u16) -> u32 { 69 let mapped_address = match (self.chr_mode, address) { 70 // 2KB banks are treated as 1KB bank numbers while ignoring the lowest bit 71 (ChrMode::Mode0, 0x0000..=0x07FF) | (ChrMode::Mode1, 0x1000..=0x17FF) => { 72 BankSizeKb::Two.to_absolute_address(self.chr_banks[0] >> 1, address) 73 } 74 (ChrMode::Mode0, 0x0800..=0x0FFF) | (ChrMode::Mode1, 0x1800..=0x1FFF) => { 75 BankSizeKb::Two.to_absolute_address(self.chr_banks[1] >> 1, address) 76 } 77 (ChrMode::Mode0, 0x1000..=0x13FF) | (ChrMode::Mode1, 0x0000..=0x03FF) => { 78 BankSizeKb::One.to_absolute_address(self.chr_banks[2], address) 79 } 80 (ChrMode::Mode0, 0x1400..=0x17FF) | (ChrMode::Mode1, 0x0400..=0x07FF) => { 81 BankSizeKb::One.to_absolute_address(self.chr_banks[3], address) 82 } 83 (ChrMode::Mode0, 0x1800..=0x1BFF) | (ChrMode::Mode1, 0x0800..=0x0BFF) => { 84 BankSizeKb::One.to_absolute_address(self.chr_banks[4], address) 85 } 86 (ChrMode::Mode0, 0x1C00..=0x1FFF) | (ChrMode::Mode1, 0x0C00..=0x0FFF) => { 87 BankSizeKb::One.to_absolute_address(self.chr_banks[5], address) 88 } 89 (_, 0x2000..=0xFFFF) => { 90 panic!("invalid MMC3 CHR pattern table address: 0x{address:04X}") 91 } 92 }; 93 mapped_address & (self.chr_len - 1) 94 } 95} 96 97#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 98enum BankUpdate { 99 PrgBank0, 100 PrgBank1, 101 ChrBank(u8), 102} 103 104#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 105enum Variant { 106 Mmc3, 107 Mmc6, 108 McAcc, 109 Namco108, 110 Namco108LargeChr, 111 Namcot3425, 112 Namcot3446, 113 Namcot3453, 114} 115 116impl Variant { 117 fn name(self) -> &'static str { 118 match self { 119 Self::Mmc3 => "MMC3", 120 Self::Mmc6 => "MMC6", 121 Self::McAcc => "MMC3 (MC-ACC variant)", 122 Self::Namco108 | Self::Namco108LargeChr => "Namco 108", 123 Self::Namcot3425 => "NAMCOT-3425", 124 Self::Namcot3446 => "NAMCOT-3446", 125 Self::Namcot3453 => "NAMCOT-3453", 126 } 127 } 128 129 fn is_namco_variant(self) -> bool { 130 matches!( 131 self, 132 Self::Namco108 133 | Self::Namco108LargeChr 134 | Self::Namcot3425 135 | Self::Namcot3446 136 | Self::Namcot3453 137 ) 138 } 139} 140 141#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 142enum RamMode { 143 Mmc3Enabled, 144 Mmc3WritesDisabled, 145 Mmc6Enabled { 146 first_half_reads: bool, 147 first_half_writes: bool, 148 second_half_reads: bool, 149 second_half_writes: bool, 150 }, 151 Disabled, 152} 153 154impl RamMode { 155 fn reads_enabled(self, address: u16) -> bool { 156 match self { 157 Self::Mmc3Enabled | Self::Mmc3WritesDisabled => true, 158 Self::Mmc6Enabled { first_half_reads, second_half_reads, .. } => { 159 if address.bit(9) { 160 second_half_reads 161 } else { 162 first_half_reads 163 } 164 } 165 Self::Disabled => false, 166 } 167 } 168 169 fn writes_enabled(self, address: u16) -> bool { 170 match self { 171 Self::Mmc3Enabled => true, 172 Self::Mmc6Enabled { first_half_writes, second_half_writes, .. } => { 173 if address.bit(9) { 174 second_half_writes 175 } else { 176 first_half_writes 177 } 178 } 179 Self::Disabled | Self::Mmc3WritesDisabled => false, 180 } 181 } 182} 183 184#[derive(Debug, Clone, Encode, Decode)] 185enum Mmc3NametableMirroring { 186 Standard(NametableMirroring), 187 FourScreenVram { external_vram: Box<[u8; 4096]> }, 188} 189 190#[derive(Debug, Clone, Encode, Decode)] 191pub(crate) struct Mmc3 { 192 variant: Variant, 193 chr_type: ChrType, 194 bank_mapping: BankMapping, 195 nametable_mirroring: Mmc3NametableMirroring, 196 bank_update_select: BankUpdate, 197 ram_mode: RamMode, 198 interrupt_flag: bool, 199 irq_counter: u8, 200 irq_reload_value: u8, 201 irq_reload_flag: bool, 202 irq_enabled: bool, 203 last_a12_read: bool, 204 a12_low_cycles: u32, 205 mc_acc_pulse_counter: u8, 206} 207 208const ACC_COUNTER_INIT_VALUE: u8 = 6; 209 210impl Mmc3 { 211 pub(crate) fn new( 212 chr_type: ChrType, 213 prg_rom_len: u32, 214 chr_size: u32, 215 mapper_number: u16, 216 sub_mapper_number: u8, 217 nametable_mirroring: NametableMirroring, 218 has_four_screen_vram: bool, 219 ) -> Self { 220 let variant = match (mapper_number, sub_mapper_number) { 221 (4, 1) => Variant::Mmc6, 222 (4, 3) => Variant::McAcc, 223 (4, _) => Variant::Mmc3, 224 (76, _) => Variant::Namcot3446, 225 (88, _) => Variant::Namco108LargeChr, 226 (95, _) => Variant::Namcot3425, 227 (154, _) => Variant::Namcot3453, 228 (206, _) => Variant::Namco108, 229 _ => panic!("invalid MMC3 mapper number: {mapper_number}"), 230 }; 231 Self { 232 variant, 233 chr_type, 234 bank_mapping: BankMapping::new(prg_rom_len, chr_size), 235 nametable_mirroring: if has_four_screen_vram { 236 Mmc3NametableMirroring::FourScreenVram { external_vram: Box::new([0; 4096]) } 237 } else if variant == Variant::Namcot3453 { 238 Mmc3NametableMirroring::Standard(NametableMirroring::SingleScreenBank0) 239 } else if variant.is_namco_variant() { 240 Mmc3NametableMirroring::Standard(nametable_mirroring) 241 } else { 242 Mmc3NametableMirroring::Standard(NametableMirroring::Vertical) 243 }, 244 bank_update_select: BankUpdate::ChrBank(0), 245 ram_mode: RamMode::Disabled, 246 interrupt_flag: false, 247 irq_counter: 0, 248 irq_reload_value: 0, 249 irq_reload_flag: false, 250 irq_enabled: false, 251 last_a12_read: false, 252 a12_low_cycles: 0, 253 mc_acc_pulse_counter: ACC_COUNTER_INIT_VALUE, 254 } 255 } 256} 257 258impl MapperImpl<Mmc3> { 259 pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 { 260 match address { 261 0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"), 262 0x4020..=0x5FFF => cpu_open_bus, 263 0x6000..=0x7FFF => { 264 if self.data.ram_mode.reads_enabled(address) && !self.cartridge.prg_ram.is_empty() { 265 self.cartridge.get_prg_ram(u32::from(address & 0x1FFF)) 266 } else { 267 cpu_open_bus 268 } 269 } 270 0x8000..=0xFFFF => { 271 self.cartridge.get_prg_rom(self.data.bank_mapping.map_prg_rom_address(address)) 272 } 273 } 274 } 275 276 pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) { 277 match address { 278 0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"), 279 0x4020..=0x5FFF => {} 280 0x6000..=0x7FFF => { 281 if self.data.ram_mode.writes_enabled(address) && !self.cartridge.prg_ram.is_empty() 282 { 283 self.cartridge.set_prg_ram(u32::from(address & 0x1FFF), value); 284 } 285 } 286 0x8000..=0x9FFF => { 287 if !address.bit(0) { 288 if !self.data.variant.is_namco_variant() { 289 self.data.bank_mapping.chr_mode = 290 if value.bit(7) { ChrMode::Mode1 } else { ChrMode::Mode0 }; 291 self.data.bank_mapping.prg_mode = 292 if value.bit(6) { PrgMode::Mode1 } else { PrgMode::Mode0 }; 293 } 294 295 self.data.bank_update_select = match value & 0x07 { 296 masked_value @ 0x00..=0x05 => BankUpdate::ChrBank(masked_value), 297 0x06 => BankUpdate::PrgBank0, 298 0x07 => BankUpdate::PrgBank1, 299 _ => unreachable!( 300 "masking with 0x07 should always be in the range 0x00..=0x07" 301 ), 302 }; 303 304 if self.data.variant == Variant::Mmc6 { 305 let ram_enabled = value.bit(5); 306 if !ram_enabled { 307 self.data.ram_mode = RamMode::Disabled; 308 } else if ram_enabled && self.data.ram_mode == RamMode::Disabled { 309 self.data.ram_mode = RamMode::Mmc6Enabled { 310 first_half_reads: false, 311 first_half_writes: false, 312 second_half_reads: false, 313 second_half_writes: false, 314 }; 315 } 316 } 317 } else { 318 match self.data.bank_update_select { 319 BankUpdate::ChrBank(chr_bank) => { 320 self.data.bank_mapping.chr_banks[chr_bank as usize] = value; 321 } 322 BankUpdate::PrgBank0 => { 323 self.data.bank_mapping.prg_bank_0 = value; 324 } 325 BankUpdate::PrgBank1 => { 326 self.data.bank_mapping.prg_bank_1 = value; 327 } 328 } 329 } 330 } 331 0xA000..=0xBFFF => { 332 if !address.bit(0) 333 && !self.data.variant.is_namco_variant() 334 && matches!(self.data.nametable_mirroring, Mmc3NametableMirroring::Standard(..)) 335 { 336 let nametable_mirroring = if value.bit(0) { 337 NametableMirroring::Horizontal 338 } else { 339 NametableMirroring::Vertical 340 }; 341 self.data.nametable_mirroring = 342 Mmc3NametableMirroring::Standard(nametable_mirroring); 343 } else if address.bit(0) { 344 match self.data.variant { 345 Variant::Mmc6 => { 346 self.data.ram_mode = if self.data.ram_mode == RamMode::Disabled { 347 // $A001 writes are ignored if RAM is disabled via $8000 348 RamMode::Disabled 349 } else { 350 let first_half_writes = value.bit(4); 351 let first_half_reads = value.bit(5); 352 let second_half_writes = value.bit(6); 353 let second_half_reads = value.bit(7); 354 RamMode::Mmc6Enabled { 355 first_half_reads, 356 first_half_writes, 357 second_half_reads, 358 second_half_writes, 359 } 360 }; 361 } 362 Variant::Mmc3 | Variant::McAcc => { 363 self.data.ram_mode = if !value.bit(7) { 364 RamMode::Disabled 365 } else if value.bit(6) { 366 RamMode::Mmc3WritesDisabled 367 } else { 368 RamMode::Mmc3Enabled 369 }; 370 } 371 Variant::Namco108 372 | Variant::Namco108LargeChr 373 | Variant::Namcot3425 374 | Variant::Namcot3446 375 | Variant::Namcot3453 => {} 376 } 377 } 378 } 379 0xC000..=0xDFFF => { 380 if !address.bit(0) { 381 self.data.irq_reload_value = value; 382 } else { 383 self.data.irq_reload_flag = true; 384 self.data.mc_acc_pulse_counter = ACC_COUNTER_INIT_VALUE; 385 } 386 } 387 0xE000..=0xFFFF => { 388 if !address.bit(0) { 389 self.data.irq_enabled = false; 390 self.data.interrupt_flag = false; 391 } else { 392 self.data.irq_enabled = true; 393 } 394 } 395 } 396 397 if self.data.variant == Variant::Namcot3453 && (0x8000..=0xFFFF).contains(&address) { 398 self.data.nametable_mirroring = if value.bit(6) { 399 Mmc3NametableMirroring::Standard(NametableMirroring::SingleScreenBank0) 400 } else { 401 Mmc3NametableMirroring::Standard(NametableMirroring::SingleScreenBank1) 402 }; 403 } 404 } 405 406 fn clock_irq(&mut self) { 407 log::trace!( 408 "IRQ clocked; counter={}, reload_flag={}, reload_value={}", 409 self.data.irq_counter, 410 self.data.irq_reload_flag, 411 self.data.irq_reload_value 412 ); 413 414 if self.data.irq_counter == 0 || self.data.irq_reload_flag { 415 self.data.irq_counter = self.data.irq_reload_value; 416 self.data.irq_reload_flag = false; 417 } else { 418 self.data.irq_counter -= 1; 419 } 420 421 if self.data.irq_counter == 0 && self.data.irq_enabled { 422 self.data.interrupt_flag = true; 423 } 424 } 425 426 fn process_ppu_address(&mut self, address: u16) { 427 log::trace!("PPU bus address: {address:04X}"); 428 429 let a12 = address.bit(12); 430 431 match self.data.variant { 432 Variant::Mmc3 | Variant::Mmc6 => { 433 if a12 && !self.data.last_a12_read && self.data.a12_low_cycles >= 10 { 434 self.clock_irq(); 435 } 436 } 437 Variant::McAcc => { 438 if !a12 && self.data.last_a12_read { 439 self.data.mc_acc_pulse_counter += 1; 440 if self.data.mc_acc_pulse_counter == 8 { 441 self.clock_irq(); 442 self.data.mc_acc_pulse_counter = 0; 443 } 444 } 445 } 446 Variant::Namco108 447 | Variant::Namco108LargeChr 448 | Variant::Namcot3425 449 | Variant::Namcot3446 450 | Variant::Namcot3453 => {} 451 } 452 453 self.data.last_a12_read = a12; 454 } 455 456 fn map_pattern_table_address(&self, address: u16) -> PpuMapResult { 457 match self.data.variant { 458 Variant::Namco108LargeChr | Variant::Namcot3453 => { 459 let chr_outer_bank = address.bit(12); 460 let chr_addr = (self.data.bank_mapping.map_pattern_table_address(address) 461 & 0x0000FFFF) 462 | (u32::from(chr_outer_bank) << 16); 463 self.data.chr_type.to_map_result(chr_addr) 464 } 465 Variant::Namcot3446 => { 466 let bank_index = address / 0x0800 + 2; 467 let bank_number = self.data.bank_mapping.chr_banks[bank_index as usize]; 468 let chr_addr = BankSizeKb::Two.to_absolute_address(bank_number, address); 469 self.data.chr_type.to_map_result(chr_addr) 470 } 471 _ => self 472 .data 473 .chr_type 474 .to_map_result(self.data.bank_mapping.map_pattern_table_address(address)), 475 } 476 } 477 478 fn map_namcot_3425_nametable_addr(&self, address: u16) -> u32 { 479 let bank_index = (address & 0x0FFF) / 0x0800; 480 let bank_number = self.data.bank_mapping.chr_banks[bank_index as usize]; 481 let vram_bank = bank_number.bit(5); 482 (u32::from(vram_bank) << 10) | u32::from(address & 0x03FF) 483 } 484 485 pub(crate) fn read_ppu_address(&mut self, address: u16, vram: &[u8; 2048]) -> u8 { 486 match address & 0x3FFF { 487 0x0000..=0x1FFF => self.map_pattern_table_address(address).read(&self.cartridge, vram), 488 0x2000..=0x3EFF => match self.data.variant { 489 Variant::Namcot3425 => { 490 let vram_addr = self.map_namcot_3425_nametable_addr(address); 491 vram[vram_addr as usize] 492 } 493 _ => match &self.data.nametable_mirroring { 494 Mmc3NametableMirroring::Standard(nametable_mirroring) => { 495 vram[nametable_mirroring.map_to_vram(address) as usize] 496 } 497 Mmc3NametableMirroring::FourScreenVram { external_vram } => { 498 external_vram[(address & 0x0FFF) as usize] 499 } 500 }, 501 }, 502 0x3F00..=0xFFFF => panic!("invalid PPU map address: 0x{address:04X}"), 503 } 504 } 505 506 pub(crate) fn write_ppu_address(&mut self, address: u16, value: u8, vram: &mut [u8; 2048]) { 507 self.process_ppu_address(address); 508 509 match address & 0x3FFF { 510 0x0000..=0x1FFF => { 511 self.map_pattern_table_address(address).write(value, &mut self.cartridge, vram); 512 } 513 0x2000..=0x3EFF => match self.data.variant { 514 Variant::Namcot3425 => { 515 let vram_addr = self.map_namcot_3425_nametable_addr(address); 516 vram[vram_addr as usize] = value; 517 } 518 _ => match &mut self.data.nametable_mirroring { 519 Mmc3NametableMirroring::Standard(nametable_mirroring) => { 520 vram[nametable_mirroring.map_to_vram(address) as usize] = value; 521 } 522 Mmc3NametableMirroring::FourScreenVram { external_vram } => { 523 external_vram[(address & 0x0FFF) as usize] = value; 524 } 525 }, 526 }, 527 0x3F00..=0xFFFF => panic!("invalid PPU map address: 0x{address:04X}"), 528 } 529 } 530 531 pub(crate) fn interrupt_flag(&self) -> bool { 532 self.data.interrupt_flag 533 } 534 535 pub(crate) fn tick(&mut self, ppu_bus_address: u16) { 536 self.process_ppu_address(ppu_bus_address); 537 538 if !self.data.last_a12_read { 539 self.data.a12_low_cycles += 1; 540 } else { 541 self.data.a12_low_cycles = 0; 542 } 543 } 544 545 pub(crate) fn name(&self) -> &'static str { 546 self.data.variant.name() 547 } 548}