6use crate::memory::mappers::{Mapper, Sg1000Mapper}; 7use crate::{SmsGgEmulatorConfig, SmsGgHardware}; 8use bincode::{Decode, Encode}; 9use crc::Crc; 10use jgenesis_common::cheats::CheatByteOverridesU16Address; 11use jgenesis_common::num::{GetBit, U16Ext}; 12use jgenesis_proc_macros::{FakeDecode, FakeEncode, PartialClone}; 13use smsgg_config::SmsGgRegion; 14use std::ops::Index; 15use std::{array, mem}; 16 17const SYSTEM_RAM_SIZE: usize = 8 * 1024;
Most cartridges with RAM only had 8KB, but up to 32KB was supported, and the header contains no information on RAM size (or even whether RAM is present)
21const CARTRIDGE_RAM_SIZE: usize = 32 * 1024;
23const CRC: Crc<u32> = Crc::<u32>::new(&crc::CRC_32_ISO_HDLC); 24 25#[derive(Debug, Clone, Default, FakeEncode, FakeDecode)] 26struct Rom(Vec<u8>); 27 28impl Index<usize> for Rom { 29 type Output = <Vec<u8> as Index<usize>>::Output; 30 31 fn index(&self, index: usize) -> &Self::Output { 32 &self.0[index] 33 } 34} 35 36#[derive(Debug, Clone, Encode, Decode, PartialClone)] 37struct Cartridge { 38 #[partial_clone(default)] 39 rom: Rom, 40 ram: Vec<u8>, 41 mapper: Mapper, 42 has_battery: bool, 43 ram_dirty: bool, 44} 45 46impl Cartridge { 47 fn new(hardware: SmsGgHardware, mut rom: Vec<u8>, initial_ram: Option<Vec<u8>>) -> Self { 48 let mapper = match hardware { 49 SmsGgHardware::Sg1000 => Mapper::Sg1000(Sg1000Mapper::new(&rom)), 50 SmsGgHardware::MasterSystem | SmsGgHardware::GameGear => Mapper::detect_from_rom(&rom), 51 }; 52 log::info!("Detected mapper {} from ROM header", mapper.name()); 53 54 let checksum = CRC.checksum(&rom); 55 log::info!("ROM CRC32: {checksum:08X}"); 56 57 let has_battery = metadata::has_battery_backup(checksum); 58 log::info!("Cartridge has battery-backed RAM: {has_battery}"); 59 60 let ram = match initial_ram { 61 Some(ram) if ram.len() == CARTRIDGE_RAM_SIZE => { 62 log::info!("Successfully loaded cartridge SRAM"); 63 ram 64 } 65 _ => vec![0; CARTRIDGE_RAM_SIZE], 66 }; 67 68 jgenesis_common::rom::mirror_to_next_power_of_two(&mut rom); 69 70 Self { rom: Rom(rom), ram, mapper, has_battery, ram_dirty: false } 71 } 72 73 fn read(&self, address: u16) -> u8 { 74 self.mapper.read(address, &self.rom.0, &self.ram) 75 } 76 77 fn write(&mut self, address: u16, value: u8) { 78 self.mapper.write(address, value, &mut self.ram, &mut self.ram_dirty); 79 } 80} 81 82#[derive(Debug, Clone, Copy, Encode, Decode)] 83struct AudioControl { 84 fm_enabled: bool, 85 psg_enabled: bool, 86} 87 88impl Default for AudioControl { 89 fn default() -> Self { 90 Self { fm_enabled: false, psg_enabled: true } 91 } 92} 93 94#[derive(Debug, Clone, Encode, Decode)] 95pub struct MemoryControl { 96 pub cartridge_enabled: bool, 97 pub bios_enabled: bool, 98} 99 100impl MemoryControl { 101 pub fn new(bios_rom: Option<&Vec<u8>>) -> Self { 102 match bios_rom { 103 Some(_) => Self { cartridge_enabled: false, bios_enabled: true }, 104 None => Self { cartridge_enabled: true, bios_enabled: false }, 105 } 106 } 107} 108 109#[derive(Debug, Clone, Encode, Decode)] 110pub struct GameGearRegisters { 111 // The EXT and parallel communication ports are not actually emulated, but some things depend on 112 // them being R/W 113 pub ext_port: u8, 114 pub parallel_port: u8, 115 // TODO emulate serial port registers 116} 117 118impl GameGearRegisters { 119 fn new() -> Self { 120 Self { ext_port: 0x7F, parallel_port: 0xFF } 121 } 122} 123 124impl SmsGgHardware { 125 fn ram_mask(self) -> u16 { 126 match self { 127 Self::MasterSystem | Self::GameGear => 0x1FFF, // 8KB RAM 128 Self::Sg1000 => 0x03FF, // 1KB RAM 129 } 130 } 131} 132 133#[derive(Debug, Clone, Encode, Decode, PartialClone)] 134pub struct Memory { 135 #[partial_clone(partial)] 136 cartridge: Cartridge, 137 bios_rom: Option<Vec<u8>>, 138 bios_rom_banks: [u32; 3], 139 ram: Box<[u8; SYSTEM_RAM_SIZE]>, 140 memory_control: MemoryControl, 141 audio_control: AudioControl, 142 gg_registers: GameGearRegisters, 143 hardware: SmsGgHardware, 144 cheat_overrides: CheatByteOverridesU16Address, 145} 146 147impl Memory { 148 pub fn new( 149 rom: Vec<u8>, 150 bios_rom: Option<Vec<u8>>, 151 initial_cartridge_ram: Option<Vec<u8>>, 152 hardware: SmsGgHardware, 153 config: &SmsGgEmulatorConfig, 154 ) -> Self { 155 let memory_control = MemoryControl::new(bios_rom.as_ref()); 156 157 let mut ram = Box::new(array::from_fn(|_| 0)); 158 if bios_rom.is_none() { 159 // Some BIOS versions write their last port $3E write to the beginning of RAM; mimic 160 // this if no BIOS is present 161 // 0xAB = cartridge port enabled, RAM enabled, everything else disabled 162 ram[0] = 0xAB; 163 } 164 165 Self { 166 cartridge: Cartridge::new(hardware, rom, initial_cartridge_ram), 167 bios_rom, 168 bios_rom_banks: [0, 1, 2], 169 ram, 170 memory_control, 171 audio_control: AudioControl::default(), 172 gg_registers: GameGearRegisters::new(), 173 hardware, 174 cheat_overrides: CheatByteOverridesU16Address::new(&config.cheat_codes), 175 } 176 } 177 178 pub fn update_config(&mut self, config: &SmsGgEmulatorConfig) { 179 self.cheat_overrides.update_cheat_codes(&config.cheat_codes); 180 } 181 182 pub fn read(&self, address: u16) -> u8 { 183 let value = match address { 184 0x0000..=0xBFFF => { 185 match self.hardware { 186 SmsGgHardware::MasterSystem | SmsGgHardware::Sg1000 => { 187 let bios_enabled = self.hardware == SmsGgHardware::MasterSystem 188 && self.memory_control.bios_enabled; 189 if self.memory_control.cartridge_enabled { 190 let cartridge_byte = self.cartridge.read(address); 191 if bios_enabled { 192 // Cartridge and BIOS are both enabled; return logical AND of their bytes 193 let bios_byte = self.read_bios_sms(address); 194 cartridge_byte & bios_byte 195 } else { 196 cartridge_byte 197 } 198 } else if bios_enabled { 199 self.read_bios_sms(address) 200 } else { 201 log::debug!( 202 "Slot read ${address:04X} with neither cartridge nor BIOS enabled" 203 ); 204 // TODO this should be open bus; fake it by returning address high byte 205 address.msb() 206 } 207 } 208 SmsGgHardware::GameGear => { 209 // Cartridge is always enabled on Game Gear 210 // BIOS is mapped to $0000-$03FF if enabled 211 if self.memory_control.bios_enabled && address <= 0x03FF { 212 self.bios_rom 213 .as_ref() 214 .and_then(|bios| bios.get(address as usize)) 215 .copied() 216 .unwrap_or(0xFF) 217 } else { 218 self.cartridge.read(address) 219 } 220 } 221 } 222 } 223 0xC000..=0xFFFF => { 224 // TODO only if RAM enabled 225 let ram_addr = address & self.hardware.ram_mask(); 226 self.ram[ram_addr as usize] 227 } 228 }; 229 230 if let Some(cheat) = self.cheat_overrides.get(address, value) { 231 return cheat; 232 } 233 234 value 235 } 236 237 fn read_bios_sms(&self, address: u16) -> u8 { 238 let Some(bios_rom) = &self.bios_rom else { 239 log::debug!("BIOS ROM read ${address:04X} with no BIOS"); 240 return 0xFF; 241 }; 242 243 let bios_addr: u32 = if bios_rom.len() > 32 * 1024 { 244 match address { 245 0x0000..=0x03FF => address.into(), 246 0x0400..=0xBFFF => { 247 let rom_bank_idx = address / 0x4000; 248 let rom_bank = self.bios_rom_banks[rom_bank_idx as usize]; 249 (rom_bank << 14) | u32::from(address & 0x3FFF) 250 } 251 0xC000..=0xFFFF => panic!("Invalid BIOS address: {address:04X}"), 252 } 253 } else { 254 address.into() 255 }; 256 257 bios_rom[(bios_addr as usize) & (bios_rom.len() - 1)] 258 } 259 260 pub fn write(&mut self, address: u16, value: u8) { 261 if address >= 0xC000 { 262 // TODO only if RAM enabled 263 let ram_addr = address & self.hardware.ram_mask(); 264 self.ram[ram_addr as usize] = value; 265 } 266 267 if self.hardware != SmsGgHardware::Sg1000 268 && self.memory_control.bios_enabled 269 && (0xFFFD..=0xFFFF).contains(&address) 270 { 271 log::debug!("BIOS ROM bank {} set to {value:02X}", address - 0xFFFD); 272 self.bios_rom_banks[(address - 0xFFFD) as usize] = value.into(); 273 } 274 275 if self.memory_control.cartridge_enabled { 276 self.cartridge.write(address, value); 277 } 278 279 // If a game ever writes to cartridge RAM, assume it is battery-backed (since this isn't in 280 // the cartridge header) 281 self.cartridge.has_battery |= self.cartridge.ram_dirty; 282 } 283 284 pub fn cartridge_ram(&self) -> &[u8] { 285 &self.cartridge.ram 286 } 287 288 pub fn cartridge_has_battery(&self) -> bool { 289 self.cartridge.has_battery 290 } 291 292 pub fn cartridge_ram_dirty(&self) -> bool { 293 self.cartridge.ram_dirty 294 } 295 296 pub fn clear_cartridge_ram_dirty(&mut self) { 297 self.cartridge.ram_dirty = false; 298 } 299 300 pub fn take_rom_from(&mut self, other: &mut Self) { 301 self.cartridge.rom = mem::take(&mut other.cartridge.rom); 302 } 303 304 pub fn reset(&mut self, config: &SmsGgEmulatorConfig) { 305 *self = Self::new( 306 mem::take(&mut self.cartridge.rom.0), 307 mem::take(&mut self.bios_rom), 308 Some(mem::take(&mut self.cartridge.ram)), 309 self.hardware, 310 config, 311 ); 312 } 313 314 pub fn fm_enabled(&self) -> bool { 315 self.audio_control.fm_enabled 316 } 317 318 pub fn psg_enabled(&self) -> bool { 319 self.audio_control.psg_enabled 320 } 321 322 pub fn read_audio_control(&self) -> u8 { 323 match (self.audio_control.fm_enabled, self.audio_control.psg_enabled) { 324 (false, true) => 0x00, 325 (true, false) => 0x01, 326 (false, false) => 0x02, 327 (true, true) => 0x03, 328 } 329 } 330 331 pub fn write_audio_control(&mut self, value: u8) { 332 let control_bits = value & 0x03; 333 self.audio_control.fm_enabled = control_bits.bit(0); 334 self.audio_control.psg_enabled = control_bits == 0 || control_bits == 3; 335 } 336 337 pub fn guess_cartridge_region(&self) -> SmsGgRegion { 338 const POSSIBLE_HEADER_LOCATIONS: [usize; 3] = [0x7FF0, 0x3FF0, 0x1FF0]; 339 340 let rom = &self.cartridge.rom.0; 341 for header_start in POSSIBLE_HEADER_LOCATIONS { 342 if rom.len() < header_start + 16 { 343 // ROM is too small for the header to be here 344 continue; 345 } 346 347 // The first 8 bytes of a valid header should be the string "TMR SEGA" 348 if &rom[header_start..header_start + 8] != b"TMR SEGA" { 349 continue; 350 } 351 352 // Intentionally don't validate checksum; some games have invalid checksums in their headers 353 354 let region_code = rom[header_start + 15] >> 4; 355 match region_code { 356 // SMS Domestic / GG Domestic 357 3 | 5 => return SmsGgRegion::Domestic, 358 // SMS Export / GG Export / GG International 359 4 | 6 | 7 => return SmsGgRegion::International, 360 _ => { 361 log::warn!("Unexpected region code in cartridge header: {region_code:X}"); 362 } 363 } 364 } 365 366 // If no valid header was found, assume region Domestic/Japan 367 // Every GG game and non-JP SMS game should have a header, but some JP SMS games do not 368 SmsGgRegion::Domestic 369 } 370 371 pub fn gg_registers(&mut self) -> &mut GameGearRegisters { 372 &mut self.gg_registers 373 } 374 375 pub fn memory_control(&mut self) -> &mut MemoryControl { 376 &mut self.memory_control 377 } 378}