nrom.rsannotatednrom.rssource446 lines · 15.4 KB · raw

Code for the NROM board (iNES mapper 0) as well as simple NROM variants.

Variants implemented here include:

  • UxROM (iNES mapper 2)
  • CNROM (iNES mapper 3)
  • AxROM (iNES mapper 7)
  • Color Dreams unlicensed board (iNES mapper 11, very similar to GxROM)
  • BNROM (iNES mapper 34)
  • NINA-001 (also iNES mapper 34, very similar to BNROM)
  • GxROM (iNES mapper 66)
  • Codemasters unlicensed board (iNES mapper 71, very similar to UxROM)
  • Jaleco JF-11 / JF-14 (iNES mapper 140, very similar to GxROM)
14use crate::bus::cartridge::mappers::{BankSizeKb, ChrType, NametableMirroring, PpuMapResult};
15use crate::bus::cartridge::{HasBasicPpuMapping, MapperImpl};
16use bincode::{Decode, Encode};
17use jgenesis_common::num::GetBit;
19fn basic_map_ppu_address(
20    address: u16,
21    chr_type: ChrType,
22    nametable_mirroring: NametableMirroring,
23) -> PpuMapResult {
24    match address {
25        0x0000..=0x1FFF => chr_type.to_map_result(address.into()),
26        0x2000..=0x3EFF => PpuMapResult::Vram(nametable_mirroring.map_to_vram(address)),
27        _ => panic!("invalid PPU map address: 0x{address:04X}"),
28    }
29}
30
31#[derive(Debug, Clone, Encode, Decode)]
32pub(crate) struct Nrom {
33    chr_type: ChrType,
34    nametable_mirroring: NametableMirroring,
35}
36
37impl Nrom {
38    pub(crate) fn new(chr_type: ChrType, nametable_mirroring: NametableMirroring) -> Self {
39        Self { chr_type, nametable_mirroring }
40    }
41}
42
43impl MapperImpl<Nrom> {
44    pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 {
45        match address {
46            0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"),
47            0x4020..=0x7FFF => cpu_open_bus,
48            0x8000..=0xFFFF => self.cartridge.get_prg_rom((address & 0x7FFF).into()),
49        }
50    }
51
52    // Intentionally blank implementation that is present because every MapperImpl must have a
53    // write_cpu_address method; see Mapper::write_cpu_address
54    #[allow(clippy::unused_self)]
55    pub(crate) fn write_cpu_address(&self, _address: u16, _value: u8) {}
56}
57
58impl HasBasicPpuMapping for MapperImpl<Nrom> {
59    fn map_ppu_address(&self, address: u16) -> PpuMapResult {
60        basic_map_ppu_address(address, self.data.chr_type, self.data.nametable_mirroring)
61    }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
65pub(crate) enum UxromVariant {
66    Uxrom,
67    Codemasters,
68    FireHawk,
69}
70
71#[derive(Debug, Clone, Encode, Decode)]
72pub(crate) struct Uxrom {
73    variant: UxromVariant,
74    prg_bank: u8,
75    chr_type: ChrType,
76    nametable_mirroring: NametableMirroring,
77}
78
79impl Uxrom {
80    pub(crate) fn new(
81        mapper_number: u16,
82        sub_mapper_number: u8,
83        chr_type: ChrType,
84        nametable_mirroring: NametableMirroring,
85    ) -> Self {
86        let variant = match (mapper_number, sub_mapper_number) {
87            (2, _) => UxromVariant::Uxrom,
88            (71, 0) => UxromVariant::Codemasters,
89            (71, 1) => UxromVariant::FireHawk,
90            _ => panic!(
91                "invalid UxROM mapper/submapper: mapper={mapper_number}, submapper={sub_mapper_number}"
92            ),
93        };
94
95        let nametable_mirroring = match variant {
96            UxromVariant::FireHawk => NametableMirroring::SingleScreenBank0,
97            UxromVariant::Uxrom | UxromVariant::Codemasters => nametable_mirroring,
98        };
99        Self { variant, prg_bank: 0, chr_type, nametable_mirroring }
100    }
101}
102
103impl MapperImpl<Uxrom> {
104    pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 {
105        match address {
106            0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"),
107            0x4020..=0x5FFF => cpu_open_bus,
108            0x6000..=0x7FFF => self.cartridge.get_prg_ram((address & 0x1FFF).into()),
109            0x8000..=0xBFFF => {
110                let prg_rom_addr =
111                    BankSizeKb::Sixteen.to_absolute_address(self.data.prg_bank, address);
112                self.cartridge.get_prg_rom(prg_rom_addr)
113            }
114            0xC000..=0xFFFF => {
115                let prg_rom_addr = BankSizeKb::Sixteen
116                    .to_absolute_address_last_bank(self.cartridge.prg_rom.len() as u32, address);
117                self.cartridge.get_prg_rom(prg_rom_addr)
118            }
119        }
120    }
121
122    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
123        match (self.data.variant, address) {
124            (_, 0x0000..=0x401F) => panic!("invalid CPU map address: 0x{address:04X}"),
125            (UxromVariant::Uxrom, 0x8000..=0xFFFF)
126            | (UxromVariant::Codemasters | UxromVariant::FireHawk, 0xC000..=0xFFFF) => {
127                self.data.prg_bank = value;
128            }
129            (UxromVariant::FireHawk, 0x8000..=0x9FFF) => {
130                self.data.nametable_mirroring = if value.bit(4) {
131                    NametableMirroring::SingleScreenBank1
132                } else {
133                    NametableMirroring::SingleScreenBank0
134                };
135            }
136            (_, 0x6000..=0x7FFF) => {
137                self.cartridge.set_prg_ram((address & 0x1FFF).into(), value);
138            }
139            (_, 0x4020..=0x5FFF)
140            | (UxromVariant::Codemasters, 0x8000..=0x9FFF)
141            | (UxromVariant::Codemasters | UxromVariant::FireHawk, 0xA000..=0xBFFF) => {}
142        }
143    }
144
145    pub(crate) fn name(&self) -> &'static str {
146        match self.data.variant {
147            UxromVariant::Uxrom => "UxROM",
148            UxromVariant::Codemasters => "Codemasters",
149            UxromVariant::FireHawk => "Codemasters (Fire Hawk variant)",
150        }
151    }
152}
153
154impl HasBasicPpuMapping for MapperImpl<Uxrom> {
155    fn map_ppu_address(&self, address: u16) -> PpuMapResult {
156        basic_map_ppu_address(address, self.data.chr_type, self.data.nametable_mirroring)
157    }
158}
159
160#[derive(Debug, Clone, Encode, Decode)]
161pub(crate) struct Cnrom {
162    chr_type: ChrType,
163    chr_bank: u8,
164    nametable_mirroring: NametableMirroring,
165    emulate_bus_conflicts: bool,
166}
167
168impl Cnrom {
169    pub(crate) fn new(
170        chr_type: ChrType,
171        nametable_mirroring: NametableMirroring,
172        sub_mapper_number: u8,
173    ) -> Self {
174        let emulate_bus_conflicts = sub_mapper_number == 0 || sub_mapper_number == 2;
175        log::info!(
176            "Emulating CNROM bus conflicts: {emulate_bus_conflicts} (sub mapper {sub_mapper_number})"
177        );
178
179        Self { chr_type, chr_bank: 0, nametable_mirroring, emulate_bus_conflicts }
180    }
181}
182
183impl MapperImpl<Cnrom> {
184    pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 {
185        match address {
186            0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"),
187            0x4020..=0x5FFF => cpu_open_bus,
188            0x6000..=0x7FFF => {
189                if !self.cartridge.prg_ram.is_empty() {
190                    self.cartridge.get_prg_ram((address & 0x1FFF).into())
191                } else {
192                    cpu_open_bus
193                }
194            }
195            0x8000..=0xFFFF => self.cartridge.get_prg_rom((address & 0x7FFF).into()),
196        }
197    }
198
199    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
200        match address {
201            0x0000..=0x401F => panic!("invalid CPU map address: 0x{address:04X}"),
202            0x4020..=0x5FFF => {}
203            0x6000..=0x7FFF => {
204                if !self.cartridge.prg_ram.is_empty() {
205                    self.cartridge.set_prg_ram((address & 0x1FFF).into(), value);
206                }
207            }
208            0x8000..=0xFFFF => {
209                // Mapper bus conflict behavior: the value written by the CPU is bitwise ANDed with
210                // the value at that address in PRG ROM.
211                // Cybernoid depends on this or graphics will be corrupted after certain parts of
212                // its audio code run
213                self.data.chr_bank = if self.data.emulate_bus_conflicts {
214                    value & self.cartridge.get_prg_rom(address.into())
215                } else {
216                    value
217                };
218            }
219        }
220    }
221}
222
223impl HasBasicPpuMapping for MapperImpl<Cnrom> {
224    fn map_ppu_address(&self, address: u16) -> PpuMapResult {
225        match address {
226            0x0000..=0x1FFF => {
227                let chr_addr = BankSizeKb::Eight.to_absolute_address(self.data.chr_bank, address);
228                self.data.chr_type.to_map_result(chr_addr)
229            }
230            0x2000..=0x3EFF => {
231                PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address))
232            }
233            _ => panic!("invalid PPU map address: 0x{address:04X}"),
234        }
235    }
236}
237
238#[derive(Debug, Clone, Encode, Decode)]
239pub(crate) struct Axrom {
240    chr_type: ChrType,
241    prg_bank: u8,
242    nametable_mirroring: NametableMirroring,
243}
244
245impl Axrom {
246    pub(crate) fn new(chr_type: ChrType) -> Self {
247        Self { chr_type, prg_bank: 0, nametable_mirroring: NametableMirroring::SingleScreenBank0 }
248    }
249}
250
251impl MapperImpl<Axrom> {
252    pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 {
253        if address < 0x8000 {
254            return cpu_open_bus;
255        }
256
257        let prg_rom_addr = BankSizeKb::ThirtyTwo.to_absolute_address(self.data.prg_bank, address);
258        self.cartridge.get_prg_rom(prg_rom_addr)
259    }
260
261    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
262        if address < 0x8000 {
263            return;
264        }
265
266        self.data.prg_bank = value & 0x07;
267        self.data.nametable_mirroring = if value.bit(4) {
268            NametableMirroring::SingleScreenBank1
269        } else {
270            NametableMirroring::SingleScreenBank0
271        };
272    }
273}
274
275impl HasBasicPpuMapping for MapperImpl<Axrom> {
276    fn map_ppu_address(&self, address: u16) -> PpuMapResult {
277        basic_map_ppu_address(address, self.data.chr_type, self.data.nametable_mirroring)
278    }
279}
280
281#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
282enum GxromVariant {
283    Gxrom,
284    ColorDreams,
285    Jaleco,
286}
287
288#[derive(Debug, Clone, Encode, Decode)]
289pub(crate) struct Gxrom {
290    variant: GxromVariant,
291    prg_bank: u8,
292    chr_bank: u8,
293    nametable_mirroring: NametableMirroring,
294}
295
296impl Gxrom {
297    pub(crate) fn new(mapper_number: u16, nametable_mirroring: NametableMirroring) -> Self {
298        let variant = match mapper_number {
299            11 => GxromVariant::ColorDreams,
300            66 => GxromVariant::Gxrom,
301            140 => GxromVariant::Jaleco,
302            _ => panic!("invalid GxROM mapper number: {mapper_number}"),
303        };
304
305        Self { variant, prg_bank: 0, chr_bank: 0, nametable_mirroring }
306    }
307}
308
309impl MapperImpl<Gxrom> {
310    pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 {
311        match address {
312            0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"),
313            0x4020..=0x7FFF => cpu_open_bus,
314            0x8000..=0xFFFF => {
315                let prg_rom_addr =
316                    BankSizeKb::ThirtyTwo.to_absolute_address(self.data.prg_bank, address);
317                self.cartridge.get_prg_rom(prg_rom_addr)
318            }
319        }
320    }
321
322    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
323        match (self.data.variant, address) {
324            (_, 0x0000..=0x401F) => panic!("invalid CPU map address: {address:04X}"),
325            (_, 0x4020..=0x5FFF)
326            | (GxromVariant::Gxrom | GxromVariant::ColorDreams, 0x6000..=0x7FFF)
327            | (GxromVariant::Jaleco, 0x8000..=0xFFFF) => {}
328            (GxromVariant::Gxrom | GxromVariant::ColorDreams, 0x8000..=0xFFFF)
329            | (GxromVariant::Jaleco, 0x6000..=0x7FFF) => {
330                let high_nibble = (value & 0xF0) >> 4;
331                let low_nibble = value & 0x0F;
332
333                match self.data.variant {
334                    GxromVariant::ColorDreams => {
335                        self.data.prg_bank = low_nibble;
336                        self.data.chr_bank = high_nibble;
337                    }
338                    GxromVariant::Gxrom | GxromVariant::Jaleco => {
339                        self.data.prg_bank = high_nibble;
340                        self.data.chr_bank = low_nibble;
341                    }
342                }
343            }
344        }
345    }
346
347    pub(crate) fn name(&self) -> &'static str {
348        match self.data.variant {
349            GxromVariant::Gxrom => "GxROM",
350            GxromVariant::Jaleco => "Jaleco JF-11 / JF-14",
351            GxromVariant::ColorDreams => "Color Dreams",
352        }
353    }
354}
355
356impl HasBasicPpuMapping for MapperImpl<Gxrom> {
357    fn map_ppu_address(&self, address: u16) -> PpuMapResult {
358        match address {
359            0x0000..=0x1FFF => {
360                let chr_rom_addr =
361                    BankSizeKb::Eight.to_absolute_address(self.data.chr_bank, address);
362                PpuMapResult::ChrROM(chr_rom_addr)
363            }
364            0x2000..=0x3EFF => {
365                PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address))
366            }
367            0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"),
368        }
369    }
370}
371
372#[derive(Debug, Clone, Encode, Decode)]
373pub(crate) struct Bnrom {
374    prg_bank: u8,
375    chr_bank_0: u8,
376    chr_bank_1: u8,
377    chr_type: ChrType,
378    nametable_mirroring: NametableMirroring,
379}
380
381impl Bnrom {
382    pub(crate) fn new(chr_type: ChrType, nametable_mirroring: NametableMirroring) -> Self {
383        Self { prg_bank: 0, chr_bank_0: 0, chr_bank_1: 1, chr_type, nametable_mirroring }
384    }
385}
386
387impl MapperImpl<Bnrom> {
388    pub(crate) fn read_cpu_address(&self, address: u16, cpu_open_bus: u8) -> u8 {
389        match address {
390            0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"),
391            0x4020..=0x5FFF => cpu_open_bus,
392            0x6000..=0x7FFF => {
393                if !self.cartridge.prg_ram.is_empty() {
394                    self.cartridge.get_prg_ram((address & 0x1FFF).into())
395                } else {
396                    cpu_open_bus
397                }
398            }
399            0x8000..=0xFFFF => {
400                let prg_rom_addr =
401                    BankSizeKb::ThirtyTwo.to_absolute_address(self.data.prg_bank, address);
402                self.cartridge.get_prg_rom(prg_rom_addr)
403            }
404        }
405    }
406
407    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
408        match address {
409            0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"),
410            0x4020..=0x5FFF => {}
411            0x6000..=0x7FFC => {
412                if !self.cartridge.prg_ram.is_empty() {
413                    self.cartridge.set_prg_ram((address & 0x1FFF).into(), value);
414                }
415            }
416            0x7FFD | 0x8000..=0xFFFF => {
417                self.data.prg_bank = value & 0x03;
418            }
419            0x7FFE => {
420                self.data.chr_bank_0 = value & 0x0F;
421            }
422            0x7FFF => {
423                self.data.chr_bank_1 = value & 0x0F;
424            }
425        }
426    }
427}
428
429impl HasBasicPpuMapping for MapperImpl<Bnrom> {
430    fn map_ppu_address(&self, address: u16) -> PpuMapResult {
431        match address {
432            0x0000..=0x0FFF => {
433                let chr_addr = BankSizeKb::Four.to_absolute_address(self.data.chr_bank_0, address);
434                self.data.chr_type.to_map_result(chr_addr)
435            }
436            0x1000..=0x1FFF => {
437                let chr_addr = BankSizeKb::Four.to_absolute_address(self.data.chr_bank_1, address);
438                self.data.chr_type.to_map_result(chr_addr)
439            }
440            0x2000..=0x3EFF => {
441                PpuMapResult::Vram(self.data.nametable_mirroring.map_to_vram(address))
442            }
443            0x3F00..=0xFFFF => panic!("invalid PPU map address: {address:04X}"),
444        }
445    }
446}