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}