1mod mappers; 2 3use bincode::de::{BorrowDecoder, Decoder}; 4use bincode::enc::Encoder; 5use bincode::error::{DecodeError, EncodeError}; 6use bincode::{BorrowDecode, Decode, Encode}; 7use jgenesis_common::frontend::{PartialClone, TimingMode}; 8use jgenesis_common::num::GetBit; 9use jgenesis_proc_macros::MatchEachVariantMacro; 10use std::fmt::{Display, Formatter}; 11use std::{io, mem}; 12use thiserror::Error; 13 14use crate::bus::cartridge::mappers::action52::Action52; 15use crate::bus::cartridge::mappers::bandai::BandaiFcg; 16use crate::bus::cartridge::mappers::konami::{Vrc4, Vrc6, Vrc7}; 17use crate::bus::cartridge::mappers::mmc1::Mmc1; 18use crate::bus::cartridge::mappers::mmc2::Mmc2; 19use crate::bus::cartridge::mappers::mmc3::Mmc3; 20use crate::bus::cartridge::mappers::mmc5::Mmc5; 21use crate::bus::cartridge::mappers::namco163::Namco163; 22use crate::bus::cartridge::mappers::namco175::Namco175; 23use crate::bus::cartridge::mappers::nrom::{Axrom, Bnrom, Cnrom, Gxrom, Nrom, Uxrom}; 24use crate::bus::cartridge::mappers::sunsoft::Sunsoft; 25use crate::bus::cartridge::mappers::unrom512::Unrom512; 26use crate::bus::cartridge::mappers::{ChrType, NametableMirroring, PpuMapResult, unrom512}; 27#[cfg(test)] 28pub(crate) use mappers::new_mmc1; 29 30#[derive(Debug, Clone, PartialClone)] 31pub struct Cartridge { 32 timing_mode: TimingMode, 33 #[partial_clone(default)] 34 prg_rom: Vec<u8>, 35 prg_ram: Vec<u8>, 36 has_ram_battery: bool, 37 prg_ram_dirty_bit: bool, 38 #[partial_clone(default)] 39 chr_rom: Vec<u8>, 40 chr_ram: Vec<u8>, 41}
Encode and Decode are implemented explicitly instead of using derive in order to avoid serializing ROM bytes as part of save states
45impl Encode for Cartridge { 46 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> { 47 self.timing_mode.encode(encoder)?; 48 self.prg_ram.encode(encoder)?; 49 self.has_ram_battery.encode(encoder)?; 50 self.prg_ram_dirty_bit.encode(encoder)?; 51 self.chr_ram.encode(encoder)?; 52 53 Ok(()) 54 } 55}
57impl<Context> Decode<Context> for Cartridge { 58 fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> { 59 let timing_mode = Decode::decode(decoder)?; 60 let prg_ram = Decode::decode(decoder)?; 61 let has_ram_battery = Decode::decode(decoder)?; 62 let prg_ram_dirty_bit = Decode::decode(decoder)?; 63 let chr_ram = Decode::decode(decoder)?; 64 65 Ok(Self { 66 timing_mode, 67 prg_rom: vec![], 68 prg_ram, 69 has_ram_battery, 70 prg_ram_dirty_bit, 71 chr_rom: vec![], 72 chr_ram, 73 }) 74 } 75} 76 77impl<'de, Context> BorrowDecode<'de, Context> for Cartridge { 78 fn borrow_decode<D: BorrowDecoder<'de, Context = Context>>( 79 decoder: &mut D, 80 ) -> Result<Self, DecodeError> { 81 let timing_mode = BorrowDecode::borrow_decode(decoder)?; 82 let prg_ram = BorrowDecode::borrow_decode(decoder)?; 83 let has_ram_battery = BorrowDecode::borrow_decode(decoder)?; 84 let prg_ram_dirty_bit = BorrowDecode::borrow_decode(decoder)?; 85 let chr_ram = BorrowDecode::borrow_decode(decoder)?; 86 87 Ok(Self { 88 timing_mode, 89 prg_rom: vec![], 90 prg_ram, 91 has_ram_battery, 92 prg_ram_dirty_bit, 93 chr_rom: vec![], 94 chr_ram, 95 }) 96 } 97} 98 99impl Cartridge { 100 fn get_prg_rom(&self, address: u32) -> u8 { 101 self.prg_rom[(address as usize) & (self.prg_rom.len() - 1)] 102 } 103 104 fn get_prg_ram(&self, address: u32) -> u8 { 105 if self.prg_ram.is_empty() { 106 return 0xFF; 107 } 108 109 self.prg_ram[(address as usize) & (self.prg_ram.len() - 1)] 110 } 111 112 fn set_prg_ram(&mut self, address: u32, value: u8) { 113 if self.prg_ram.is_empty() { 114 return; 115 } 116 117 let prg_ram_len = self.prg_ram.len(); 118 self.prg_ram[(address as usize) & (prg_ram_len - 1)] = value; 119 if self.has_ram_battery { 120 self.prg_ram_dirty_bit = true; 121 } 122 } 123 124 fn get_chr_rom(&self, address: u32) -> u8 { 125 if self.chr_rom.is_empty() { 126 return 0xFF; 127 } 128 129 self.chr_rom[(address as usize) & (self.chr_rom.len() - 1)] 130 } 131 132 fn get_chr_ram(&self, address: u32) -> u8 { 133 if self.chr_ram.is_empty() { 134 return 0xFF; 135 } 136 137 self.chr_ram[(address as usize) & (self.chr_ram.len() - 1)] 138 } 139 140 fn set_chr_ram(&mut self, address: u32, value: u8) { 141 if self.chr_ram.is_empty() { 142 return; 143 } 144 145 let chr_ram_len = self.chr_ram.len(); 146 self.chr_ram[(address as usize) & (chr_ram_len - 1)] = value; 147 } 148 149 fn move_rom_from(&mut self, other: &mut Self) { 150 self.prg_rom = mem::take(&mut other.prg_rom); 151 self.chr_rom = mem::take(&mut other.chr_rom); 152 } 153} 154 155#[derive(Debug, Clone, Encode, Decode)] 156pub(crate) struct MapperImpl<MapperData> { 157 cartridge: Cartridge, 158 data: MapperData, 159}
Not using derive macro because it only impls the trait when MapperData implements PartialClone, which is not a necessary bound here
169pub(crate) trait HasBasicPpuMapping { 170 fn map_ppu_address(&self, address: u16) -> PpuMapResult; 171} 172 173impl<MapperData> MapperImpl<MapperData> 174where 175 MapperImpl<MapperData>: HasBasicPpuMapping, 176{ 177 fn read_ppu_address(&self, address: u16, vram: &[u8; 2048]) -> u8 { 178 self.map_ppu_address(address).read(&self.cartridge, vram) 179 } 180 181 fn write_ppu_address(&mut self, address: u16, value: u8, vram: &mut [u8; 2048]) { 182 self.map_ppu_address(address).write(value, &mut self.cartridge, vram); 183 } 184} 185 186#[allow(clippy::large_enum_variant)] 187#[derive(Debug, Clone, Encode, Decode, PartialClone, MatchEachVariantMacro)] 188pub(crate) enum Mapper { 189 Action52(#[partial_clone(partial)] MapperImpl<Action52>), 190 Axrom(#[partial_clone(partial)] MapperImpl<Axrom>), 191 BandaiFcg(#[partial_clone(partial)] MapperImpl<BandaiFcg>), 192 Bnrom(#[partial_clone(partial)] MapperImpl<Bnrom>), 193 Cnrom(#[partial_clone(partial)] MapperImpl<Cnrom>), 194 Gxrom(#[partial_clone(partial)] MapperImpl<Gxrom>), 195 Mmc1(#[partial_clone(partial)] MapperImpl<Mmc1>), 196 Mmc2(#[partial_clone(partial)] MapperImpl<Mmc2>), 197 Mmc3(#[partial_clone(partial)] MapperImpl<Mmc3>), 198 Mmc5(#[partial_clone(partial)] MapperImpl<Mmc5>), 199 Namco163(#[partial_clone(partial)] MapperImpl<Namco163>), 200 Namco175(#[partial_clone(partial)] MapperImpl<Namco175>), 201 Nrom(#[partial_clone(partial)] MapperImpl<Nrom>), 202 Sunsoft(#[partial_clone(partial)] MapperImpl<Sunsoft>), 203 Unrom512(#[partial_clone(partial)] MapperImpl<Unrom512>), 204 Uxrom(#[partial_clone(partial)] MapperImpl<Uxrom>), 205 Vrc4(#[partial_clone(partial)] MapperImpl<Vrc4>), 206 Vrc6(#[partial_clone(partial)] MapperImpl<Vrc6>), 207 Vrc7(#[partial_clone(partial)] MapperImpl<Vrc7>), 208} 209 210impl Mapper {
Retrieve the mapper's user-readable name. Only used for logging output.
212 pub(crate) fn name(&self) -> &'static str { 213 match self { 214 Self::Action52(..) => "Action 52", 215 Self::Axrom(..) => "AxROM", 216 Self::BandaiFcg(bandai_fcg) => bandai_fcg.name(), 217 Self::Bnrom(..) => "BNROM / NINA-001", 218 Self::Cnrom(..) => "CNROM", 219 Self::Gxrom(gxrom) => gxrom.name(), 220 Self::Mmc1(..) => "MMC1", 221 Self::Mmc2(mmc2) => mmc2.name(), 222 Self::Mmc3(mmc3) => mmc3.name(), 223 Self::Mmc5(..) => "MMC5", 224 Self::Namco163(..) => "Namco 163", 225 Self::Namco175(..) => "Namco 175", 226 Self::Nrom(..) => "NROM", 227 Self::Sunsoft(..) => "Sunsoft", 228 Self::Unrom512(..) => "UNROM 512", 229 Self::Uxrom(uxrom) => uxrom.name(), 230 Self::Vrc4(vrc4) => vrc4.name(), 231 Self::Vrc6(..) => "VRC6", 232 Self::Vrc7(..) => "VRC7", 233 } 234 }
Read a value from the given address in the CPU address space.
Write a value to the given address in the CPU address space.
Read a value from the given address in the PPU address space.
Write a value to the given address in the PPU address space.
Perform any processing that should be performed after every PPU cycle.
Perform any processing that should be performed after every CPU cycle. Commonly used for interrupt counters and expansion audio.
265 pub(crate) fn tick_cpu(&mut self) { 266 match self { 267 Self::BandaiFcg(bandai_fcg) => { 268 bandai_fcg.tick_cpu(); 269 } 270 Self::Mmc1(mmc1) => { 271 mmc1.tick_cpu(); 272 } 273 Self::Mmc5(mmc5) => { 274 mmc5.tick_cpu(); 275 } 276 Self::Namco163(namco163) => { 277 namco163.tick_cpu(); 278 } 279 Self::Sunsoft(sunsoft) => { 280 sunsoft.tick_cpu(); 281 } 282 Self::Vrc4(vrc4) => { 283 vrc4.tick_cpu(); 284 } 285 Self::Vrc6(vrc6) => { 286 vrc6.tick_cpu(); 287 } 288 Self::Vrc7(vrc7) => { 289 vrc7.tick_cpu(); 290 } 291 _ => {} 292 } 293 }
Return whether this board is currently generating an IRQ.
296 pub(crate) fn interrupt_flag(&self) -> bool { 297 match self { 298 Self::BandaiFcg(bandai_fcg) => bandai_fcg.interrupt_flag(), 299 Self::Mmc3(mmc3) => mmc3.interrupt_flag(), 300 Self::Mmc5(mmc5) => mmc5.interrupt_flag(), 301 Self::Namco163(namco163) => namco163.interrupt_flag(), 302 Self::Sunsoft(sunsoft) => sunsoft.interrupt_flag(), 303 Self::Vrc4(vrc4) => vrc4.interrupt_flag(), 304 Self::Vrc6(vrc6) => vrc6.interrupt_flag(), 305 Self::Vrc7(vrc7) => vrc7.interrupt_flag(), 306 _ => false, 307 } 308 }
Process a PPUCTRL write. Only needed by the MMC5 mapper in order to know whether double height sprites are enabled.
Process a PPUMASK write. Used by MMC5 to know whether rendering is currently enabled
Notify the mapper that the CPU will imminently access the PPUDATA register. This is required by MMC5 to map PPUDATA reads/writes to the correct CHR banks.
This should be called before the actual memory access.
Return whether the board's writable memory (if any) has been written to since the last time this method was called.
337 pub(crate) fn get_and_clear_ram_dirty_bit(&mut self) -> bool { 338 match self { 339 Mapper::BandaiFcg(mapper) => { 340 if mapper.get_and_clear_eeprom_dirty_bit() { 341 return true; 342 } 343 } 344 Mapper::Namco163(mapper) if mapper.has_battery_backed_internal_ram() => { 345 if mapper.get_and_clear_internal_ram_dirty_bit() { 346 return true; 347 } 348 } 349 Mapper::Unrom512(mapper) => { 350 if mapper.get_and_clear_dirty_bit() { 351 return true; 352 } 353 } 354 _ => {} 355 } 356 357 match_each_variant!(self, mapper => { 358 let dirty_bit = mapper.cartridge.prg_ram_dirty_bit; 359 mapper.cartridge.prg_ram_dirty_bit = false; 360 dirty_bit 361 }) 362 }
Return the board's writable memory as a slice. This will be an empty slice if the board has no PRG RAM or EEPROM.
366 pub(crate) fn get_prg_ram(&self) -> &[u8] { 367 match self { 368 Mapper::BandaiFcg(mapper) if let Some(eeprom) = mapper.eeprom() => { 369 return eeprom; 370 } 371 Mapper::Namco163(mapper) if mapper.has_battery_backed_internal_ram() => { 372 return mapper.get_internal_ram(); 373 } 374 Mapper::Unrom512(mapper) if mapper.is_flashable() => { 375 // Some UNROM 512 cartridges have flashable PRG ROM that is used to store save data 376 return &mapper.cartridge.prg_rom; 377 } 378 _ => {} 379 } 380 381 match_each_variant!(self, mapper => &mapper.cartridge.prg_ram) 382 }
Retrieve the timing mode of the cartridge (NTSC/PAL).
If the board has expansion audio, generate an audio sample and mix it with the mixed APU sample.
If the board does not have expansion audio or it is not enabled then this method will simply return the mixed APU sample as-is.
394 pub(crate) fn sample_audio(&self, mixed_apu_sample: f64) -> f64 { 395 match self { 396 Self::Mmc5(mmc5) => mmc5.sample_audio(mixed_apu_sample), 397 Self::Namco163(namco163) => namco163.sample_audio(mixed_apu_sample), 398 Self::Sunsoft(sunsoft) => sunsoft.sample_audio(mixed_apu_sample), 399 Self::Vrc6(vrc6) => vrc6.sample_audio(mixed_apu_sample), 400 Self::Vrc7(vrc7) => vrc7.sample_audio(mixed_apu_sample), 401 _ => mixed_apu_sample, 402 } 403 }
Move cartridge ROM fields from another Mapper instance. Used when loading save states.
411 pub(crate) fn reset(&mut self) { 412 if let Self::Action52(action52) = self { 413 action52.reset(); 414 } 415 } 416} 417 418#[derive(Debug, Error)] 419pub enum CartridgeFileError { 420 #[error("I/O error: {source}")] 421 Io { 422 #[from] 423 source: io::Error, 424 }, 425 #[error("invalid or unsupported file format")] 426 Format, 427 #[error("unsupported mapper: {mapper_number}")] 428 UnsupportedMapper { mapper_number: u16 }, 429 #[error("cartridge header specifies both volatile and non-volatile PRG RAM")] 430 MultiplePrgRamTypes, 431 #[error( 432 "ROM is not large enough to hold an iNES header; expected at least 16 bytes, was {file_size} bytes" 433 )] 434 TooSmallForHeader { file_size: u32 }, 435 #[error( 436 "Invalid PRG/CHR ROM size in ROM header: file size is {file_size} bytes, PRG ROM size is {prg_rom_size} bytes, CHR ROM size is {chr_rom_size} bytes" 437 )] 438 InvalidRomSize { file_size: u32, prg_rom_size: u32, chr_rom_size: u32 }, 439 #[error("unsupported timing mode byte: {byte}")] 440 UnsupportedTimingMode { byte: u8 }, 441} 442 443#[derive(Debug, Clone, Copy, PartialEq, Eq)] 444enum FileFormat { 445 INes, 446 Nes2Point0, 447} 448 449impl Display for FileFormat { 450 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 451 match self { 452 Self::INes => write!(f, "iNES"), 453 Self::Nes2Point0 => write!(f, "NES 2.0"), 454 } 455 } 456} 457 458#[derive(Debug, Clone)] 459pub struct INesHeader { 460 mapper_number: u16, 461 sub_mapper_number: u8, 462 timing_mode: TimingMode, 463 prg_rom_size: u32, 464 prg_ram_size: u32, 465 chr_rom_size: u32, 466 chr_ram_size: u32, 467 chr_type: ChrType, 468 nametable_mirroring: NametableMirroring, 469 has_trainer: bool, 470 has_battery: bool, 471 has_four_screen_vram: bool, 472} 473 474impl INesHeader { 475 fn parse_from_file(file_bytes: &[u8]) -> Result<INesHeader, CartridgeFileError> { 476 if file_bytes.len() < 16 { 477 return Err(CartridgeFileError::TooSmallForHeader { 478 file_size: file_bytes.len() as u32, 479 }); 480 } 481 482 let header = &file_bytes[..16]; 483 484 // All iNES headers should begin with this 4-byte sequence, which is "NES" followed by the 485 // character that MS-DOS used for EOF 486 if header[..4] != [b'N', b'E', b'S', 0x1A] { 487 return Err(CartridgeFileError::Format); 488 } 489 490 let format = 491 if header[7] & 0x0C == 0x08 { FileFormat::Nes2Point0 } else { FileFormat::INes }; 492 let has_trainer = header[6].bit(2); 493 let mapper_number = u16::from((header[7] & 0xF0) | ((header[6] & 0xF0) >> 4)); 494 495 let chr_rom_size = { 496 let mut chr_rom_size_8kb = u32::from(header[5]); 497 if format == FileFormat::Nes2Point0 { 498 chr_rom_size_8kb |= u32::from(header[9] & 0xF0) << 4; 499 } 500 8 * 1024 * chr_rom_size_8kb 501 }; 502 503 let prg_rom_size = { 504 let mut prg_rom_size_16kb = u32::from(header[4]); 505 if format == FileFormat::Nes2Point0 { 506 prg_rom_size_16kb |= u32::from(header[9] & 0x0F) << 8; 507 } 508 let prg_rom_size = 16 * 1024 * prg_rom_size_16kb; 509 510 // Hack: Galaxian (J) only has 8KB of PRG ROM, and the iNES header usually contains a 511 // nonsensical PRG ROM size because it can't represent this 512 if should_apply_8kb_prg_rom_hack(file_bytes, mapper_number, has_trainer, chr_rom_size) { 513 log::info!("Ignoring PRG ROM size in header of {prg_rom_size} bytes"); 514 8 * 1024 515 } else { 516 prg_rom_size 517 } 518 }; 519 520 if header.len() + (prg_rom_size + chr_rom_size) as usize > file_bytes.len() { 521 return Err(CartridgeFileError::InvalidRomSize { 522 file_size: file_bytes.len() as u32, 523 prg_rom_size, 524 chr_rom_size, 525 }); 526 } 527 528 let chr_type = if chr_rom_size == 0 { ChrType::RAM } else { ChrType::ROM }; 529 530 let nametable_mirroring = if header[6].bit(0) { 531 NametableMirroring::Vertical 532 } else { 533 NametableMirroring::Horizontal 534 }; 535 536 let has_four_screen_vram = header[6].bit(3); 537 538 let has_battery = header[6].bit(1); 539 540 log::info!("ROM header format: {format}"); 541 542 let sub_mapper_number = match format { 543 FileFormat::Nes2Point0 => header[8] >> 4, 544 FileFormat::INes => 0, 545 }; 546 547 let timing_mode = match format { 548 FileFormat::Nes2Point0 => { 549 let timing_mode_byte = header[12] & 0x03; 550 match timing_mode_byte { 551 0x00 | 0x02 => TimingMode::Ntsc, 552 0x01 => TimingMode::Pal, 553 0x03 => { 554 return Err(CartridgeFileError::UnsupportedTimingMode { 555 byte: timing_mode_byte, 556 }); 557 } 558 _ => unreachable!("value & 0x03 should always be 0x00/0x01/0x02/0x03"), 559 } 560 } 561 FileFormat::INes => { 562 if header[9].bit(0) { 563 TimingMode::Pal 564 } else { 565 TimingMode::Ntsc 566 } 567 } 568 }; 569 570 let prg_ram_size = determine_prg_ram_size(header, mapper_number, format); 571 572 let chr_ram_size = match (chr_type, format) { 573 (ChrType::RAM, FileFormat::Nes2Point0) => { 574 let chr_ram_shift = header[11] & 0x0F; 575 if chr_ram_shift > 0 { 64 << chr_ram_shift } else { 0 } 576 } 577 (ChrType::RAM, FileFormat::INes) => { 578 if mapper_number == unrom512::MAPPER_NUMBER { 579 unrom512::INES_CHR_RAM_LEN 580 } else { 581 8 * 1024 582 } 583 } 584 (ChrType::ROM, _) => 0, 585 }; 586 587 Ok(Self { 588 mapper_number, 589 sub_mapper_number, 590 timing_mode, 591 prg_rom_size, 592 prg_ram_size, 593 chr_rom_size, 594 chr_ram_size, 595 chr_type, 596 nametable_mirroring, 597 has_trainer, 598 has_battery, 599 has_four_screen_vram, 600 }) 601 } 602}
Check whether to assume the cartridge has 8KB of PRG ROM, which the iNES format cannot represent
605fn should_apply_8kb_prg_rom_hack( 606 file_bytes: &[u8], 607 mapper_number: u16, 608 has_trainer: bool, 609 chr_rom_size: u32, 610) -> bool { 611 if mapper_number != 0 { 612 // Only apply this hack for mapper 0 613 return false; 614 } 615 616 let trainer_len = if has_trainer { 512 } else { 0 }; 617 let header_len = 16 + trainer_len; 618 619 file_bytes.len().saturating_sub(header_len).saturating_sub(chr_rom_size as usize) == 8 * 1024 620}
622fn determine_prg_ram_size(header: &[u8], mapper_number: u16, format: FileFormat) -> u32 { 623 let prg_ram_size = match format { 624 FileFormat::Nes2Point0 => { 625 let volatile_shift = header[10] & 0x0F; 626 let non_volatile_shift = header[10] >> 4; 627 // TODO separate these? very very few games have both volatile and non-volatile RAM 628 let volatile_ram = if volatile_shift > 0 { 64 << volatile_shift } else { 0 }; 629 let non_volatile_ram = 630 if non_volatile_shift > 0 { 64 << non_volatile_shift } else { 0 }; 631 let total_ram = volatile_ram + non_volatile_ram; 632 633 // Hack to handle MMC5 headers that don't specify PRG RAM size but expect 32KB/64KB of 634 // PRG RAM 635 if mapper_number == 5 && total_ram == 0 { 636 log::info!( 637 "Ignoring PRG RAM size of 0 in MMC5 NES 2.0 header; setting to 64KB instead" 638 ); 639 None 640 } else { 641 Some(total_ram) 642 } 643 } 644 FileFormat::INes => None, 645 }; 646 647 // Default to 64KB for MMC5, 8KB for all other mappers 648 let default_ram_size = match mapper_number { 649 5 => 64 * 1024, 650 _ => 8 * 1024, 651 }; 652 prg_ram_size.unwrap_or(default_ram_size) 653}
Parse cartridge data out of an iNES file.
Errors
This function will return an error if the given bytes do not appear to represent an iNES file.
660pub(crate) fn from_ines_file( 661 file_bytes: &[u8], 662 sav_bytes: Option<Vec<u8>>, 663 forced_timing_mode: Option<TimingMode>, 664) -> Result<Mapper, CartridgeFileError> { 665 let header = INesHeader::parse_from_file(file_bytes)?; 666 667 // Header is 16 bytes, trainer is 512 bytes if present 668 let prg_rom_start_address = if header.has_trainer { 16 + 512 } else { 16 } as usize; 669 let prg_rom_end_address = prg_rom_start_address + header.prg_rom_size as usize; 670 let chr_rom_end_address = prg_rom_end_address + header.chr_rom_size as usize; 671 672 let mut prg_rom = Vec::from(&file_bytes[prg_rom_start_address..prg_rom_end_address]); 673 let mut chr_rom = Vec::from(&file_bytes[prg_rom_end_address..chr_rom_end_address]); 674 if !chr_rom.is_empty() { 675 jgenesis_common::rom::mirror_to_next_power_of_two(&mut chr_rom); 676 } 677 678 // UNROM 512 stores save data in flashable PRG ROM instead of PRG RAM; replace PRG ROM with 679 // save contents if save is present and size matches 680 let original_prg_rom = prg_rom.clone(); 681 if header.mapper_number == unrom512::MAPPER_NUMBER { 682 match &sav_bytes { 683 Some(sav_bytes) if sav_bytes.len() == prg_rom.len() => { 684 log::info!("Replacing UNROM 512 PRG ROM with contents of save file"); 685 prg_rom.clone_from(sav_bytes); 686 } 687 _ => {} 688 } 689 } 690 691 let prg_ram = match &sav_bytes { 692 Some(sav_bytes) if sav_bytes.len() == header.prg_ram_size as usize => sav_bytes.clone(), 693 _ => vec![0xFF; header.prg_ram_size as usize], 694 }; 695 696 let timing_mode = forced_timing_mode.unwrap_or(header.timing_mode); 697 if timing_mode != header.timing_mode { 698 log::info!( 699 "Forcing timing mode to {timing_mode}; ignoring timing mode {} from cartridge header", 700 header.timing_mode 701 ); 702 } 703 704 let cartridge = Cartridge { 705 timing_mode, 706 prg_rom, 707 prg_ram, 708 has_ram_battery: header.has_battery, 709 prg_ram_dirty_bit: header.has_battery, 710 chr_rom, 711 chr_ram: vec![0; header.chr_ram_size as usize], 712 }; 713 714 let chr_size = match header.chr_type { 715 ChrType::ROM => cartridge.chr_rom.len() as u32, 716 ChrType::RAM => header.chr_ram_size, 717 }; 718 719 let mapper = match header.mapper_number { 720 0 => Mapper::Nrom(MapperImpl { 721 cartridge, 722 data: Nrom::new(header.chr_type, header.nametable_mirroring), 723 }), 724 1 => Mapper::Mmc1(MapperImpl { cartridge, data: Mmc1::new(header.chr_type) }), 725 2 | 71 => Mapper::Uxrom(MapperImpl { 726 cartridge, 727 data: Uxrom::new( 728 header.mapper_number, 729 header.sub_mapper_number, 730 header.chr_type, 731 header.nametable_mirroring, 732 ), 733 }), 734 3 => Mapper::Cnrom(MapperImpl { 735 cartridge, 736 data: Cnrom::new(header.chr_type, header.nametable_mirroring, header.sub_mapper_number), 737 }), 738 4 | 76 | 88 | 95 | 154 | 206 => Mapper::Mmc3(MapperImpl { 739 cartridge, 740 data: Mmc3::new( 741 header.chr_type, 742 header.prg_rom_size, 743 chr_size, 744 header.mapper_number, 745 header.sub_mapper_number, 746 header.nametable_mirroring, 747 header.has_four_screen_vram, 748 ), 749 }), 750 5 => Mapper::Mmc5(MapperImpl { cartridge, data: Mmc5::new() }), 751 7 => Mapper::Axrom(MapperImpl { cartridge, data: Axrom::new(header.chr_type) }), 752 9 => Mapper::Mmc2(MapperImpl { cartridge, data: Mmc2::new_mmc2() }), 753 10 => Mapper::Mmc2(MapperImpl { cartridge, data: Mmc2::new_mmc4() }), 754 11 | 66 | 140 => Mapper::Gxrom(MapperImpl { 755 cartridge, 756 data: Gxrom::new(header.mapper_number, header.nametable_mirroring), 757 }), 758 16 | 153 | 159 => Mapper::BandaiFcg(MapperImpl { 759 cartridge, 760 data: BandaiFcg::new( 761 header.mapper_number, 762 header.sub_mapper_number, 763 header.chr_type, 764 header.prg_ram_size, 765 sav_bytes.as_ref(), 766 ), 767 }), 768 19 => Mapper::Namco163(MapperImpl { 769 cartridge, 770 data: Namco163::new( 771 header.sub_mapper_number, 772 header.chr_type, 773 header.has_battery, 774 header.prg_ram_size, 775 sav_bytes, 776 ), 777 }), 778 21 | 22 | 23 | 25 => Mapper::Vrc4(MapperImpl { 779 cartridge, 780 data: Vrc4::new(header.mapper_number, header.sub_mapper_number, header.chr_type), 781 }), 782 24 | 26 => Mapper::Vrc6(MapperImpl { 783 cartridge, 784 data: Vrc6::new(header.mapper_number, header.chr_type), 785 }), 786 30 => { 787 let mapper = Unrom512::new(&original_prg_rom, &header); 788 Mapper::Unrom512(MapperImpl { cartridge, data: mapper }) 789 } 790 34 => Mapper::Bnrom(MapperImpl { 791 cartridge, 792 data: Bnrom::new(header.chr_type, header.nametable_mirroring), 793 }), 794 69 => Mapper::Sunsoft(MapperImpl { cartridge, data: Sunsoft::new(header.chr_type) }), 795 85 => Mapper::Vrc7(MapperImpl { 796 cartridge, 797 data: Vrc7::new(header.sub_mapper_number, header.chr_type), 798 }), 799 210 => Mapper::Namco175(MapperImpl { 800 cartridge, 801 data: Namco175::new( 802 header.sub_mapper_number, 803 header.chr_type, 804 header.nametable_mirroring, 805 ), 806 }), 807 228 => Mapper::Action52(MapperImpl { cartridge, data: Action52::new(header.prg_rom_size) }), 808 _ => { 809 return Err(CartridgeFileError::UnsupportedMapper { 810 mapper_number: header.mapper_number, 811 }); 812 } 813 }; 814 815 log::info!("Timing mode: {timing_mode}"); 816 log::info!("Mapper number: {} ({})", header.mapper_number, mapper.name()); 817 log::info!("PRG ROM size: {}", header.prg_rom_size); 818 log::info!("PRG RAM size: {}", header.prg_ram_size); 819 log::info!("Cartridge has battery-backed PRG RAM: {}", header.has_battery); 820 log::info!("CHR ROM size: {}", header.chr_rom_size); 821 log::info!("CHR RAM size: {}", header.chr_ram_size); 822 log::info!("CHR memory type: {:?}", header.chr_type); 823 log::info!( 824 "Hardwired nametable mirroring: {:?} (not applicable to all mappers)", 825 header.nametable_mirroring 826 ); 827 log::info!("Has 4-screen nametable VRAM: {}", header.has_four_screen_vram); 828 829 Ok(mapper) 830}