1//! GBA cartridge / game pak code
2
3mod eeprom;
4mod flashrom;
5mod gpio;
6pub mod rtc;
7pub mod solar;
8
9use crate::cartridge::eeprom::{Eeprom8K, Eeprom512};
10use crate::cartridge::flashrom::{FlashRom64K, FlashRom128K};
11use crate::cartridge::gpio::GpioPort;
12use crate::cartridge::rtc::SeikoRealTimeClock;
13use crate::cartridge::solar::SolarSensor;
14use crate::dma::TransferUnit;
15use crate::interrupts::InterruptRegisters;
16use bincode::{Decode, Encode};
17use crc::Crc;
18use gba_config::GbaSaveMemory;
19use jgenesis_common::boxedarray::BoxedByteArray;
20use jgenesis_common::debug::{DebugBytesView, DebugMemoryView};
21use jgenesis_common::num::GetBit;
22use jgenesis_proc_macros::{FakeDecode, FakeEncode, PartialClone};
23use std::mem;
24use std::ops::Deref;
25
26const MAX_ROM_LEN: usize = 32 * 1024 * 1024;
27const SRAM_LEN: usize = 32 * 1024;
28
29const GAME_CODE_ADDRESS: usize = 0x00000AC;
30
31const CRC: Crc<u32> = Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
32
33#[derive(Debug, Clone, FakeEncode, FakeDecode)]
34struct Rom(Box<[u8]>);
35
36impl Default for Rom {
37    fn default() -> Self {
38        Self(vec![].into_boxed_slice())
39    }
40}
41
42impl Deref for Rom {
43    type Target = Box<[u8]>;
44
45    fn deref(&self) -> &Self::Target {
46        &self.0
47    }
48}
49
50const AUTO_SAVE_TYPE_OVERRIDES: &[(u32, GbaSaveMemory)] = &[
51    (0xDD8A7104, GbaSaveMemory::None), // Iridion II (USA)
52    (0x57930C6A, GbaSaveMemory::None), // Iridion II (Europe)
53    (0xDFC88D3E, GbaSaveMemory::None), // Top Gun - Combat Zones (USA)
54];
55
56#[derive(Debug, Clone, Encode, Decode)]
57enum RwMemory {
58    Unknown,
59    Sram(BoxedByteArray<SRAM_LEN>),
60    EepromUnknownSize,
61    Eeprom512(Eeprom512),
62    Eeprom8K(Eeprom8K),
63    FlashRom64K(FlashRom64K),
64    FlashRom128K(FlashRom128K),
65    None,
66}
67
68impl RwMemory {
69    fn new_sram(initial_save: Option<&Vec<u8>>) -> Self {
70        let mut sram = BoxedByteArray::<SRAM_LEN>::new();
71
72        if let Some(initial_save) = initial_save
73            && initial_save.len() >= SRAM_LEN
74        {
75            sram.copy_from_slice(&initial_save[..SRAM_LEN]);
76        } else {
77            sram.fill(0xFF);
78        }
79
80        Self::Sram(sram)
81    }
82
83    fn new_flash_rom_64k(initial_save: Option<&Vec<u8>>) -> Self {
84        Self::FlashRom64K(FlashRom64K::new(initial_save))
85    }
86
87    fn new_flash_rom_128k(initial_save: Option<&Vec<u8>>) -> Self {
88        Self::FlashRom128K(FlashRom128K::new(initial_save))
89    }
90
91    fn from_forced_type(initial_save: Option<&Vec<u8>>, forced_type: GbaSaveMemory) -> Self {
92        match forced_type {
93            GbaSaveMemory::Sram => Self::new_sram(initial_save),
94            GbaSaveMemory::EepromUnknownSize => Self::EepromUnknownSize,
95            GbaSaveMemory::Eeprom512 => Self::Eeprom512(Eeprom512::new(initial_save)),
96            GbaSaveMemory::Eeprom8K => Self::Eeprom8K(Eeprom8K::new(initial_save)),
97            GbaSaveMemory::FlashRom64K => Self::new_flash_rom_64k(initial_save),
98            GbaSaveMemory::FlashRom128K => Self::new_flash_rom_128k(initial_save),
99            GbaSaveMemory::None => Self::None,
100        }
101    }
102
103    fn initial(
104        rom: &[u8],
105        initial_save: Option<&Vec<u8>>,
106        forced_type: Option<GbaSaveMemory>,
107    ) -> Self {
108        type RwMemoryFn = fn(Option<&Vec<u8>>) -> RwMemory;
109
110        // If the given ASCII string exists in ROM and followed by 3 digits, assume that type
111        // of save memory (e.g. "SRAM_V123" indicates SRAM)
112        const MEMORY_STRINGS: &[(&[u8], &str, RwMemoryFn)] = &[
113            (b"SRAM_V", "SRAM", RwMemory::new_sram),
114            (b"EEPROM_V", "EEPROM", |_| RwMemory::EepromUnknownSize),
115            (b"FLASH_V", "Flash ROM 64 KB", RwMemory::new_flash_rom_64k),
116            (b"FLASH512_V", "Flash ROM 64 KB", RwMemory::new_flash_rom_64k),
117            (b"FLASH1M_V", "Flash ROM 128 KB", RwMemory::new_flash_rom_128k),
118        ];
119
120        if let Some(forced_type) = forced_type {
121            log::info!("Forcing save memory type to {}", forced_type.name());
122            return Self::from_forced_type(initial_save, forced_type);
123        }
124
125        let rom_checksum = CRC.checksum(rom);
126        for &(checksum, forced_type) in AUTO_SAVE_TYPE_OVERRIDES {
127            if checksum == rom_checksum {
128                log::info!(
129                    "Forcing save memory type to {} due to checksum match: {checksum:08X}",
130                    forced_type.name()
131                );
132                return Self::from_forced_type(initial_save, forced_type);
133            }
134        }
135
136        for i in 0..rom.len() {
137            for &(string, name, init_fn) in MEMORY_STRINGS {
138                if i + string.len() + 3 > rom.len() {
139                    continue;
140                }
141
142                if &rom[i..i + string.len()] != string {
143                    continue;
144                }
145
146                if !(i + string.len()..i + string.len() + 3).all(|j| rom[j].is_ascii_digit()) {
147                    continue;
148                }
149
150                log::info!(
151                    "Auto-detected save memory type {name} from string in ROM at ${i:07X}: {}",
152                    str::from_utf8(&rom[i..i + string.len() + 3]).unwrap()
153                );
154
155                return init_fn(initial_save);
156            }
157        }
158
159        log::info!("No matching save memory string found in ROM; will auto-detect based on usage");
160
161        Self::Unknown
162    }
163
164    fn min_eeprom_address(&self, rom_len: u32) -> u32 {
165        // EEPROM is at $D000000-$DFFFFFF for ROMs <=16MB, and $DFFFF00-$DFFFFFF for 32MB
166        match self {
167            Self::EepromUnknownSize | Self::Eeprom8K(_) | Self::Eeprom512(_) => {
168                if rom_len <= 16 * 1024 * 1024 { 0xD000000 } else { 0xDFFFF00 }
169            }
170            _ => u32::MAX,
171        }
172    }
173}
174
175fn pad_if_classic_nes_rom(rom: &mut Vec<u8>) {
176    if rom.len() <= GAME_CODE_ADDRESS {
177        return;
178    }
179
180    // Classic NES series games uniquely have a game code of 'F' (uppercase ASCII character)
181    // Most other games have 'A' or 'B', some cartridges with special hardware use other characters
182    if rom[GAME_CODE_ADDRESS] == b'F' {
183        log::info!(
184            "Detected Classic NES Series cartridge; padding to 32 MB to emulate ROM mirroring"
185        );
186
187        // Manually mirror ROM up to 32 MB
188        while rom.len() < MAX_ROM_LEN {
189            for i in 0..rom.len() {
190                rom.push(rom[i]);
191            }
192        }
193    }
194}
195
196#[derive(Debug, Clone, PartialClone, Encode, Decode)]
197pub struct Cartridge {
198    #[partial_clone(default)]
199    rom: Rom,
200    rom_len: u32,
201    burst_active: bool,
202    burst_address: u32,
203    rw_memory: RwMemory,
204    rw_memory_dirty: bool,
205    min_eeprom_address: u32,
206    gpio: GpioPort,
207    rtc: Option<SeikoRealTimeClock>,
208    solar: Option<SolarSensor>,
209    // Kept here so that they can be copied into R/W memory after auto-detection
210    initial_save: Option<Vec<u8>>,
211    initial_rtc: Option<SeikoRealTimeClock>,
212}
213
214impl Cartridge {
215    pub fn new(
216        mut rom: Vec<u8>,
217        initial_save: Option<Vec<u8>>,
218        initial_rtc: Option<SeikoRealTimeClock>,
219        forced_save_memory_type: Option<GbaSaveMemory>,
220    ) -> Self {
221        jgenesis_common::rom::mirror_to_next_power_of_two(&mut rom);
222
223        // Record ROM length before possibly mirroring; Classic NES Series games depend on this to
224        // map EEPROM correctly
225        let rom_len = rom.len() as u32;
226        pad_if_classic_nes_rom(&mut rom);
227
228        let rw_memory = RwMemory::initial(&rom, initial_save.as_ref(), forced_save_memory_type);
229        let min_eeprom_address = rw_memory.min_eeprom_address(rom_len);
230
231        let has_solar_sensor = rom.get(0xAC).copied() == Some(b'U');
232        if has_solar_sensor {
233            log::info!("Detected solar sensor peripheral; assuming RTC is also present");
234        }
235
236        // TODO allow forcing RTC?
237
238        Self {
239            rom: Rom(rom.into_boxed_slice()),
240            rom_len,
241            burst_active: false,
242            burst_address: 0,
243            rw_memory,
244            rw_memory_dirty: false,
245            min_eeprom_address,
246            gpio: GpioPort::new(),
247            rtc: has_solar_sensor
248                .then(|| initial_rtc.clone().unwrap_or_else(SeikoRealTimeClock::new)),
249            solar: has_solar_sensor.then(SolarSensor::new),
250            initial_save,
251            initial_rtc,
252        }
253    }
254
255    pub fn rom_burst_active(&self) -> bool {
256        self.burst_active
257    }
258
259    pub fn end_rom_burst(&mut self) {
260        self.burst_active = false;
261    }
262
263    // Returns address that should be used for the ROM access
264    #[must_use]
265    fn update_burst_state(&mut self, address: u32) -> u32 {
266        if !self.burst_active {
267            // Non-sequential ROM accesses begin a burst and latch A1-16
268            self.burst_active = true;
269            self.burst_address = address & 0x1FFFE;
270        } else if address & 0x1FFFE != self.burst_address {
271            log::debug!(
272                "Cartridge read address does not match burst address! {address:08X} {:05X}",
273                self.burst_address
274            );
275        }
276
277        let rom_addr = (address & !0x1FFFF) | self.burst_address;
278        self.burst_address = (self.burst_address + 2) & 0x1FFFF;
279
280        if address & 0x1FFFE == 0x1FFFE {
281            // Bursts always end when the final halfword of a 128KB page is requested
282            self.burst_active = false;
283        }
284
285        rom_addr
286    }
287
288    pub fn read_rom(&mut self, address: u32) -> u16 {
289        debug_assert!((0x08000000..0x0E000000).contains(&address));
290
291        let address = self.update_burst_state(address);
292
293        if address >= self.min_eeprom_address
294            && let Some(bit) = self.try_eeprom_read()
295        {
296            return bit.into();
297        }
298
299        if (0x080000C4..0x080000CA).contains(&address)
300            && let Some(gpio_value) = self.try_gpio_read(address)
301        {
302            return gpio_value;
303        }
304
305        let rom_addr = (address as usize) & 0x1FFFFFF & !1;
306        if rom_addr >= self.rom.len() {
307            log::debug!(
308                "Out of bounds cartridge ROM read {address:07X}, len {:07X}",
309                self.rom.len()
310            );
311            let open_bus = rom_addr >> 1;
312            return open_bus as u16;
313        }
314
315        u16::from_le_bytes(self.rom[rom_addr..rom_addr + 2].try_into().unwrap())
316    }
317
318    fn try_eeprom_read(&mut self) -> Option<bool> {
319        match &mut self.rw_memory {
320            RwMemory::Eeprom512(eeprom) => Some(eeprom.read()),
321            RwMemory::Eeprom8K(eeprom) => Some(eeprom.read()),
322            _ => None,
323        }
324    }
325
326    fn try_gpio_read(&mut self, address: u32) -> Option<u16> {
327        match address {
328            0x080000C4 => self.gpio.read_data(self.rtc.as_ref(), self.solar.as_ref()),
329            0x080000C6 => self.gpio.read_pin_directions(),
330            0x080000C8 => self.gpio.read_mode(),
331            _ => None,
332        }
333    }
334
335    pub fn write_rom(&mut self, address: u32, value: u16) {
336        debug_assert!((0x08000000..0x0E000000).contains(&address));
337
338        let address = self.update_burst_state(address);
339
340        if (0x080000C4..0x080000CA).contains(&address) {
341            self.gpio_write(address, value);
342            return;
343        }
344
345        if address < self.min_eeprom_address {
346            log::debug!("Ignoring write to ROM address: {address:08X} {value:04X}");
347            return;
348        }
349
350        self.rw_memory_dirty = true;
351
352        match &mut self.rw_memory {
353            RwMemory::Eeprom512(eeprom) => eeprom.write(value.bit(0)),
354            RwMemory::Eeprom8K(eeprom) => eeprom.write(value.bit(0)),
355            _ => {
356                log::debug!("Ignoring write to ROM address: {address:08X} {value:04X}");
357            }
358        }
359    }
360
361    fn gpio_write(&mut self, address: u32, value: u16) {
362        self.rtc.get_or_insert_with(|| {
363            log::info!("Auto-detected RTC based on write to ${address:07X}");
364            self.initial_rtc.clone().unwrap_or_else(SeikoRealTimeClock::new)
365        });
366
367        match address {
368            0x080000C4 => self.gpio.write_data(value, self.rtc.as_mut(), self.solar.as_mut()),
369            0x080000C6 => self.gpio.write_pin_directions(value),
370            0x080000C8 => self.gpio.write_mode(value),
371            _ => {}
372        }
373    }
374
375    pub fn notify_dma_to_rom(&mut self, address: u32, length: u16, unit: TransferUnit) {
376        if unit != TransferUnit::Halfword {
377            // Word transfers will never be the correct length (and don't make sense for EEPROM anyway)
378            return;
379        }
380
381        if !matches!(self.rw_memory, RwMemory::Unknown | RwMemory::EepromUnknownSize) {
382            return;
383        }
384
385        // Check against original ROM length instead of possibly-padded length (Classic NES Series)
386        let rom_len = self.rom_len;
387        if address < RwMemory::EepromUnknownSize.min_eeprom_address(rom_len) {
388            return;
389        }
390
391        match length {
392            9 => {
393                // 6-bit address; 512 B EEPROM
394                self.rw_memory = RwMemory::Eeprom512(Eeprom512::new(self.initial_save.as_ref()));
395                self.min_eeprom_address = self.rw_memory.min_eeprom_address(rom_len);
396
397                log::info!(
398                    "Auto-detected EEPROM size of 512 bytes from DMA of length {length} to ${address:07X}"
399                );
400            }
401            17 => {
402                // 14-bit address; 8 KB EEPROM
403                self.rw_memory = RwMemory::Eeprom8K(Eeprom8K::new(self.initial_save.as_ref()));
404                self.min_eeprom_address = self.rw_memory.min_eeprom_address(rom_len);
405
406                log::info!(
407                    "Auto-detected EEPROM size of 8 KB from DMA of length {length} to ${address:07X}"
408                );
409            }
410            _ => {
411                log::warn!("Unexpected initial EEPROM DMA length: {length}");
412            }
413        }
414    }
415
416    pub fn take_rom(&mut self) -> Vec<u8> {
417        mem::take(&mut self.rom.0).into_vec()
418    }
419
420    pub fn take_rom_from(&mut self, other: &mut Self) {
421        self.rom = mem::take(&mut other.rom);
422    }
423
424    pub fn read_sram(&mut self, address: u32) -> u8 {
425        debug_assert!((0x0E000000..0x10000000).contains(&address));
426
427        if matches!(self.rw_memory, RwMemory::Unknown) {
428            self.rw_memory = RwMemory::new_sram(self.initial_save.as_ref());
429            log::info!("Auto-detected save memory type as SRAM due to read ${address:08X}");
430        }
431
432        match &self.rw_memory {
433            RwMemory::Sram(sram) => {
434                let sram_addr = (address as usize) & (SRAM_LEN - 1);
435                sram[sram_addr]
436            }
437            RwMemory::FlashRom64K(flash_rom) => flash_rom.read(address),
438            RwMemory::FlashRom128K(flash_rom) => flash_rom.read(address),
439            _ => {
440                // SRAM area reads always return 0xFF when no SRAM or flash ROM is present
441                // (save/none in jsmolka gba-tests)
442                0xFF
443            }
444        }
445    }
446
447    pub fn write_sram(&mut self, address: u32, value: u8) {
448        debug_assert!((0x0E000000..0x10000000).contains(&address));
449
450        if matches!(self.rw_memory, RwMemory::Unknown) {
451            if address & 0xFFFF == 0x5555 && value == 0xAA {
452                // Probably Flash ROM
453                // TODO is it possible to auto-detect 64K vs. 128K?
454                self.rw_memory = RwMemory::new_flash_rom_128k(self.initial_save.as_ref());
455                log::info!(
456                    "Auto-detected save memory type as Flash ROM 64 KB due to write ${address:08X} 0x{value:02X}"
457                );
458            } else {
459                // Probably SRAM
460                self.rw_memory = RwMemory::new_sram(self.initial_save.as_ref());
461                log::info!(
462                    "Auto-detected save memory type as SRAM due to write ${address:08X} 0x{value:02X}"
463                );
464            }
465        }
466
467        self.rw_memory_dirty = true;
468
469        match &mut self.rw_memory {
470            RwMemory::Sram(sram) => {
471                let sram_addr = (address as usize) & (SRAM_LEN - 1);
472                sram[sram_addr] = value;
473            }
474            RwMemory::FlashRom64K(flash_rom) => flash_rom.write(address, value),
475            RwMemory::FlashRom128K(flash_rom) => flash_rom.write(address, value),
476            _ => {
477                log::debug!("Unexpected SRAM address write {address:08X} {value:02X}");
478            }
479        }
480    }
481
482    pub fn take_rw_memory_dirty(&mut self) -> bool {
483        mem::take(&mut self.rw_memory_dirty)
484    }
485
486    pub fn rw_memory(&self) -> Option<&[u8]> {
487        match &self.rw_memory {
488            RwMemory::Sram(sram) => Some(sram.as_slice()),
489            RwMemory::Eeprom8K(eeprom) => Some(eeprom.memory()),
490            RwMemory::Eeprom512(eeprom) => Some(eeprom.memory()),
491            RwMemory::FlashRom64K(flash_rom) => Some(flash_rom.memory()),
492            RwMemory::FlashRom128K(flash_rom) => Some(flash_rom.memory()),
493            RwMemory::Unknown | RwMemory::EepromUnknownSize | RwMemory::None => None,
494        }
495    }
496
497    pub fn update_rtc_time(&mut self, cycles: u64, interrupts: &mut InterruptRegisters) {
498        if let Some(rtc) = &mut self.rtc {
499            rtc.update_time(cycles, interrupts);
500        }
501    }
502
503    pub fn rtc(&self) -> Option<impl Encode> {
504        self.rtc.as_ref()
505    }
506
507    pub fn set_solar_brightness(&mut self, brightness: u8) {
508        if let Some(solar) = &mut self.solar {
509            solar.set_brightness(brightness);
510        }
511    }
512
513    pub fn debug_rom_view(&mut self) -> impl DebugMemoryView {
514        DebugBytesView(self.rom.0.as_mut())
515    }
516}