1use crate::{
4 common::{NesRegion, Regional},
5 fs,
6 mapper::{
7 self, Axrom, BandaiFCG, Bf909x, Bnrom, Cnrom, ColorDreams, Dxrom76, Dxrom88, Dxrom95,
8 Dxrom154, Dxrom206, Exrom, Fxrom, Gxrom, JalecoSs88006, Mapper, Mmc1Revision, Namco163,
9 Nina003006, Nrom, Pxrom, SunsoftFme7, Sxrom, Txrom, Uxrom, Vrc6,
10 m024_m026_vrc6::Revision as Vrc6Revision, m034_nina001::Nina001,
11 },
12 mem::{Memory, RamState},
13 ppu::Mirroring,
14};
15use serde::{Deserialize, Serialize};
16use std::{
17 fs::File,
18 io::{BufReader, Read},
19 path::Path,
20};
21use thiserror::Error;
22use tracing::{debug, error, info};
23
24const PRG_ROM_BANK_SIZE: usize = 0x4000;
25const CHR_ROM_BANK_SIZE: usize = 0x2000;
26
27pub type Result<T> = std::result::Result<T, Error>;
28
29#[derive(Error, Debug)]
30#[must_use]
31pub enum Error {
32 #[error("invalid nes header (found: ${value:04X} at byte: {byte}). {message}")]
33 InvalidHeader {
34 byte: u8,
35 value: u8,
36 message: String,
37 },
38 #[error("mapper: {0}")]
39 InvalidMapper(#[from] mapper::Error),
40 #[error("{context}: {source:?}")]
41 Io {
42 context: String,
43 source: std::io::Error,
44 },
45}
46
47impl Error {
48 pub fn io(source: std::io::Error, context: impl Into<String>) -> Self {
49 Self::Io {
50 context: context.into(),
51 source,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[must_use]
58pub struct GameInfo {
59 pub crc32: u32,
60 pub region: NesRegion,
61 pub mapper_num: u16,
62 pub submapper_num: u8,
63}
64
65#[must_use]
67pub struct Cart {
68 name: String,
69 header: NesHeader,
70 region: NesRegion,
71 ram_state: RamState,
72 pub(crate) mapper: Mapper,
73 pub(crate) chr_rom: Memory<Vec<u8>>, pub(crate) chr_ram: Memory<Vec<u8>>, pub(crate) prg_rom: Memory<Vec<u8>>, pub(crate) prg_ram: Memory<Vec<u8>>, pub(crate) ex_ram: Memory<Vec<u8>>, pub(crate) game_info: Option<GameInfo>,
79}
80
81impl Default for Cart {
82 fn default() -> Self {
83 Self::empty()
84 }
85}
86
87impl Cart {
88 pub fn empty() -> Self {
89 let ram_state = RamState::default();
90 let mut empty = Self {
91 name: "Empty Cart".to_string(),
92 header: NesHeader::default(),
93 region: NesRegion::Ntsc,
94 ram_state: RamState::default(),
95 mapper: Mapper::none(),
96 chr_rom: Memory::rom().with_size(CHR_ROM_BANK_SIZE),
97 chr_ram: Memory::ram(ram_state),
98 prg_rom: Memory::rom().with_size(PRG_ROM_BANK_SIZE),
99 prg_ram: Memory::ram(ram_state),
100 ex_ram: Memory::ram(ram_state),
101 game_info: None,
102 };
103 empty.mapper = Nrom::load(&mut empty).expect("valid empty mapper");
104 empty
105 }
106
107 pub fn from_path<P: AsRef<Path>>(path: P, ram_state: RamState) -> Result<Self> {
114 let path = path.as_ref();
115 let mut rom = BufReader::new(
116 File::open(path)
117 .map_err(|err| Error::io(err, format!("failed to open rom {path:?}")))?,
118 );
119 Self::from_rom(path.to_string_lossy(), &mut rom, ram_state)
120 }
121
122 pub fn from_rom<S, F>(name: S, mut rom_data: &mut F, ram_state: RamState) -> Result<Self>
129 where
130 S: ToString,
131 F: Read,
132 {
133 let name = name.to_string();
134 let header = NesHeader::load(&mut rom_data)?;
135 debug!("{header:?}");
136
137 let prg_rom_len = (header.prg_rom_banks as usize) * PRG_ROM_BANK_SIZE;
138 let mut prg_rom = Memory::rom().with_size(prg_rom_len);
139 rom_data.read_exact(&mut prg_rom).map_err(|err| {
140 if let std::io::ErrorKind::UnexpectedEof = err.kind() {
141 Error::InvalidHeader {
142 byte: 4,
143 value: header.prg_rom_banks as u8,
144 message: format!(
145 "expected `{}` prg-rom banks ({prg_rom_len} total bytes)",
146 header.prg_rom_banks
147 ),
148 }
149 } else {
150 Error::io(err, "failed to read prg-rom")
151 }
152 })?;
153
154 let prg_ram_size = Self::calculate_ram_size(header.prg_ram_shift)?;
155 let prg_ram = Memory::ram(ram_state).with_size(prg_ram_size);
156
157 let mut chr_rom =
158 Memory::rom().with_size((header.chr_rom_banks as usize) * CHR_ROM_BANK_SIZE);
159 let chr_ram = if header.chr_rom_banks > 0 {
160 rom_data.read_exact(&mut chr_rom).map_err(|err| {
161 if let std::io::ErrorKind::UnexpectedEof = err.kind() {
162 Error::InvalidHeader {
163 byte: 5,
164 value: header.chr_rom_banks as u8,
165 message: format!(
166 "expected `{}` chr-rom banks ({prg_rom_len} total bytes)",
167 header.chr_rom_banks
168 ),
169 }
170 } else {
171 Error::io(err, "failed to read chr-rom")
172 }
173 })?;
174 Memory::ram(ram_state)
175 } else {
176 let chr_ram_size = Self::calculate_ram_size(header.chr_ram_shift)?;
177 Memory::ram(ram_state).with_size(chr_ram_size)
178 };
179
180 let game_info = Self::lookup_info(&prg_rom, &chr_rom);
181 let region = if matches!(header.variant, NesVariant::INes | NesVariant::Nes2) {
182 match header.tv_mode {
183 1 => NesRegion::Pal,
184 3 => NesRegion::Dendy,
185 _ => game_info
186 .as_ref()
187 .map(|info| info.region)
188 .unwrap_or_default(),
189 }
190 } else {
191 game_info
192 .as_ref()
193 .map(|info| info.region)
194 .unwrap_or_default()
195 };
196
197 let mut cart = Self {
198 name,
199 header,
200 region,
201 ram_state,
202 mapper: Mapper::none(),
203 chr_rom,
204 chr_ram,
205 prg_rom,
206 prg_ram,
207 ex_ram: Memory::ram(ram_state),
208 game_info,
209 };
210 cart.mapper = match cart.header.mapper_num {
211 0 => Nrom::load(&mut cart)?,
212 1 => Sxrom::load(&mut cart, Mmc1Revision::BC)?,
213 2 => Uxrom::load(&mut cart)?,
214 3 => Cnrom::load(&mut cart)?,
215 4 => Txrom::load(&mut cart)?,
216 5 => Exrom::load(&mut cart)?,
217 7 => Axrom::load(&mut cart)?,
218 9 => Pxrom::load(&mut cart)?,
219 10 => Fxrom::load(&mut cart)?,
220 11 => ColorDreams::load(&mut cart)?,
221 16 | 153 | 157 | 159 => BandaiFCG::load(&mut cart)?,
222 18 => JalecoSs88006::load(&mut cart)?,
223 19 | 210 => Namco163::load(&mut cart)?,
224 24 => Vrc6::load(&mut cart, Vrc6Revision::A)?,
225 26 => Vrc6::load(&mut cart, Vrc6Revision::B)?,
226 34 => {
227 if cart.has_chr_rom() && cart.chr_rom.len() >= 0x4000 {
229 Nina001::load(&mut cart)?
230 } else {
231 Bnrom::load(&mut cart)?
232 }
233 }
234 66 => Gxrom::load(&mut cart)?,
235 69 => SunsoftFme7::load(&mut cart)?,
236 71 => Bf909x::load(&mut cart)?,
237 76 => Dxrom76::load(&mut cart)?,
238 79 | 113 | 146 => Nina003006::load(&mut cart)?,
239 88 => Dxrom88::load(&mut cart)?,
240 95 => Dxrom95::load(&mut cart)?,
241 154 => Dxrom154::load(&mut cart)?,
242 155 => Sxrom::load(&mut cart, Mmc1Revision::A)?,
243 206 => Dxrom206::load(&mut cart)?,
244 _ => Mapper::none(),
245 };
246
247 info!("loaded ROM `{cart}`");
248 debug!("{cart:?}");
249
250 Ok(cart)
251 }
252
253 #[must_use]
254 #[allow(clippy::missing_const_for_fn)] pub fn name(&self) -> &str {
256 &self.name
257 }
258
259 #[must_use]
260 #[allow(clippy::missing_const_for_fn)] pub fn chr_rom(&self) -> &[u8] {
262 &self.chr_rom
263 }
264
265 #[must_use]
266 #[allow(clippy::missing_const_for_fn)] pub fn chr_ram(&self) -> &[u8] {
268 &self.chr_ram
269 }
270
271 #[must_use]
272 #[allow(clippy::missing_const_for_fn)] pub fn prg_rom(&self) -> &[u8] {
274 &self.prg_rom
275 }
276
277 #[must_use]
278 #[allow(clippy::missing_const_for_fn)] pub fn prg_ram(&self) -> &[u8] {
280 &self.prg_ram
281 }
282
283 #[must_use]
284 #[allow(clippy::missing_const_for_fn)] pub fn has_chr_rom(&self) -> bool {
286 !self.chr_rom.is_empty()
287 }
288
289 #[must_use]
290 #[allow(clippy::missing_const_for_fn)] pub fn has_chr_ram(&self) -> bool {
292 !self.chr_ram.is_empty()
293 }
294
295 #[must_use]
296 #[allow(clippy::missing_const_for_fn)] pub fn has_prg_ram(&self) -> bool {
298 !self.prg_ram.is_empty()
299 }
300
301 #[must_use]
302 pub const fn is_ines(&self) -> bool {
303 matches!(
304 self.header.variant,
305 NesVariant::ArchaicINes | NesVariant::INes07 | NesVariant::INes
306 )
307 }
308
309 #[must_use]
310 pub const fn is_nes2(&self) -> bool {
311 matches!(self.header.variant, NesVariant::Nes2)
312 }
313
314 #[must_use]
316 pub const fn battery_backed(&self) -> bool {
317 self.header.flags & 0x02 == 0x02
318 }
319
320 pub const fn ram_state(&self) -> RamState {
322 self.ram_state
323 }
324
325 pub fn mirroring(&self) -> Mirroring {
327 if self.header.flags & 0x08 == 0x08 {
328 Mirroring::FourScreen
329 } else {
330 match self.header.flags & 0x01 {
331 0 => Mirroring::Horizontal,
332 1 => Mirroring::Vertical,
333 _ => unreachable!("impossible mirroring"),
334 }
335 }
336 }
337
338 #[must_use]
340 pub fn mapper_num(&self) -> u16 {
341 self.game_info
342 .as_ref()
343 .map(|info| info.mapper_num)
344 .unwrap_or(self.header.mapper_num)
345 }
346
347 #[must_use]
349 pub fn submapper_num(&self) -> u8 {
350 self.game_info
351 .as_ref()
352 .map(|info| info.submapper_num)
353 .unwrap_or(self.header.submapper_num)
354 }
355
356 #[must_use]
358 pub fn mapper_board(&self) -> &'static str {
359 NesHeader::mapper_board(self.mapper_num())
360 }
361
362 pub(crate) fn add_prg_ram(&mut self, capacity: usize) {
364 self.prg_ram.resize(capacity);
365 }
366
367 pub(crate) fn add_chr_ram(&mut self, capacity: usize) {
369 self.chr_ram.resize(capacity);
370 }
371
372 pub(crate) fn add_exram(&mut self, capacity: usize) {
374 self.ex_ram.resize(capacity);
375 }
376
377 fn calculate_ram_size(value: u8) -> Result<usize> {
378 if value > 0 {
379 64usize
380 .checked_shl(value.into())
381 .ok_or_else(|| Error::InvalidHeader {
382 byte: 11,
383 value,
384 message: "header ram size larger than 64".to_string(),
385 })
386 } else {
387 Ok(0)
388 }
389 }
390
391 fn lookup_info(prg_rom: &[u8], chr: &[u8]) -> Option<GameInfo> {
392 const GAME_DB: &[u8] = include_bytes!("../game_db.dat");
393
394 let Ok(games) = fs::load_bytes::<Vec<GameInfo>>(GAME_DB) else {
395 error!("failed to load `game_regions.dat`");
396 return None;
397 };
398
399 let mut crc32 = fs::compute_crc32(prg_rom);
400 if !chr.is_empty() {
401 crc32 = fs::compute_combine_crc32(crc32, chr);
402 }
403
404 match games.binary_search_by(|game| game.crc32.cmp(&crc32)) {
405 Ok(index) => {
406 info!(
407 "found game matching crc: {crc32:#010X}. info: {:?}",
408 games[index]
409 );
410 Some(games[index].clone())
411 }
412 Err(_) => {
413 info!("no game found matching crc: {crc32:#010X}");
414 None
415 }
416 }
417 }
418}
419
420impl Regional for Cart {
421 fn region(&self) -> NesRegion {
422 self.region
423 }
424
425 fn set_region(&mut self, region: NesRegion) {
426 self.region = region;
427 }
428}
429
430impl std::fmt::Display for Cart {
431 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
432 write!(
433 f,
434 "{} - {}, CHR-ROM: {}K, CHR-RAM: {}K, PRG-ROM: {}K, PRG-RAM: {}K, EX-RAM: {}K, Mirroring: {:?}, Battery: {}",
435 self.name,
436 self.mapper_board(),
437 self.chr_rom.len() / 0x0400,
438 self.chr_ram.len() / 0x0400,
439 self.prg_rom.len() / 0x0400,
440 self.prg_ram.len() / 0x0400,
441 self.ex_ram.len() / 0x0400,
442 self.mirroring(),
443 self.battery_backed(),
444 )
445 }
446}
447
448impl std::fmt::Debug for Cart {
449 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
450 f.debug_struct("Cart")
451 .field("name", &self.name)
452 .field("header", &self.header)
453 .field("region", &self.region)
454 .field("ram_state", &self.ram_state)
455 .field("mapper", &self.mapper)
456 .field("mirroring", &self.mirroring())
457 .field("battery_backed", &self.battery_backed())
458 .field("chr_rom", &self.chr_rom)
459 .field("chr_ram", &self.chr_ram)
460 .field("prg_rom", &self.prg_rom)
461 .field("prg_ram", &self.prg_ram)
462 .field("ex_ram", &self.ex_ram)
463 .finish()
464 }
465}
466
467#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
468#[must_use]
469pub enum NesVariant {
470 #[default]
471 ArchaicINes,
472 INes07,
473 INes,
474 Nes2,
475}
476
477#[derive(Default, Copy, Clone, PartialEq, Eq)]
483#[must_use]
484pub struct NesHeader {
485 pub variant: NesVariant,
486 pub mapper_num: u16, pub submapper_num: u8, pub flags: u8, pub prg_rom_banks: u16, pub chr_rom_banks: u16, pub prg_ram_shift: u8, pub chr_ram_shift: u8, pub tv_mode: u8, pub vs_data: u8, }
496
497impl NesHeader {
498 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
505 let path = path.as_ref();
506 let mut rom = BufReader::new(
507 File::open(path)
508 .map_err(|err| Error::io(err, format!("failed to open rom {path:?}")))?,
509 );
510 Self::load(&mut rom)
511 }
512
513 pub fn load<F: Read>(rom_data: &mut F) -> Result<Self> {
519 let mut header = [0u8; 16];
520 rom_data.read_exact(&mut header).map_err(|err| {
521 if let std::io::ErrorKind::UnexpectedEof = err.kind() {
522 Error::InvalidHeader {
523 byte: 0,
524 value: 0,
525 message: "expected 16-byte header".to_string(),
526 }
527 } else {
528 Error::io(err, "failed to read nes header")
529 }
530 })?;
531
532 if header[0..4] != *b"NES\x1a" {
534 return Err(Error::InvalidHeader {
535 byte: 0,
536 value: header[0],
537 message: "nes header signature not found".to_string(),
538 });
539 }
540 if (header[7] & 0x0C) == 0x04 {
541 return Err(Error::InvalidHeader {
542 byte: 7,
543 value: header[7],
544 message: "header is corrupted by `DiskDude!`. repair and try again".to_string(),
545 });
546 }
547 if (header[7] & 0x0C) == 0x0C {
548 return Err(Error::InvalidHeader {
549 byte: 7,
550 value: header[7],
551 message: "unrecognized header format. repair and try again".to_string(),
552 });
553 }
554
555 let mut prg_rom_banks = u16::from(header[4]);
556 let mut chr_rom_banks = u16::from(header[5]);
557 let mut mapper_num = u16::from(((header[6] & 0xF0) >> 4) | (header[7] & 0xF0));
559 let flags = (header[6] & 0x0F) | ((header[7] & 0x0F) << 4);
561
562 let mut submapper_num = 0;
564 let mut prg_ram_shift = 0;
565 let mut chr_ram_shift = 0;
566 let mut tv_mode = 0;
567 let mut vs_data = 0;
568 let variant = if header[7] & 0x0C == 0x08 {
570 mapper_num |= u16::from(header[8] & 0x0F) << 8;
572 submapper_num = (header[8] & 0xF0) >> 4;
574 prg_rom_banks |= u16::from(header[9] & 0x0F) << 8;
576 chr_rom_banks |= u16::from(header[9] & 0xF0) << 4;
578 prg_ram_shift = header[10];
579 chr_ram_shift = header[11];
580 tv_mode = header[12];
581 vs_data = header[13];
582
583 if prg_ram_shift & 0x0F == 0x0F || prg_ram_shift & 0xF0 == 0xF0 {
584 return Err(Error::InvalidHeader {
585 byte: 10,
586 value: prg_ram_shift,
587 message: "invalid prg-ram size in header".to_string(),
588 });
589 }
590 if chr_ram_shift & 0x0F == 0x0F || chr_ram_shift & 0xF0 == 0xF0 {
591 return Err(Error::InvalidHeader {
592 byte: 11,
593 value: chr_ram_shift,
594 message: "invalid chr-ram size in header".to_string(),
595 });
596 }
597 if chr_ram_shift & 0xF0 == 0xF0 {
598 return Err(Error::InvalidHeader {
599 byte: 11,
600 value: chr_ram_shift,
601 message: "battery-backed chr-ram is currently not supported".to_string(),
602 });
603 }
604 if header[14] > 0 || header[15] > 0 {
605 return Err(Error::InvalidHeader {
606 byte: 14,
607 value: header[14],
608 message: "unrecognized data found at header offsets 14-15".to_string(),
609 });
610 }
611 NesVariant::Nes2
612 } else if header[7] & 0x0C == 0x04 {
613 for (i, value) in header.iter().enumerate().take(16).skip(8) {
615 if *value > 0 {
616 return Err(Error::InvalidHeader {
617 byte: i as u8,
618 value: *value,
619 message: format!(
620 "unrecogonized data found at header byte {i}. repair and try again"
621 ),
622 });
623 }
624 }
625 NesVariant::ArchaicINes
626 } else if header[7] & 0x0C == 00 && header[12..=15].iter().all(|v| *v == 0) {
627 NesVariant::INes
629 } else {
630 NesVariant::INes07
632 };
633
634 if flags & 0x04 == 0x04 {
636 return Err(Error::InvalidHeader {
637 byte: 6,
638 value: header[6],
639 message: "trained roms are currently not supported.".to_string(),
640 });
641 }
642
643 Ok(Self {
644 variant,
645 mapper_num,
646 submapper_num,
647 flags,
648 prg_rom_banks,
649 chr_rom_banks,
650 prg_ram_shift,
651 chr_ram_shift,
652 tv_mode,
653 vs_data,
654 })
655 }
656
657 #[must_use]
658 pub const fn mapper_board(mapper_num: u16) -> &'static str {
659 match mapper_num {
660 0 => "Mapper 000 - NROM",
661 1 => "Mapper 001 - SxROM/MMC1B/C",
662 2 => "Mapper 002 - UxROM",
663 3 => "Mapper 003 - CNROM",
664 4 => "Mapper 004 - TxROM/MMC3/MMC6",
665 5 => "Mapper 005 - ExROM/MMC5",
666 6 => "Mapper 006 - FFE 1M/2M",
667 7 => "Mapper 007 - AxROM",
668 8 => "Mapper 008 - FFE 1M/2M", 9 => "Mapper 009 - PxROM/MMC2",
670 10 => "Mapper 010 - FxROM/MMC4",
671 11 => "Mapper 011 - Color Dreams",
672 12 => "Mapper 012 - Gouder/FFE 4M/MMC3",
673 13 => "Mapper 013 - CPROM",
674 14 => "Mapper 014 - UNL SL1632",
675 15 => "Mapper 015 - K1029/30",
676 16 => "Mapper 016 - Bandai FCG",
677 17 => "Mapper 017 - FFE",
678 18 => "Mapper 018 - Jaleco SS 88006",
679 19 => "Mapper 019 - Namco 129/163",
680 20 => "Mapper 020 - FDS",
681 21 => "Mapper 021 - Vrc4a/Vrc4c",
682 22 => "Mapper 022 - Vrc2a",
683 23 => "Mapper 023 - Vrc4e",
684 24 => "Mapper 024 - Vrc6a",
685 25 => "Mapper 025 - Vrc4b",
686 26 => "Mapper 026 - Vrc6b",
687 27 => "Mapper 027 - Vrc4x",
688 28 => "Mapper 028 - Action 53",
689 29 => "Mapper 029 - Sealie Computing",
690 30 => "Mapper 030 - UNROM 512",
691 31 => "Mapper 031 - NSF",
692 32 => "Mapper 032 - Irem G101",
693 33 => "Mapper 033 - Taito TC0190",
694 34 => "Mapper 034 - BNROM/NINA-001",
695 35 => "Mapper 035 - JY Company",
696 36 => "Mapper 036 - TXC 22000",
697 37 => "Mapper 037 - MMC3 Multicart",
698 38 => "Mapper 038 - UNL PCI556",
699 39 => "Mapper 039 - Subor",
700 40 => "Mapper 040 - NTDEC 2722",
701 41 => "Mapper 041 - Caltron 6-in-1",
702 42 => "Mapper 042",
703 43 => "Mapper 043 - TONY-I/YS-612",
704 44 => "Mapper 044 - MMC3 Multicart",
705 45 => "Mapper 045 - MMC3 Multicart",
706 46 => "Mapper 046 - Color Dreams",
707 47 => "Mapper 047 - MMC3 Multicart",
708 48 => "Mapper 048 - Taito TC0690",
709 49 => "Mapper 049 - MMC Multicart",
710 50 => "Mapper 050",
711 51 => "Mapper 051",
712 52 => "Mapper 052 - Realtec 8213/MMC Multicaart",
713 53 => "Mapper 053 - Supervision",
714 54 => "Mapper 054 - Novel Diamond",
715 55 => "Mapper 055 - UNIF BTL-MARIO1-MALEE2",
716 56 => "Mapper 056",
717 57 => "Mapper 057",
718 58 => "Mapper 058",
719 59 => "Mapper 059 - BMC T3H53/D1038",
720 60 => "Mapper 060",
721 61 => "Mapper 061",
722 62 => "Mapper 062",
723 63 => "Mapper 063",
724 64 => "Mapper 064 - RAMBO-1",
725 65 => "Mapper 065 - Irem H3001",
726 66 => "Mapper 066 - GxROM/MxROM",
727 67 => "Mapper 067 - Sunsoft-3",
728 68 => "Mapper 068 - Sunsoft-4",
729 69 => "Mapper 069 - Sunsoft FME-7",
730 70 => "Mapper 070 - Bandai",
731 71 => "Mapper 071 - BF909x",
732 72 => "Mapper 072 - Jaleco JF-17",
733 73 => "Mapper 073 - Vrc3",
734 74 => "Mapper 074",
735 75 => "Mapper 075 - Vrc1",
736 76 => "Mapper 076 - NAMCOT-108",
737 77 => "Mapper 077",
738 78 => "Mapper 078",
739 79 => "Mapper 079 - NINA-03/06",
740 80 => "Mapper 080 - Taito X1005",
741 81 => "Mapper 081 - NTDEC 715021",
742 82 => "Mapper 082 - Taito X1017",
743 83 => "Mapper 083",
744 84 => "Mapper 084",
745 85 => "Mapper 085 - Vrc7",
746 86 => "Mapper 086 - Jaleco JF-13",
747 87 => "Mapper 087 - Jaleco JF-xx",
748 88 => "Mapper 088",
749 89 => "Mapper 089 - Sunsoft",
750 90 => "Mapper 090 - JY Company",
751 91 => "Mapper 091",
752 92 => "Mapper 092",
753 93 => "Mapper 093 - Sunsoft",
754 94 => "Mapper 094 - UxROM",
755 95 => "Mapper 095 - NAMCOT-3425",
756 96 => "Mapper 096 - Oeka Kids",
757 97 => "Mapper 097 - Irem TAM-S1",
758 98 => "Mapper 098",
759 99 => "Mapper 099 - Vs. System",
760 100 => "Mapper 100",
761 101 => "Mapper 101 - Jaleco JF-10",
762 102 => "Mapper 102",
763 103 => "Mapper 103",
764 104 => "Mapper 104 - Golden Five",
765 105 => "Mapper 105 - MMC1",
766 106 => "Mapper 106",
767 107 => "Mapper 107",
768 108 => "Mapper 108",
769 109 => "Mapper 109",
770 110 => "Mapper 110",
771 111 => "Mapper 111 - GTROM",
772 112 => "Mapper 112",
773 113 => "Mapper 113 - NINA-03/06",
774 114 => "Mapper 114 - MMC3",
775 115 => "Mapper 115 - MMC3",
776 116 => "Mapper 116 - SOMARI-P",
777 117 => "Mapper 117",
778 118 => "Mapper 118 - TxSROM",
779 119 => "Mapper 119 - TQROM",
780 120 => "Mapper 120",
781 121 => "Mapper 121 - MMC3",
782 122 => "Mapper 122",
783 123 => "Mapper 123 - MMC3",
784 124 => "Mapper 124",
785 125 => "Mapper 125 - UNL-LH32",
786 126 => "Mapper 126 - MMC36",
787 127 => "Mapper 127",
788 128 => "Mapper 128",
789 129 => "Mapper 129",
790 130 => "Mapper 130",
791 131 => "Mapper 131",
792 132 => "Mapper 132 - TXC",
793 133 => "Mapper 133 - Sachen 3009",
794 134 => "Mapper 134 - MMC3",
795 135 => "Mapper 135 - Sachen 8259A",
796 136 => "Mapper 136 - Sachen 3011",
797 137 => "Mapper 137 - Sachen 8259D",
798 138 => "Mapper 138 - Sachen 8259B",
799 139 => "Mapper 139 - Sachen 8259C",
800 140 => "Mapper 140 - Jaleco JF-11/14",
801 141 => "Mapper 141 - Sachen 8259A",
802 142 => "Mapper 142 - Kaiser KS-7032",
803 143 => "Mapper 143 - NROM",
804 144 => "Mapper 144 - Color Dreams",
805 145 => "Mapper 145 - Sachen SA-72007",
806 146 => "Mapper 146 - NINA-03/06",
807 147 => "Mapper 147 - Sachen 3018",
808 148 => "Mapper 148 - Sachen SA-008-A/Tengen 800008",
809 149 => "Mapper 149 - Sachen SA-0036",
810 150 => "Mapper 150 - Sach SA-015/630",
811 151 => "Mapper 151 - Vrc1",
812 152 => "Mapper 152",
813 153 => "Mapper 153 - Bandai FCG",
814 154 => "Mapper 154 - NAMCOT-3453",
815 155 => "Mapper 155 - SxROM/MMC1A",
816 156 => "Mapper 156 - Daou",
817 157 => "Mapper 157 - Bandai FCG",
818 158 => "Mapper 158 - Tengen 800037",
819 159 => "Mapper 159 - Bandai FCG",
820 160 => "Mapper 160",
821 161 => "Mapper 161",
822 162 => "Mapper 162 - Wàixīng",
823 163 => "Mapper 163 - Nánjīng",
824 164 => "Mapper 164 - Dōngdá/Yànchéng",
825 165 => "Mapper 165 - MMC3",
826 166 => "Mapper 166 - Subor",
827 167 => "Mapper 167 - Subor",
828 168 => "Mapper 168 - Racermate",
829 169 => "Mapper 169 - Yuxing",
830 170 => "Mapper 170",
831 171 => "Mapper 171 - Kaiser KS-7058",
832 172 => "Mapper 172",
833 173 => "Mapper 173",
834 174 => "Mapper 174",
835 175 => "Mapper 175 - Kaiser KS-7022",
836 176 => "Mapper 176 - MMC3",
837 177 => "Mapper 177 - Hénggé Diànzǐ",
838 178 => "Mapper 178",
839 179 => "Mapper 179",
840 180 => "Mapper 180 - UNROM",
841 181 => "Mapper 181",
842 182 => "Mapper 182 - MMC3",
843 183 => "Mapper 183",
844 184 => "Mapper 184 - Sunsoft",
845 185 => "Mapper 185 - CNROM",
846 186 => "Mapper 186",
847 187 => "Mapper 187 - Kǎshèng/MMC3",
848 188 => "Mapper 188 - Bandai Karaoke",
849 189 => "Mapper 189 - MMC3",
850 190 => "Mapper 190 -",
851 191 => "Mapper 191 - MMC3",
852 192 => "Mapper 192 - Wàixīng",
853 193 => "Mapper 193 - NTDEC TC-112",
854 194 => "Mapper 194 - MMC3",
855 195 => "Mapper 195 - Wàixīng/MMC3",
856 196 => "Mapper 196 - MMC3",
857 197 => "Mapper 197 - MMC3",
858 198 => "Mapper 198 - MMC3",
859 199 => "Mapper 199 - Wàixīng/MMC3",
860 200 => "Mapper 200",
861 201 => "Mapper 201 - NROM",
862 202 => "Mapper 202",
863 203 => "Mapper 203",
864 204 => "Mapper 204",
865 205 => "Mapper 205 - MMC3",
866 206 => "Mapper 206 - DxROM",
867 207 => "Mapper 207 - Taito X1-005",
868 208 => "Mapper 208 - MMC3",
869 209 => "Mapper 209 - JY Company",
870 210 => "Mapper 210 - Namco",
871 211 => "Mapper 211 - JyCompany",
872 212 => "Mapper 212",
873 213 => "Mapper 213",
874 214 => "Mapper 214",
875 215 => "Mapper 215 - MMC3",
876 216 => "Mapper 216",
877 217 => "Mapper 217 - MMC3",
878 218 => "Mapper 218",
879 219 => "Mapper 219 - Kǎshèng/MMC3",
880 220 => "Mapper 220",
881 221 => "Mapper 221 - NTDEC N625092",
882 222 => "Mapper 222",
883 223 => "Mapper 223",
884 224 => "Mapper 224 - Jncota/MMC3",
885 225 => "Mapper 225",
886 226 => "Mapper 226",
887 227 => "Mapper 227",
888 228 => "Mapper 228- Active Enterprises",
889 229 => "Mapper 229",
890 230 => "Mapper 230",
891 231 => "Mapper 231",
892 232 => "Mapper 232 - BF909x",
893 233 => "Mapper 233",
894 234 => "Mapper 234 - Maxi 15 Multicart",
895 235 => "Mapper 235",
896 236 => "Mapper 236 - Realtec",
897 237 => "Mapper 237",
898 238 => "Mapper 238 - MMC3",
899 239 => "Mapper 239",
900 240 => "Mapper 240",
901 241 => "Mapper 241 - BxROM",
902 242 => "Mapper 242",
903 243 => "Mapper 243 - Sachen SA-020A",
904 244 => "Mapper 244",
905 245 => "Mapper 245 - Wàixīng/MMC3",
906 246 => "Mapper 246",
907 247 => "Mapper 247",
908 248 => "Mapper 248",
909 249 => "Mapper 249 - MMC3",
910 250 => "Mapper 250 - Nitra/MMC3",
911 251 => "Mapper 251",
912 252 => "Mapper 252 - Wàixīng",
913 253 => "Mapper 253 - Wàixīng",
914 254 => "Mapper 254 - MMC3",
915 255 => "Mapper 255",
916 _ => "Invalid Mapper",
917 }
918 }
919}
920
921impl std::fmt::Debug for NesHeader {
922 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
923 f.debug_struct("NesHeader")
924 .field("version", &self.variant)
925 .field("mapper_num", &format_args!("{:03}", &self.mapper_num))
926 .field("submapper_num", &self.submapper_num)
927 .field("flags", &format_args!("0b{:08b}", &self.flags))
928 .field("prg_rom_banks", &self.prg_rom_banks)
929 .field("chr_rom_banks", &self.chr_rom_banks)
930 .field("prg_ram_shift", &self.prg_ram_shift)
931 .field("chr_ram_shift", &self.chr_ram_shift)
932 .field("tv_mode", &self.tv_mode)
933 .field("vs_data", &self.vs_data)
934 .finish()
935 }
936}
937
938#[cfg(test)]
939mod tests {
940 use super::*;
941
942 macro_rules! test_headers {
943 ($(($test:ident, $data:expr, $header:expr$(,)?)),*$(,)?) => {$(
944 #[test]
945 fn $test() {
946 let header = NesHeader::load(&mut $data.as_slice()).expect("valid header");
947 assert_eq!(header, $header);
948 }
949 )*};
950 }
951
952 #[rustfmt::skip]
953 test_headers!(
954 (
955 mapper000_horizontal,
956 [0x4E, 0x45, 0x53, 0x1A,
957 0x02, 0x01, 0x01, 0x00,
958 0x00, 0x00, 0x00, 0x00,
959 0x00, 0x00, 0x00, 0x00],
960 NesHeader {
961 variant: NesVariant::INes,
962 mapper_num: 0,
963 flags: 0b0000_0001,
964 prg_rom_banks: 2,
965 chr_rom_banks: 1,
966 ..NesHeader::default()
967 },
968 ),
969 (
970 mapper001_vertical,
971 [0x4E, 0x45, 0x53, 0x1A,
972 0x08, 0x00, 0x10, 0x00,
973 0x00, 0x00, 0x00, 0x00,
974 0x00, 0x00, 0x00, 0x00],
975 NesHeader {
976 variant: NesVariant::INes,
977 mapper_num: 1,
978 flags: 0b0000_0000,
979 prg_rom_banks: 8,
980 chr_rom_banks: 0,
981 ..NesHeader::default()
982 },
983 ),
984 );
985}