1pub mod huc3;
2pub mod mbc3;
3
4use crate::cartridge::HasBasicRamMapping;
5use crate::cartridge::mappers::mbc3::Mbc3Rtc;
6use bincode::{Decode, Encode};
7use jgenesis_common::frontend::SaveWriter;
8use jgenesis_common::num::{GetBit, U16Ext};
9
10#[derive(Debug, Clone, Copy, PartialEq, Encode, Decode)]
11enum BankingMode {
12    Simple,
13    Complex,
14}
15
16#[derive(Debug, Clone, Encode, Decode)]
17pub struct Mbc1 {
18    rom_bank: u8,
19    rom_addr_mask: u32,
20    ram_bank: u8,
21    ram_addr_mask: u32,
22    ram_enabled: bool,
23    banking_mode: BankingMode,
24}
25
26impl Mbc1 {
27    pub fn new(rom_len: u32, ram_len: u32) -> Self {
28        Self {
29            rom_bank: 1,
30            rom_addr_mask: rom_len - 1,
31            ram_bank: 0,
32            ram_addr_mask: ram_len.saturating_sub(1),
33            ram_enabled: false,
34            banking_mode: BankingMode::Simple,
35        }
36    }
37
38    pub fn map_rom_address(&self, address: u16) -> u32 {
39        if !address.bit(14) {
40            // $0000-$3FFF mapping depends on banking mode
41            match self.banking_mode {
42                BankingMode::Simple => {
43                    // $0000-$3FFF is always mapped to the first 16KB of ROM
44                    (address & 0x3FFF).into()
45                }
46                BankingMode::Complex => {
47                    // $0000-$3FFF uses the highest 2 bits of ROM bank
48                    let rom_bank = self.rom_bank & 0x60;
49                    ((u32::from(rom_bank) << 14) | u32::from(address & 0x3FFF)) & self.rom_addr_mask
50                }
51            }
52        } else {
53            // $4000-$7FFF is mapped to the currently selected ROM bank
54            ((u32::from(self.rom_bank) << 14) | u32::from(address & 0x3FFF)) & self.rom_addr_mask
55        }
56    }
57
58    pub fn write_rom_address(&mut self, address: u16, value: u8) {
59        log::trace!("MBC1 register write: {address:04X} {value:02X}");
60
61        match address {
62            0x0000..=0x1FFF => {
63                self.ram_enabled = value & 0x0F == 0x0A;
64                log::trace!("  RAM enabled: {}", self.ram_enabled);
65            }
66            0x2000..=0x3FFF => {
67                let mut rom_bank_low = value & 0x1F;
68                if rom_bank_low == 0 {
69                    rom_bank_low = 1;
70                }
71                self.rom_bank = (self.rom_bank & !0x1F) | rom_bank_low;
72                log::trace!("  ROM bank: {:02X}", self.rom_bank);
73            }
74            0x4000..=0x5FFF => {
75                self.rom_bank = (self.rom_bank & 0x1F) | ((value & 0x03) << 5);
76                self.ram_bank = value & 0x03;
77                log::trace!("  ROM bank: {:02X}", self.rom_bank);
78                log::trace!("  RAM bank: {:02X}", self.ram_bank);
79            }
80            0x6000..=0x7FFF => {
81                self.banking_mode =
82                    if value.bit(0) { BankingMode::Complex } else { BankingMode::Simple };
83                log::trace!("  Banking mode: {:?}", self.banking_mode);
84            }
85            _ => panic!("Invalid cartridge address: {address:04X}"),
86        }
87    }
88}
89
90impl HasBasicRamMapping for Mbc1 {
91    fn map_ram_address(&self, address: u16) -> Option<u32> {
92        if !self.ram_enabled {
93            return None;
94        }
95
96        let ram_addr = match self.banking_mode {
97            BankingMode::Simple => {
98                // RAM is not banked in simple mode; always mapped to the first 8KB of RAM
99                (address & 0x1FFF).into()
100            }
101            BankingMode::Complex => {
102                ((u32::from(self.ram_bank) << 13) | u32::from(address & 0x1FFF))
103                    & self.ram_addr_mask
104            }
105        };
106
107        Some(ram_addr)
108    }
109}

Every MBC2 cartridge has 512x4 bits of RAM

