mmc2.rsannotatedmmc2.rssource217 lines · 7.7 KB · raw

Code for the MMC2 and MMC4 boards (iNES mappers 9 + 10).

3use crate::bus::cartridge::MapperImpl;
4use crate::bus::cartridge::mappers::{BankSizeKb, CpuMapResult, NametableMirroring};
5use bincode::{Decode, Encode};
6use jgenesis_common::num::GetBit;
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
9enum ChrBankLatch {
10    FD,
11    FE,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
15enum Variant {
16    Mmc2,
17    Mmc4,
18}
19
20#[derive(Debug, Clone, Encode, Decode)]
21pub(crate) struct Mmc2 {
22    variant: Variant,
23    prg_bank: u8,
24    chr_0_fd_bank: u8,
25    chr_0_fe_bank: u8,
26    chr_0_latch: ChrBankLatch,
27    chr_1_fd_bank: u8,
28    chr_1_fe_bank: u8,
29    chr_1_latch: ChrBankLatch,
30    nametable_mirroring: NametableMirroring,
31}
32
33impl Mmc2 {
34    fn new(variant: Variant) -> Self {
35        Self {
36            variant,
37            prg_bank: 0,
38            chr_0_fd_bank: 0,
39            chr_0_fe_bank: 0,
40            chr_0_latch: ChrBankLatch::FD,
41            chr_1_fd_bank: 0,
42            chr_1_fe_bank: 0,
43            chr_1_latch: ChrBankLatch::FD,
44            nametable_mirroring: NametableMirroring::Vertical,
45        }
46    }
47
48    pub(crate) fn new_mmc2() -> Self {
49        Self::new(Variant::Mmc2)
50    }
51
52    pub(crate) fn new_mmc4() -> Self {
53        Self::new(Variant::Mmc4)
54    }
55}
56
57impl MapperImpl<Mmc2> {
58    fn map_cpu_address(&self, address: u16) -> CpuMapResult {
59        match (self.data.variant, address) {
60            (_, 0x0000..=0x401F) => panic!("invalid CPU map address: {address:04X}"),
61            (_, 0x4020..=0x5FFF) => CpuMapResult::None,
62            (_, 0x6000..=0x7FFF) => {
63                if !self.cartridge.prg_ram.is_empty() {
64                    CpuMapResult::PrgRAM(u32::from(address & 0x1FFF))
65                } else {
66                    CpuMapResult::None
67                }
68            }
69            (Variant::Mmc2, 0x8000..=0x9FFF) => {
70                let prg_rom_addr =
71                    BankSizeKb::Eight.to_absolute_address(self.data.prg_bank, address);
72                CpuMapResult::PrgROM(prg_rom_addr)
73            }
74            (Variant::Mmc2, 0xA000..=0xBFFF) => {
75                // Fixed at third-to-last PRG ROM bank
76                let prg_rom_addr = BankSizeKb::Eight.to_absolute_address_from_end(
77                    3_u32,
78                    self.cartridge.prg_rom.len() as u32,
79                    address,
80                );
81                CpuMapResult::PrgROM(prg_rom_addr)
82            }
83            (Variant::Mmc2, 0xC000..=0xDFFF) => {
84                // Fixed at second-to-last PRG ROM bank
85                let prg_rom_addr = BankSizeKb::Eight.to_absolute_address_from_end(
86                    2_u32,
87                    self.cartridge.prg_rom.len() as u32,
88                    address,
89                );
90                CpuMapResult::PrgROM(prg_rom_addr)
91            }
92            (Variant::Mmc2, 0xE000..=0xFFFF) => {
93                // Fixed at last PRG ROM bank
94                let prg_rom_addr = BankSizeKb::Eight
95                    .to_absolute_address_last_bank(self.cartridge.prg_rom.len() as u32, address);
96                CpuMapResult::PrgROM(prg_rom_addr)
97            }
98            (Variant::Mmc4, 0x8000..=0xBFFF) => {
99                let prg_rom_addr =
100                    BankSizeKb::Sixteen.to_absolute_address(self.data.prg_bank, address);
101                CpuMapResult::PrgROM(prg_rom_addr)
102            }
103            (Variant::Mmc4, 0xC000..=0xFFFF) => {
104                // Fixed at last PRG ROM bank
105                let prg_rom_addr = BankSizeKb::Sixteen
106                    .to_absolute_address_last_bank(self.cartridge.prg_rom.len() as u32, address);
107                CpuMapResult::PrgROM(prg_rom_addr)
108            }
109        }
110    }
111
112    pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 {
113        self.map_cpu_address(address).read(&self.cartridge).unwrap_or(cpu_open_bus)
114    }
115
116    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
117        match address {
118            0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"),
119            0x4020..=0x5FFF | 0x8000..=0x9FFF => {}
120            0x6000..=0x7FFF => {
121                if !self.cartridge.prg_ram.is_empty() {
122                    self.cartridge.set_prg_ram(u32::from(address & 0x1FFF), value);
123                }
124            }
125            0xA000..=0xAFFF => {
126                self.data.prg_bank = value & 0x0F;
127            }
128            0xB000..=0xBFFF => {
129                self.data.chr_0_fd_bank = value & 0x1F;
130            }
131            0xC000..=0xCFFF => {
132                self.data.chr_0_fe_bank = value & 0x1F;
133            }
134            0xD000..=0xDFFF => {
135                self.data.chr_1_fd_bank = value & 0x1F;
136            }
137            0xE000..=0xEFFF => {
138                self.data.chr_1_fe_bank = value & 0x1F;
139            }
140            0xF000..=0xFFFF => {
141                self.data.nametable_mirroring = if value.bit(0) {
142                    NametableMirroring::Horizontal
143                } else {
144                    NametableMirroring::Vertical
145                };
146            }
147        }
148    }
149
150    pub(crate) fn read_ppu_address(&mut self, address: u16, vram: &[u8; 2048]) -> u8 {
151        let value = match address {
152            0x0000..=0x0FFF => match self.data.chr_0_latch {
153                ChrBankLatch::FD => {
154                    let chr_rom_addr =
155                        BankSizeKb::Four.to_absolute_address(self.data.chr_0_fd_bank, address);
156                    self.cartridge.get_chr_rom(chr_rom_addr)
157                }
158                ChrBankLatch::FE => {
159                    let chr_rom_addr =
160                        BankSizeKb::Four.to_absolute_address(self.data.chr_0_fe_bank, address);
161                    self.cartridge.get_chr_rom(chr_rom_addr)
162                }
163            },
164            0x1000..=0x1FFF => match self.data.chr_1_latch {
165                ChrBankLatch::FD => {
166                    let chr_rom_addr =
167                        BankSizeKb::Four.to_absolute_address(self.data.chr_1_fd_bank, address);
168                    self.cartridge.get_chr_rom(chr_rom_addr)
169                }
170                ChrBankLatch::FE => {
171                    let chr_rom_addr =
172                        BankSizeKb::Four.to_absolute_address(self.data.chr_1_fe_bank, address);
173                    self.cartridge.get_chr_rom(chr_rom_addr)
174                }
175            },
176            0x2000..=0x3EFF => vram[self.data.nametable_mirroring.map_to_vram(address) as usize],
177            0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"),
178        };
179
180        // Check for FD/FE latch updates
181        match (self.data.variant, address) {
182            (Variant::Mmc2, 0x0FD8) | (Variant::Mmc4, 0x0FD8..=0x0FDF) => {
183                self.data.chr_0_latch = ChrBankLatch::FD;
184            }
185            (Variant::Mmc2, 0x0FE8) | (Variant::Mmc4, 0x0FE8..=0x0FEF) => {
186                self.data.chr_0_latch = ChrBankLatch::FE;
187            }
188            (_, 0x1FD8..=0x1FDF) => {
189                self.data.chr_1_latch = ChrBankLatch::FD;
190            }
191            (_, 0x1FE8..=0x1FEF) => {
192                self.data.chr_1_latch = ChrBankLatch::FE;
193            }
194            _ => {}
195        }
196
197        value
198    }
199
200    pub(crate) fn write_ppu_address(&mut self, address: u16, value: u8, vram: &mut [u8; 2048]) {
201        match address {
202            0x0000..=0x1FFF => {}
203            0x2000..=0x3EFF => {
204                let vram_addr = self.data.nametable_mirroring.map_to_vram(address);
205                vram[vram_addr as usize] = value;
206            }
207            0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"),
208        }
209    }
210
211    pub(crate) fn name(&self) -> &'static str {
212        match self.data.variant {
213            Variant::Mmc2 => "MMC2",
214            Variant::Mmc4 => "MMC4",
215        }
216    }
217}