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} 42 43// Encode and Decode are implemented explicitly instead of using derive in order to avoid 44// 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} 56 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} 160 161// Not using derive macro because it only impls the trait when MapperData implements PartialClone, which is not a 162// necessary bound here 163impl<MapperData: Clone> PartialClone for MapperImpl<MapperData> { 164 fn partial_clone(&self) -> Self { 165 Self { cartridge: self.cartridge.partial_clone(), data: self.data.clone() } 166 } 167} 168 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 { 211 /// 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 } 235 236 /// Read a value from the given address in the CPU address space. 237 pub(crate) fn read_cpu_address(&mut self, address: u16, cpu_open_bus: u8) -> u8 { 238 match_each_variant!(self, mapper => mapper.read_cpu_address(address, cpu_open_bus)) 239 } 240 241 /// Write a value to the given address in the CPU address space. 242 pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) { 243 match_each_variant!(self, mapper => mapper.write_cpu_address(address, value)); 244 } 245 246 /// Read a value from the given address in the PPU address space. 247 pub(crate) fn read_ppu_address(&mut self, address: u16, vram: &[u8; 2048]) -> u8 { 248 match_each_variant!(self, mapper => mapper.read_ppu_address(address, vram)) 249 } 250 251 /// Write a value to the given address in the PPU address space. 252 pub(crate) fn write_ppu_address(&mut self, address: u16, value: u8, vram: &mut [u8; 2048]) { 253 match_each_variant!(self, mapper => mapper.write_ppu_address(address, value, vram)); 254 } 255 256 /// Perform any processing that should be performed after every PPU cycle. 257 pub(crate) fn tick(&mut self, ppu_bus_address: u16) { 258 if let Self::Mmc3(mmc3) = self { 259 mmc3.tick(ppu_bus_address); 260 } 261 } 262 263 /// Perform any processing that should be performed after every CPU cycle. Commonly used for 264 /// 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 } 294 295 /// 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 } 309 310 /// Process a PPUCTRL write. Only needed by the MMC5 mapper in order to know whether double 311 /// height sprites are enabled. 312 pub(crate) fn process_ppu_ctrl_update(&mut self, value: u8) { 313 if let Self::Mmc5(mmc5) = self { 314 mmc5.process_ppu_ctrl_update(value); 315 } 316 } 317 318 /// Process a PPUMASK write. Used by MMC5 to know whether rendering is currently enabled 319 pub(crate) fn process_ppu_mask_update(&mut self, value: u8) { 320 if let Self::Mmc5(mmc5) = self { 321 mmc5.process_ppu_mask_update(value); 322 } 323 } 324 325 /// Notify the mapper that the CPU will imminently access the PPUDATA register. This is required 326 /// by MMC5 to map PPUDATA reads/writes to the correct CHR banks. 327 /// 328 /// This should be called *before* the actual memory access. 329 pub(crate) fn about_to_access_ppu_data(&mut self) { 330 if let Self::Mmc5(mmc5) = self { 331 mmc5.about_to_access_ppu_data(); 332 } 333 } 334 335 /// Return whether the board's writable memory (if any) has been written to since the last time 336 /// 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 } 363 364 /// Return the board's writable memory as a slice. This will be an empty slice if the board 365 /// 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 } 383 384 /// Retrieve the timing mode of the cartridge (NTSC/PAL). 385 pub(crate) fn timing_mode(&self) -> TimingMode { 386 match_each_variant!(self, mapper => mapper.cartridge.timing_mode) 387 } 388 389 /// If the board has expansion audio, generate an audio sample and mix it with the mixed APU 390 /// sample. 391 /// 392 /// If the board does not have expansion audio or it is not enabled then this method will simply 393 /// 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 } 404 405 /// Move cartridge ROM fields from another `Mapper` instance. Used when loading save states. 406 pub(crate) fn move_rom_from(&mut self, other: &mut Self) { 407 let other_cartridge = match_each_variant!(other, mapper => &mut mapper.cartridge); 408 match_each_variant!(self, mapper => mapper.cartridge.move_rom_from(other_cartridge)); 409 } 410 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} 603 604// 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} 621 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} 654 655/// Parse cartridge data out of an iNES file. 656/// 657/// # Errors 658/// 659/// 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}