Code for the NROM board (iNES mapper 0) as well as simple NROM variants.
Variants implemented here include:
- UxROM (iNES mapper 2)
- CNROM (iNES mapper 3)
- AxROM (iNES mapper 7)
- Color Dreams unlicensed board (iNES mapper 11, very similar to GxROM)
- BNROM (iNES mapper 34)
- NINA-001 (also iNES mapper 34, very similar to BNROM)
- GxROM (iNES mapper 66)
- Codemasters unlicensed board (iNES mapper 71, very similar to UxROM)
- Jaleco JF-11 / JF-14 (iNES mapper 140, very similar to GxROM)
19fn basic_map_ppu_address( 20 address: u16, 21 chr_type: ChrType, 22 nametable_mirroring: NametableMirroring, 23) -> PpuMapResult { 24 match address { 25 0x0000..=0x1FFF => chr_type.to_map_result(address.into()), 26 0x2000..=0x3EFF => PpuMapResult::Vram(nametable_mirroring.map_to_vram(address)), 27 _ => panic!("invalid PPU map address: 0x{address:04X}"), 28 } 29} 30 31#[derive(Debug, Clone, Encode, Decode)] 32pub(crate) struct Nrom { 33 chr_type: ChrType, 34 nametable_mirroring: NametableMirroring, 35} 36 37impl Nrom { 38 pub(crate) fn new(chr_type: ChrType, nametable_mirroring: NametableMirroring) -> Self { 39 Self { chr_type, nametable_mirroring } 40 } 41} 42 43impl MapperImpl<Nrom> { 44 pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 { 45 match address { 46 0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"), 47 0x4020..=0x7FFF => cpu_open_bus, 48 0x8000..=0xFFFF => self.cartridge.get_prg_rom((address & 0x7FFF).into()), 49 } 50 } 51 52 // Intentionally blank implementation that is present because every MapperImpl must have a 53 // write_cpu_address method; see Mapper::write_cpu_address 54 #[allow(clippy::unused_self)] 55 pub(crate) fn write_cpu_address(&self, _address: u16, _value: u8) {} 56} 57 58impl HasBasicPpuMapping for MapperImpl<Nrom> { 59 fn map_ppu_address(&self, address: u16) -> PpuMapResult { 60 basic_map_ppu_address(address, self.data.chr_type, self.data.nametable_mirroring) 61 } 62} 63 64#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 65pub(crate) enum UxromVariant { 66 Uxrom, 67 Codemasters, 68 FireHawk, 69} 70 71#[derive(Debug, Clone, Encode, Decode)] 72pub(crate) struct Uxrom { 73 variant: UxromVariant, 74 prg_bank: u8, 75 chr_type: ChrType, 76 nametable_mirroring: NametableMirroring, 77} 78 79impl Uxrom { 80 pub(crate) fn new( 81 mapper_number: u16, 82 sub_mapper_number: u8, 83 chr_type: ChrType, 84 nametable_mirroring: NametableMirroring, 85 ) -> Self { 86 let variant = match (mapper_number, sub_mapper_number) { 87 (2, _) => UxromVariant::Uxrom, 88 (71, 0) => UxromVariant::Codemasters, 89 (71, 1) => UxromVariant::FireHawk, 90 _ => panic!( 91 "invalid UxROM mapper/submapper: mapper={mapper_number}, submapper={sub_mapper_number}" 92 ), 93 }; 94 95 let nametable_mirroring = match variant { 96 UxromVariant::FireHawk => NametableMirroring::SingleScreenBank0, 97 UxromVariant::Uxrom | UxromVariant::Codemasters => nametable_mirroring, 98 }; 99 Self { variant, prg_bank: 0, chr_type, nametable_mirroring } 100 } 101} 102 103impl MapperImpl<Uxrom> { 104 pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 { 105 match address { 106 0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"), 107 0x4020..=0x5FFF => cpu_open_bus, 108 0x6000..=0x7FFF => self.cartridge.get_prg_ram((address & 0x1FFF).into()), 109 0x8000..=0xBFFF => { 110 let prg_rom_addr = 111 BankSizeKb::Sixteen.to_absolute_address(self.data.prg_bank, address); 112 self.cartridge.get_prg_rom(prg_rom_addr) 113 } 114 0xC000..=0xFFFF => { 115 let prg_rom_addr = BankSizeKb::Sixteen 116 .to_absolute_address_last_bank(self.cartridge.prg_rom.len() as u32, address); 117 self.cartridge.get_prg_rom(prg_rom_addr) 118 } 119 } 120 } 121 122 pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) { 123 match (self.data.variant, address) { 124 (_, 0x0000..=0x401F) => panic!("invalid CPU map address: 0x{address:04X}"), 125 (UxromVariant::Uxrom, 0x8000..=0xFFFF) 126 | (UxromVariant::Codemasters | UxromVariant::FireHawk, 0xC000..=0xFFFF) => { 127 self.data.prg_bank = value; 128 } 129 (UxromVariant::FireHawk, 0x8000..=0x9FFF) => { 130 self.data.nametable_mirroring = if value.bit(4) { 131 NametableMirroring::SingleScreenBank1 132 } else { 133 NametableMirroring::SingleScreenBank0 134 }; 135 } 136 (_, 0x6000..=0x7FFF) => { 137 self.cartridge.set_prg_ram((address & 0x1FFF).into(), value); 138 } 139 (_, 0x4020..=0x5FFF) 140 | (UxromVariant::Codemasters, 0x8000..=0x9FFF) 141 | (UxromVariant::Codemasters | UxromVariant::FireHawk, 0xA000..=0xBFFF) => {} 142 } 143 } 144 145 pub(crate) fn name(&self) -> &'static str { 146 match self.data.variant { 147 UxromVariant::Uxrom => "UxROM", 148 UxromVariant::Codemasters => "Codemasters", 149 UxromVariant::FireHawk => "Codemasters (Fire Hawk variant)", 150 } 151 } 152} 153 154impl HasBasicPpuMapping for MapperImpl<Uxrom> { 155 fn map_ppu_address(&self, address: u16) -> PpuMapResult { 156 basic_map_ppu_address(address, self.data.chr_type, self.data.nametable_mirroring) 157 } 158} 159 160#[derive(Debug, Clone, Encode, Decode)] 161pub(crate) struct Cnrom { 162 chr_type: ChrType, 163 chr_bank: u8, 164 nametable_mirroring: NametableMirroring, 165 emulate_bus_conflicts: bool, 166} 167 168impl Cnrom { 169 pub(crate) fn new( 170 chr_type: ChrType, 171 nametable_mirroring: NametableMirroring, 172 sub_mapper_number: u8, 173 ) -> Self { 174 let emulate_bus_conflicts = sub_mapper_number == 0 || sub_mapper_number == 2; 175 log::info!( 176 "Emulating CNROM bus conflicts: {emulate_bus_conflicts} (sub mapper {sub_mapper_number})" 177 ); 178 179 Self { chr_type, chr_bank: 0, nametable_mirroring, emulate_bus_conflicts } 180 } 181} 182 183impl MapperImpl<Cnrom> { 184 pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 { 185 match address { 186 0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"), 187 0x4020..=0x5FFF => cpu_open_bus, 188 0x6000..=0x7FFF => { 189 if !self.cartridge.prg_ram.is_empty() { 190 self.cartridge.get_prg_ram((address & 0x1FFF).into()) 191 } else { 192 cpu_open_bus 193 } 194 } 195 0x8000..=0xFFFF => self.cartridge.get_prg_rom((address & 0x7FFF).into()), 196 } 197 } 198 199 pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) { 200 match address { 201 0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"), 202 0x4020..=0x5FFF => {} 203 0x6000..=0x7FFF => { 204 if !self.cartridge.prg_ram.is_empty() { 205 self.cartridge.set_prg_ram((address & 0x1FFF).into(), value); 206 } 207 } 208 0x8000..=0xFFFF => { 209 // Mapper bus conflict behavior: the value written by the CPU is bitwise ANDed with 210 // the value at that address in PRG ROM. 211 // Cybernoid depends on this or graphics will be corrupted after certain parts of 212 // its audio code run 213 self.data.chr_bank = if self.data.emulate_bus_conflicts { 214 value & self.cartridge.get_prg_rom(address.into()) 215 } else { 216 value 217 }; 218 } 219 } 220 } 221} 222 223impl HasBasicPpuMapping for MapperImpl<Cnrom> { 224 fn map_ppu_address(&self, address: u16) -> PpuMapResult { 225 match address { 226 0x0000..=0x1FFF => { 227 let chr_addr = BankSizeKb::Eight.to_absolute_address(self.data.chr_bank, address); 228 self.data.chr_type.to_map_result(chr_addr) 229 } 230 0x2000..=0x3EFF => { 231 PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address)) 232 } 233 _ => panic!("invalid PPU map address: 0x{address:04X}"), 234 } 235 } 236} 237 238#[derive(Debug, Clone, Encode, Decode)] 239pub(crate) struct Axrom { 240 chr_type: ChrType, 241 prg_bank: u8, 242 nametable_mirroring: NametableMirroring, 243} 244 245impl Axrom { 246 pub(crate) fn new(chr_type: ChrType) -> Self { 247 Self { chr_type, prg_bank: 0, nametable_mirroring: NametableMirroring::SingleScreenBank0 } 248 } 249} 250 251impl MapperImpl<Axrom> { 252 pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 { 253 if address < 0x8000 { 254 return cpu_open_bus; 255 } 256 257 let prg_rom_addr = BankSizeKb::ThirtyTwo.to_absolute_address(self.data.prg_bank, address); 258 self.cartridge.get_prg_rom(prg_rom_addr) 259 } 260 261 pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) { 262 if address < 0x8000 { 263 return; 264 } 265 266 self.data.prg_bank = value & 0x07; 267 self.data.nametable_mirroring = if value.bit(4) { 268 NametableMirroring::SingleScreenBank1 269 } else { 270 NametableMirroring::SingleScreenBank0 271 }; 272 } 273} 274 275impl HasBasicPpuMapping for MapperImpl<Axrom> { 276 fn map_ppu_address(&self, address: u16) -> PpuMapResult { 277 basic_map_ppu_address(address, self.data.chr_type, self.data.nametable_mirroring) 278 } 279} 280 281#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 282enum GxromVariant { 283 Gxrom, 284 ColorDreams, 285 Jaleco, 286} 287 288#[derive(Debug, Clone, Encode, Decode)] 289pub(crate) struct Gxrom { 290 variant: GxromVariant, 291 prg_bank: u8, 292 chr_bank: u8, 293 nametable_mirroring: NametableMirroring, 294} 295 296impl Gxrom { 297 pub(crate) fn new(mapper_number: u16, nametable_mirroring: NametableMirroring) -> Self { 298 let variant = match mapper_number { 299 11 => GxromVariant::ColorDreams, 300 66 => GxromVariant::Gxrom, 301 140 => GxromVariant::Jaleco, 302 _ => panic!("invalid GxROM mapper number: {mapper_number}"), 303 }; 304 305 Self { variant, prg_bank: 0, chr_bank: 0, nametable_mirroring } 306 } 307} 308 309impl MapperImpl<Gxrom> { 310 pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 { 311 match address { 312 0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"), 313 0x4020..=0x7FFF => cpu_open_bus, 314 0x8000..=0xFFFF => { 315 let prg_rom_addr = 316 BankSizeKb::ThirtyTwo.to_absolute_address(self.data.prg_bank, address); 317 self.cartridge.get_prg_rom(prg_rom_addr) 318 } 319 } 320 } 321 322 pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) { 323 match (self.data.variant, address) { 324 (_, 0x0000..=0x401F) => panic!("invalid CPU map address: {address:04X}"), 325 (_, 0x4020..=0x5FFF) 326 | (GxromVariant::Gxrom | GxromVariant::ColorDreams, 0x6000..=0x7FFF) 327 | (GxromVariant::Jaleco, 0x8000..=0xFFFF) => {} 328 (GxromVariant::Gxrom | GxromVariant::ColorDreams, 0x8000..=0xFFFF) 329 | (GxromVariant::Jaleco, 0x6000..=0x7FFF) => { 330 let high_nibble = (value & 0xF0) >> 4; 331 let low_nibble = value & 0x0F; 332 333 match self.data.variant { 334 GxromVariant::ColorDreams => { 335 self.data.prg_bank = low_nibble; 336 self.data.chr_bank = high_nibble; 337 } 338 GxromVariant::Gxrom | GxromVariant::Jaleco => { 339 self.data.prg_bank = high_nibble; 340 self.data.chr_bank = low_nibble; 341 } 342 } 343 } 344 } 345 } 346 347 pub(crate) fn name(&self) -> &'static str { 348 match self.data.variant { 349 GxromVariant::Gxrom => "GxROM", 350 GxromVariant::Jaleco => "Jaleco JF-11 / JF-14", 351 GxromVariant::ColorDreams => "Color Dreams", 352 } 353 } 354} 355 356impl HasBasicPpuMapping for MapperImpl<Gxrom> { 357 fn map_ppu_address(&self, address: u16) -> PpuMapResult { 358 match address { 359 0x0000..=0x1FFF => { 360 let chr_rom_addr = 361 BankSizeKb::Eight.to_absolute_address(self.data.chr_bank, address); 362 PpuMapResult::ChrROM(chr_rom_addr) 363 } 364 0x2000..=0x3EFF => { 365 PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address)) 366 } 367 0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"), 368 } 369 } 370} 371 372#[derive(Debug, Clone, Encode, Decode)] 373pub(crate) struct Bnrom { 374 prg_bank: u8, 375 chr_bank_0: u8, 376 chr_bank_1: u8, 377 chr_type: ChrType, 378 nametable_mirroring: NametableMirroring, 379} 380 381impl Bnrom { 382 pub(crate) fn new(chr_type: ChrType, nametable_mirroring: NametableMirroring) -> Self { 383 Self { prg_bank: 0, chr_bank_0: 0, chr_bank_1: 1, chr_type, nametable_mirroring } 384 } 385} 386 387impl MapperImpl<Bnrom> { 388 pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 { 389 match address { 390 0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"), 391 0x4020..=0x5FFF => cpu_open_bus, 392 0x6000..=0x7FFF => { 393 if !self.cartridge.prg_ram.is_empty() { 394 self.cartridge.get_prg_ram((address & 0x1FFF).into()) 395 } else { 396 cpu_open_bus 397 } 398 } 399 0x8000..=0xFFFF => { 400 let prg_rom_addr = 401 BankSizeKb::ThirtyTwo.to_absolute_address(self.data.prg_bank, address); 402 self.cartridge.get_prg_rom(prg_rom_addr) 403 } 404 } 405 } 406 407 pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) { 408 match address { 409 0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"), 410 0x4020..=0x5FFF => {} 411 0x6000..=0x7FFC => { 412 if !self.cartridge.prg_ram.is_empty() { 413 self.cartridge.set_prg_ram((address & 0x1FFF).into(), value); 414 } 415 } 416 0x7FFD | 0x8000..=0xFFFF => { 417 self.data.prg_bank = value & 0x03; 418 } 419 0x7FFE => { 420 self.data.chr_bank_0 = value & 0x0F; 421 } 422 0x7FFF => { 423 self.data.chr_bank_1 = value & 0x0F; 424 } 425 } 426 } 427} 428 429impl HasBasicPpuMapping for MapperImpl<Bnrom> { 430 fn map_ppu_address(&self, address: u16) -> PpuMapResult { 431 match address { 432 0x0000..=0x0FFF => { 433 let chr_addr = BankSizeKb::Four.to_absolute_address(self.data.chr_bank_0, address); 434 self.data.chr_type.to_map_result(chr_addr) 435 } 436 0x1000..=0x1FFF => { 437 let chr_addr = BankSizeKb::Four.to_absolute_address(self.data.chr_bank_1, address); 438 self.data.chr_type.to_map_result(chr_addr) 439 } 440 0x2000..=0x3EFF => { 441 PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address)) 442 } 443 0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"), 444 } 445 } 446}