Skip to main content

nes_sim/
cartridge.rs

1use std::error::Error;
2use std::fmt::{Display, Formatter};
3
4pub(crate) mod expansion_audio;
5mod mappers;
6
7use self::mappers::{Mapper, from_mapper_id};
8use crate::apu::ExpansionAudioChip;
9use crate::savestate::{SaveStateError, StateReader, StateWriter};
10
11const INES_HEADER_LEN: usize = 16;
12const TRAINER_LEN: usize = 512;
13const PRG_BANK_LEN: usize = 0x4000;
14const CHR_BANK_LEN: usize = 0x2000;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum Mirroring {
18    Horizontal,
19    Vertical,
20    FourScreen,
21    SPAGE0,
22    SPAGE1,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum TVSystem {
27    NTSC,
28    PAL,
29    DENDY,
30}
31
32pub enum TimingMode {
33    NTSC,
34    Pal,
35    MultipleRegion,
36    Dendy,
37}
38
39impl TimingMode {
40    fn decode_ines(flag9: u8, flag10: u8) -> Self {
41        match flag10 & 0x03 {
42            0x02 => Self::Pal,
43            0x03 => Self::MultipleRegion,
44            _ => {
45                if (flag9 & 0x01) != 0 {
46                    Self::Pal
47                } else {
48                    Self::NTSC
49                }
50            }
51        }
52    }
53
54    fn decode_nes20(encoded: u8) -> Self {
55        match encoded {
56            1 => Self::Pal,
57            2 => Self::MultipleRegion,
58            3 => Self::Dendy,
59            _ => Self::NTSC,
60        }
61    }
62
63    fn to_tv_system(&self) -> TVSystem {
64        match self {
65            TimingMode::Pal => TVSystem::PAL,
66            TimingMode::Dendy => TVSystem::DENDY,
67            TimingMode::NTSC | TimingMode::MultipleRegion => TVSystem::NTSC,
68        }
69    }
70}
71
72pub enum RomFormat {
73    INES,
74    NES20,
75}
76
77#[derive(Debug, PartialEq, Eq)]
78pub enum CartridgeError {
79    FileTooSmall,
80    InvalidMagic,
81    Nes2Unsupported,
82    UnsupportedMapper(u16),
83    TruncatedData,
84}
85
86impl Display for CartridgeError {
87    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88        match self {
89            Self::FileTooSmall => f.write_str("ROM is smaller than the 16-byte iNES header"),
90            Self::InvalidMagic => f.write_str("ROM is not in iNES format"),
91            Self::Nes2Unsupported => f.write_str("NES 2.0 ROMs are not supported yet"),
92            Self::UnsupportedMapper(id) => write!(f, "mapper {} is not supported yet", id),
93            Self::TruncatedData => f.write_str("ROM ended before PRG/CHR data was fully present"),
94        }
95    }
96}
97
98impl Error for CartridgeError {}
99
100fn decode_nes20_rom_size(lsb: u16, msb_nibble: u16, unit_size: u16) -> u32 {
101    if msb_nibble != 0x0F {
102        return ((msb_nibble << 8) | lsb) as u32 * (unit_size as u32);
103    }
104
105    let multiplier = (lsb & 0x03) * 2 + 1;
106    let exponent = lsb >> 2;
107    (multiplier as u32) << exponent
108}
109
110fn decode_nes20_ram_size(encode_shift_count: u8) -> u32 {
111    if encode_shift_count == 0 {
112        0
113    } else {
114        64 << encode_shift_count
115    }
116}
117
118fn decode_ines_prg_ram_size(encoded_units: u8) -> u16 {
119    let unit = if encoded_units == 0 { 1 } else { encoded_units };
120    unit as u16 * 0x2000
121}
122
123fn decode_mirroring(flags6: u8) -> Mirroring {
124    if flags6 & 0x08 != 0 {
125        return Mirroring::FourScreen;
126    }
127    if flags6 & 0x01 != 0 {
128        return Mirroring::Vertical;
129    }
130    Mirroring::Horizontal
131}
132
133#[allow(dead_code)]
134struct CartridgeHeader {
135    raw: Vec<u8>,
136    format: RomFormat,
137    mapper_id: u16,
138    submapper: u8,
139    mirroring: Mirroring,
140    has_battery_backed_ram: bool,
141    has_trainer: bool,
142    has_bus_conflicts: bool,
143    console_type: u8,
144    console_type_data: u8,
145    timing_mode: TimingMode,
146    tv_system: TVSystem,
147    prg_rom_size: u32,
148    prg_ram_size: u32,
149    prg_nvram_size: u32,
150    chr_rom_size: u32,
151    chr_ram_size: u32,
152    chr_nvram_size: u32,
153    misc_rom_count: u8,
154    defaut_expansion_device: u8,
155    has_prg_ram_info: bool,
156    uses_exponent_rom_size_encoding: bool,
157}
158
159impl CartridgeHeader {
160    fn parse(rom: &[u8]) -> Result<CartridgeHeader, CartridgeError> {
161        if rom.len() < INES_HEADER_LEN {
162            return Err(CartridgeError::FileTooSmall);
163        }
164
165        if &rom[0..4] != b"NES\x1A" {
166            return Err(CartridgeError::FileTooSmall);
167        }
168
169        let total_len = rom.len();
170        let raw = rom[0..INES_HEADER_LEN].to_vec();
171        let flags6 = raw[6] as u16;
172        let flags7 = raw[7] as u16;
173        let flags8 = raw[8] as u16;
174        let has_trainer = (flags6 & 0x04) != 0;
175
176        let mut format = if (raw[7] & 0x0C) != 0x08 {
177            RomFormat::INES
178        } else {
179            RomFormat::NES20
180        };
181
182        let prg_rom_size =
183            decode_nes20_rom_size(raw[4] as u16, (raw[9] as u16) & 0x0F, PRG_BANK_LEN as u16);
184        let chr_rom_size =
185            decode_nes20_rom_size(raw[5] as u16, (raw[9] as u16) >> 4, PRG_BANK_LEN as u16);
186
187        let trainer_size = if (raw[6] & 0x04) != 0 { total_len } else { 0 };
188        let required_bytes =
189            INES_HEADER_LEN + trainer_size + (prg_rom_size + chr_rom_size) as usize;
190
191        if required_bytes <= total_len {
192            format = RomFormat::INES;
193        }
194
195        let mirroring = decode_mirroring(raw[6]);
196        let has_sram = flags6 & 0x02 == 0;
197        let console_type = raw[7] & 0x03;
198        let console_type_data = raw[13];
199
200        return match format {
201            RomFormat::NES20 => {
202                let timing_mode = TimingMode::decode_nes20(raw[12] & 0x03);
203                let mapper_id = (flags6 >> 4) | (flags7 & 0xF0) | ((flags8 & 0x0F) << 8);
204                let submapper = (flags8 >> 4) as u8;
205                let tv_system = timing_mode.to_tv_system();
206                let prg_ram_size = decode_nes20_ram_size(raw[10] & 0x0F);
207                let prg_nvram_size = decode_nes20_ram_size(raw[10] << 4);
208                let chr_ram_size = decode_nes20_ram_size(raw[11] & 0x0F);
209                let chr_nvram_size = decode_nes20_ram_size(raw[11] >> 4);
210                let misc_rom_count = raw[14] & 0x03;
211                let defaut_expansion_device = raw[15] & 0x3F;
212                let uses_exponent_rom_size_encoding =
213                    ((raw[9] & 0x0F) == 0x0F) || ((raw[9] >> 4) == 0x0F);
214
215                Ok(CartridgeHeader {
216                    raw,
217                    format,
218                    mapper_id,
219                    submapper,
220                    mirroring,
221                    has_battery_backed_ram: has_sram,
222                    has_trainer,
223                    has_bus_conflicts: false,
224                    console_type,
225                    console_type_data,
226                    timing_mode,
227                    tv_system,
228                    prg_rom_size,
229                    chr_rom_size,
230                    prg_ram_size,
231                    prg_nvram_size,
232                    chr_ram_size,
233                    chr_nvram_size,
234                    misc_rom_count,
235                    defaut_expansion_device,
236                    has_prg_ram_info: true,
237                    uses_exponent_rom_size_encoding,
238                })
239            }
240            RomFormat::INES => {
241                let has_trusted_ines_extension = raw[12..INES_HEADER_LEN].iter().all(|b| *b == 0);
242                let mapper_id = if has_sram {
243                    (flags6 >> 4) | (flags7 & 0xF0)
244                } else {
245                    flags6 >> 4
246                };
247
248                let prg_rom_size = (raw[4] as u32) * (PRG_BANK_LEN as u32);
249                let chr_rom_size = (raw[5] as u32) * (CHR_BANK_LEN as u32);
250
251                let required_bytes =
252                    INES_HEADER_LEN + trainer_size + (prg_rom_size + chr_rom_size) as usize;
253
254                if required_bytes > total_len {
255                    return Err(CartridgeError::TruncatedData);
256                }
257
258                let inferred_prg_ram_size = if has_trusted_ines_extension {
259                    decode_ines_prg_ram_size(raw[8]) as u32
260                } else {
261                    0x2000
262                };
263
264                let has_prg_ram = !has_trusted_ines_extension || (raw[10] & 0x10) == 0;
265                let timing_mode = if has_trusted_ines_extension {
266                    TimingMode::decode_ines(raw[9], raw[10])
267                } else {
268                    TimingMode::NTSC
269                };
270                let tv_system = timing_mode.to_tv_system();
271                let has_bus_conflicts = has_trusted_ines_extension && (raw[10] & 0x20 != 0);
272
273                Ok(CartridgeHeader {
274                    raw,
275                    format,
276                    mapper_id,
277                    submapper: 0,
278                    mirroring,
279                    has_battery_backed_ram: has_sram,
280                    has_trainer,
281                    has_bus_conflicts,
282                    console_type,
283                    console_type_data,
284                    timing_mode,
285                    tv_system,
286                    prg_rom_size,
287                    prg_ram_size: if has_prg_ram && !has_sram {
288                        inferred_prg_ram_size
289                    } else {
290                        0
291                    },
292                    prg_nvram_size: if has_prg_ram && has_sram {
293                        inferred_prg_ram_size
294                    } else {
295                        0
296                    },
297                    chr_rom_size,
298                    chr_ram_size: if chr_rom_size == 0 {
299                        CHR_BANK_LEN as u32
300                    } else {
301                        0
302                    },
303                    chr_nvram_size: 0,
304                    misc_rom_count: 0,
305                    defaut_expansion_device: 0,
306                    has_prg_ram_info: has_trusted_ines_extension,
307                    uses_exponent_rom_size_encoding: false,
308                })
309            }
310        };
311    }
312}
313
314#[allow(dead_code)]
315pub struct Cartridge {
316    mapper: Box<dyn Mapper>,
317    expansion_chips: Vec<Box<dyn ExpansionAudioChip>>,
318    header: CartridgeHeader,
319}
320
321impl Cartridge {
322    pub fn from_ines(rom: &[u8]) -> Result<Self, CartridgeError> {
323        let header = CartridgeHeader::parse(rom)?;
324
325        let flags6 = rom[6];
326        let has_trainer = (flags6 & 0x04) != 0;
327        let trainer_len = if has_trainer { TRAINER_LEN } else { 0 };
328
329        let prg_len = rom[4] as usize * PRG_BANK_LEN;
330        let chr_len = rom[5] as usize * CHR_BANK_LEN;
331        let data_start = INES_HEADER_LEN + trainer_len;
332        let data_end = data_start + prg_len + chr_len;
333        if rom.len() < data_end {
334            return Err(CartridgeError::TruncatedData);
335        }
336
337        let prg_rom = rom[data_start..data_start + prg_len].to_vec();
338        let chr_rom = rom[data_start + prg_len..data_end].to_vec();
339        let (mapper, expansion_chips) =
340            from_mapper_id(header.mapper_id, header.mirroring, prg_rom, chr_rom)?;
341
342        Ok(Self {
343            mapper,
344            expansion_chips,
345            header,
346        })
347    }
348
349    pub fn mirroring(&self) -> Mirroring {
350        self.mapper.mirroring()
351    }
352
353    pub fn tv_system(&self) -> TVSystem {
354        self.header.tv_system
355    }
356
357    pub fn cpu_read(&mut self, addr: u16) -> Option<u8> {
358        self.mapper.cpu_read(addr)
359    }
360
361    pub fn cpu_write(&mut self, addr: u16, data: u8) -> bool {
362        self.mapper.cpu_write(addr, data)
363    }
364
365    pub fn ppu_read(&mut self, addr: u16) -> Option<u8> {
366        self.mapper.ppu_read(addr)
367    }
368
369    pub fn ppu_write(&mut self, addr: u16, data: u8) -> bool {
370        self.mapper.ppu_write(addr, data)
371    }
372
373    pub fn take_expansion_audio_chips(&mut self) -> Vec<Box<dyn ExpansionAudioChip>> {
374        std::mem::take(&mut self.expansion_chips)
375    }
376
377    pub fn check_a12(&mut self, addr: u16, ppu_cycle: u64) {
378        self.mapper.check_a12(addr, ppu_cycle);
379    }
380
381    pub fn map_nametable_addr(&self, addr: u16) -> Option<usize> {
382        self.mapper.map_nametable_addr(addr)
383    }
384
385    pub fn irq_line(&self) -> bool {
386        self.mapper.irq_line()
387    }
388
389    pub fn tick_cpu_cycle(&mut self) {
390        self.mapper.tick_cpu_cycle();
391    }
392
393    pub fn notify_scanline(&mut self, scanline: i16, rendering_on: bool) {
394        self.mapper.notify_scanline(scanline, rendering_on);
395    }
396
397    pub fn set_ppu_sprite_phase(&mut self, sprite_phase: bool) {
398        self.mapper.set_ppu_sprite_phase(sprite_phase);
399    }
400
401    pub fn ppu_read_nametable(&mut self, addr: u16) -> Option<u8> {
402        self.mapper.ppu_read_nametable(addr)
403    }
404
405    pub fn ppu_write_nametable(&mut self, addr: u16, data: u8) -> bool {
406        self.mapper.ppu_write_nametable(addr, data)
407    }
408
409    pub(crate) fn save_state(&self, writer: &mut StateWriter) {
410        writer.write_u16(self.header.mapper_id);
411        self.mapper.save_state(writer);
412    }
413
414    pub(crate) fn load_state(
415        &mut self,
416        reader: &mut StateReader<'_>,
417    ) -> Result<(), SaveStateError> {
418        let actual = reader.read_u16()?;
419        let expected = self.header.mapper_id;
420        if actual != expected {
421            return Err(SaveStateError::MapperMismatch { expected, actual });
422        }
423        self.mapper.load_state(reader)
424    }
425}
426
427#[cfg(test)]
428mod tests;