1mod mappers;
2
3use bincode::de::{BorrowDecoder, Decoder};
4use bincode::enc::Encoder;
5use bincode::error::{DecodeError, EncodeError};
6use bincode::{BorrowDecode, Decode, Encode};
7use jgenesis_common::frontend::{PartialClone, TimingMode};
8use jgenesis_common::num::GetBit;
9use jgenesis_proc_macros::MatchEachVariantMacro;
10use std::fmt::{Display, Formatter};
11use std::{io, mem};
12use thiserror::Error;
13
14use crate::bus::cartridge::mappers::action52::Action52;
15use crate::bus::cartridge::mappers::bandai::BandaiFcg;
16use crate::bus::cartridge::mappers::konami::{Vrc4, Vrc6, Vrc7};
17use crate::bus::cartridge::mappers::mmc1::Mmc1;
18use crate::bus::cartridge::mappers::mmc2::Mmc2;
19use crate::bus::cartridge::mappers::mmc3::Mmc3;
20use crate::bus::cartridge::mappers::mmc5::Mmc5;
21use crate::bus::cartridge::mappers::namco163::Namco163;
22use crate::bus::cartridge::mappers::namco175::Namco175;
23use crate::bus::cartridge::mappers::nrom::{Axrom, Bnrom, Cnrom, Gxrom, Nrom, Uxrom};
24use crate::bus::cartridge::mappers::sunsoft::Sunsoft;
25use crate::bus::cartridge::mappers::unrom512::Unrom512;
26use crate::bus::cartridge::mappers::{ChrType, NametableMirroring, PpuMapResult, unrom512};
27#[cfg(test)]
28pub(crate) use mappers::new_mmc1;
29
30#[derive(Debug, Clone, PartialClone)]
31pub struct Cartridge {
32    timing_mode: TimingMode,
33    #[partial_clone(default)]
34    prg_rom: Vec<u8>,
35    prg_ram: Vec<u8>,
36    has_ram_battery: bool,
37    prg_ram_dirty_bit: bool,
38    #[partial_clone(default)]
39    chr_rom: Vec<u8>,
40    chr_ram: Vec<u8>,
41}
42
43// Encode and Decode are implemented explicitly instead of using derive in order to avoid
44// serializing ROM bytes as part of save states
45impl Encode for Cartridge {
46    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
47        self.timing_mode.encode(encoder)?;
48        self.prg_ram.encode(encoder)?;
49        self.has_ram_battery.encode(encoder)?;
50        self.prg_ram_dirty_bit.encode(encoder)?;
51        self.chr_ram.encode(encoder)?;
52
53        Ok(())
54    }
55}
56
57impl<Context> Decode<Context> for Cartridge {
58    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
59        let timing_mode = Decode::decode(decoder)?;
60        let prg_ram = Decode::decode(decoder)?;
61        let has_ram_battery = Decode::decode(decoder)?;
62        let prg_ram_dirty_bit = Decode::decode(decoder)?;
63        let chr_ram = Decode::decode(decoder)?;
64
65        Ok(Self {
66            timing_mode,
67            prg_rom: vec![],
68            prg_ram,
69            has_ram_battery,
70            prg_ram_dirty_bit,
71            chr_rom: vec![],
72            chr_ram,
73        })
74    }
75}
76
77impl<'de, Context> BorrowDecode<'de, Context> for Cartridge {
78    fn borrow_decode<D: BorrowDecoder<'de, Context = Context>>(
79        decoder: &mut D,
80    ) -> Result<Self, DecodeError> {
81        let timing_mode = BorrowDecode::borrow_decode(decoder)?;
82        let prg_ram = BorrowDecode::borrow_decode(decoder)?;
83        let has_ram_battery = BorrowDecode::borrow_decode(decoder)?;
84        let prg_ram_dirty_bit = BorrowDecode::borrow_decode(decoder)?;
85        let chr_ram = BorrowDecode::borrow_decode(decoder)?;
86
87        Ok(Self {
88            timing_mode,
89            prg_rom: vec![],
90            prg_ram,
91            has_ram_battery,
92            prg_ram_dirty_bit,
93            chr_rom: vec![],
94            chr_ram,
95        })
96    }
97}
98
99impl Cartridge {
100    fn get_prg_rom(&self, address: u32) -> u8 {
101        self.prg_rom[(address as usize) & (self.prg_rom.len() - 1)]
102    }
103
104    fn get_prg_ram(&self, address: u32) -> u8 {
105        if self.prg_ram.is_empty() {
106            return 0xFF;
107        }
108
109        self.prg_ram[(address as usize) & (self.prg_ram.len() - 1)]
110    }
111
112    fn set_prg_ram(&mut self, address: u32, value: u8) {
113        if self.prg_ram.is_empty() {
114            return;
115        }
116
117        let prg_ram_len = self.prg_ram.len();
118        self.prg_ram[(address as usize) & (prg_ram_len - 1)] = value;
119        if self.has_ram_battery {
120            self.prg_ram_dirty_bit = true;
121        }
122    }
123
124    fn get_chr_rom(&self, address: u32) -> u8 {
125        if self.chr_rom.is_empty() {
126            return 0xFF;
127        }
128
129        self.chr_rom[(address as usize) & (self.chr_rom.len() - 1)]
130    }
131
132    fn get_chr_ram(&self, address: u32) -> u8 {
133        if self.chr_ram.is_empty() {
134            return 0xFF;
135        }
136
137        self.chr_ram[(address as usize) & (self.chr_ram.len() - 1)]
138    }
139
140    fn set_chr_ram(&mut self, address: u32, value: u8) {
141        if self.chr_ram.is_empty() {
142            return;
143        }
144
145        let chr_ram_len = self.chr_ram.len();
146        self.chr_ram[(address as usize) & (chr_ram_len - 1)] = value;
147    }
148
149    fn move_rom_from(&mut self, other: &mut Self) {
150        self.prg_rom = mem::take(&mut other.prg_rom);
151        self.chr_rom = mem::take(&mut other.chr_rom);
152    }
153}
154
155#[derive(Debug, Clone, Encode, Decode)]
156pub(crate) struct MapperImpl<MapperData> {
157    cartridge: Cartridge,
158    data: MapperData,
159}
160
161// Not using derive macro because it only impls the trait when MapperData implements PartialClone, which is not a
162// necessary bound here
163impl<MapperData: Clone> PartialClone for MapperImpl<MapperData> {
164    fn partial_clone(&self) -> Self {
165        Self { cartridge: self.cartridge.partial_clone(), data: self.data.clone() }
166    }
167}
168
169pub(crate) trait HasBasicPpuMapping {
170    fn map_ppu_address(&self, address: u16) -> PpuMapResult;
171}
172
173impl<MapperData> MapperImpl<MapperData>
174where
175    MapperImpl<MapperData>: HasBasicPpuMapping,
176{
177    fn read_ppu_address(&self, address: u16, vram: &[u8; 2048]) -> u8 {
178        self.map_ppu_address(address).read(&self.cartridge, vram)
179    }
180
181    fn write_ppu_address(&mut self, address: u16, value: u8, vram: &mut [u8; 2048]) {
182        self.map_ppu_address(address).write(value, &mut self.cartridge, vram);
183    }
184}
185
186#[allow(clippy::large_enum_variant)]
187#[derive(Debug, Clone, Encode, Decode, PartialClone, MatchEachVariantMacro)]
188pub(crate) enum Mapper {
189    Action52(#[partial_clone(partial)] MapperImpl<Action52>),
190    Axrom(#[partial_clone(partial)] MapperImpl<Axrom>),
191    BandaiFcg(#[partial_clone(partial)] MapperImpl<BandaiFcg>),
192    Bnrom(#[partial_clone(partial)] MapperImpl<Bnrom>),
193    Cnrom(#[partial_clone(partial)] MapperImpl<Cnrom>),
194    Gxrom(#[partial_clone(partial)] MapperImpl<Gxrom>),
195    Mmc1(#[partial_clone(partial)] MapperImpl<Mmc1>),
196    Mmc2(#[partial_clone(partial)] MapperImpl<Mmc2>),
197    Mmc3(#[partial_clone(partial)] MapperImpl<Mmc3>),
198    Mmc5(#[partial_clone(partial)] MapperImpl<Mmc5>),
199    Namco163(#[partial_clone(partial)] MapperImpl<Namco163>),
200    Namco175(#[partial_clone(partial)] MapperImpl<Namco175>),
201    Nrom(#[partial_clone(partial)] MapperImpl<Nrom>),
202    Sunsoft(#[partial_clone(partial)] MapperImpl<Sunsoft>),
203    Unrom512(#[partial_clone(partial)] MapperImpl<Unrom512>),
204    Uxrom(#[partial_clone(partial)] MapperImpl<Uxrom>),
205    Vrc4(#[partial_clone(partial)] MapperImpl<Vrc4>),
206    Vrc6(#[partial_clone(partial)] MapperImpl<Vrc6>),
207    Vrc7(#[partial_clone(partial)] MapperImpl<Vrc7>),
208}
209
210impl Mapper {
211    /// Retrieve the mapper's user-readable name. Only used for logging output.
212    pub(crate) fn name(&self) -> &'static str {
213        match self {
214            Self::Action52(..) => "Action 52",
215            Self::Axrom(..) => "AxROM",
216            Self::BandaiFcg(bandai_fcg) => bandai_fcg.name(),
217            Self::Bnrom(..) => "BNROM / NINA-001",
218            Self::Cnrom(..) => "CNROM",
219            Self::Gxrom(gxrom) => gxrom.name(),
220            Self::Mmc1(..) => "MMC1",
221            Self::Mmc2(mmc2) => mmc2.name(),
222            Self::Mmc3(mmc3) => mmc3.name(),
223            Self::Mmc5(..) => "MMC5",
224            Self::Namco163(..) => "Namco 163",
225            Self::Namco175(..) => "Namco 175",
226            Self::Nrom(..) => "NROM",
227            Self::Sunsoft(..) => "Sunsoft",
228            Self::Unrom512(..) => "UNROM 512",
229            Self::Uxrom(uxrom) => uxrom.name(),
230            Self::Vrc4(vrc4) => vrc4.name(),
231            Self::Vrc6(..) => "VRC6",
232            Self::Vrc7(..) => "VRC7",
233        }
234    }
235
236    /// Read a value from the given address in the CPU address space.
237    pub(crate) fn read_cpu_address(&mut self, address: u16, cpu_open_bus: u8) -> u8 {
238        match_each_variant!(self, mapper => mapper.read_cpu_address(address, cpu_open_bus))
239    }
240
241    /// Write a value to the given address in the CPU address space.
242    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
243        match_each_variant!(self, mapper => mapper.write_cpu_address(address, value));
244    }
245
246    /// Read a value from the given address in the PPU address space.
247    pub(crate) fn read_ppu_address(&mut self, address: u16, vram: &[u8; 2048]) -> u8 {
248        match_each_variant!(self, mapper => mapper.read_ppu_address(address, vram))
249    }
250
251    /// Write a value to the given address in the PPU address space.
252    pub(crate) fn write_ppu_address(&mut self, address: u16, value: u8, vram: &mut [u8; 2048]) {
253        match_each_variant!(self, mapper => mapper.write_ppu_address(address, value, vram));
254    }
255
256    /// Perform any processing that should be performed after every PPU cycle.
257    pub(crate) fn tick(&mut self, ppu_bus_address: u16) {
258        if let Self::Mmc3(mmc3) = self {
259            mmc3.tick(ppu_bus_address);
260        }
261    }
262
263    /// Perform any processing that should be performed after every CPU cycle. Commonly used for
264    /// interrupt counters and expansion audio.
265    pub(crate) fn tick_cpu(&mut self) {
266        match self {
267            Self::BandaiFcg(bandai_fcg) => {
268                bandai_fcg.tick_cpu();
269            }
270            Self::Mmc1(mmc1) => {
271                mmc1.tick_cpu();
272            }
273            Self::Mmc5(mmc5) => {
274                mmc5.tick_cpu();
275            }
276            Self::Namco163(namco163) => {
277                namco163.tick_cpu();
278            }
279            Self::Sunsoft(sunsoft) => {
280                sunsoft.tick_cpu();
281            }
282            Self::Vrc4(vrc4) => {
283                vrc4.tick_cpu();
284            }
285            Self::Vrc6(vrc6) => {
286                vrc6.tick_cpu();
287            }
288            Self::Vrc7(vrc7) => {
289                vrc7.tick_cpu();
290            }
291            _ => {}
292        }
293    }
294
295    /// Return whether this board is currently generating an IRQ.
296    pub(crate) fn interrupt_flag(&self) -> bool {
297        match self {
298            Self::BandaiFcg(bandai_fcg) => bandai_fcg.interrupt_flag(),
299            Self::Mmc3(mmc3) => mmc3.interrupt_flag(),
300            Self::Mmc5(mmc5) => mmc5.interrupt_flag(),
301            Self::Namco163(namco163) => namco163.interrupt_flag(),
302            Self::Sunsoft(sunsoft) => sunsoft.interrupt_flag(),
303            Self::Vrc4(vrc4) => vrc4.interrupt_flag(),
304            Self::Vrc6(vrc6) => vrc6.interrupt_flag(),
305            Self::Vrc7(vrc7) => vrc7.interrupt_flag(),
306            _ => false,
307        }
308    }
309
310    /// Process a PPUCTRL write. Only needed by the MMC5 mapper in order to know whether double
311    /// height sprites are enabled.
312    pub(crate) fn process_ppu_ctrl_update(&mut self, value: u8) {
313        if let Self::Mmc5(mmc5) = self {
314            mmc5.process_ppu_ctrl_update(value);
315        }
316    }
317
318    /// Process a PPUMASK write. Used by MMC5 to know whether rendering is currently enabled
319    pub(crate) fn process_ppu_mask_update(&mut self, value: u8) {
320        if let Self::Mmc5(mmc5) = self {
321            mmc5.process_ppu_mask_update(value);
322        }
323    }
324
325    /// Notify the mapper that the CPU will imminently access the PPUDATA register. This is required
326    /// by MMC5 to map PPUDATA reads/writes to the correct CHR banks.
327    ///
328    /// This should be called *before* the actual memory access.
329    pub(crate) fn about_to_access_ppu_data(&mut self) {
330        if let Self::Mmc5(mmc5) = self {
331            mmc5.about_to_access_ppu_data();
332        }
333    }
334
335    /// Return whether the board's writable memory (if any) has been written to since the last time
336    /// this method was called.
337    pub(crate) fn get_and_clear_ram_dirty_bit(&mut self) -> bool {
338        match self {
339            Mapper::BandaiFcg(mapper) => {
340                if mapper.get_and_clear_eeprom_dirty_bit() {
341                    return true;
342                }
343            }
344            Mapper::Namco163(mapper) if mapper.has_battery_backed_internal_ram() => {
345                if mapper.get_and_clear_internal_ram_dirty_bit() {
346                    return true;
347                }
348            }
349            Mapper::Unrom512(mapper) => {
350                if mapper.get_and_clear_dirty_bit() {
351                    return true;
352                }
353            }
354            _ => {}
355        }
356
357        match_each_variant!(self, mapper => {
358            let dirty_bit = mapper.cartridge.prg_ram_dirty_bit;
359            mapper.cartridge.prg_ram_dirty_bit = false;
360            dirty_bit
361        })
362    }
363
364    /// Return the board's writable memory as a slice. This will be an empty slice if the board
365    /// has no PRG RAM or EEPROM.
366    pub(crate) fn get_prg_ram(&self) -> &[u8] {
367        match self {
368            Mapper::BandaiFcg(mapper) if let Some(eeprom) = mapper.eeprom() => {
369                return eeprom;
370            }
371            Mapper::Namco163(mapper) if mapper.has_battery_backed_internal_ram() => {
372                return mapper.get_internal_ram();
373            }
374            Mapper::Unrom512(mapper) if mapper.is_flashable() => {
375                // Some UNROM 512 cartridges have flashable PRG ROM that is used to store save data
376                return &mapper.cartridge.prg_rom;
377            }
378            _ => {}
379        }
380
381        match_each_variant!(self, mapper => &mapper.cartridge.prg_ram)
382    }
383
384    /// Retrieve the timing mode of the cartridge (NTSC/PAL).
385    pub(crate) fn timing_mode(&self) -> TimingMode {
386        match_each_variant!(self, mapper => mapper.cartridge.timing_mode)
387    }
388
389    /// If the board has expansion audio, generate an audio sample and mix it with the mixed APU
390    /// sample.
391    ///
392    /// If the board does not have expansion audio or it is not enabled then this method will simply
393    /// return the mixed APU sample as-is.
394    pub(crate) fn sample_audio(&self, mixed_apu_sample: f64) -> f64 {
395        match self {
396            Self::Mmc5(mmc5) => mmc5.sample_audio(mixed_apu_sample),
397            Self::Namco163(namco163) => namco163.sample_audio(mixed_apu_sample),
398            Self::Sunsoft(sunsoft) => sunsoft.sample_audio(mixed_apu_sample),
399            Self::Vrc6(vrc6) => vrc6.sample_audio(mixed_apu_sample),
400            Self::Vrc7(vrc7) => vrc7.sample_audio(mixed_apu_sample),
401            _ => mixed_apu_sample,
402        }
403    }
404
405    /// Move cartridge ROM fields from another `Mapper` instance. Used when loading save states.
406    pub(crate) fn move_rom_from(&mut self, other: &mut Self) {
407        let other_cartridge = match_each_variant!(other, mapper => &mut mapper.cartridge);
408        match_each_variant!(self, mapper => mapper.cartridge.move_rom_from(other_cartridge));
409    }
410
411    pub(crate) fn reset(&mut self) {
412        if let Self::Action52(action52) = self {
413            action52.reset();
414        }
415    }
416}
417
418#[derive(Debug, Error)]
419pub enum CartridgeFileError {
420    #[error("I/O error: {source}")]
421    Io {
422        #[from]
423        source: io::Error,
424    },
425    #[error("invalid or unsupported file format")]
426    Format,
427    #[error("unsupported mapper: {mapper_number}")]
428    UnsupportedMapper { mapper_number: u16 },
429    #[error("cartridge header specifies both volatile and non-volatile PRG RAM")]
430    MultiplePrgRamTypes,
431    #[error(
432        "ROM is not large enough to hold an iNES header; expected at least 16 bytes, was {file_size} bytes"
433    )]
434    TooSmallForHeader { file_size: u32 },
435    #[error(
436        "Invalid PRG/CHR ROM size in ROM header: file size is {file_size} bytes, PRG ROM size is {prg_rom_size} bytes, CHR ROM size is {chr_rom_size} bytes"
437    )]
438    InvalidRomSize { file_size: u32, prg_rom_size: u32, chr_rom_size: u32 },
439    #[error("unsupported timing mode byte: {byte}")]
440    UnsupportedTimingMode { byte: u8 },
441}
442
443#[derive(Debug, Clone, Copy, PartialEq, Eq)]
444enum FileFormat {
445    INes,
446    Nes2Point0,
447}
448
449impl Display for FileFormat {
450    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
451        match self {
452            Self::INes => write!(f, "iNES"),
453            Self::Nes2Point0 => write!(f, "NES 2.0"),
454        }
455    }
456}
457
458#[derive(Debug, Clone)]
459pub struct INesHeader {
460    mapper_number: u16,
461    sub_mapper_number: u8,
462    timing_mode: TimingMode,
463    prg_rom_size: u32,
464    prg_ram_size: u32,
465    chr_rom_size: u32,
466    chr_ram_size: u32,
467    chr_type: ChrType,
468    nametable_mirroring: NametableMirroring,
469    has_trainer: bool,
470    has_battery: bool,
471    has_four_screen_vram: bool,
472}
473
474impl INesHeader {
475    fn parse_from_file(file_bytes: &[u8]) -> Result<INesHeader, CartridgeFileError> {
476        if file_bytes.len() < 16 {
477            return Err(CartridgeFileError::TooSmallForHeader {
478                file_size: file_bytes.len() as u32,
479            });
480        }
481
482        let header = &file_bytes[..16];
483
484        // All iNES headers should begin with this 4-byte sequence, which is "NES" followed by the
485        // character that MS-DOS used for EOF
486        if header[..4] != [b'N', b'E', b'S', 0x1A] {
487            return Err(CartridgeFileError::Format);
488        }
489
490        let format =
491            if header[7] & 0x0C == 0x08 { FileFormat::Nes2Point0 } else { FileFormat::INes };
492        let has_trainer = header[6].bit(2);
493        let mapper_number = u16::from((header[7] & 0xF0) | ((header[6] & 0xF0) >> 4));
494
495        let chr_rom_size = {
496            let mut chr_rom_size_8kb = u32::from(header[5]);
497            if format == FileFormat::Nes2Point0 {
498                chr_rom_size_8kb |= u32::from(header[9] & 0xF0) << 4;
499            }
500            8 * 1024 * chr_rom_size_8kb
501        };
502
503        let prg_rom_size = {
504            let mut prg_rom_size_16kb = u32::from(header[4]);
505            if format == FileFormat::Nes2Point0 {
506                prg_rom_size_16kb |= u32::from(header[9] & 0x0F) << 8;
507            }
508            let prg_rom_size = 16 * 1024 * prg_rom_size_16kb;
509
510            // Hack: Galaxian (J) only has 8KB of PRG ROM, and the iNES header usually contains a
511            // nonsensical PRG ROM size because it can't represent this
512            if should_apply_8kb_prg_rom_hack(file_bytes, mapper_number, has_trainer, chr_rom_size) {
513                log::info!("Ignoring PRG ROM size in header of {prg_rom_size} bytes");
514                8 * 1024
515            } else {
516                prg_rom_size
517            }
518        };
519
520        if header.len() + (prg_rom_size + chr_rom_size) as usize > file_bytes.len() {
521            return Err(CartridgeFileError::InvalidRomSize {
522                file_size: file_bytes.len() as u32,
523                prg_rom_size,
524                chr_rom_size,
525            });
526        }
527
528        let chr_type = if chr_rom_size == 0 { ChrType::RAM } else { ChrType::ROM };
529
530        let nametable_mirroring = if header[6].bit(0) {
531            NametableMirroring::Vertical
532        } else {
533            NametableMirroring::Horizontal
534        };
535
536        let has_four_screen_vram = header[6].bit(3);
537
538        let has_battery = header[6].bit(1);
539
540        log::info!("ROM header format: {format}");
541
542        let sub_mapper_number = match format {
543            FileFormat::Nes2Point0 => header[8] >> 4,
544            FileFormat::INes => 0,
545        };
546
547        let timing_mode = match format {
548            FileFormat::Nes2Point0 => {
549                let timing_mode_byte = header[12] & 0x03;
550                match timing_mode_byte {
551                    0x00 | 0x02 => TimingMode::Ntsc,
552                    0x01 => TimingMode::Pal,
553                    0x03 => {
554                        return Err(CartridgeFileError::UnsupportedTimingMode {
555                            byte: timing_mode_byte,
556                        });
557                    }
558                    _ => unreachable!("value & 0x03 should always be 0x00/0x01/0x02/0x03"),
559                }
560            }
561            FileFormat::INes => {
562                if header[9].bit(0) {
563                    TimingMode::Pal
564                } else {
565                    TimingMode::Ntsc
566                }
567            }
568        };
569
570        let prg_ram_size = determine_prg_ram_size(header, mapper_number, format);
571
572        let chr_ram_size = match (chr_type, format) {
573            (ChrType::RAM, FileFormat::Nes2Point0) => {
574                let chr_ram_shift = header[11] & 0x0F;
575                if chr_ram_shift > 0 { 64 << chr_ram_shift } else { 0 }
576            }
577            (ChrType::RAM, FileFormat::INes) => {
578                if mapper_number == unrom512::MAPPER_NUMBER {
579                    unrom512::INES_CHR_RAM_LEN
580                } else {
581                    8 * 1024
582                }
583            }
584            (ChrType::ROM, _) => 0,
585        };
586
587        Ok(Self {
588            mapper_number,
589            sub_mapper_number,
590            timing_mode,
591            prg_rom_size,
592            prg_ram_size,
593            chr_rom_size,
594            chr_ram_size,
595            chr_type,
596            nametable_mirroring,
597            has_trainer,
598            has_battery,
599            has_four_screen_vram,
600        })
601    }
602}
603
604// Check whether to assume the cartridge has 8KB of PRG ROM, which the iNES format cannot represent
605fn should_apply_8kb_prg_rom_hack(
606    file_bytes: &[u8],
607    mapper_number: u16,
608    has_trainer: bool,
609    chr_rom_size: u32,
610) -> bool {
611    if mapper_number != 0 {
612        // Only apply this hack for mapper 0
613        return false;
614    }
615
616    let trainer_len = if has_trainer { 512 } else { 0 };
617    let header_len = 16 + trainer_len;
618
619    file_bytes.len().saturating_sub(header_len).saturating_sub(chr_rom_size as usize) == 8 * 1024
620}
621
622fn determine_prg_ram_size(header: &[u8], mapper_number: u16, format: FileFormat) -> u32 {
623    let prg_ram_size = match format {
624        FileFormat::Nes2Point0 => {
625            let volatile_shift = header[10] & 0x0F;
626            let non_volatile_shift = header[10] >> 4;
627            // TODO separate these? very very few games have both volatile and non-volatile RAM
628            let volatile_ram = if volatile_shift > 0 { 64 << volatile_shift } else { 0 };
629            let non_volatile_ram =
630                if non_volatile_shift > 0 { 64 << non_volatile_shift } else { 0 };
631            let total_ram = volatile_ram + non_volatile_ram;
632
633            // Hack to handle MMC5 headers that don't specify PRG RAM size but expect 32KB/64KB of
634            // PRG RAM
635            if mapper_number == 5 && total_ram == 0 {
636                log::info!(
637                    "Ignoring PRG RAM size of 0 in MMC5 NES 2.0 header; setting to 64KB instead"
638                );
639                None
640            } else {
641                Some(total_ram)
642            }
643        }
644        FileFormat::INes => None,
645    };
646
647    // Default to 64KB for MMC5, 8KB for all other mappers
648    let default_ram_size = match mapper_number {
649        5 => 64 * 1024,
650        _ => 8 * 1024,
651    };
652    prg_ram_size.unwrap_or(default_ram_size)
653}
654
655/// Parse cartridge data out of an iNES file.
656///
657/// # Errors
658///
659/// This function will return an error if the given bytes do not appear to represent an iNES file.
660pub(crate) fn from_ines_file(
661    file_bytes: &[u8],
662    sav_bytes: Option<Vec<u8>>,
663    forced_timing_mode: Option<TimingMode>,
664) -> Result<Mapper, CartridgeFileError> {
665    let header = INesHeader::parse_from_file(file_bytes)?;
666
667    // Header is 16 bytes, trainer is 512 bytes if present
668    let prg_rom_start_address = if header.has_trainer { 16 + 512 } else { 16 } as usize;
669    let prg_rom_end_address = prg_rom_start_address + header.prg_rom_size as usize;
670    let chr_rom_end_address = prg_rom_end_address + header.chr_rom_size as usize;
671
672    let mut prg_rom = Vec::from(&file_bytes[prg_rom_start_address..prg_rom_end_address]);
673    let mut chr_rom = Vec::from(&file_bytes[prg_rom_end_address..chr_rom_end_address]);
674    if !chr_rom.is_empty() {
675        jgenesis_common::rom::mirror_to_next_power_of_two(&mut chr_rom);
676    }
677
678    // UNROM 512 stores save data in flashable PRG ROM instead of PRG RAM; replace PRG ROM with
679    // save contents if save is present and size matches
680    let original_prg_rom = prg_rom.clone();
681    if header.mapper_number == unrom512::MAPPER_NUMBER {
682        match &sav_bytes {
683            Some(sav_bytes) if sav_bytes.len() == prg_rom.len() => {
684                log::info!("Replacing UNROM 512 PRG ROM with contents of save file");
685                prg_rom.clone_from(sav_bytes);
686            }
687            _ => {}
688        }
689    }
690
691    let prg_ram = match &sav_bytes {
692        Some(sav_bytes) if sav_bytes.len() == header.prg_ram_size as usize => sav_bytes.clone(),
693        _ => vec![0xFF; header.prg_ram_size as usize],
694    };
695
696    let timing_mode = forced_timing_mode.unwrap_or(header.timing_mode);
697    if timing_mode != header.timing_mode {
698        log::info!(
699            "Forcing timing mode to {timing_mode}; ignoring timing mode {} from cartridge header",
700            header.timing_mode
701        );
702    }
703
704    let cartridge = Cartridge {
705        timing_mode,
706        prg_rom,
707        prg_ram,
708        has_ram_battery: header.has_battery,
709        prg_ram_dirty_bit: header.has_battery,
710        chr_rom,
711        chr_ram: vec![0; header.chr_ram_size as usize],
712    };
713
714    let chr_size = match header.chr_type {
715        ChrType::ROM => cartridge.chr_rom.len() as u32,
716        ChrType::RAM => header.chr_ram_size,
717    };
718
719    let mapper = match header.mapper_number {
720        0 => Mapper::Nrom(MapperImpl {
721            cartridge,
722            data: Nrom::new(header.chr_type, header.nametable_mirroring),
723        }),
724        1 => Mapper::Mmc1(MapperImpl { cartridge, data: Mmc1::new(header.chr_type) }),
725        2 | 71 => Mapper::Uxrom(MapperImpl {
726            cartridge,
727            data: Uxrom::new(
728                header.mapper_number,
729                header.sub_mapper_number,
730                header.chr_type,
731                header.nametable_mirroring,
732            ),
733        }),
734        3 => Mapper::Cnrom(MapperImpl {
735            cartridge,
736            data: Cnrom::new(header.chr_type, header.nametable_mirroring, header.sub_mapper_number),
737        }),
738        4 | 76 | 88 | 95 | 154 | 206 => Mapper::Mmc3(MapperImpl {
739            cartridge,
740            data: Mmc3::new(
741                header.chr_type,
742                header.prg_rom_size,
743                chr_size,
744                header.mapper_number,
745                header.sub_mapper_number,
746                header.nametable_mirroring,
747                header.has_four_screen_vram,
748            ),
749        }),
750        5 => Mapper::Mmc5(MapperImpl { cartridge, data: Mmc5::new() }),
751        7 => Mapper::Axrom(MapperImpl { cartridge, data: Axrom::new(header.chr_type) }),
752        9 => Mapper::Mmc2(MapperImpl { cartridge, data: Mmc2::new_mmc2() }),
753        10 => Mapper::Mmc2(MapperImpl { cartridge, data: Mmc2::new_mmc4() }),
754        11 | 66 | 140 => Mapper::Gxrom(MapperImpl {
755            cartridge,
756            data: Gxrom::new(header.mapper_number, header.nametable_mirroring),
757        }),
758        16 | 153 | 159 => Mapper::BandaiFcg(MapperImpl {
759            cartridge,
760            data: BandaiFcg::new(
761                header.mapper_number,
762                header.sub_mapper_number,
763                header.chr_type,
764                header.prg_ram_size,
765                sav_bytes.as_ref(),
766            ),
767        }),
768        19 => Mapper::Namco163(MapperImpl {
769            cartridge,
770            data: Namco163::new(
771                header.sub_mapper_number,
772                header.chr_type,
773                header.has_battery,
774                header.prg_ram_size,
775                sav_bytes,
776            ),
777        }),
778        21 | 22 | 23 | 25 => Mapper::Vrc4(MapperImpl {
779            cartridge,
780            data: Vrc4::new(header.mapper_number, header.sub_mapper_number, header.chr_type),
781        }),
782        24 | 26 => Mapper::Vrc6(MapperImpl {
783            cartridge,
784            data: Vrc6::new(header.mapper_number, header.chr_type),
785        }),
786        30 => {
787            let mapper = Unrom512::new(&original_prg_rom, &header);
788            Mapper::Unrom512(MapperImpl { cartridge, data: mapper })
789        }
790        34 => Mapper::Bnrom(MapperImpl {
791            cartridge,
792            data: Bnrom::new(header.chr_type, header.nametable_mirroring),
793        }),
794        69 => Mapper::Sunsoft(MapperImpl { cartridge, data: Sunsoft::new(header.chr_type) }),
795        85 => Mapper::Vrc7(MapperImpl {
796            cartridge,
797            data: Vrc7::new(header.sub_mapper_number, header.chr_type),
798        }),
799        210 => Mapper::Namco175(MapperImpl {
800            cartridge,
801            data: Namco175::new(
802                header.sub_mapper_number,
803                header.chr_type,
804                header.nametable_mirroring,
805            ),
806        }),
807        228 => Mapper::Action52(MapperImpl { cartridge, data: Action52::new(header.prg_rom_size) }),
808        _ => {
809            return Err(CartridgeFileError::UnsupportedMapper {
810                mapper_number: header.mapper_number,
811            });
812        }
813    };
814
815    log::info!("Timing mode: {timing_mode}");
816    log::info!("Mapper number: {} ({})", header.mapper_number, mapper.name());
817    log::info!("PRG ROM size: {}", header.prg_rom_size);
818    log::info!("PRG RAM size: {}", header.prg_ram_size);
819    log::info!("Cartridge has battery-backed PRG RAM: {}", header.has_battery);
820    log::info!("CHR ROM size: {}", header.chr_rom_size);
821    log::info!("CHR RAM size: {}", header.chr_ram_size);
822    log::info!("CHR memory type: {:?}", header.chr_type);
823    log::info!(
824        "Hardwired nametable mirroring: {:?} (not applicable to all mappers)",
825        header.nametable_mirroring
826    );
827    log::info!("Has 4-screen nametable VRAM: {}", header.has_four_screen_vram);
828
829    Ok(mapper)
830}