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;