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}