Code for the unlicensed board used in Action 52 and Cheetahmen II (iNES mapper 228).

3use crate::bus::cartridge::mappers::{BankSizeKb, NametableMirroring, PpuMapResult};
4use crate::bus::cartridge::{HasBasicPpuMapping, MapperImpl};
5use bincode::{Decode, Encode};
6use jgenesis_common::num::GetBit;
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
9enum PrgMode {
10    // Maps prg_bank to both $8000-$BFFF and $C000-$FFFF
11    Zero,
12    // Maps (prg_bank & !1) to $8000-$BFFF and (prg_bank | 1) to $C000-$FFFF
13    One,
14}
15
16impl PrgMode {
17    fn from_bit(bit: bool) -> Self {
18        if bit { Self::One } else { Self::Zero }
19    }
20}
21
22#[derive(Debug, Clone, Encode, Decode)]
23pub struct Action52 {
24    selected_chip: u8,
25    prg_bank: u8,
26    prg_bank_mask: u8,
27    prg_mode: PrgMode,
28    chr_bank: u8,
29    nametable_mirroring: NametableMirroring,
30}
31
32impl Action52 {
33    pub fn new(prg_rom_len: u32) -> Self {
34        let prg_bank_mask =
35            if prg_rom_len < 512 * 1024 { ((prg_rom_len - 1) >> 14) as u8 } else { 0xFF };
36
37        Self::new_with_mask(prg_bank_mask)
38    }
39
40    fn new_with_mask(prg_bank_mask: u8) -> Self {
41        Self {
42            selected_chip: 0,
43            prg_bank: 0,
44            prg_bank_mask,
45            prg_mode: PrgMode::Zero,
46            chr_bank: 0,
47            nametable_mirroring: NametableMirroring::Vertical,
48        }
49    }
50}
51
52impl MapperImpl<Action52> {
53    pub fn reset(&mut self) {
54        self.data = Action52::new_with_mask(self.data.prg_bank_mask);
55    }
56
57    pub fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 {
58        let prg_bank = match address {
59            0x0000..=0x7FFF => return cpu_open_bus,
60            0x8000..=0xBFFF => match self.data.prg_mode {
61                PrgMode::Zero => self.data.prg_bank & !1,
62                PrgMode::One => self.data.prg_bank,
63            },
64            0xC000..=0xFFFF => match self.data.prg_mode {
65                PrgMode::Zero => self.data.prg_bank | 1,
66                PrgMode::One => self.data.prg_bank,
67            },
68        };
69
70        let full_prg_bank = (prg_bank & self.data.prg_bank_mask) | (self.data.selected_chip << 5);
71        let rom_addr = BankSizeKb::Sixteen.to_absolute_address(full_prg_bank, address);
72        self.cartridge.prg_rom.get(rom_addr as usize).copied().unwrap_or(cpu_open_bus)
73    }
74
75    pub fn write_cpu_address(&mut self, address: u16, value: u8) {
76        if address < 0x8000 {
77            // Action 52 crashes after the title screen if writes to $4020-$7FFF are allowed to do
78            // anything
79            return;
80        }
81
82        self.data.chr_bank = (value & 3) | ((address & 0xF) << 2) as u8;
83        self.data.prg_mode = PrgMode::from_bit(address.bit(5));
84
85        self.data.prg_bank = ((address >> 6) & 0x1F) as u8;
86
87        // In Action 52 (1.5MB PRG ROM), chip 3 should map to the last 512KB of ROM and chip 2
88        // should be open bus. Emulate this by swapping chips 2 and 3 on write.
89        // Only apply these writes if there is more than 512KB of PRG ROM or else Cheetahmen II will
90        // fail to boot.
91        if self.cartridge.prg_rom.len() > 512 * 1024 {
92            let selected_chip = ((address >> 11) & 3) as u8;
93            self.data.selected_chip = selected_chip ^ (selected_chip >> 1);
94        }
95
96        self.data.nametable_mirroring = if address.bit(14) {
97            NametableMirroring::Horizontal
98        } else {
99            NametableMirroring::Vertical
100        };
101
102        log::trace!("Mapper write: {address:04X} {value:02X}");
103        log::trace!("  Mapper state: {:?}", self.data);
104    }
105}
106
107impl HasBasicPpuMapping for MapperImpl<Action52> {
108    fn map_ppu_address(&self, address: u16) -> PpuMapResult {
109        match address {
110            0x0000..=0x1FFF => PpuMapResult::ChrROM(
111                BankSizeKb::Eight.to_absolute_address(self.data.chr_bank, address),
112            ),
113            0x2000..=0x3EFF => {
114                PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address))
115            }
116            _ => panic!("Invalid PPU mapper address: {address:04X}"),
117        }
118    }
119}