memory.rsannotatedmemory.rssource378 lines · 12.4 KB · raw
1//! Sega Master System / Game Gear memory map
2
3mod mappers;
4mod metadata;
5
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;
18
19// Most cartridges with RAM only had 8KB, but up to 32KB was supported, and the header contains
20// no information on RAM size (or even whether RAM is present)
21const CARTRIDGE_RAM_SIZE: usize = 32 * 1024;
22
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}