112pub const MBC2_RAM_LEN: usize = 512;
114#[derive(Debug, Clone, Encode, Decode)]
115pub struct Mbc2 {
116    rom_bank: u8,
117    rom_addr_mask: u32,
118    ram: Box<[u8; MBC2_RAM_LEN]>,
119    ram_enabled: bool,
120}
121
122impl Mbc2 {
123    pub fn new(rom_len: u32, initial_ram: Vec<u8>) -> Self {
124        let ram =
125            if initial_ram.len() == MBC2_RAM_LEN { initial_ram } else { vec![0; MBC2_RAM_LEN] };
126
127        Self {
128            rom_bank: 0,
129            rom_addr_mask: rom_len - 1,
130            ram: ram.into_boxed_slice().try_into().unwrap(),
131            ram_enabled: false,
132        }
133    }
134
135    pub fn map_rom_address(&self, address: u16) -> u32 {
136        basic_map_rom_address(address, self.rom_bank.into(), false, self.rom_addr_mask)
137    }
138
139    pub fn read_ram(&self, address: u16) -> u8 {
140        if !self.ram_enabled {
141            return 0xFF;
142        }
143
144        // MBC2 RAM is nibble-sized
145        self.ram[(address & 0x1FF) as usize] | 0xF0
146    }
147
148    pub fn write_ram(&mut self, address: u16, value: u8) {
149        if !self.ram_enabled {
150            return;
151        }
152
153        // MBC2 RAM is nibble-sized
154        self.ram[(address & 0x1FF) as usize] = value & 0x0F;
155    }
156
157    pub fn ram(&self) -> &[u8] {
158        self.ram.as_ref()
159    }
160
161    pub fn write_rom_address(&mut self, address: u16, value: u8) {
162        // MBC2 only has two registers, both mapped to $0000-$3FFF
163        if !(0x0000..0x4000).contains(&address) {
164            return;
165        }
166
167        // Address bit 8 determines whether this sets RAM enabled (clear) or ROM bank (set)
168        if !address.bit(8) {
169            // Set RAM enabled
170            self.ram_enabled = value & 0x0F == 0x0A;
171        } else {
172            // Set ROM bank
173            self.rom_bank = value & 0x0F;
174        }
175    }
176}
177
178#[derive(Debug, Clone, Encode, Decode)]
179pub struct Mbc3 {
180    rom_bank: u8,
181    rom_addr_mask: u32,
182    ram_bank: u8,
183    ram_addr_mask: u32,
184    ram_enabled: bool,
185    rtc: Option<Mbc3Rtc>,
186}
187
188impl Mbc3 {
189    pub fn new(rom_len: u32, ram_len: u32, rtc: Option<Mbc3Rtc>) -> Self {
190        Self {
191            rom_bank: 0,
192            rom_addr_mask: rom_len - 1,
193            ram_bank: 0,
194            ram_addr_mask: if ram_len != 0 { ram_len - 1 } else { 0 },
195            ram_enabled: false,
196            rtc,
197        }
198    }
199
200    pub fn map_rom_address(&self, address: u16) -> u32 {
201        basic_map_rom_address(address, self.rom_bank.into(), false, self.rom_addr_mask)
202    }
203
204    pub fn write_rom_address(&mut self, address: u16, value: u8) {
205        match address {
206            0x0000..=0x1FFF => {
207                // RAM/RTC enabled
208                self.ram_enabled = value & 0x0F == 0x0A;
209            }
210            0x2000..=0x3FFF => {
211                // ROM bank
212                self.rom_bank = value;
213            }
214            0x4000..=0x5FFF => {
215                // RAM bank number / RTC register select
216                self.ram_bank = value & 0x0F;
217            }
218            0x6000..=0x7FFF => {
219                // RTC latch
220                if let Some(rtc) = &mut self.rtc {
221                    rtc.write_latch(value);
222                }
223            }
224            0x8000..=0xFFFF => panic!("Invalid cartridge address: {address:06X}"),
225        }
226    }
227
228    pub fn read_ram(&self, address: u16, sram: &[u8]) -> u8 {
229        match self.ram_bank {
230            0x00..=0x07 => {
231                // SRAM
232                basic_map_ram_address(
233                    self.ram_enabled,
234                    address,
235                    self.ram_bank.into(),
236                    self.ram_addr_mask,
237                )
238                .map_or(0xFF, |ram_addr| sram[ram_addr as usize])
239            }
240            0x08..=0x0C => {
241                // RTC registers
242                self.rtc.as_ref().map_or(0xFF, |rtc| rtc.read_register(self.ram_bank))
243            }
244            _ => 0xFF,
245        }
246    }
247
248    pub fn write_ram(&mut self, address: u16, value: u8, sram: &mut [u8]) {
249        match self.ram_bank {
250            0x00..=0x07 => {
251                // SRAM
252                let ram_addr = basic_map_ram_address(
253                    self.ram_enabled,
254                    address,
255                    self.ram_bank.into(),
256                    self.ram_addr_mask,
257                );
258                if let Some(ram_addr) = ram_addr {
259                    sram[ram_addr as usize] = value;
260                }
261            }
262            0x08..=0x0C => {
263                // RTC registers
264                if let Some(rtc) = &mut self.rtc {
265                    rtc.write_register(self.ram_bank, value);
266                }
267            }
268            _ => {}
269        }
270    }
271
272    pub fn update_rtc_time(&mut self) {
273        if let Some(rtc) = &mut self.rtc {
274            rtc.update_time();
275        }
276    }
277
278    pub fn save_rtc_state<S: SaveWriter>(&self, save_writer: &mut S) -> Result<(), S::Err> {
279        if let Some(rtc) = &self.rtc {
280            save_writer.persist_serialized("rtc", rtc)?;
281        }
282
283        Ok(())
284    }
285}
286
287#[derive(Debug, Clone, Encode, Decode)]
288pub struct Mbc5 {
289    rom_bank: u16,
290    rom_addr_mask: u32,
291    ram_bank: u8,
292    ram_addr_mask: u32,
293    ram_enabled: bool,
294}
295
296impl Mbc5 {
297    pub fn new(rom_len: u32, ram_len: u32) -> Self {
298        Self {
299            rom_bank: 1,
300            rom_addr_mask: rom_len - 1,
301            ram_bank: 0,
302            ram_addr_mask: if ram_len != 0 { ram_len - 1 } else { 0 },
303            ram_enabled: false,
304        }
305    }
306
307    pub fn map_rom_address(&self, address: u16) -> u32 {
308        basic_map_rom_address(address, self.rom_bank.into(), true, self.rom_addr_mask)
309    }
310
311    pub fn write_rom_address(&mut self, address: u16, value: u8) {
312        log::trace!("MBC5 register write: {address:04X} {value:02X}");
313
314        match address {
315            0x0000..=0x1FFF => {
316                // RAM enabled
317                self.ram_enabled = value & 0x0F == 0x0A;
318                log::trace!("RAM enabled: {}", self.ram_enabled);
319            }
320            0x2000..=0x2FFF => {
321                // Low 8 bits of ROM bank
322                self.rom_bank.set_lsb(value);
323                log::trace!("ROM bank: {:03X}", self.rom_bank);
324            }
325            0x3000..=0x3FFF => {
326                // Highest bit of ROM bank
327                self.rom_bank.set_msb(value & 0x01);
328                log::trace!("ROM bank: {:03X}", self.rom_bank);
329            }
330            0x4000..=0x5FFF => {
331                // RAM bank / rumble motor
332                // TODO support rumble
333                self.ram_bank = value & 0x0F;
334                log::trace!("RAM bank: {:02X}", self.ram_bank);
335            }
336            0x6000..=0x7FFF => {}
337            _ => panic!("Invalid cartridge address: {address:06X}"),
338        }
339    }
340}
341
342impl HasBasicRamMapping for Mbc5 {
343    fn map_ram_address(&self, address: u16) -> Option<u32> {
344        basic_map_ram_address(self.ram_enabled, address, self.ram_bank.into(), self.ram_addr_mask)
345    }
346}

MBC2 / MBC3 / MBC5

349fn basic_map_rom_address(
350    address: u16,
351    rom_bank: u32,
352    allow_bank_0: bool,
353    rom_addr_mask: u32,
354) -> u32 {
355    if address < 0x4000 {
356        // First 16KB of ROM
357        address.into()
358    } else {
359        // 16KB ROM bank
360        let rom_bank = if !allow_bank_0 && rom_bank == 0 { 1 } else { rom_bank };
361        ((rom_bank << 14) | u32::from(address & 0x3FFF)) & rom_addr_mask
362    }
363}

MBC3 / MBC5

366fn basic_map_ram_address(
367    ram_enabled: bool,
368    address: u16,
369    ram_bank: u32,
370    ram_addr_mask: u32,
371) -> Option<u32> {
372    if !ram_enabled || ram_addr_mask == 0 {
373        return None;
374    }
375
376    Some(((ram_bank << 13) | u32::from(address & 0x1FFF)) & ram_addr_mask)
377}