SNES cartridge loading and mapping code
3use crate::api::{CoprocessorRoms, SnesLoadError, SnesLoadResult}; 4use bincode::{Decode, Encode}; 5use crc::Crc; 6use jgenesis_common::frontend::{PartialClone, SaveWriter, TimingMode}; 7use jgenesis_proc_macros::{FakeDecode, FakeEncode}; 8use snes_coprocessors::cx4::Cx4; 9use snes_coprocessors::obc1::Obc1; 10use snes_coprocessors::sa1::Sa1; 11use snes_coprocessors::sdd1::Sdd1; 12use snes_coprocessors::spc7110::Spc7110; 13use snes_coprocessors::srtc::SRtc; 14use snes_coprocessors::st018::St018; 15use snes_coprocessors::superfx::SuperFx; 16use snes_coprocessors::upd77c25::{Upd77c25, Upd77c25Variant}; 17use snes_coprocessors::{superfx, upd77c25}; 18use std::cmp::Ordering; 19use std::fmt::{Display, Formatter}; 20use std::mem; 21use std::num::NonZeroU64; 22use std::ops::{Deref, Range};
24const CRC: Crc<u32> = Crc::<u32>::new(&crc::CRC_32_ISO_HDLC); 25 26#[derive(Debug, Clone, FakeEncode, FakeDecode)] 27pub struct Rom(pub Box<[u8]>); 28 29impl Default for Rom { 30 fn default() -> Self { 31 Rom(vec![].into_boxed_slice()) 32 } 33} 34 35impl Deref for Rom { 36 type Target = Box<[u8]>; 37 38 fn deref(&self) -> &Self::Target { 39 &self.0 40 } 41} 42 43#[derive(Debug, Clone, Copy, PartialEq, Eq)] 44enum CartridgeType { 45 LoRom, 46 HiRom, 47 ExHiRom, 48 Cx4, 49 Obc1, 50 Sa1, 51 Sdd1, 52 Spc7110, 53 SuperFx, 54} 55 56impl Display for CartridgeType { 57 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 58 match self { 59 Self::LoRom => write!(f, "LoROM"), 60 Self::HiRom => write!(f, "HiROM"), 61 Self::ExHiRom => write!(f, "ExHiROM"), 62 Self::Cx4 => write!(f, "CX4"), 63 Self::Obc1 => write!(f, "OBC1"), 64 Self::Sa1 => write!(f, "SA-1"), 65 Self::Sdd1 => write!(f, "S-DD1"), 66 Self::Spc7110 => write!(f, "SPC7110"), 67 Self::SuperFx => write!(f, "Super FX"), 68 } 69 } 70} 71 72#[derive(Debug, Clone, Copy, PartialEq, Eq)] 73enum DspVariant { 74 Dsp1, 75 Dsp2, 76 Dsp3, 77 Dsp4, 78} 79 80impl Display for DspVariant { 81 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 82 match self { 83 Self::Dsp1 => write!(f, "DSP-1"), 84 Self::Dsp2 => write!(f, "DSP-2"), 85 Self::Dsp3 => write!(f, "DSP-3"), 86 Self::Dsp4 => write!(f, "DSP-4"), 87 } 88 } 89} 90 91#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] 92pub struct DspLoromPortAddresses { 93 banks: Range<u32>, 94 offset_mask: u32, 95} 96 97impl DspLoromPortAddresses { 98 // PCB SHVC-1B0N-01 99 // DSP-1 or DSP-4 with no SRAM and at most 1MB of ROM 100 const DSP_1_4: Self = Self { banks: 0x30..0x40, offset_mask: 0x8000 }; 101 102 // PCB SHVC-2B3B-01 103 // DSP-1 with 8KB SRAM and up to 2MB of ROM 104 const DSP_1_LARGE: Self = Self { banks: 0x60..0x70, offset_mask: 0x0000 }; 105 106 // PCB SHVC-1B5B-01 (DSP-2 w/ 32KB SRAM) or SHVC-1B3B-01 (DSP-3 w/ 8KB SRAM) 107 const DSP_2_3: Self = Self { banks: 0x20..0x40, offset_mask: 0x8000 }; 108 109 fn from_metadata(rom_len: usize, sram_len: usize) -> Self { 110 if rom_len > 1024 * 1024 { 111 // More than 1MB of ROM; assume DSP-1 large variant 112 Self::DSP_1_LARGE 113 } else if sram_len != 0 { 114 // At most 1MB of ROM but has SRAM; assume DSP-2 or DSP-3 115 Self::DSP_2_3 116 } else { 117 // At most 1MB of ROM and no SRAM; assume DSP-1 normal variant or DSP-4 118 Self::DSP_1_4 119 } 120 } 121 122 fn is_dsp_port(&self, bank: u32, offset: u32) -> bool { 123 self.banks.contains(&(bank & 0x7F)) && offset & 0x8000 == self.offset_mask 124 } 125} 126 127#[derive(Debug, Clone, Copy, PartialEq, Eq)] 128enum St01xVariant { 129 St010, 130 St011, 131} 132 133impl From<St01xVariant> for Upd77c25Variant { 134 fn from(value: St01xVariant) -> Self { 135 match value { 136 St01xVariant::St010 => Self::St010, 137 St01xVariant::St011 => Self::St011, 138 } 139 } 140} 141 142impl Display for St01xVariant { 143 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 144 match self { 145 Self::St010 => write!(f, "ST010"), 146 Self::St011 => write!(f, "ST011"), 147 } 148 } 149} 150 151const LOROM_HEADER_ADDR: usize = 0x007FC0; 152const HIROM_HEADER_ADDR: usize = 0x00FFC0; 153const EXHIROM_HEADER_ADDR: usize = 0x40FFC0; 154 155const HEADER_MAP_OFFSET: usize = 0x15; 156 157const LOROM_RESET_VECTOR: usize = 0x7FFC; 158const HIROM_RESET_VECTOR: usize = 0xFFFC; 159 160#[derive(Debug, Clone, Encode, Decode, PartialClone)] 161pub enum Cartridge { 162 LoRom { 163 #[partial_clone(default)] 164 rom: Rom, 165 sram: Box<[u8]>, 166 }, 167 HiRom { 168 #[partial_clone(default)] 169 rom: Rom, 170 sram: Box<[u8]>, 171 }, 172 ExHiRom { 173 #[partial_clone(default)] 174 rom: Rom, 175 sram: Box<[u8]>, 176 srtc: Option<SRtc>, 177 }, 178 Cx4(#[partial_clone(partial)] Cx4), 179 DspLoRom { 180 #[partial_clone(default)] 181 rom: Rom, 182 sram: Box<[u8]>, 183 upd77c25: Upd77c25, 184 port_addresses: DspLoromPortAddresses, 185 }, 186 DspHiRom { 187 #[partial_clone(default)] 188 rom: Rom, 189 sram: Box<[u8]>, 190 upd77c25: Upd77c25, 191 }, 192 Obc1(#[partial_clone(partial)] Obc1), 193 Sa1(#[partial_clone(partial)] Sa1), 194 Sdd1(#[partial_clone(partial)] Sdd1), 195 Spc7110(#[partial_clone(partial)] Spc7110), 196 SuperFx(#[partial_clone(partial)] SuperFx), 197 St01x { 198 #[partial_clone(default)] 199 rom: Rom, 200 upd77c25: Upd77c25, 201 }, 202 St018 { 203 #[partial_clone(default)] 204 rom: Rom, 205 sram: Box<[u8]>, 206 st018: St018, 207 }, 208} 209 210impl Cartridge { 211 pub fn create<S: SaveWriter>( 212 mut rom: Vec<u8>, 213 initial_sram: Option<Vec<u8>>, 214 coprocessor_roms: &CoprocessorRoms, 215 forced_timing_mode: Option<TimingMode>, 216 gsu_overclock_factor: NonZeroU64, 217 save_writer: &mut S, 218 ) -> SnesLoadResult<(Self, TimingMode)> { 219 // Older SNES ROM images have an extra 512-byte header; check for that and strip it off 220 if rom.len() & 0x7FFF == 0x0200 { 221 rom = rom[0x200..].to_vec(); 222 } 223 224 if rom.len() < 32 * 1024 { 225 return Err(SnesLoadError::RomTooSmall { size: rom.len() }); 226 } 227 228 let cartridge_type = guess_cartridge_type(&rom).unwrap_or_else(|| { 229 log::error!("Unable to confidently determine ROM type; defaulting to LoROM"); 230 CartridgeType::LoRom 231 }); 232 233 if cartridge_type != CartridgeType::Spc7110 { 234 jgenesis_common::rom::mirror_to_next_power_of_two(&mut rom); 235 log::debug!("ROM length after mirroring is {} bytes", rom.len()); 236 237 debug_assert_eq!(rom.len().count_ones(), 1); 238 } 239 240 let rom = rom.into_boxed_slice(); 241 242 let rom_header_addr = match cartridge_type { 243 CartridgeType::LoRom 244 | CartridgeType::Cx4 245 | CartridgeType::Obc1 246 | CartridgeType::Sa1 247 | CartridgeType::Sdd1 248 | CartridgeType::SuperFx => LOROM_HEADER_ADDR, 249 CartridgeType::HiRom | CartridgeType::Spc7110 => HIROM_HEADER_ADDR, 250 CartridgeType::ExHiRom => EXHIROM_HEADER_ADDR, 251 }; 252 253 // Determine NTSC/PAL 254 let region_byte = rom[rom_header_addr + 0x19]; 255 let timing_mode = 256 forced_timing_mode.unwrap_or_else(|| region_to_timing_mode(region_byte, &rom)); 257 258 // $FFD8 contains SRAM size as a kilobytes power of 2 259 let sram_header_byte = rom[rom_header_addr | 0x0018]; 260 261 // Check if cartridge is ST010/ST011; these don't report RAM size in the header (always 4KB) 262 let chipset_byte = rom[rom_header_addr + 0x16]; 263 let is_st01x = chipset_byte == 0xF6 264 && rom[rom_header_addr + 0x14] == 0x00 265 && rom[rom_header_addr - 1] == 0x01; 266 267 let sram_len = if is_st01x { 268 upd77c25::ST01X_RAM_LEN_BYTES 269 } else if cartridge_type == CartridgeType::SuperFx { 270 superfx::guess_ram_len(&rom) 271 } else if sram_header_byte == 0 { 272 0 273 } else if sram_header_byte > 21 { 274 log::warn!( 275 "Invalid SRAM header byte ${sram_header_byte:02X}, giving 256KB of SRAM; game may not work properly" 276 ); 277 256 * 1024 278 } else { 279 1 << (10 + sram_header_byte) 280 }; 281 282 let sram = match initial_sram { 283 Some(sram) if sram.len() == sram_len => sram.into_boxed_slice(), 284 _ => vec![0xFF; sram_len].into_boxed_slice(), 285 }; 286 287 log::info!("Using mapper {cartridge_type} with SRAM size {sram_len}"); 288 289 if is_st01x { 290 let st01x_variant = guess_st01x_variant(&rom); 291 292 log::info!("Detected {st01x_variant} coprocessor"); 293 294 let st01x_rom_fn = match st01x_variant { 295 St01xVariant::St010 => { 296 coprocessor_roms.st010.as_ref().ok_or(SnesLoadError::MissingSt010Rom)? 297 } 298 St01xVariant::St011 => { 299 coprocessor_roms.st011.as_ref().ok_or(SnesLoadError::MissingSt011Rom)? 300 } 301 }; 302 303 let st01x_rom = st01x_rom_fn() 304 .map_err(|(source, path)| SnesLoadError::CoprocessorRomLoad { source, path })?; 305 let upd77c25 = Upd77c25::new(&st01x_rom, st01x_variant.into(), &sram, timing_mode); 306 307 return Ok((Self::St01x { rom: Rom(rom), upd77c25 }, timing_mode)); 308 } 309 310 let is_st018 = chipset_byte == 0xF5 && rom[rom_header_addr - 1] == 0x02; 311 if is_st018 { 312 log::info!("Detected ST018 coprocessor"); 313 314 let st018_rom_fn = 315 coprocessor_roms.st018.as_ref().ok_or(SnesLoadError::MissingSt018Rom)?; 316 let st018_rom = st018_rom_fn() 317 .map_err(|(source, path)| SnesLoadError::CoprocessorRomLoad { source, path })?; 318 let st018 = St018::new(&st018_rom).map_err(SnesLoadError::St018RomLoad)?; 319 320 return Ok((Self::St018 { rom: Rom(rom), sram, st018 }, timing_mode)); 321 } 322 323 let rom_checksum = CRC.checksum(&rom); 324 log::info!("ROM CRC32: {rom_checksum:08X}"); 325 326 // Check for DSP-1/2/3/4 coprocessor (identified by chipset $03-$05, can be LoROM or HiROM) 327 let force_dsp1 = should_force_dsp1(rom_checksum); 328 if force_dsp1 329 || ((0x03..0x06).contains(&chipset_byte) 330 && matches!(cartridge_type, CartridgeType::LoRom | CartridgeType::HiRom)) 331 { 332 let dsp_variant = guess_dsp_variant(rom_checksum); 333 334 log::info!("Detected DSP coprocessor of type {dsp_variant}"); 335 336 let dsp_rom_fn = match dsp_variant { 337 DspVariant::Dsp1 => { 338 coprocessor_roms.dsp1.as_ref().ok_or(SnesLoadError::MissingDsp1Rom)? 339 } 340 DspVariant::Dsp2 => { 341 coprocessor_roms.dsp2.as_ref().ok_or(SnesLoadError::MissingDsp2Rom)? 342 } 343 DspVariant::Dsp3 => { 344 coprocessor_roms.dsp3.as_ref().ok_or(SnesLoadError::MissingDsp3Rom)? 345 } 346 DspVariant::Dsp4 => { 347 coprocessor_roms.dsp4.as_ref().ok_or(SnesLoadError::MissingDsp4Rom)? 348 } 349 }; 350 351 let dsp_rom = dsp_rom_fn() 352 .map_err(|(source, path)| SnesLoadError::CoprocessorRomLoad { source, path })?; 353 let upd77c25 = Upd77c25::new(&dsp_rom, Upd77c25Variant::Dsp, &sram, timing_mode); 354 355 return match cartridge_type { 356 CartridgeType::LoRom => { 357 let port_addresses = DspLoromPortAddresses::from_metadata(rom.len(), sram_len); 358 359 log::debug!( 360 "DSP-n port banks: {:02X?}, offset mask: {:04X}", 361 port_addresses.banks, 362 port_addresses.offset_mask 363 ); 364 365 Ok(( 366 Self::DspLoRom { rom: Rom(rom), sram, upd77c25, port_addresses }, 367 timing_mode, 368 )) 369 } 370 CartridgeType::HiRom => { 371 Ok((Self::DspHiRom { rom: Rom(rom), sram, upd77c25 }, timing_mode)) 372 } 373 _ => unreachable!("nested match expressions"), 374 }; 375 } 376 377 let cartridge = match cartridge_type { 378 CartridgeType::LoRom => Self::LoRom { rom: Rom(rom), sram }, 379 CartridgeType::HiRom => Self::HiRom { rom: Rom(rom), sram }, 380 CartridgeType::ExHiRom => new_exhirom_cartridge(rom, sram, save_writer), 381 CartridgeType::Cx4 => Self::Cx4(Cx4::new(rom)), 382 CartridgeType::Obc1 => Self::Obc1(Obc1::new(rom, sram)), 383 CartridgeType::Sa1 => Self::Sa1(Sa1::new(rom, sram, timing_mode)), 384 CartridgeType::Sdd1 => Self::Sdd1(Sdd1::new(rom, sram)), 385 CartridgeType::Spc7110 => Self::Spc7110(Spc7110::new(rom, sram, save_writer)), 386 CartridgeType::SuperFx => Self::SuperFx(SuperFx::new(rom, sram, gsu_overclock_factor)), 387 }; 388 389 Ok((cartridge, timing_mode)) 390 } 391 392 pub fn read(&mut self, address: u32) -> Option<u8> { 393 let bank = (address >> 16) & 0xFF; 394 let offset = address & 0xFFFF; 395 let (mapped_address, rom, sram) = match self { 396 Self::LoRom { rom, sram } => { 397 (lorom_map_address(address, rom.len() as u32, sram.len() as u32), rom, sram) 398 } 399 Self::DspLoRom { rom, sram, upd77c25, port_addresses } => { 400 if port_addresses.is_dsp_port(bank, offset) { 401 return Some(if offset & 0x4000 == 0 { 402 upd77c25.read_data() 403 } else { 404 upd77c25.read_status() 405 }); 406 } 407 408 (lorom_map_address(address, rom.len() as u32, sram.len() as u32), rom, sram) 409 } 410 Self::HiRom { rom, sram } => { 411 (hirom_map_address(address, rom.len() as u32, sram.len() as u32), rom, sram) 412 } 413 Self::DspHiRom { rom, sram, upd77c25 } => match (bank, offset) { 414 (0x00..=0x0F | 0x80..=0x8F, 0x6000..=0x6FFF) => return Some(upd77c25.read_data()), 415 (0x00..=0x0F | 0x80..=0x8F, 0x7000..=0x7FFF) => { 416 return Some(upd77c25.read_status()); 417 } 418 _ => (hirom_map_address(address, rom.len() as u32, sram.len() as u32), rom, sram), 419 }, 420 Self::ExHiRom { rom, sram, srtc, .. } => match (bank, offset, srtc) { 421 (0x00..=0x3F | 0x80..=0xBF, 0x2800, Some(srtc)) => return Some(srtc.read()), 422 _ => (exhirom_map_address(address, rom.len() as u32, sram.len() as u32), rom, sram), 423 }, 424 Self::Cx4(cx4) => return cx4.read(address), 425 Self::Obc1(obc1) => return obc1.read(address), 426 Self::Sa1(sa1) => return sa1.snes_read(address), 427 Self::Sdd1(sdd1) => return sdd1.read(address), 428 Self::Spc7110(spc7110) => return spc7110.read(address), 429 Self::SuperFx(sfx) => return sfx.read(address), 430 Self::St01x { rom, upd77c25 } => { 431 return match (bank, offset) { 432 (0x60..=0x67, 0x0000) => Some(upd77c25.read_data()), 433 (0x60..=0x67, 0x0001) => Some(upd77c25.read_status()), 434 (0x68..=0x6F, 0x0000..=0x0FFF) => { 435 let sram_addr = ((bank & 0x7) << 12) | (offset & 0xFFF); 436 Some(upd77c25.read_ram(sram_addr)) 437 } 438 _ => match lorom_map_address(address, rom.len() as u32, 0) { 439 CartridgeAddress::Rom(rom_addr) => Some(rom[rom_addr as usize]), 440 _ => None, 441 }, 442 }; 443 } 444 Self::St018 { rom, sram, st018 } => { 445 return match (bank, offset) { 446 (0x00..=0x3F | 0x80..=0xBF, 0x3800..=0x3804) => st018.snes_read(address), 447 (0x68..=0x6F, 0x0000..=0x7FFF) => { 448 let sram_addr = (offset as usize) & (sram.len() - 1); 449 Some(sram[sram_addr]) 450 } 451 _ => match lorom_map_address(address, rom.len() as u32, 0) { 452 CartridgeAddress::Rom(rom_addr) => Some(rom[rom_addr as usize]), 453 _ => None, 454 }, 455 }; 456 } 457 }; 458 459 match mapped_address { 460 CartridgeAddress::None => None, 461 CartridgeAddress::Rom(rom_addr) => Some(rom[rom_addr as usize]), 462 CartridgeAddress::Sram(sram_addr) => Some(sram[sram_addr as usize]), 463 } 464 } 465 466 pub fn write(&mut self, address: u32, value: u8) { 467 let bank = (address >> 16) & 0xFF; 468 let offset = address & 0xFFFF; 469 match self { 470 Self::LoRom { rom, sram } => { 471 if let CartridgeAddress::Sram(sram_addr) = 472 lorom_map_address(address, rom.len() as u32, sram.len() as u32) 473 { 474 sram[sram_addr as usize] = value; 475 } 476 } 477 Self::DspLoRom { rom, sram, upd77c25, port_addresses } => { 478 if port_addresses.is_dsp_port(bank, offset) { 479 if offset & 0x4000 == 0 { 480 upd77c25.write_data(value); 481 } 482 // Status port is not writable 483 return; 484 } 485 486 if let CartridgeAddress::Sram(sram_addr) = 487 lorom_map_address(address, rom.len() as u32, sram.len() as u32) 488 { 489 sram[sram_addr as usize] = value; 490 } 491 } 492 Self::HiRom { rom, sram, .. } => { 493 if let CartridgeAddress::Sram(sram_addr) = 494 hirom_map_address(address, rom.len() as u32, sram.len() as u32) 495 { 496 sram[sram_addr as usize] = value; 497 } 498 } 499 Self::DspHiRom { rom, sram, upd77c25 } => match (bank, offset) { 500 (0x00..=0x0F | 0x80..=0x8F, 0x6000..=0x6FFF) => { 501 upd77c25.write_data(value); 502 } 503 _ => { 504 if let CartridgeAddress::Sram(sram_addr) = 505 hirom_map_address(address, rom.len() as u32, sram.len() as u32) 506 { 507 sram[sram_addr as usize] = value; 508 } 509 } 510 }, 511 Self::ExHiRom { rom, sram, srtc, .. } => match (bank, offset, srtc) { 512 (0x00..=0x3F | 0x80..=0xBF, 0x2801, Some(srtc)) => srtc.write(value), 513 _ => { 514 if let CartridgeAddress::Sram(sram_addr) = 515 exhirom_map_address(address, rom.len() as u32, sram.len() as u32) 516 { 517 sram[sram_addr as usize] = value; 518 } 519 } 520 }, 521 Self::Cx4(cx4) => { 522 cx4.write(address, value); 523 } 524 Self::Obc1(obc1) => { 525 obc1.write(address, value); 526 } 527 Self::Sa1(sa1) => { 528 sa1.snes_write(address, value); 529 } 530 Self::Sdd1(sdd1) => { 531 sdd1.write(address, value); 532 } 533 Self::Spc7110(spc7110) => { 534 spc7110.write(address, value); 535 } 536 Self::SuperFx(sfx) => { 537 sfx.write(address, value); 538 } 539 Self::St01x { upd77c25, .. } => match (bank, offset) { 540 (0x60..=0x67, 0x0000) => upd77c25.write_data(value), 541 (0x68..=0x6F, 0x0000..=0x0FFF) => { 542 let sram_addr = ((bank & 0x7) << 12) | (offset & 0xFFF); 543 upd77c25.write_ram(sram_addr, value); 544 } 545 _ => {} 546 }, 547 Self::St018 { sram, st018, .. } => match (bank, offset) { 548 (0x00..=0x3F | 0x80..=0xBF, 0x3800..=0x3804) => st018.snes_write(address, value), 549 (0x68..=0x6F, 0x0000..=0x7FFF) => { 550 let sram_addr = (address as usize) & (sram.len() - 1); 551 sram[sram_addr] = value; 552 } 553 _ => {} 554 }, 555 } 556 } 557 558 pub fn irq(&self) -> bool { 559 match self { 560 Self::Sa1(sa1) => sa1.snes_irq(), 561 Self::SuperFx(sfx) => sfx.irq(), 562 _ => false, 563 } 564 } 565 566 pub fn take_rom(&mut self) -> Vec<u8> { 567 match self { 568 Self::LoRom { rom, .. } 569 | Self::HiRom { rom, .. } 570 | Self::ExHiRom { rom, .. } 571 | Self::DspLoRom { rom, .. } 572 | Self::DspHiRom { rom, .. } 573 | Self::St01x { rom, .. } 574 | Self::St018 { rom, .. } => mem::take(&mut rom.0).into_vec(), 575 Self::Cx4(cx4) => cx4.take_rom(), 576 Self::Obc1(obc1) => obc1.take_rom(), 577 Self::Sa1(sa1) => sa1.take_rom(), 578 Self::Sdd1(sdd1) => sdd1.take_rom(), 579 Self::Spc7110(spc7110) => spc7110.take_rom(), 580 Self::SuperFx(sfx) => sfx.take_rom(), 581 } 582 } 583 584 pub fn take_rom_from(&mut self, other: &mut Self) { 585 let other_rom = other.take_rom(); 586 587 match self { 588 Self::LoRom { rom, .. } 589 | Self::HiRom { rom, .. } 590 | Self::ExHiRom { rom, .. } 591 | Self::DspLoRom { rom, .. } 592 | Self::DspHiRom { rom, .. } 593 | Self::St01x { rom, .. } 594 | Self::St018 { rom, .. } => { 595 *rom = Rom(other_rom.into_boxed_slice()); 596 } 597 Self::Cx4(cx4) => { 598 cx4.set_rom(other_rom); 599 } 600 Self::Obc1(obc1) => { 601 obc1.set_rom(other_rom); 602 } 603 Self::Sa1(sa1) => { 604 sa1.set_rom(other_rom); 605 } 606 Self::Sdd1(sdd1) => { 607 sdd1.set_rom(other_rom); 608 } 609 Self::Spc7110(spc7110) => { 610 spc7110.set_rom(other_rom); 611 } 612 Self::SuperFx(sfx) => { 613 sfx.set_rom(other_rom); 614 } 615 } 616 } 617 618 pub fn has_battery(&self) -> bool { 619 match self { 620 Self::Cx4(..) => false, 621 Self::ExHiRom { .. } 622 | Self::Obc1(..) 623 | Self::Spc7110(..) 624 | Self::St01x { .. } 625 | Self::St018 { .. } => true, 626 Self::LoRom { sram, .. } 627 | Self::HiRom { sram, .. } 628 | Self::DspLoRom { sram, .. } 629 | Self::DspHiRom { sram, .. } => !sram.is_empty(), 630 Self::Sa1(sa1) => sa1.has_battery(), 631 Self::Sdd1(sdd1) => sdd1.has_battery(), 632 Self::SuperFx(sfx) => sfx.has_battery(), 633 } 634 } 635 636 pub fn sram(&self) -> Option<&[u8]> { 637 match self { 638 Self::LoRom { sram, .. } 639 | Self::HiRom { sram, .. } 640 | Self::ExHiRom { sram, .. } 641 | Self::DspLoRom { sram, .. } 642 | Self::DspHiRom { sram, .. } 643 if !sram.is_empty() => 644 { 645 Some(sram) 646 } 647 Self::LoRom { .. } 648 | Self::HiRom { .. } 649 | Self::ExHiRom { .. } 650 | Self::DspLoRom { .. } 651 | Self::DspHiRom { .. } 652 | Self::Cx4 { .. } => None, 653 Self::Obc1(obc1) => Some(obc1.sram()), 654 Self::Sa1(sa1) => sa1.sram(), 655 Self::Sdd1(sdd1) => sdd1.sram(), 656 Self::Spc7110(spc7110) => Some(spc7110.sram()), 657 Self::SuperFx(sfx) => Some(sfx.sram()), 658 Self::St01x { upd77c25, .. } => Some(upd77c25.sram()), 659 Self::St018 { sram, .. } => Some(sram), 660 } 661 } 662 663 pub fn write_auxiliary_save_files<S: SaveWriter>( 664 &self, 665 save_writer: &mut S, 666 ) -> Result<(), S::Err> { 667 match self { 668 Self::ExHiRom { srtc: Some(srtc), .. } => { 669 save_writer.persist_serialized("rtc", srtc)?; 670 } 671 Self::Spc7110(spc7110) => { 672 if let Some(rtc) = spc7110.rtc() { 673 save_writer.persist_serialized("rtc", rtc)?; 674 } 675 } 676 _ => {} 677 } 678 679 Ok(()) 680 } 681 682 pub fn tick(&mut self, master_cycles_elapsed: u64) { 683 match self { 684 Self::DspLoRom { upd77c25, .. } 685 | Self::DspHiRom { upd77c25, .. } 686 | Self::St01x { upd77c25, .. } => { 687 upd77c25.tick(master_cycles_elapsed); 688 } 689 Self::Sa1(sa1) => { 690 sa1.tick(master_cycles_elapsed); 691 } 692 Self::SuperFx(sfx) => { 693 sfx.tick(master_cycles_elapsed); 694 } 695 Self::St018 { st018, .. } => { 696 st018.tick(master_cycles_elapsed); 697 } 698 _ => {} 699 } 700 } 701 702 pub fn reset(&mut self) { 703 match self { 704 Self::ExHiRom { srtc: Some(srtc), .. } => { 705 srtc.reset_state(); 706 } 707 Self::DspLoRom { upd77c25, .. } 708 | Self::DspHiRom { upd77c25, .. } 709 | Self::St01x { upd77c25, .. } => { 710 upd77c25.reset(); 711 } 712 Self::Sa1(sa1) => { 713 sa1.reset(); 714 } 715 Self::Sdd1(sdd1) => { 716 sdd1.notify_dma_end(); 717 } 718 Self::SuperFx(sfx) => { 719 sfx.reset(); 720 } 721 _ => {} 722 } 723 } 724 725 pub fn notify_dma_start(&mut self, channel: u8, source_address: u32) { 726 match self { 727 Self::Sa1(sa1) => sa1.notify_dma_start(source_address), 728 Self::Sdd1(sdd1) => sdd1.notify_dma_start(channel, source_address), 729 _ => {} 730 } 731 } 732 733 pub fn notify_dma_end(&mut self) { 734 match self { 735 Self::Sa1(sa1) => sa1.notify_dma_end(), 736 Self::Sdd1(sdd1) => sdd1.notify_dma_end(), 737 _ => {} 738 } 739 } 740 741 pub fn update_gsu_overclock_factor(&mut self, overclock_factor: NonZeroU64) { 742 if let Self::SuperFx(sfx) = self { 743 sfx.update_gsu_overclock_factor(overclock_factor); 744 } 745 } 746} 747 748fn new_exhirom_cartridge<S: SaveWriter>( 749 rom: Box<[u8]>, 750 initial_sram: Box<[u8]>, 751 save_writer: &mut S, 752) -> Cartridge { 753 // Chipset byte of $55 indicates S-RTC present (only used by Daikaijuu Monogatari II) 754 let has_srtc = rom[EXHIROM_HEADER_ADDR + 0x16] == 0x55; 755 let srtc = has_srtc.then(|| { 756 let mut srtc = save_writer.load_serialized("rtc").ok().unwrap_or_else(SRtc::new); 757 srtc.reset_state(); 758 srtc 759 }); 760 761 log::info!("ExHiROM cartridge has S-RTC: {}", srtc.is_some()); 762 763 Cartridge::ExHiRom { rom: Rom(rom), sram: initial_sram, srtc } 764} 765 766fn region_to_timing_mode(region_byte: u8, rom: &[u8]) -> TimingMode { 767 // Games with invalid region bytes that require PAL timings 768 const FORCE_PAL_CHECKSUMS: &[u32] = &[ 769 0x39E5E4A3, // Tintin in Tibet (Europe) (En,Es,Sv) 770 0x9F279963, // Total Football (Europe) (Proto) 771 ]; 772 773 match region_byte { 774 // Japan / USA / South Korea / Canada / Brazil 775 0x00 | 0x01 | 0x0D | 0x0F | 0x10 => TimingMode::Ntsc, 776 // various European and Asian countries (other than Japan/Korea) + Australia 777 0x02..=0x0C | 0x11 => TimingMode::Pal, 778 _ => { 779 let checksum = CRC.checksum(rom); 780 if FORCE_PAL_CHECKSUMS.contains(&checksum) { 781 TimingMode::Pal 782 } else { 783 log::warn!( 784 "Unrecognized region byte in ROM header, defaulting to NTSC: {region_byte:02X}" 785 ); 786 TimingMode::Ntsc 787 } 788 } 789 } 790} 791 792fn guess_cartridge_type(rom: &[u8]) -> Option<CartridgeType> { 793 if rom.len() < 0x8000 { 794 log::error!("ROM is too small; all ROMs should be at least 32KB, was {} bytes", rom.len()); 795 return None; 796 } 797 798 if rom.len() < 0x10000 { 799 // Any ROM less than 64KB must be LoROM; HiROM <64KB wouldn't have anywhere to store 800 // the 65816 interrupt vectors 801 if let Some(coprocessor) = check_for_lorom_coprocessor(rom) { 802 return Some(coprocessor); 803 } 804 805 return Some(CartridgeType::LoRom); 806 } 807 808 if rom.len() >= 0x410000 { 809 // $25 = ExHiROM, $35 = ExHiROM + FastROM 810 // A ROM >4MB with $25/$35 in the header is almost certainly ExHiROM; only 2 (?) non-ExHiROM 811 // games are larger than 4MB 812 let exhirom_map_byte = rom[EXHIROM_HEADER_ADDR + HEADER_MAP_OFFSET]; 813 if exhirom_map_byte == 0x25 || exhirom_map_byte == 0x35 { 814 return Some(CartridgeType::ExHiRom); 815 } 816 } 817 818 let mut lorom_points = 0; 819 let mut hirom_points = 0; 820 821 let lorom_map_byte = rom[LOROM_HEADER_ADDR + HEADER_MAP_OFFSET]; 822 if lorom_map_byte == 0x20 || lorom_map_byte == 0x30 { 823 // $20 == LoROM, $30 == LoROM + FastROM 824 lorom_points += 1; 825 } 826 827 let hirom_map_byte = rom[HIROM_HEADER_ADDR + HEADER_MAP_OFFSET]; 828 if hirom_map_byte == 0x21 || hirom_map_byte == 0x31 { 829 // $21 == HiROM, $31 == HiROM + FastROM 830 hirom_points += 1; 831 } 832 833 // All LoROM vectors should be in the range $8000-$FFFF, and A15 is ignored for mapping to ROM 834 let lorom_vector = u16::from_le_bytes([rom[LOROM_RESET_VECTOR], rom[LOROM_RESET_VECTOR + 1]]); 835 if lorom_vector >= 0x8000 && seems_like_valid_reset_vector(rom, lorom_vector & 0x7FFF) { 836 lorom_points += 1; 837 } 838 839 let hirom_vector = u16::from_le_bytes([rom[HIROM_RESET_VECTOR], rom[HIROM_RESET_VECTOR + 1]]); 840 if seems_like_valid_reset_vector(rom, hirom_vector) { 841 hirom_points += 1; 842 } 843 844 // Check for any coprocessors that put the header in the LoROM header location 845 if lorom_points >= hirom_points 846 && let Some(coprocessor) = check_for_lorom_coprocessor(rom) 847 { 848 return Some(coprocessor); 849 } 850 851 // Check for SPC7110 852 // Identified by chipset == $F5/$F9 and sub-chipset == $00 in HiROM header area 853 let hirom_chipset_byte = rom[HIROM_HEADER_ADDR + 0x16]; 854 if hirom_points >= lorom_points 855 && rom[HIROM_HEADER_ADDR + 0x1A] == 0x33 856 && (hirom_chipset_byte == 0xF5 || hirom_chipset_byte == 0xF9) 857 && rom[HIROM_HEADER_ADDR - 1] == 0x00 858 { 859 return Some(CartridgeType::Spc7110); 860 } 861 862 match lorom_points.cmp(&hirom_points) { 863 Ordering::Less => Some(CartridgeType::HiRom), 864 Ordering::Greater => Some(CartridgeType::LoRom), 865 Ordering::Equal => None, 866 } 867} 868 869fn check_for_lorom_coprocessor(rom: &[u8]) -> Option<CartridgeType> { 870 const FORCE_COPROCESSOR_CHECKSUMS: &[(u32, CartridgeType)] = &[ 871 (0x239D2862, CartridgeType::Sa1), // SA1 Demonstration Program (Unknown) (Tech Demo) 872 ]; 873 874 let checksum = CRC.checksum(rom); 875 for &(other_checksum, cartridge_type) in FORCE_COPROCESSOR_CHECKSUMS { 876 if checksum == other_checksum { 877 return Some(cartridge_type); 878 } 879 } 880 881 let lorom_map_byte = rom[LOROM_HEADER_ADDR + HEADER_MAP_OFFSET]; 882 883 // Check for CX4 884 // Identified by chipset == $Fx and subtype == $10 885 if rom[LOROM_HEADER_ADDR + 0x1A] == 0x33 886 && rom[LOROM_HEADER_ADDR + 0x16] & 0xF0 == 0xF0 887 && rom[LOROM_HEADER_ADDR - 1] == 0x10 888 { 889 return Some(CartridgeType::Cx4); 890 } 891 892 // Check for S-DD1 893 // Identified by map == $22/$32 and chipset == $4x in the LoROM header area 894 if (lorom_map_byte == 0x22 || lorom_map_byte == 0x32) 895 && (0x43..0x46).contains(&rom[LOROM_HEADER_ADDR + 0x16]) 896 { 897 return Some(CartridgeType::Sdd1); 898 } 899 900 // Check for SA-1 901 // Identified by map == $23 and chipset == $3x in the LoROM header area 902 if lorom_map_byte == 0x23 && (0x33..0x36).contains(&rom[LOROM_HEADER_ADDR + 0x16]) { 903 return Some(CartridgeType::Sa1); 904 } 905 906 // Check for Super FX 907 // Identified by map == $20 and chipset $13-$1A in the LoROM header area 908 if lorom_map_byte == 0x20 && (0x13..0x1B).contains(&rom[LOROM_HEADER_ADDR + 0x16]) { 909 return Some(CartridgeType::SuperFx); 910 } 911 912 // Check for OBC1 913 // Identified by chipset $25 in the LoROM header area 914 if rom[LOROM_HEADER_ADDR + 0x16] == 0x25 { 915 return Some(CartridgeType::Obc1); 916 } 917 918 None 919} 920 921fn should_force_dsp1(checksum: u32) -> bool { 922 // DSP1 Tech Demo (World) (Tech Demo) 923 checksum == 0xD18A00CD 924} 925 926fn guess_dsp_variant(checksum: u32) -> DspVariant { 927 match checksum { 928 // Dungeon Master (U/J/J Rev 1/E) 929 0x0DFD9CEB | 0x7A1BA194 | 0xAA79FA33 | 0x89A67ADF => DspVariant::Dsp2, 930 // SD Gundam GX (J) 931 0x4DC3D903 => DspVariant::Dsp3, 932 // Top Gear 3000 (U/E) / The Planet's Champ TG 3000 (J) 933 0xA20BE998 | 0x493FDB13 | 0xB9B9DF06 => DspVariant::Dsp4, 934 _ => DspVariant::Dsp1, 935 } 936} 937 938fn guess_st01x_variant(rom: &[u8]) -> St01xVariant { 939 let checksum = CRC.checksum(rom); 940 941 // Hayazashi Nidan Morita Shougi (J) 942 if checksum == 0x81E822AD { St01xVariant::St011 } else { St01xVariant::St010 } 943} 944 945const CLC_OPCODE: u8 = 0x18; 946const SEI_OPCODE: u8 = 0x78; 947 948fn seems_like_valid_reset_vector(rom: &[u8], vector: u16) -> bool { 949 // Nearly all games execute either SEI or CLC as the first instruction at the RESET vector 950 let vector = vector as usize; 951 vector < rom.len() && (rom[vector] == CLC_OPCODE || rom[vector] == SEI_OPCODE) 952} 953 954pub(crate) enum CartridgeAddress { 955 None, 956 Rom(u32), 957 Sram(u32), 958} 959 960pub(crate) fn lorom_map_address(address: u32, rom_len: u32, sram_len: u32) -> CartridgeAddress { 961 let bank = address >> 16; 962 let offset = address & 0xFFFF; 963 match (bank, offset) { 964 (0x00..=0x3F | 0x80..=0xBF | 0x70..=0x7D | 0xF0..=0xFF, 0x8000..=0xFFFF) 965 | (0x40..=0x6F | 0xC0..=0xEF, _) => { 966 // ROM; typically at $8000-$FFFF and sometimes mirrored into $0000-$7FFF 967 let rom_addr = lorom_map_rom_address(address, rom_len); 968 CartridgeAddress::Rom(rom_addr) 969 } 970 (0x70..=0x7D | 0xF0..=0xFF, 0x0000..=0x7FFF) => { 971 // SRAM, if mapped 972 if sram_len != 0 { 973 // Ignore A15 and shift A16-19 right by one 974 // Games with more than 32KB of SRAM depend on this, e.g. Dezaemon (128KB SRAM) 975 let sram_addr = (address & 0x7FFF) | ((address & 0xF0000) >> 1); 976 977 // SRAM size is always a power of 2; use that to mask address 978 let sram_addr = sram_addr & (sram_len - 1); 979 CartridgeAddress::Sram(sram_addr) 980 } else { 981 // Treat as ROM mirror 982 let rom_addr = lorom_map_rom_address(address, rom_len); 983 CartridgeAddress::Rom(rom_addr) 984 } 985 } 986 _ => CartridgeAddress::None, 987 } 988} 989 990pub(crate) fn lorom_map_rom_address(address: u32, rom_len: u32) -> u32 { 991 // LoROM mapping ignores A23 and A15, and A16-22 are shifted right 1 992 let rom_addr = ((address & 0x7F0000) >> 1) | (address & 0x007FFF); 993 rom_addr & (rom_len - 1) 994} 995 996fn hirom_map_address(address: u32, rom_len: u32, sram_len: u32) -> CartridgeAddress { 997 let bank = address >> 16; 998 let offset = address & 0xFFFF; 999 match (bank, offset) { 1000 (0x40..=0x7D | 0xC0..=0xFF, _) | (0x00..=0x3F | 0x80..=0xBF, 0x8000..=0xFFFF) => { 1001 // ROM 1002 let rom_addr = hirom_map_rom_address(address, rom_len); 1003 CartridgeAddress::Rom(rom_addr) 1004 } 1005 (0x20..=0x3F | 0xA0..=0xBF, 0x6000..=0x7FFF) if sram_len != 0 => { 1006 // SRAM, if mapped 1007 let sram_bank = bank & 0x1F; 1008 let sram_addr = (sram_bank << 13) | (offset & 0x1FFF); 1009 CartridgeAddress::Sram(sram_addr & (sram_len - 1)) 1010 } 1011 _ => CartridgeAddress::None, 1012 } 1013} 1014 1015fn hirom_map_rom_address(address: u32, rom_len: u32) -> u32 { 1016 // HiROM mapping simply ignores A23 and A22 1017 let rom_addr = address & 0x3FFFFF; 1018 rom_addr & (rom_len - 1) 1019} 1020 1021fn exhirom_map_address(address: u32, rom_len: u32, sram_len: u32) -> CartridgeAddress { 1022 let bank = address >> 16; 1023 let offset = address & 0xFFFF; 1024 match (bank, offset) { 1025 (0x40..=0x7D | 0xC0..=0xFF, _) | (0x00..=0x3F | 0x80..=0xBF, 0x8000..=0xFFFF) => { 1026 // ROM 1027 let rom_addr = exhirom_map_rom_address(address, rom_len); 1028 CartridgeAddress::Rom(rom_addr) 1029 } 1030 (0x00..=0x3F | 0x80..=0xBF, 0x6000..=0x7FFF) if sram_len != 0 => { 1031 // SRAM, if mapped (note bank range is different from regular HiROM) 1032 let sram_bank = bank & 0x1F; 1033 let sram_addr = ((sram_bank << 13) | (offset & 0x1FFF)) & (sram_len - 1); 1034 CartridgeAddress::Sram(sram_addr) 1035 } 1036 _ => CartridgeAddress::None, 1037 } 1038} 1039 1040fn exhirom_map_rom_address(address: u32, rom_len: u32) -> u32 { 1041 // ExHiROM mapping ignores A22, and A23 is inverted and shifted right 1 1042 let rom_addr = (address & 0x3FFFFF) | (((address >> 1) & 0x400000) ^ 0x400000); 1043 rom_addr & (rom_len - 1) 1044}