1//! Implementation for external memory on the cartridge, which can be SRAM or EEPROM 2 3mod metadata; 4 5use crate::cartridge::eeprom::{X24C01Chip, X24C02Chip, X24C08Chip, X24C16Chip, X24C64Chip}; 6use crate::cartridge::external::metadata::{EepromMetadata, EepromType}; 7use bincode::{Decode, Encode}; 8use jgenesis_common::num::GetBit; 9use std::ops::Range; 10 11// Games that expect to have 8KB of SRAM mapped to $200001-$203FFF but don't specify that in the header 12const FORCE_8KB_SRAM_CHECKSUMS: &[u32] = &[ 13 0x8135702C, // NHL 96 (USA, Europe) 14 0xF509145F, // Might and Magic: Gates to Another World (USA, Europe) 15 0x6EF7104A, // Might and Magic III: Isles of Terra (USA) (Proto) 16 0x2491DF2F, // NBA Action '94 (USA) (Beta) (1994-01-04) 17]; 18 19// Same as above but with 32KB of SRAM at $200001-$20FFFF 20const FORCE_32KB_SRAM_CHECKSUM: &[u32] = &[ 21 0xA4F2F011, // Al Michaels Announces HardBall III (USA, Europe) 22]; 23 24const SONIC_AND_KNUCKLES_SERIAL: &[u8] = b"GM MK-1563 "; 25 26#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 27pub(crate) enum RamType { 28 SixteenBit, 29 EightBitOddAddress, 30 EightBitEvenAddress, 31} 32 33#[derive(Debug, Clone, Encode, Decode)] 34pub struct Ram { 35 ram: Vec<u8>, 36 address_mask: u32, 37 ram_type: RamType, 38 persistent: bool, 39 dirty: bool, 40 start_address: u32, 41 end_address: u32, 42} 43 44impl Ram { 45 #[must_use] 46 pub fn from_rom_header( 47 mut rom: &[u8], 48 checksum: u32, 49 initial_ram: &mut Option<Vec<u8>>, 50 ) -> Option<Self> { 51 // Several games have SRAM but don't declare it in the header 52 if FORCE_8KB_SRAM_CHECKSUMS.contains(&checksum) { 53 log::info!("Forcibly mapping 8KB of SRAM to $200001-$203FFF"); 54 return Some(Self::forced_sram(8 * 1024, initial_ram)); 55 } 56 57 if FORCE_32KB_SRAM_CHECKSUM.contains(&checksum) { 58 log::info!("Forcibly mapping 32KB of SRAM to $200001-$20FFFF"); 59 return Some(Self::forced_sram(32 * 1024, initial_ram)); 60 } 61 62 // Sonic & Knuckles doesn't have SRAM itself, but the locked on game might (e.g. Sonic 3) 63 // S&K has 2MB of ROM so make sure the ROM is larger than that 64 let serial_number = &rom[0x180..0x18B]; 65 if serial_number == SONIC_AND_KNUCKLES_SERIAL && rom.len() > 2 * 1024 * 1024 { 66 // Skip past the first 2MB of ROM and check the header of the locked-on cartridge 67 rom = &rom[2 * 1024 * 1024..]; 68 } 69 70 let ram_header_bytes = &rom[0x1B0..0x1BC]; 71 72 // RAM header should always start with ASCII "RA" followed by RAM type byte and $20 73 if !matches!(&ram_header_bytes[..4], &[b'R', b'A', _, 0x20]) { 74 return None; 75 } 76 77 // Third byte indicates RAM type and whether or not it is persistent memory 78 let (ram_type, persistent) = match ram_header_bytes[2] { 79 0xA0 => (RamType::SixteenBit, false), 80 0xB0 => (RamType::EightBitEvenAddress, false), 81 0xB8 => (RamType::EightBitOddAddress, false), 82 0xE0 => (RamType::SixteenBit, true), 83 0xF0 => (RamType::EightBitEvenAddress, true), 84 0xF8 => (RamType::EightBitOddAddress, true), 85 _ => { 86 return None; 87 } 88 }; 89 90 // Next 8 bytes indicate start and end addresses 91 let start_address = u32::from_be_bytes([ 92 ram_header_bytes[4], 93 ram_header_bytes[5], 94 ram_header_bytes[6], 95 ram_header_bytes[7], 96 ]); 97 let end_address = u32::from_be_bytes([ 98 ram_header_bytes[8], 99 ram_header_bytes[9], 100 ram_header_bytes[10], 101 ram_header_bytes[11], 102 ]); 103 104 log::info!( 105 "RAM header information: type={ram_type:?}, persistent={persistent}, start_address={start_address:06X}, end_address={end_address:06X}" 106 ); 107 108 let ram_len = if ram_type == RamType::SixteenBit { 109 end_address - start_address + 1 110 } else { 111 (end_address - start_address) / 2 + 1 112 }; 113 114 let ram = match initial_ram.take() { 115 Some(ram) if ram.len() as u32 == ram_len => ram, 116 _ => vec![0; ram_len as usize], 117 }; 118 119 // TODO support RAM persistence 120 Some(Self { 121 ram, 122 address_mask: ram_len - 1, 123 ram_type, 124 persistent, 125 dirty: false, 126 start_address, 127 end_address, 128 }) 129 } 130 131 fn map_address(&self, address: u32) -> Option<u32> { 132 if !(self.start_address..=self.end_address).contains(&address) { 133 return None; 134 } 135 136 match (self.ram_type, address.bit(0)) { 137 (RamType::SixteenBit, _) => Some(address & self.address_mask), 138 (RamType::EightBitOddAddress, false) | (RamType::EightBitEvenAddress, true) => None, 139 (RamType::EightBitEvenAddress, false) | (RamType::EightBitOddAddress, true) => { 140 Some((address >> 1) & self.address_mask) 141 } 142 } 143 } 144 145 fn read_byte(&self, address: u32) -> Option<u8> { 146 self.map_address(address).map(|address| self.ram[address as usize]) 147 } 148 149 fn write_byte(&mut self, address: u32, value: u8) { 150 match self.map_address(address) { 151 Some(address) => { 152 self.ram[address as usize] = value; 153 self.dirty = true; 154 } 155 None => { 156 log::debug!("Write to invalid address: {address:06X} {value:02X}"); 157 } 158 } 159 } 160 161 fn read_word(&self, address: u32) -> Option<u16> { 162 let msb = self.read_byte(address); 163 let lsb = self.read_byte(address.wrapping_add(1)); 164 match (msb, lsb) { 165 (Some(msb), Some(lsb)) => Some(u16::from_be_bytes([msb, lsb])), 166 (Some(msb), None) => Some(u16::from_be_bytes([msb, msb])), 167 (None, Some(lsb)) => Some(u16::from_be_bytes([lsb, lsb])), 168 (None, None) => None, 169 } 170 } 171 172 fn write_word(&mut self, address: u32, value: u16) { 173 let msb_address = self.map_address(address); 174 let lsb_address = self.map_address(address.wrapping_add(1)); 175 176 let [msb, lsb] = value.to_be_bytes(); 177 if let Some(msb_address) = msb_address { 178 self.ram[msb_address as usize] = msb; 179 self.dirty = true; 180 } 181 if let Some(lsb_address) = lsb_address { 182 self.ram[lsb_address as usize] = lsb; 183 self.dirty = true; 184 } 185 186 if msb_address.is_none() && lsb_address.is_none() { 187 log::debug!("Write to invalid address: {address:06X} {value:04X}"); 188 } 189 } 190 191 fn forced_sram(sram_len: usize, initial_ram: &mut Option<Vec<u8>>) -> Self { 192 assert_ne!(sram_len, 0); 193 194 let ram = match initial_ram.take() { 195 Some(ram) if ram.len() == sram_len => ram, 196 _ => vec![0; sram_len], 197 }; 198 199 let start_address = 0x200001; 200 let end_address = 0x200001 | ((sram_len - 1) << 1); 201 202 Self { 203 ram, 204 address_mask: (sram_len - 1) as u32, 205 ram_type: RamType::EightBitOddAddress, 206 persistent: true, 207 dirty: false, 208 start_address, 209 end_address: end_address as u32, 210 } 211 } 212} 213 214#[derive(Debug, Clone, Encode, Decode)] 215#[allow(clippy::large_enum_variant)] 216pub enum Eeprom { 217 X24C01(X24C01Chip), 218 X24C02(X24C02Chip), 219 X24C08(X24C08Chip), 220 X24C16(X24C16Chip), 221 X24C64(X24C64Chip), 222} 223 224macro_rules! match_each_eeprom { 225 ($eeprom:expr, $chip:ident => $match_arm:expr) => { 226 match $eeprom { 227 Eeprom::X24C01($chip) => $match_arm, 228 Eeprom::X24C02($chip) => $match_arm, 229 Eeprom::X24C08($chip) => $match_arm, 230 Eeprom::X24C16($chip) => $match_arm, 231 Eeprom::X24C64($chip) => $match_arm, 232 } 233 }; 234} 235 236impl Eeprom { 237 fn handle_read(&self) -> bool { 238 match_each_eeprom!(self, chip => chip.handle_read()) 239 } 240 241 fn handle_data_write(&mut self, data: bool) { 242 match_each_eeprom!(self, chip => chip.handle_data_write(data)); 243 } 244 245 fn handle_clock_write(&mut self, clock: bool) { 246 match_each_eeprom!(self, chip => chip.handle_clock_write(clock)); 247 } 248 249 fn handle_dual_write(&mut self, data: bool, clock: bool) { 250 match_each_eeprom!(self, chip => chip.handle_dual_write(data, clock)); 251 } 252 253 fn get_and_clear_dirty_bit(&mut self) -> bool { 254 match_each_eeprom!(self, chip => chip.get_and_clear_dirty_bit()) 255 } 256 257 fn get_memory(&self) -> &[u8] { 258 match_each_eeprom!(self, chip => chip.get_memory()) 259 } 260} 261 262#[derive(Debug, Clone, Encode, Decode)] 263pub enum ExternalMemory { 264 None, 265 Ram(Ram), 266 Eeprom { 267 chip: Box<Eeprom>, 268 sda_in_addr: u32, 269 sda_in_bit: u8, 270 sda_out_addr: u32, 271 sda_out_bit: u8, 272 scl_addr: u32, 273 scl_bit: u8, 274 }, 275} 276 277impl ExternalMemory { 278 #[must_use] 279 pub fn from_rom(rom: &[u8], checksum: u32, mut initial_ram: Option<Vec<u8>>) -> Self { 280 if let Some(ram) = Ram::from_rom_header(rom, checksum, &mut initial_ram) { 281 return Self::Ram(ram); 282 } 283 284 if let Some(eeprom_metadata) = metadata::eeprom(rom, checksum) { 285 log::info!("EEPROM metadata: {eeprom_metadata:X?}"); 286 return new_eeprom(rom, initial_ram, eeprom_metadata); 287 } 288 289 Self::None 290 } 291 292 #[must_use] 293 pub fn read_byte(&self, address: u32) -> Option<u8> { 294 match self { 295 Self::None => None, 296 Self::Ram(ram) => ram.read_byte(address), 297 Self::Eeprom { chip, sda_out_addr, sda_out_bit, .. } => { 298 (*sda_out_addr == address).then(|| u8::from(chip.handle_read()) << *sda_out_bit) 299 } 300 } 301 } 302 303 #[must_use] 304 pub fn read_word(&self, address: u32) -> Option<u16> { 305 match self { 306 Self::None => None, 307 Self::Ram(ram) => ram.read_word(address), 308 &Self::Eeprom { sda_out_addr, .. } => { 309 if address == sda_out_addr { 310 // TODO shift left 8? 311 self.read_byte(address).map(u16::from) 312 } else if address + 1 == sda_out_addr { 313 self.read_byte(address + 1).map(u16::from) 314 } else { 315 None 316 } 317 } 318 } 319 } 320 321 pub fn write_byte(&mut self, address: u32, value: u8) { 322 match self { 323 Self::None => { 324 log::debug!("Write to invalid address {address:06X} {value:02X}"); 325 } 326 Self::Ram(ram) => { 327 ram.write_byte(address, value); 328 } 329 Self::Eeprom { chip, sda_in_addr, sda_in_bit, scl_addr, scl_bit, .. } => { 330 if address == *sda_in_addr && address == *scl_addr { 331 chip.handle_dual_write(value.bit(*sda_in_bit), value.bit(*scl_bit)); 332 } else if address == *sda_in_addr { 333 chip.handle_data_write(value.bit(*sda_in_bit)); 334 } else if address == *scl_addr { 335 chip.handle_clock_write(value.bit(*scl_bit)); 336 } 337 } 338 } 339 } 340 341 pub fn write_word(&mut self, address: u32, value: u16) { 342 match self { 343 Self::None => { 344 log::debug!("Write to invalid address {address:06X} {value:04X}"); 345 } 346 Self::Ram(ram) => { 347 ram.write_word(address, value); 348 } 349 Self::Eeprom { .. } => { 350 self.write_byte(address, value as u8); 351 } 352 } 353 } 354 355 #[must_use] 356 pub fn get_memory(&self) -> &[u8] { 357 const EMPTY_SLICE: &[u8] = &[]; 358 359 match self { 360 Self::None => EMPTY_SLICE, 361 Self::Ram(ram) => &ram.ram, 362 Self::Eeprom { chip, .. } => chip.get_memory(), 363 } 364 } 365 366 #[must_use] 367 pub fn is_persistent(&self) -> bool { 368 match self { 369 Self::None => false, 370 Self::Ram(ram) => ram.persistent, 371 Self::Eeprom { .. } => true, 372 } 373 } 374 375 #[must_use] 376 pub fn get_and_clear_dirty_bit(&mut self) -> bool { 377 match self { 378 Self::None => false, 379 Self::Ram(ram) => { 380 let dirty = ram.dirty; 381 ram.dirty = false; 382 dirty 383 } 384 Self::Eeprom { chip, .. } => chip.get_and_clear_dirty_bit(), 385 } 386 } 387 388 #[must_use] 389 #[allow(clippy::missing_panics_doc)] 390 #[allow(clippy::range_plus_one)] 391 pub fn address_range(&self) -> Range<u32> { 392 match self { 393 Self::None => 0..0, 394 Self::Ram(ram) => ram.start_address..ram.end_address + 1, 395 &Self::Eeprom { sda_in_addr, sda_out_addr, scl_addr, .. } => { 396 let start = [sda_in_addr, sda_out_addr, scl_addr].into_iter().min().unwrap(); 397 let end = [sda_in_addr, sda_out_addr, scl_addr].into_iter().max().unwrap(); 398 start..end + 1 399 } 400 } 401 } 402} 403 404fn new_eeprom( 405 rom: &[u8], 406 initial_ram: Option<Vec<u8>>, 407 metadata: EepromMetadata, 408) -> ExternalMemory { 409 let chip = Box::new(match metadata.eeprom_type { 410 EepromType::X24C01 => Eeprom::X24C01(X24C01Chip::new(initial_ram.as_ref())), 411 EepromType::X24C02 => Eeprom::X24C02(X24C02Chip::new(initial_ram.as_ref())), 412 EepromType::X24C08 => { 413 if metadata::is_micro_machines_2(rom) && initial_ram.is_none() { 414 // Micro Machines 2 supposedly requires EEPROM to be initialized with the string 415 // "PETETEST01234567" repeatedly or it won't use chip 416 let ram = "PETETEST01234567".bytes().cycle().take(1024).collect::<Vec<_>>(); 417 Eeprom::X24C08(X24C08Chip::new(Some(ram.as_ref()))) 418 } else { 419 Eeprom::X24C08(X24C08Chip::new(initial_ram.as_ref())) 420 } 421 } 422 EepromType::X24C16 => Eeprom::X24C16(X24C16Chip::new(initial_ram.as_ref())), 423 EepromType::X24C64 => Eeprom::X24C64(X24C64Chip::new(initial_ram.as_ref())), 424 }); 425 426 ExternalMemory::Eeprom { 427 chip, 428 sda_in_addr: metadata.sda_in_addr, 429 sda_in_bit: metadata.sda_in_bit, 430 sda_out_addr: metadata.sda_out_addr, 431 sda_out_bit: metadata.sda_out_bit, 432 scl_addr: metadata.scl_addr, 433 scl_bit: metadata.scl_bit, 434 } 435}