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::{MapperEnum, NoMapper, 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 from_no_header() -> Self {
161        Self {
162            raw: Vec::new(),
163            format: RomFormat::INES,
164            mapper_id: 9999,
165            submapper: 255,
166            mirroring: Mirroring::Vertical,
167            has_battery_backed_ram: false,
168            has_trainer: false,
169            has_bus_conflicts: false,
170            console_type: 0,
171            console_type_data: 0,
172            timing_mode: TimingMode::NTSC,
173            tv_system: TVSystem::NTSC,
174            prg_rom_size: 0,
175            prg_ram_size: 0,
176            prg_nvram_size: 0,
177            chr_rom_size: 0,
178            chr_ram_size: 0,
179            chr_nvram_size: 0,
180            misc_rom_count: 0,
181            defaut_expansion_device: 0,
182            has_prg_ram_info: false,
183            uses_exponent_rom_size_encoding: false,
184        }
185    }
186
187    fn parse(rom: &[u8]) -> Result<CartridgeHeader, CartridgeError> {
188        if rom.len() < INES_HEADER_LEN {
189            return Err(CartridgeError::FileTooSmall);
190        }
191
192        if &rom[0..4] != b"NES\x1A" {
193            return Err(CartridgeError::FileTooSmall);
194        }
195
196        let total_len = rom.len();
197        let raw = rom[0..INES_HEADER_LEN].to_vec();
198        let flags6 = raw[6] as u16;
199        let flags7 = raw[7] as u16;
200        let flags8 = raw[8] as u16;
201        let has_trainer = (flags6 & 0x04) != 0;
202
203        let mut format = if (raw[7] & 0x0C) != 0x08 {
204            RomFormat::INES
205        } else {
206            RomFormat::NES20
207        };
208
209        let prg_rom_size =
210            decode_nes20_rom_size(raw[4] as u16, (raw[9] as u16) & 0x0F, PRG_BANK_LEN as u16);
211        let chr_rom_size =
212            decode_nes20_rom_size(raw[5] as u16, (raw[9] as u16) >> 4, PRG_BANK_LEN as u16);
213
214        let trainer_size = if (raw[6] & 0x04) != 0 { total_len } else { 0 };
215        let required_bytes =
216            INES_HEADER_LEN + trainer_size + (prg_rom_size + chr_rom_size) as usize;
217
218        if required_bytes <= total_len {
219            format = RomFormat::INES;
220        }
221
222        let mirroring = decode_mirroring(raw[6]);
223        let has_sram = flags6 & 0x02 == 0;
224        let console_type = raw[7] & 0x03;
225        let console_type_data = raw[13];
226
227        return match format {
228            RomFormat::NES20 => {
229                let timing_mode = TimingMode::decode_nes20(raw[12] & 0x03);
230                let mapper_id = (flags6 >> 4) | (flags7 & 0xF0) | ((flags8 & 0x0F) << 8);
231                let submapper = (flags8 >> 4) as u8;
232                let tv_system = timing_mode.to_tv_system();
233                let prg_ram_size = decode_nes20_ram_size(raw[10] & 0x0F);
234                let prg_nvram_size = decode_nes20_ram_size(raw[10] << 4);
235                let chr_ram_size = decode_nes20_ram_size(raw[11] & 0x0F);
236                let chr_nvram_size = decode_nes20_ram_size(raw[11] >> 4);
237                let misc_rom_count = raw[14] & 0x03;
238                let defaut_expansion_device = raw[15] & 0x3F;
239                let uses_exponent_rom_size_encoding =
240                    ((raw[9] & 0x0F) == 0x0F) || ((raw[9] >> 4) == 0x0F);
241
242                Ok(CartridgeHeader {
243                    raw,
244                    format,
245                    mapper_id,
246                    submapper,
247                    mirroring,
248                    has_battery_backed_ram: has_sram,
249                    has_trainer,
250                    has_bus_conflicts: false,
251                    console_type,
252                    console_type_data,
253                    timing_mode,
254                    tv_system,
255                    prg_rom_size,
256                    chr_rom_size,
257                    prg_ram_size,
258                    prg_nvram_size,
259                    chr_ram_size,
260                    chr_nvram_size,
261                    misc_rom_count,
262                    defaut_expansion_device,
263                    has_prg_ram_info: true,
264                    uses_exponent_rom_size_encoding,
265                })
266            }
267            RomFormat::INES => {
268                let has_trusted_ines_extension = raw[12..INES_HEADER_LEN].iter().all(|b| *b == 0);
269                let mapper_id = if has_sram {
270                    (flags6 >> 4) | (flags7 & 0xF0)
271                } else {
272                    flags6 >> 4
273                };
274
275                let prg_rom_size = (raw[4] as u32) * (PRG_BANK_LEN as u32);
276                let chr_rom_size = (raw[5] as u32) * (CHR_BANK_LEN as u32);
277
278                let required_bytes =
279                    INES_HEADER_LEN + trainer_size + (prg_rom_size + chr_rom_size) as usize;
280
281                if required_bytes > total_len {
282                    return Err(CartridgeError::TruncatedData);
283                }
284
285                let inferred_prg_ram_size = if has_trusted_ines_extension {
286                    decode_ines_prg_ram_size(raw[8]) as u32
287                } else {
288                    0x2000
289                };
290
291                let has_prg_ram = !has_trusted_ines_extension || (raw[10] & 0x10) == 0;
292                let timing_mode = if has_trusted_ines_extension {
293                    TimingMode::decode_ines(raw[9], raw[10])
294                } else {
295                    TimingMode::NTSC
296                };
297                let tv_system = timing_mode.to_tv_system();
298                let has_bus_conflicts = has_trusted_ines_extension && (raw[10] & 0x20 != 0);
299
300                Ok(CartridgeHeader {
301                    raw,
302                    format,
303                    mapper_id,
304                    submapper: 0,
305                    mirroring,
306                    has_battery_backed_ram: has_sram,
307                    has_trainer,
308                    has_bus_conflicts,
309                    console_type,
310                    console_type_data,
311                    timing_mode,
312                    tv_system,
313                    prg_rom_size,
314                    prg_ram_size: if has_prg_ram && !has_sram {
315                        inferred_prg_ram_size
316                    } else {
317                        0
318                    },
319                    prg_nvram_size: if has_prg_ram && has_sram {
320                        inferred_prg_ram_size
321                    } else {
322                        0
323                    },
324                    chr_rom_size,
325                    chr_ram_size: if chr_rom_size == 0 {
326                        CHR_BANK_LEN as u32
327                    } else {
328                        0
329                    },
330                    chr_nvram_size: 0,
331                    misc_rom_count: 0,
332                    defaut_expansion_device: 0,
333                    has_prg_ram_info: has_trusted_ines_extension,
334                    uses_exponent_rom_size_encoding: false,
335                })
336            }
337        };
338    }
339}
340
341pub struct Cartridge {
342    mapper: MapperEnum,
343    expansion_chips: Vec<Box<dyn ExpansionAudioChip>>,
344    header: CartridgeHeader,
345}
346
347impl Cartridge {
348    pub fn from_no_cart() -> Self {
349        Self {
350            mapper: MapperEnum::NoMapper(NoMapper::new()),
351            expansion_chips: Vec::new(),
352            header: CartridgeHeader::from_no_header(),
353        }
354    }
355
356    pub fn from_ines(rom: &[u8]) -> Result<Self, CartridgeError> {
357        let header = CartridgeHeader::parse(rom)?;
358
359        let flags6 = rom[6];
360        let has_trainer = (flags6 & 0x04) != 0;
361        let trainer_len = if has_trainer { TRAINER_LEN } else { 0 };
362
363        let prg_len = rom[4] as usize * PRG_BANK_LEN;
364        let chr_len = rom[5] as usize * CHR_BANK_LEN;
365        let data_start = INES_HEADER_LEN + trainer_len;
366        let data_end = data_start + prg_len + chr_len;
367        if rom.len() < data_end {
368            return Err(CartridgeError::TruncatedData);
369        }
370
371        let prg_rom = rom[data_start..data_start + prg_len].to_vec();
372        let chr_rom = rom[data_start + prg_len..data_end].to_vec();
373        let (mapper, expansion_chips) =
374            from_mapper_id(header.mapper_id, header.mirroring, prg_rom, chr_rom)?;
375
376        Ok(Self {
377            mapper,
378            expansion_chips,
379            header,
380        })
381    }
382
383    pub fn mirroring(&self) -> Mirroring {
384        self.mapper.mirroring()
385    }
386
387    pub fn tv_system(&self) -> TVSystem {
388        self.header.tv_system
389    }
390
391    pub fn cpu_read(&mut self, addr: u16) -> Option<u8> {
392        self.mapper.cpu_read(addr)
393    }
394
395    pub fn cpu_write(&mut self, addr: u16, data: u8) -> bool {
396        self.mapper.cpu_write(addr, data)
397    }
398
399    pub fn ppu_read(&mut self, addr: u16) -> Option<u8> {
400        self.mapper.ppu_read(addr)
401    }
402
403    pub fn ppu_write(&mut self, addr: u16, data: u8) -> bool {
404        self.mapper.ppu_write(addr, data)
405    }
406
407    pub fn take_expansion_audio_chips(&mut self) -> Vec<Box<dyn ExpansionAudioChip>> {
408        std::mem::take(&mut self.expansion_chips)
409    }
410
411    pub fn check_a12(&mut self, addr: u16, ppu_cycle: u64) {
412        self.mapper.check_a12(addr, ppu_cycle);
413    }
414
415    pub fn map_nametable_addr(&self, addr: u16) -> Option<usize> {
416        self.mapper.map_nametable_addr(addr)
417    }
418
419    pub fn irq_line(&self) -> bool {
420        self.mapper.irq_line()
421    }
422
423    pub fn tick_cpu_cycle(&mut self) {
424        self.mapper.tick_cpu_cycle();
425    }
426
427    pub fn notify_scanline(&mut self, scanline: i16, rendering_on: bool) {
428        self.mapper.notify_scanline(scanline, rendering_on);
429    }
430
431    pub fn set_ppu_sprite_phase(&mut self, sprite_phase: bool) {
432        self.mapper.set_ppu_sprite_phase(sprite_phase);
433    }
434
435    pub fn ppu_read_nametable(&mut self, addr: u16) -> Option<u8> {
436        self.mapper.ppu_read_nametable(addr)
437    }
438
439    pub fn ppu_write_nametable(&mut self, addr: u16, data: u8) -> bool {
440        self.mapper.ppu_write_nametable(addr, data)
441    }
442
443    pub(crate) fn save_state(&self, writer: &mut StateWriter) {
444        writer.write_u16(self.header.mapper_id);
445        self.mapper.save_state(writer);
446    }
447
448    pub(crate) fn load_state(
449        &mut self,
450        reader: &mut StateReader<'_>,
451    ) -> Result<(), SaveStateError> {
452        let actual = reader.read_u16()?;
453        let expected = self.header.mapper_id;
454        if actual != expected {
455            return Err(SaveStateError::MapperMismatch { expected, actual });
456        }
457        self.mapper.load_state(reader)
458    }
459}
460
461#[cfg(test)]
462mod tests;