1//! Game Boy cartridge loading and mapping 2 3mod mappers; 4 5use crate::api::GameBoyLoadError; 6use crate::cartridge::mappers::huc3::Huc3; 7use crate::cartridge::mappers::mbc3::Mbc3Rtc; 8use crate::cartridge::mappers::{Mbc1, Mbc2, Mbc3, Mbc5}; 9use bincode::{Decode, Encode}; 10use jgenesis_common::frontend::SaveWriter; 11use jgenesis_proc_macros::{FakeDecode, FakeEncode, PartialClone}; 12use std::fmt::{Display, Formatter}; 13use std::mem; 14use std::ops::Deref; 15 16#[derive(Debug, Clone, Default, FakeEncode, FakeDecode)] 17struct Rom(Box<[u8]>); 18 19impl Deref for Rom { 20 type Target = Box<[u8]>; 21 22 fn deref(&self) -> &Self::Target { 23 &self.0 24 } 25} 26 27trait HasBasicRamMapping { 28 fn map_ram_address(&self, address: u16) -> Option<u32>; 29 30 fn read_ram(&self, address: u16, sram: &[u8]) -> u8 { 31 let Some(ram_addr) = self.map_ram_address(address) else { return 0xFF }; 32 sram.get(ram_addr as usize).copied().unwrap_or(0xFF) 33 } 34 35 fn write_ram(&mut self, address: u16, value: u8, sram: &mut [u8]) { 36 let Some(ram_addr) = self.map_ram_address(address) else { return }; 37 38 if ram_addr < sram.len() as u32 { 39 sram[ram_addr as usize] = value; 40 } 41 } 42} 43 44#[derive(Debug, Clone, Encode, Decode)] 45enum Mapper { 46 None, 47 Mbc1(Mbc1), 48 Mbc2(Mbc2), 49 Mbc3(Mbc3), 50 Mbc5(Mbc5), 51 Huc3(Huc3), 52} 53 54impl Mapper { 55 fn map_rom_address(&self, address: u16) -> u32 { 56 match self { 57 Self::None => address.into(), 58 Self::Mbc1(mbc1) => mbc1.map_rom_address(address), 59 Self::Mbc2(mbc2) => mbc2.map_rom_address(address), 60 Self::Mbc3(mbc3) => mbc3.map_rom_address(address), 61 Self::Mbc5(mbc5) => mbc5.map_rom_address(address), 62 Self::Huc3(huc3) => huc3.map_rom_address(address), 63 } 64 } 65 66 fn read_ram(&self, address: u16, sram: &[u8]) -> u8 { 67 match self { 68 Self::None => sram.get((address & 0x1FFF) as usize).copied().unwrap_or(0xFF), 69 Self::Mbc1(mbc1) => mbc1.read_ram(address, sram), 70 Self::Mbc2(mbc2) => mbc2.read_ram(address), 71 Self::Mbc3(mbc3) => mbc3.read_ram(address, sram), 72 Self::Mbc5(mbc5) => mbc5.read_ram(address, sram), 73 Self::Huc3(huc3) => huc3.read_ram(address, sram), 74 } 75 } 76 77 fn write_ram(&mut self, address: u16, value: u8, sram: &mut [u8]) { 78 match self { 79 Self::None => { 80 if let Some(ram_value) = sram.get_mut((address & 0x1FFF) as usize) { 81 *ram_value = value; 82 } 83 } 84 Self::Mbc1(mbc1) => mbc1.write_ram(address, value, sram), 85 Self::Mbc2(mbc2) => mbc2.write_ram(address, value), 86 Self::Mbc3(mbc3) => mbc3.write_ram(address, value, sram), 87 Self::Mbc5(mbc5) => mbc5.write_ram(address, value, sram), 88 Self::Huc3(huc3) => huc3.write_ram(address, value, sram), 89 } 90 } 91 92 fn write_rom_address(&mut self, address: u16, value: u8) { 93 match self { 94 Self::None => {} 95 Self::Mbc1(mbc1) => mbc1.write_rom_address(address, value), 96 Self::Mbc2(mbc2) => mbc2.write_rom_address(address, value), 97 Self::Mbc3(mbc3) => mbc3.write_rom_address(address, value), 98 Self::Mbc5(mbc5) => mbc5.write_rom_address(address, value), 99 Self::Huc3(huc3) => huc3.write_rom_address(address, value), 100 } 101 } 102 103 fn mapper_type(&self) -> &'static str { 104 match self { 105 Self::None => "None", 106 Self::Mbc1(..) => "MBC1", 107 Self::Mbc2(..) => "MBC2", 108 Self::Mbc3(..) => "MBC3", 109 Self::Mbc5(..) => "MBC5", 110 Self::Huc3(..) => "HuC-3", 111 } 112 } 113} 114 115#[derive(Debug, Clone, Copy, PartialEq, Eq)] 116pub enum SoftwareType { 117 DmgOnly, 118 CgbEnhanced, 119 CgbOnly, 120} 121 122impl SoftwareType { 123 pub fn from_rom(rom: &[u8]) -> Self { 124 // $0143 in header contains CGB flag: 125 // https://gbdev.io/pandocs/The_Cartridge_Header.html#0143--cgb-flag 126 match rom.get(0x0143) { 127 Some(0x80) => Self::CgbEnhanced, 128 Some(0xC0) => Self::CgbOnly, 129 _ => Self::DmgOnly, 130 } 131 } 132} 133 134impl Display for SoftwareType { 135 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 136 match self { 137 SoftwareType::DmgOnly => write!(f, "DMG-only"), 138 SoftwareType::CgbEnhanced => write!(f, "CGB-enhanced"), 139 SoftwareType::CgbOnly => write!(f, "CGB-only"), 140 } 141 } 142} 143 144#[derive(Debug, Clone, Encode, Decode, PartialClone)] 145pub struct Cartridge { 146 #[partial_clone(default)] 147 rom: Rom, 148 sram: Box<[u8]>, 149 mapper: Mapper, 150 has_battery: bool, 151 sram_dirty: bool, 152} 153 154impl Cartridge { 155 pub fn create<S: SaveWriter>( 156 rom: Box<[u8]>, 157 initial_sram: Option<Vec<u8>>, 158 save_writer: &mut S, 159 ) -> Result<Self, GameBoyLoadError> { 160 // Every ROM image should have at least 0x150 bytes because cartridge header is at $0100-$014F 161 const MIN_ROM_LEN: usize = 0x0150; 162 163 if rom.len() < MIN_ROM_LEN { 164 return Err(GameBoyLoadError::RomTooSmall { size: rom.len(), expected: MIN_ROM_LEN }); 165 } 166 167 // Cartridge type is always at $0147 in ROM 168 let mapper_byte = rom[0x0147]; 169 let is_mbc2 = mapper_byte == 0x05 || mapper_byte == 0x06; 170 171 let sram_len = if is_mbc2 { 172 // MBC2 has a fixed 512x4 bits of RAM 173 mappers::MBC2_RAM_LEN 174 } else { 175 // The byte at $0149 in the ROM header indicates SRAM size 176 let sram_len_byte = rom[0x0149]; 177 match sram_len_byte { 178 0x00 => 0, 179 0x01 => { 180 // $01 (2KB) is an unofficial value used by some homebrew/bootlegs 181 log::warn!("Cartridge header reports 2KB of SRAM ($01); this is unusual"); 182 2 * 1024 183 } 184 0x02 => 8 * 1024, 185 0x03 => 32 * 1024, 186 0x04 => 128 * 1024, 187 0x05 => 64 * 1024, 188 _ => return Err(GameBoyLoadError::InvalidSramByte(sram_len_byte)), 189 } 190 }; 191 192 log::info!("SRAM size: {sram_len} bytes"); 193 194 let mut sram = match initial_sram { 195 Some(initial_sram) if initial_sram.len() == sram_len => initial_sram, 196 _ => vec![0xFF; sram_len], 197 }; 198 199 let (mapper, has_battery) = match mapper_byte { 200 0x00 => (Mapper::None, false), 201 0x01..=0x03 => { 202 let mapper = Mapper::Mbc1(Mbc1::new(rom.len() as u32, sram_len as u32)); 203 let has_battery = mapper_byte == 0x03; 204 205 (mapper, has_battery) 206 } 207 0x05..=0x06 => { 208 let mapper = Mapper::Mbc2(Mbc2::new(rom.len() as u32, mem::take(&mut sram))); 209 let has_battery = mapper_byte == 0x06; 210 211 (mapper, has_battery) 212 } 213 0x0F..=0x13 => { 214 let has_rtc = mapper_byte == 0x0F || mapper_byte == 0x10; 215 let rtc = has_rtc 216 .then(|| save_writer.load_serialized("rtc").ok().unwrap_or_else(Mbc3Rtc::new)); 217 let mapper = Mapper::Mbc3(Mbc3::new(rom.len() as u32, sram_len as u32, rtc)); 218 let has_battery = matches!(mapper_byte, 0x0F | 0x10 | 0x13); 219 220 log::info!("MBC3 real-time clock: {has_rtc}"); 221 222 (mapper, has_battery) 223 } 224 0x19..=0x1E => { 225 let mapper = Mapper::Mbc5(Mbc5::new(rom.len() as u32, sram_len as u32)); 226 let has_battery = mapper_byte == 0x1B || mapper_byte == 0x1E; 227 228 (mapper, has_battery) 229 } 230 0xFE => { 231 let rtc = save_writer.load_serialized("rtc").ok(); 232 let mapper = Mapper::Huc3(Huc3::new(rom.len() as u32, sram_len as u32, rtc)); 233 (mapper, true) 234 } 235 _ => return Err(GameBoyLoadError::UnsupportedMapperByte(mapper_byte)), 236 }; 237 238 log::info!("Using mapper {}", mapper.mapper_type()); 239 240 Ok(Self { 241 rom: Rom(rom), 242 sram: sram.into_boxed_slice(), 243 mapper, 244 has_battery, 245 sram_dirty: true, 246 }) 247 } 248 249 pub fn read_rom(&self, address: u16) -> u8 { 250 let rom_addr = self.mapper.map_rom_address(address); 251 self.rom[rom_addr as usize] 252 } 253 254 pub fn write_rom(&mut self, address: u16, value: u8) { 255 self.mapper.write_rom_address(address, value); 256 } 257 258 pub fn read_ram(&self, address: u16) -> u8 { 259 self.mapper.read_ram(address, &self.sram) 260 } 261 262 pub fn write_ram(&mut self, address: u16, value: u8) { 263 self.mapper.write_ram(address, value, &mut self.sram); 264 self.sram_dirty = true; 265 } 266 267 pub fn take_rom(&mut self) -> Vec<u8> { 268 mem::take(&mut self.rom.0).into_vec() 269 } 270 271 pub fn take_rom_from(&mut self, other: &mut Self) { 272 self.rom = mem::take(&mut other.rom); 273 } 274 275 pub fn has_battery(&self) -> bool { 276 self.has_battery 277 } 278 279 pub fn sram(&self) -> &[u8] { 280 match &self.mapper { 281 Mapper::Mbc2(mbc2) => mbc2.ram(), 282 _ => &self.sram, 283 } 284 } 285 286 pub fn get_and_clear_sram_dirty(&mut self) -> bool { 287 mem::take(&mut self.sram_dirty) 288 } 289 290 pub fn update_rtc_time(&mut self) { 291 match &mut self.mapper { 292 Mapper::Mbc3(mbc3) => mbc3.update_rtc_time(), 293 Mapper::Huc3(huc3) => huc3.update_rtc_time(), 294 _ => {} 295 } 296 } 297 298 pub fn save_rtc_state<S: SaveWriter>(&mut self, save_writer: &mut S) -> Result<(), S::Err> { 299 match &mut self.mapper { 300 Mapper::Mbc3(mbc3) => mbc3.save_rtc_state(save_writer)?, 301 Mapper::Huc3(huc3) => huc3.save_rtc_state(save_writer)?, 302 _ => {} 303 } 304 305 Ok(()) 306 } 307 308 pub fn tick_cpu(&mut self) { 309 if let Mapper::Huc3(huc3) = &mut self.mapper { 310 huc3.tick_cpu(); 311 } 312 } 313}