nes_core/mapper/
m004_txrom.rs

1//! `TxROM`/`MMC3` (Mapper 004)
2//!
3//! <https://wiki.nesdev.com/w/index.php/TxROM>
4//! <https://wiki.nesdev.com/w/index.php/MMC3>
5
6use crate::{
7    cart::Cart,
8    common::{Clock, ResetKind, Regional, Reset},
9    mapper::{Mapped, MappedRead, MappedWrite, Mapper, MemMap},
10    mem::MemBanks,
11    ppu::Mirroring,
12};
13use serde::{Deserialize, Serialize};
14
15// http://forums.nesdev.com/viewtopic.php?p=62546#p62546
16// MMC3
17// Conquest of the Crystal Palace (MMC3B S 9039 1 DB)
18// Kickle Cubicle (MMC3B S 9031 3 DA)
19// M.C. Kids (MMC3B S 9152 3 AB)
20// Mega Man 3 (MMC3B S 9046 1 DB)
21// Super Mario Bros. 3 (MMC3B S 9027 5 A)
22// Startropics (MMC6B P 03'5)
23// Batman (MMC3B 9006KP006)
24// Golgo 13: The Mafat Conspiracy (MMC3B 9016KP051)
25// Crystalis (MMC3B 9024KPO53)
26// Legacy of the Wizard (MMC3A 8940EP)
27#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
28#[must_use]
29pub enum Mmc3Revision {
30    /// MMC3 Revision A
31    A,
32    /// MMC3 Revisions B & C
33    BC,
34    /// Acclaims MMC3 clone - clocks on falling edge
35    Acc,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39#[must_use]
40struct TxRegs {
41    bank_select: u8,
42    bank_values: [u8; 8],
43    irq_latch: u8,
44    irq_counter: u8,
45    irq_enabled: bool,
46    irq_reload: bool,
47    last_clock: u16,
48}
49
50impl TxRegs {
51    const fn new() -> Self {
52        Self {
53            bank_select: 0x00,
54            bank_values: [0x00; 8],
55            irq_latch: 0x00,
56            irq_counter: 0x00,
57            irq_enabled: false,
58            irq_reload: false,
59            last_clock: 0x0000,
60        }
61    }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[must_use]
66pub struct Txrom {
67    regs: TxRegs,
68    mirroring: Mirroring,
69    irq_pending: bool,
70    revision: Mmc3Revision,
71    chr_banks: MemBanks,
72    prg_ram_banks: MemBanks,
73    prg_rom_banks: MemBanks,
74}
75
76impl Txrom {
77    const PRG_WINDOW: usize = 8 * 1024;
78    const CHR_WINDOW: usize = 1024;
79
80    const FOUR_SCREEN_RAM_SIZE: usize = 4 * 1024;
81    const PRG_RAM_SIZE: usize = 8 * 1024;
82    const CHR_RAM_SIZE: usize = 8 * 1024;
83
84    const PRG_MODE_MASK: u8 = 0x40; // Bit 6 of bank select
85    const CHR_INVERSION_MASK: u8 = 0x80; // Bit 7 of bank select
86
87    pub fn load(cart: &mut Cart) -> Mapper {
88        cart.add_prg_ram(Self::PRG_RAM_SIZE);
89        if cart.mirroring() == Mirroring::FourScreen {
90            cart.add_ex_ram(Self::FOUR_SCREEN_RAM_SIZE);
91        }
92        if !cart.has_chr() {
93            cart.add_chr_ram(Self::CHR_RAM_SIZE);
94        };
95        let mut txrom = Self {
96            regs: TxRegs::new(),
97            mirroring: cart.mirroring(),
98            irq_pending: false,
99            revision: Mmc3Revision::BC, // TODO compare to known games
100            chr_banks: MemBanks::new(0x0000, 0x1FFF, cart.chr_len(), Self::CHR_WINDOW),
101            prg_ram_banks: MemBanks::new(0x6000, 0x7FFF, cart.prg_ram.len(), Self::PRG_WINDOW),
102            prg_rom_banks: MemBanks::new(0x8000, 0xFFFF, cart.prg_rom.len(), Self::PRG_WINDOW),
103        };
104        let last_bank = txrom.prg_rom_banks.last();
105        txrom.prg_rom_banks.set(2, last_bank - 1);
106        txrom.prg_rom_banks.set(3, last_bank);
107        txrom.into()
108    }
109
110    #[inline]
111    pub fn set_revision(&mut self, revision: Mmc3Revision) {
112        self.revision = revision;
113    }
114
115    fn update_banks(&mut self) {
116        let prg_last = self.prg_rom_banks.last();
117        let prg_lo = self.regs.bank_values[6] as usize;
118        let prg_hi = self.regs.bank_values[7] as usize;
119        if self.regs.bank_select & Self::PRG_MODE_MASK == Self::PRG_MODE_MASK {
120            self.prg_rom_banks.set(0, prg_last - 1);
121            self.prg_rom_banks.set(1, prg_hi);
122            self.prg_rom_banks.set(2, prg_lo);
123        } else {
124            self.prg_rom_banks.set(0, prg_lo);
125            self.prg_rom_banks.set(1, prg_hi);
126            self.prg_rom_banks.set(2, prg_last - 1);
127        }
128        self.prg_rom_banks.set(3, prg_last);
129
130        // 1: two 2K banks at $1000-$1FFF, four 1 KB banks at $0000-$0FFF
131        // 0: two 2K banks at $0000-$0FFF, four 1 KB banks at $1000-$1FFF
132        let chr = self.regs.bank_values;
133        if self.regs.bank_select & Self::CHR_INVERSION_MASK == Self::CHR_INVERSION_MASK {
134            self.chr_banks.set(0, chr[2] as usize);
135            self.chr_banks.set(1, chr[3] as usize);
136            self.chr_banks.set(2, chr[4] as usize);
137            self.chr_banks.set(3, chr[5] as usize);
138            self.chr_banks.set_range(4, 5, (chr[0] & 0xFE) as usize);
139            self.chr_banks.set_range(6, 7, (chr[1] & 0xFE) as usize);
140        } else {
141            self.chr_banks.set_range(0, 1, (chr[0] & 0xFE) as usize);
142            self.chr_banks.set_range(2, 3, (chr[1] & 0xFE) as usize);
143            self.chr_banks.set(4, chr[2] as usize);
144            self.chr_banks.set(5, chr[3] as usize);
145            self.chr_banks.set(6, chr[4] as usize);
146            self.chr_banks.set(7, chr[5] as usize);
147        }
148    }
149
150    fn clock_irq(&mut self, addr: u16) {
151        if addr < 0x2000 {
152            let next_clock = (addr >> 12) & 1;
153            let (last, next) = if self.revision == Mmc3Revision::Acc {
154                (1, 0)
155            } else {
156                (0, 1)
157            };
158            if self.regs.last_clock == last && next_clock == next {
159                let counter = self.regs.irq_counter;
160                if counter == 0 || self.regs.irq_reload {
161                    self.regs.irq_counter = self.regs.irq_latch;
162                } else {
163                    self.regs.irq_counter -= 1;
164                }
165                if (counter & 0x01 == 0x01
166                    || self.revision == Mmc3Revision::BC
167                    || self.regs.irq_reload)
168                    && self.regs.irq_counter == 0
169                    && self.regs.irq_enabled
170                {
171                    self.irq_pending = true;
172                }
173                self.regs.irq_reload = false;
174            }
175            self.regs.last_clock = next_clock;
176        }
177    }
178}
179
180impl Mapped for Txrom {
181    #[inline]
182    fn irq_pending(&self) -> bool {
183        self.irq_pending
184    }
185
186    #[inline]
187    fn mirroring(&self) -> Mirroring {
188        self.mirroring
189    }
190
191    #[inline]
192    fn set_mirroring(&mut self, mirroring: Mirroring) {
193        self.mirroring = mirroring;
194    }
195
196    #[inline]
197    fn ppu_bus_read(&mut self, addr: u16) {
198        self.clock_irq(addr);
199    }
200
201    #[inline]
202    fn ppu_bus_write(&mut self, addr: u16, _val: u8) {
203        self.clock_irq(addr);
204    }
205}
206
207impl MemMap for Txrom {
208    // PPU $0000..=$07FF (or $1000..=$17FF) 2K CHR-ROM/RAM Bank 1 Switchable --+
209    // PPU $0800..=$0FFF (or $1800..=$1FFF) 2K CHR-ROM/RAM Bank 2 Switchable --|-+
210    // PPU $1000..=$13FF (or $0000..=$03FF) 1K CHR-ROM/RAM Bank 3 Switchable --+ |
211    // PPU $1400..=$17FF (or $0400..=$07FF) 1K CHR-ROM/RAM Bank 4 Switchable --+ |
212    // PPU $1800..=$1BFF (or $0800..=$0BFF) 1K CHR-ROM/RAM Bank 5 Switchable ----+
213    // PPU $1C00..=$1FFF (or $0C00..=$0FFF) 1K CHR-ROM/RAM Bank 6 Switchable ----+
214    // PPU $2000..=$3EFF FourScreen Mirroring (optional)
215
216    // CPU $6000..=$7FFF 8K PRG-RAM Bank (optional)
217    // CPU $8000..=$9FFF (or $C000..=$DFFF) 8K PRG-ROM Bank 1 Switchable
218    // CPU $A000..=$BFFF 8K PRG-ROM Bank 2 Switchable
219    // CPU $C000..=$DFFF (or $8000..=$9FFF) 8K PRG-ROM Bank 3 Fixed to second-to-last Bank
220    // CPU $E000..=$FFFF 8K PRG-ROM Bank 4 Fixed to Last
221
222    #[inline]
223    fn map_read(&mut self, addr: u16) -> MappedRead {
224        self.clock_irq(addr);
225        self.map_peek(addr)
226    }
227
228    fn map_peek(&self, addr: u16) -> MappedRead {
229        match addr {
230            0x0000..=0x1FFF => MappedRead::Chr(self.chr_banks.translate(addr)),
231            0x2000..=0x3EFF if self.mirroring == Mirroring::FourScreen => {
232                MappedRead::ExRam((addr & 0x1FFF) as usize)
233            }
234            0x6000..=0x7FFF => MappedRead::PrgRam(self.prg_ram_banks.translate(addr)),
235            0x8000..=0xFFFF => MappedRead::PrgRom(self.prg_rom_banks.translate(addr)),
236            _ => MappedRead::None,
237        }
238    }
239
240    fn map_write(&mut self, addr: u16, val: u8) -> MappedWrite {
241        match addr {
242            0x0000..=0x1FFF => MappedWrite::Chr(self.chr_banks.translate(addr), val),
243            0x2000..=0x3EFF if self.mirroring == Mirroring::FourScreen => {
244                MappedWrite::ExRam((addr & 0x1FFF) as usize, val)
245            }
246            0x6000..=0x7FFF => MappedWrite::PrgRam(self.prg_ram_banks.translate(addr), val),
247            0x8000..=0xFFFF => {
248                //  7654 3210
249                // `CPMx xRRR`
250                //  |||   +++- Specify which bank register to update on next write to Bank Data register
251                //  |||        0: Select 2K CHR bank at PPU $0000-$07FF (or $1000-$17FF);
252                //  |||        1: Select 2K CHR bank at PPU $0800-$0FFF (or $1800-$1FFF);
253                //  |||        2: Select 1K CHR bank at PPU $1000-$13FF (or $0000-$03FF);
254                //  |||        3: Select 1K CHR bank at PPU $1400-$17FF (or $0400-$07FF);
255                //  |||        4: Select 1K CHR bank at PPU $1800-$1BFF (or $0800-$0BFF);
256                //  |||        5: Select 1K CHR bank at PPU $1C00-$1FFF (or $0C00-$0FFF);
257                //  |||        6: Select 8K PRG-ROM bank at $8000-$9FFF (or $C000-$DFFF);
258                //  |||        7: Select 8K PRG-ROM bank at $A000-$BFFF
259                //  ||+------- Nothing on the MMC3, see MMC6
260                //  |+-------- PRG-ROM bank mode (0: $8000-$9FFF swappable,
261                //  |                                $C000-$DFFF fixed to second-last bank;
262                //  |                             1: $C000-$DFFF swappable,
263                //  |                                $8000-$9FFF fixed to second-last bank)
264                //  +--------- CHR A12 inversion (0: two 2K banks at $0000-$0FFF,
265                //                                   four 1K banks at $1000-$1FFF;
266                //                                1: two 2K banks at $1000-$1FFF,
267                //                                   four 1K banks at $0000-$0FFF)
268                //
269                // Match only $8000/1, $A000/1, $C000/1, and $E000/1
270                match addr & 0xE001 {
271                    0x8000 => {
272                        self.regs.bank_select = val;
273                        self.update_banks();
274                    }
275                    0x8001 => {
276                        let bank = self.regs.bank_select & 0x07;
277                        self.regs.bank_values[bank as usize] = val;
278                        self.update_banks();
279                    }
280                    0xA000 => {
281                        if self.mirroring != Mirroring::FourScreen {
282                            self.mirroring = match val & 0x01 {
283                                0 => Mirroring::Vertical,
284                                1 => Mirroring::Horizontal,
285                                _ => unreachable!("impossible mirroring"),
286                            };
287                            self.update_banks();
288                        }
289                    }
290                    0xA001 => {
291                        // TODO RAM protect? Might conflict with MMC6
292                    }
293                    // IRQ
294                    0xC000 => self.regs.irq_latch = val,
295                    0xC001 => self.regs.irq_reload = true,
296                    0xE000 => {
297                        self.irq_pending = false;
298                        self.regs.irq_enabled = false;
299                    }
300                    0xE001 => self.regs.irq_enabled = true,
301                    _ => unreachable!("impossible address"),
302                }
303                MappedWrite::None
304            }
305            _ => MappedWrite::None,
306        }
307    }
308}
309
310impl Reset for Txrom {
311    fn reset(&mut self, _kind: ResetKind) {
312        self.irq_pending = false;
313        self.regs = TxRegs::new();
314        self.update_banks();
315    }
316}
317
318impl Clock for Txrom {}
319impl Regional for Txrom {}