1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
//! `SxROM`/`MMC1` (Mapper 001)
//!
//! <http://wiki.nesdev.com/w/index.php/SxROM>
//! <http://wiki.nesdev.com/w/index.php/MMC1>

use crate::{
    cart::Cart,
    common::{Clock, Kind, Regional, Reset},
    mapper::{Mapped, MappedRead, MappedWrite, Mapper, MemMap},
    mem::MemBanks,
    ppu::Mirroring,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[must_use]
pub enum Mmc1Revision {
    /// MMC1 Revision A
    A,
    /// MMC1 Revisions B & C
    BC,
}

#[derive(Clone, Serialize, Deserialize)]
#[must_use]
struct SxRegs {
    write_just_occurred: u8,
    shift_register: u8, // $8000-$FFFF - 5 bit shift register
    control: u8,        // $8000-$9FFF
    chr0: u8,           // $A000-$BFFF
    chr1: u8,           // $C000-$DFFF
    prg: u8,            // $E000-$FFFF
}

#[derive(Clone, Serialize, Deserialize)]
#[must_use]
pub struct Sxrom {
    regs: SxRegs,
    submapper_num: u8,
    mirroring: Mirroring,
    board: Mmc1Revision,
    chr_select: bool,
    chr_banks: MemBanks,
    prg_ram_banks: MemBanks,
    prg_rom_banks: MemBanks,
}

impl Sxrom {
    const PRG_RAM_WINDOW: usize = 8 * 1024;
    const PRG_ROM_WINDOW: usize = 16 * 1024;
    const CHR_WINDOW: usize = 4 * 1024;
    const PRG_RAM_SIZE: usize = 32 * 1024; // 32K is safely compatible sans NES 2.0 header
    const CHR_RAM_SIZE: usize = 8 * 1024;

    const SHIFT_REG_RESET: u8 = 0x80; // Reset shift register when bit 7 is set
    const DEFAULT_SHIFT_REGISTER: u8 = 0x10; // 0b10000 the 1 is used to tell when register is full
    const MIRRORING_MASK: u8 = 0x03; // 0b00011
    const PRG_MODE_MASK: u8 = 0x0C; // 0b01100
    const CHR_MODE_MASK: u8 = 0x10; // 0b10000

    const DEFAULT_PRG_MODE: u8 = 0x0C; // Mode 3, 16k Fixed Last
    const PRG_BANK_MASK: u8 = 0x0F;
    const PRG_RAM_DISABLED: u8 = 0x10; // 0b10000

    pub fn load(cart: &mut Cart, board: Mmc1Revision) -> Mapper {
        if !cart.has_prg_ram() {
            cart.add_prg_ram(Self::PRG_RAM_SIZE);
        }
        if !cart.has_chr() {
            cart.add_chr_ram(Self::CHR_RAM_SIZE);
        };
        let mut sxrom = Self {
            regs: SxRegs {
                write_just_occurred: 0x00,
                shift_register: Self::DEFAULT_SHIFT_REGISTER,
                control: Self::DEFAULT_PRG_MODE,
                chr0: 0x00,
                chr1: 0x00,
                prg: 0x00,
            },
            submapper_num: cart.submapper_num(),
            mirroring: Mirroring::SingleScreenA,
            board,
            chr_select: cart.prg_rom.len() == 0x80000,
            chr_banks: MemBanks::new(0x0000, 0x1FFF, cart.chr_len(), Self::CHR_WINDOW),
            prg_ram_banks: MemBanks::new(0x6000, 0x7FFF, cart.prg_ram.len(), Self::PRG_RAM_WINDOW),
            prg_rom_banks: MemBanks::new(0x8000, 0xFFFF, cart.prg_rom.len(), Self::PRG_ROM_WINDOW),
        };
        sxrom.update_banks(0x0000);
        sxrom.into()
    }

    fn update_banks(&mut self, addr: u16) {
        self.mirroring = match self.regs.control & Self::MIRRORING_MASK {
            0 => Mirroring::SingleScreenA,
            1 => Mirroring::SingleScreenB,
            2 => Mirroring::Vertical,
            3 => Mirroring::Horizontal,
            _ => unreachable!("impossible mirroring mode"),
        };

        let chr0 = self.regs.chr0.into();
        let chr1 = self.regs.chr1.into();
        let chr4k = self.regs.control & Self::CHR_MODE_MASK == Self::CHR_MODE_MASK;
        if chr4k {
            self.chr_banks.set(0, chr0);
            self.chr_banks.set(1, chr1);
        } else {
            self.chr_banks.set_range(0, 1, chr0 & 0x1E); // ignore low bit
        }

        if self.submapper_num == 5 {
            // Fixed PRG SEROM, SHROM, SH1ROM use a fixed 32k PRG-ROM with no banking support.
            self.prg_rom_banks.set_range(0, 1, 0);
        } else {
            let extra_reg = if matches!(addr, 0xC000..=0xDFFF) && chr4k {
                self.regs.chr1
            } else {
                self.regs.chr0
            };

            let bank_select = if self.chr_select {
                (extra_reg & Self::CHR_MODE_MASK).into()
            } else {
                0x00
            };

            let prg_bank = (self.regs.prg & Self::PRG_BANK_MASK) as usize;
            let prg_mode = (self.regs.control & Self::PRG_MODE_MASK) >> 2;
            match prg_mode {
                0 | 1 => {
                    self.prg_rom_banks
                        .set_range(0, 1, bank_select | (prg_bank & 0x1E)); // ignore low bit
                }
                2 => {
                    self.prg_rom_banks.set(0, bank_select);
                    self.prg_rom_banks.set(1, bank_select | prg_bank);
                }
                3 => {
                    let last = self.prg_rom_banks.last();
                    self.prg_rom_banks.set(0, bank_select | prg_bank);
                    self.prg_rom_banks.set(1, bank_select | last);
                }
                _ => unreachable!("impossible prg mode"),
            }
        }
    }

    #[inline]
    fn prg_ram_enabled(&self) -> bool {
        self.board == Mmc1Revision::A || self.regs.prg & Self::PRG_RAM_DISABLED == 0
    }
}

impl Mapped for Sxrom {
    #[inline]
    fn mirroring(&self) -> Mirroring {
        self.mirroring
    }

    #[inline]
    fn set_mirroring(&mut self, mirroring: Mirroring) {
        self.mirroring = mirroring;
    }
}

impl MemMap for Sxrom {
    // PPU $0000..=$1FFF 4K CHR-ROM/RAM Bank Switchable
    // CPU $6000..=$7FFF 8K PRG-RAM Bank (optional)
    // CPU $8000..=$BFFF 16K PRG-ROM Bank Switchable or Fixed to First Bank
    // CPU $C000..=$FFFF 16K PRG-ROM Bank Fixed to Last Bank or Switchable

    fn map_peek(&self, addr: u16) -> MappedRead {
        match addr {
            0x0000..=0x1FFF => MappedRead::Chr(self.chr_banks.translate(addr)),
            0x6000..=0x7FFF if self.prg_ram_enabled() => {
                MappedRead::PrgRam(self.prg_ram_banks.translate(addr))
            }
            0x8000..=0xFFFF => MappedRead::PrgRom(self.prg_rom_banks.translate(addr)),
            _ => MappedRead::None,
        }
    }

    fn map_write(&mut self, addr: u16, val: u8) -> MappedWrite {
        match addr {
            0x0000..=0x1FFF => MappedWrite::Chr(self.chr_banks.translate(addr), val),
            0x6000..=0x7FFF if self.prg_ram_enabled() => {
                MappedWrite::PrgRam(self.prg_ram_banks.translate(addr), val)
            }
            0x8000..=0xFFFF => {
                // Writes data into a shift register. At every 5th
                // write, the data is written out to the `SxROM` registers
                // and the shift register is cleared
                //
                // Load Register $8000-$FFFF
                // 7654 3210
                // Rxxx xxxD
                // |       +- Data bit to be shifted into shift register, LSB first
                // +--------- 1: Reset shift register and write control with (Control OR $0C),
                //               locking PRG-ROM at $C000-$FFFF to the last bank.
                //
                // Control $8000-$9FFF
                // 43210
                // CPPMM
                // |||++- Mirroring (0: one-screen, lower bank; 1: one-screen, upper bank;
                // |||               2: vertical; 3: horizontal)
                // |++--- PRG-ROM bank mode (0, 1: switch 32K at $8000, ignoring low bit of bank number;
                // |                         2: fix first bank at $8000 and switch 16K bank at $C000;
                // |                         3: fix last bank at $C000 and switch 16K bank at $8000)
                // +----- CHR-ROM bank mode (0: switch 8K at a time; 1: switch two separate 4K banks)
                //
                // CHR bank 0 $A000-$BFFF
                // 42310
                // CCCCC
                // +++++- Select 4K or 8K CHR bank at PPU $0000 (low bit ignored in 8K mode)
                //
                // CHR bank 1 $C000-$DFFF
                // 43210
                // CCCCC
                // +++++- Select 4K CHR bank at PPU $1000 (ignored in 8K mode)
                //
                // For Mapper001
                // $A000 and $C000:
                // 43210
                // EDCBA
                // |||||
                // ||||+- CHR A12
                // |||+-- CHR A13, if extant (CHR >= 16k)
                // ||+--- CHR A14, if extant; and PRG-RAM A14, if extant (PRG-RAM = 32k)
                // |+---- CHR A15, if extant; and PRG-RAM A13, if extant (PRG-RAM >= 16k)
                // +----- CHR A16, if extant; and PRG-ROM A18, if extant (PRG-ROM = 512k)
                //
                // PRG bank $E000-$FFFF
                // 43210
                // RPPPP
                // |++++- Select 16K PRG-ROM bank (low bit ignored in 32K mode)
                // +----- PRG-RAM chip enable (0: enabled; 1: disabled; ignored on MMC1A)

                if self.regs.write_just_occurred > 0 {
                    return MappedWrite::None;
                }
                self.regs.write_just_occurred = 2;
                if val & Self::SHIFT_REG_RESET > 0 {
                    self.regs.shift_register = Self::DEFAULT_SHIFT_REGISTER;
                    self.regs.control |= Self::PRG_MODE_MASK;
                } else {
                    // Check if its time to write
                    let write = self.regs.shift_register & 1 == 1;
                    // Move shift register and write lowest bit of val
                    self.regs.shift_register >>= 1;
                    self.regs.shift_register |= (val & 1) << 4;
                    if write {
                        match addr {
                            0x8000..=0x9FFF => self.regs.control = self.regs.shift_register,
                            0xA000..=0xBFFF => self.regs.chr0 = self.regs.shift_register & 0x1F,
                            0xC000..=0xDFFF => self.regs.chr1 = self.regs.shift_register & 0x1F,
                            0xE000..=0xFFFF => self.regs.prg = self.regs.shift_register & 0x1F,
                            _ => unreachable!("impossible write"),
                        }
                        self.regs.shift_register = Self::DEFAULT_SHIFT_REGISTER;
                        self.update_banks(addr);
                    }
                }
                MappedWrite::None
            }
            _ => MappedWrite::None,
        }
    }
}

impl Clock for Sxrom {
    fn clock(&mut self) -> usize {
        if self.regs.write_just_occurred > 0 {
            self.regs.write_just_occurred -= 1;
        }
        1
    }
}

impl Reset for Sxrom {
    fn reset(&mut self, kind: Kind) {
        self.regs.shift_register = Self::DEFAULT_SHIFT_REGISTER;
        self.regs.control = Self::DEFAULT_PRG_MODE;
        self.regs.prg = Self::PRG_RAM_DISABLED;
        self.update_banks(0x0000);
        if kind == Kind::Hard {
            self.regs.write_just_occurred = 0;
        }
    }
}

impl Regional for Sxrom {}

impl std::fmt::Debug for Sxrom {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SxRom")
            .field("regs", &self.regs)
            .field("submapper_num", &self.submapper_num)
            .field("mirroring", &self.mirroring)
            .field("board", &self.board)
            .field("chr_select", &self.chr_select)
            .field("chr_banks", &self.chr_banks)
            .field("prg_ram_banks", &self.prg_ram_banks)
            .field("prg_ram_enabled", &self.prg_ram_enabled())
            .field("prg_rom_banks", &self.prg_rom_banks)
            .finish()
    }
}

impl std::fmt::Debug for SxRegs {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SxRegs")
            .field("write_just_occurred", &self.write_just_occurred)
            .field(
                "shift_register",
                &format_args!("0b{:08b}", self.shift_register),
            )
            .field("control", &format_args!("0x{:02X}", self.control))
            .field("chr0", &format_args!("0x{:02X}", self.chr0))
            .field("chr1", &format_args!("0x{:02X}", self.chr1))
            .field("prg", &format_args!("0x{:02X}", self.prg))
            .finish()
    }
}