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;