Game Boy internal memory

3use crate::HardwareMode;
4use crate::api::GameBoyLoadError;
5use bincode::{Decode, Encode};
6use jgenesis_common::num::GetBit;
7use std::iter;
9pub const DMG_BOOT_ROM_LEN: usize = 256;
10pub const CGB_BOOT_ROM_LEN: usize = 256 + 2048;
11
12const MAIN_RAM_LEN: usize = 32 * 1024;
13const HRAM_LEN: usize = 127;
14
15type MainRam = [u8; MAIN_RAM_LEN];
16type Hram = [u8; HRAM_LEN];

Initial contents of $FF80-$FFC2; the rest of HRAM is presumably randomized These values are normally written by the CGB boot ROM

20#[rustfmt::skip]
21const CGB_INITIAL_HRAM: &[u8; 67] = &[
22    0x00, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF,
23    0x7F, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF,
24    0x7F, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF,
25    0x7F, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF,
26    0x7F, 0x00, 0x00,
27];
29#[derive(Debug, Clone, Encode, Decode)]
30pub struct Memory {
31    main_ram: Box<MainRam>,
32    main_ram_bank: u8,
33    hram: Box<Hram>,
34    boot_rom: Box<[u8]>,
35    boot_rom_present: bool,
36    boot_rom_mapped: bool,
37    hardware_mode: HardwareMode,
38}
39
40impl Memory {
41    pub fn new(
42        mut boot_rom: Option<Vec<u8>>,
43        hardware_mode: HardwareMode,
44    ) -> Result<Self, GameBoyLoadError> {
45        let boot_rom_len = match hardware_mode {
46            HardwareMode::Dmg => DMG_BOOT_ROM_LEN,
47            HardwareMode::Cgb => CGB_BOOT_ROM_LEN,
48        };
49
50        // If boot ROM is present, validate that it's the expected length
51        // Allow CGB boot ROMs to be either 2048 bytes or 2048+256 bytes (with a filler $01 page)
52        if let Some(boot_rom) = &mut boot_rom
53            && boot_rom.len() != boot_rom_len
54        {
55            if hardware_mode == HardwareMode::Cgb && boot_rom.len() == boot_rom_len - 256 {
56                *boot_rom = pad_cgb_boot_rom(boot_rom);
57            } else {
58                return Err(GameBoyLoadError::InvalidBootRomSize {
59                    actual: boot_rom.len(),
60                    expected: boot_rom_len,
61                });
62            }
63        }
64
65        let boot_rom_present = boot_rom.is_some();
66        let boot_rom = boot_rom.unwrap_or_else(|| vec![0; boot_rom_len]).into_boxed_slice();
67
68        let mut main_ram: Vec<u8> = iter::repeat_with(rand::random).take(MAIN_RAM_LEN).collect();
69        let mut hram: Vec<u8> = iter::repeat_with(rand::random).take(HRAM_LEN).collect();
70
71        if !boot_rom_present {
72            // Randomize RAM contents at power-on except for bank 2, which the CGB boot ROM normally
73            // zerofills. Worms Armageddon depends on bank 2 being zeroed out.
74            // Hardware mode check is not necessary because banks 2-7 are not accessible on DMG
75            main_ram[0x2000..0x3000].fill(0);
76
77            hram.fill(0);
78            if hardware_mode == HardwareMode::Cgb {
79                hram[..CGB_INITIAL_HRAM.len()].copy_from_slice(CGB_INITIAL_HRAM);
80            }
81        }
82
83        Ok(Self {
84            main_ram: main_ram.into_boxed_slice().try_into().unwrap(),
85            main_ram_bank: 0,
86            hram: hram.into_boxed_slice().try_into().unwrap(),
87            boot_rom,
88            boot_rom_present,
89            boot_rom_mapped: boot_rom_present,
90            hardware_mode,
91        })
92    }
93
94    pub fn read_main_ram(&self, address: u16) -> u8 {
95        let ram_addr = map_ram_address(address, self.main_ram_bank);
96        self.main_ram[ram_addr as usize]
97    }
98
99    pub fn write_main_ram(&mut self, address: u16, value: u8) {
100        let ram_addr = map_ram_address(address, self.main_ram_bank);
101        self.main_ram[ram_addr as usize] = value;
102    }
103
104    pub fn read_hram(&self, address: u16) -> u8 {
105        self.hram[(address & 0x7F) as usize]
106    }
107
108    pub fn write_hram(&mut self, address: u16, value: u8) {
109        self.hram[(address & 0x7F) as usize] = value;
110    }
111
112    pub fn try_read_boot_rom(&self, address: u16) -> Option<u8> {
113        if !self.boot_rom_mapped {
114            return None;
115        }
116
117        // DMG boot ROM is 256 bytes, mapped to $0000-$00FF
118        // CGB boot ROM is 2048 bytes, first 256 mapped to $0000-$00FF and remaining 1792 mapped
119        // to $0200-$08FF
120        match (address, self.hardware_mode) {
121            (0x0000..=0x00FF, _) | (0x0200..=0x08FF, HardwareMode::Cgb) => {
122                Some(self.boot_rom[address as usize])
123            }
124            _ => None,
125        }
126    }
127
128    pub fn read_svbk(&self) -> u8 {
129        0xF8 | self.main_ram_bank
130    }
131
132    pub fn write_svbk(&mut self, value: u8) {
133        self.main_ram_bank = value & 0x07;
134
135        log::trace!("SVBK write: WRAM bank set to {}", self.main_ram_bank);
136    }
137
138    // $FF50: BANK (used only by boot ROM to unmap itself)
139    pub fn write_bank(&mut self, value: u8) {
140        self.boot_rom_mapped &= !value.bit(0);
141
142        log::debug!("BANK write: {value:02X} (boot ROM mapped: {})", self.boot_rom_mapped);
143    }
144
145    pub fn boot_rom_mapped(&self) -> bool {
146        self.boot_rom_mapped
147    }
148}
149
150fn map_ram_address(address: u16, ram_bank: u8) -> u16 {
151    match address & 0x1FFF {
152        address @ 0x0000..=0x0FFF => address,
153        0x1000..=0x1FFF => {
154            // RAM bank of 0 functions as 1
155            let ram_bank = if ram_bank == 0 { 1 } else { u16::from(ram_bank) };
156            (ram_bank << 12) | (address & 0x0FFF)
157        }
158        _ => unreachable!("address & 0x1FFF is always <= 0x1FFF"),
159    }
160}

Pad a 2048-byte CGB boot ROM to 2304 bytes by adding a filler $01 page

163fn pad_cgb_boot_rom(boot_rom: &[u8]) -> Vec<u8> {
164    let mut new_boot_rom = vec![0; CGB_BOOT_ROM_LEN];
165    new_boot_rom[..0x0100].copy_from_slice(&boot_rom[..0x0100]);
166    new_boot_rom.extend(iter::repeat_n(0, 256));
167    new_boot_rom[0x0200..].copy_from_slice(&boot_rom[0x0100..0x0800]);
168
169    new_boot_rom
170}