1#![no_std]
27#![forbid(unsafe_code)]
31#![warn(missing_docs)]
32#![allow(clippy::redundant_pub_crate)]
38
39extern crate alloc;
40
41#[cfg(test)]
42extern crate std;
43
44use alloc::{boxed::Box, string::ToString};
45
46mod a12_filter;
47mod bmc_simple;
48mod cartridge;
49mod fds;
50mod header;
51mod homebrew_boards;
52mod jaleco_discrete;
53mod kaiser;
54mod m000_nrom;
55mod m001_mmc1;
56mod m002_uxrom;
57mod m003_cnrom;
58mod m004_mmc3;
59mod m005_mmc5;
60mod m007_axrom;
61mod m009_mmc2;
62mod m010_mmc4;
63mod m011_color_dreams;
64mod m013_cprom;
65mod m016_bandai_fcg;
66mod m018_jaleco_ss88006;
67mod m019_namco163;
68mod m021_vrc4;
69mod m022_vrc2;
70mod m024_vrc6;
71mod m032_irem_g101;
72mod m033_taito_tc0190;
73mod m034_bnrom_nina001;
74mod m035_jy_asic;
75mod m036_txc_policeman;
76mod m038_bitcorp38;
77mod m039_subor39;
78mod m041_caltron41;
79mod m042_fds_conv_bio_miracle;
80mod m048_taito_tc0690;
81mod m050_fds_conv_smb2j;
82mod m064_rambo1;
83mod m065_irem_h3001;
84mod m066_gxrom;
85mod m067_sunsoft3;
86mod m068_sunsoft4;
87mod m069_sunsoft_fme7;
88mod m070_bandai74;
89mod m071_camerica_bf9093;
90mod m073_vrc3;
91mod m075_vrc1;
92mod m076_namcot3446;
93mod m077_irem_napoleon;
94mod m078_irem_jaleco78;
95mod m079_ave_nina03_06;
96mod m080_taito_x1_005;
97mod m082_taito_x1_017;
98mod m083_cony;
99mod m085_vrc7;
100mod m087_jaleco87;
101mod m088_namco118;
102mod m089_sunsoft2;
103mod m091_jy_sf3;
104mod m093_sunsoft3r;
105mod m094_un1rom;
106mod m095_namcot3425;
107mod m096_bandai96;
108mod m097_irem_tam_s1;
109mod m099_vs_system;
110mod m105_nes_event;
111mod m107_magic_dragon107;
112mod m113_ave_nina006;
113mod m118_txsrom;
114mod m119_tqrom;
115mod m132_txc_22211;
116mod m136_sachen_3011;
117mod m151_konami_vs;
118mod m152_bandai152;
119mod m156_daou156;
120mod m163_nanjing;
121mod m176_bmc_fk23c;
122mod m177_hengedianzi;
123mod m179_hengedianzi;
124mod m180_nichibutsu180;
125mod m184_sunsoft1;
126mod m185_cnrom185;
127mod m210_namco175;
128mod m228_action52;
129mod m232_camerica_bf9096;
130mod m240_cne_multicart;
131mod m241_bxrom241;
132mod m244_cne_decathlon;
133mod m246_fong_shen_bang246;
134mod m250_nitra250;
135mod m268_bmc_coolboy;
136mod m513_sachen_9602;
137mod mapper;
138mod mmc3_boards;
139mod mmc3_clones;
140mod multicart_discrete;
141mod nsf;
142mod nsf_expansion;
143mod ntdec;
144mod sachen_8259;
145mod sachen_discrete;
146mod sst39sf040;
147mod tier;
148mod unif;
149mod waixing;
150
151pub use bmc_simple::{new_m164, new_m261, new_m286, new_m289, new_m320, new_m336, new_m349};
152pub use cartridge::{Cartridge, ConsoleType, Mirroring, Region, RomError, VsPpuPalette, VsPpuType};
153pub use fds::{
154 DISK_BYTE_CYCLES, FDS_SIDE_LEN, Fds, FdsDisk, FdsQuirk, FdsTraceRec, HEAD_RESEEK_CYCLES,
155 fds_crc32, parse_fds, quirk_for_crc,
156};
157pub use header::{
158 ExpansionDevice, ExtendedConsoleType, Header, VsHardwareType, parse_header,
159 serialize_header_preserving,
160};
161pub use homebrew_boards::{Action53M28, Cufrom29, Gtrom111, Inl31, MagicFloor218, Unrom512M30};
162pub use jaleco_discrete::{Jaleco72, Jaleco86, Jaleco92, Jaleco101, Jaleco140};
163pub use kaiser::{new_m56, new_m142, new_m303, new_m305, new_m306, new_m312};
164pub use m000_nrom::Nrom;
165pub use m001_mmc1::Mmc1;
166pub use m002_uxrom::UxRom;
167pub use m003_cnrom::CnRom;
168pub use m004_mmc3::{Mmc3, Mmc3Revision, Mmc3Variant};
169pub use m005_mmc5::Mmc5;
170pub use m007_axrom::AxRom;
171pub use m009_mmc2::Mmc2;
172pub use m010_mmc4::Mmc4;
173pub use m011_color_dreams::ColorDreams;
174pub use m013_cprom::Cprom;
175pub use m016_bandai_fcg::{BandaiFcg, FcgVariant};
176pub use m018_jaleco_ss88006::JalecoSs88006;
177pub use m019_namco163::Namco163;
178pub use m021_vrc4::Vrc4;
179pub use m022_vrc2::Vrc2;
180pub use m024_vrc6::Vrc6;
181pub use m032_irem_g101::IremG101;
182pub use m033_taito_tc0190::TaitoTc0190;
183pub use m034_bnrom_nina001::{M34, M34Variant};
184pub use m035_jy_asic::{JyAsic, JyBoard};
185pub use m036_txc_policeman::Txc36;
186pub use m038_bitcorp38::Bitcorp38;
187pub use m039_subor39::Subor39;
188pub use m041_caltron41::Caltron41;
189pub use m042_fds_conv_bio_miracle::Mapper42;
190pub use m048_taito_tc0690::TaitoTc0690;
191pub use m050_fds_conv_smb2j::Mapper50;
192pub use m064_rambo1::Rambo1;
193pub use m065_irem_h3001::IremH3001;
194pub use m066_gxrom::GxRom;
195pub use m067_sunsoft3::Sunsoft3;
196pub use m068_sunsoft4::Sunsoft4;
197pub use m069_sunsoft_fme7::Fme7;
198pub use m070_bandai74::Bandai74;
199pub use m071_camerica_bf9093::Camerica;
200pub use m073_vrc3::Vrc3;
201pub use m075_vrc1::Vrc1;
202pub use m076_namcot3446::Namcot3446M76;
203pub use m077_irem_napoleon::Irem77;
204pub use m078_irem_jaleco78::{M78, M78Variant};
205pub use m079_ave_nina03_06::Nina0379;
206pub use m080_taito_x1_005::TaitoX1005;
207pub use m082_taito_x1_017::TaitoX1017;
208pub use m083_cony::Cony83;
209pub use m085_vrc7::Vrc7;
210pub use m087_jaleco87::Jaleco87;
211pub use m088_namco118::{Namco118, Namco118Board};
212pub use m089_sunsoft2::Sunsoft2;
213pub use m091_jy_sf3::Jy91;
214pub use m093_sunsoft3r::Sunsoft3r;
215pub use m094_un1rom::Un1rom94;
216pub use m095_namcot3425::Namcot3425M95;
217pub use m096_bandai96::Bandai96;
218pub use m097_irem_tam_s1::Irem97;
219pub use m099_vs_system::VsSystem;
220pub use m105_nes_event::{NWC_TOURNAMENT_DIP, NesEvent105};
221pub use m107_magic_dragon107::MagicDragon107;
222pub use m113_ave_nina006::Nina006M113;
223pub use m118_txsrom::TxSrom;
224pub use m119_tqrom::Tqrom;
225pub use m132_txc_22211::Txc132;
226pub use m136_sachen_3011::new_m136;
227pub use m151_konami_vs::KonamiVs;
228pub use m152_bandai152::Bandai152;
229pub use m156_daou156::Daou156;
230pub use m163_nanjing::Nanjing163;
231pub use m176_bmc_fk23c::new_m176;
232pub use m177_hengedianzi::Hengedianzi177;
233pub use m179_hengedianzi::Hengedianzi179;
234pub use m180_nichibutsu180::Nichibutsu180;
235pub use m184_sunsoft1::Sunsoft1;
236pub use m185_cnrom185::CnRom185;
237pub use m210_namco175::{Namco175, Namco175Board};
238pub use m228_action52::Action52M228;
239pub use m232_camerica_bf9096::Camerica232;
240pub use m240_cne_multicart::Cne240;
241pub use m241_bxrom241::Bxrom241;
242pub use m244_cne_decathlon::Decathlon244;
243pub use m246_fong_shen_bang246::FongShenBang246;
244pub use m250_nitra250::Nitra250;
245pub use m268_bmc_coolboy::new_m268;
246pub use m513_sachen_9602::new_m513;
247pub use mapper::{
248 BgSplitState, ExAttribute, Mapper, MapperCaps, MapperDebugInfo, MapperError, MapperFrameEvents,
249 mirroring_name,
250};
251pub use mmc3_boards::{Board as Mmc3BoardKind, Mmc3Board};
252pub use mmc3_clones::{
253 Mmc3CloneMapper, new_m44, new_m49, new_m52, new_m115, new_m134, new_m189, new_m205, new_m238,
254 new_m245, new_m348, new_m366,
255};
256pub use multicart_discrete::{
257 DiscreteBoard, DiscreteMapper, Maxi15M234, Multicart15, Multicart58, Multicart60, Multicart61,
258 Multicart62, Multicart200, Multicart201, Multicart202, Multicart203, Multicart212,
259 Multicart213, Multicart214, Multicart225, Multicart226, Multicart227, Multicart229,
260 Multicart231, Multicart233, new_m46, new_m51, new_m57, new_m104, new_m120, new_m204, new_m290,
261 new_m299, new_m301,
262};
263pub use nsf::{Nsf, NsfMapper, is_nsf, parse_nsf};
264pub use ntdec::{Ntdec63, Ntdec81, Ntdec174, Ntdec2722M40, NtdecAsder112, new_m193, new_m221};
265pub use sachen_8259::{Sachen8259, Sachen8259M137, Sachen8259Variant};
266pub use sachen_discrete::{
267 Sa020aBoard, Sachen133, Sachen145, Sachen146, Sachen148, Sachen149, Sachen150, Sachen3018M147,
268 SachenTca01M143,
269};
270pub use tier::{MapperTier, mapper_tier};
271pub use unif::{UnifError, UnifImage, board_to_mapper, parse_unif, unif_to_ines};
272pub use waixing::{Waixing178, Waixing242, WaixingFs304M162, new_m253};
273
274#[must_use]
276pub const fn version() -> &'static str {
277 env!("CARGO_PKG_VERSION")
278}
279
280fn mmc3_board(
288 kind: Mmc3BoardKind,
289 prg_rom: Box<[u8]>,
290 chr_rom: Box<[u8]>,
291 h: &Header,
292) -> Result<Box<dyn Mapper>, RomError> {
293 let (wram, chr_ram) = if h.is_nes2 {
294 (h.prg_ram_window() as usize, h.chr_ram_size as usize)
295 } else {
296 (0, 0)
297 };
298 Ok(Box::new(
299 Mmc3Board::new(kind, prg_rom, chr_rom, h.mirroring, wram, chr_ram)
300 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
301 ))
302}
303
304#[allow(clippy::too_many_lines)]
320pub fn parse(bytes: &[u8]) -> Result<(Cartridge, Box<dyn Mapper>), RomError> {
321 if bytes.len() >= 4 && &bytes[0..4] == b"FDS\x1A" {
332 return Err(RomError::FdsUnsupported);
333 }
334 if bytes.len() >= 15 && bytes[0] == 0x01 && &bytes[1..15] == b"*NINTENDO-HVC*" {
335 return Err(RomError::FdsUnsupported);
336 }
337
338 if nsf::is_nsf(bytes) {
344 return Err(RomError::InvalidConfig(
345 "NSF music file — load via Nes::from_nsf, not parse()".into(),
346 ));
347 }
348
349 if bytes.len() >= 4 && &bytes[0..4] == unif::UNIF_MAGIC {
355 let img = unif::parse_unif(bytes).map_err(|e| RomError::InvalidConfig(e.to_string()))?;
356 return parse(&unif::unif_to_ines(&img));
357 }
358
359 let h = parse_header(bytes)?;
360
361 let mut cursor = header::HEADER_LEN;
362 if h.has_trainer {
363 if bytes.len() < cursor + header::TRAINER_LEN {
364 return Err(RomError::Truncated {
365 needed: cursor + header::TRAINER_LEN,
366 got: bytes.len(),
367 });
368 }
369 cursor += header::TRAINER_LEN;
370 }
371
372 if bytes.len() < cursor + h.prg_size {
373 return Err(RomError::Truncated {
374 needed: cursor + h.prg_size,
375 got: bytes.len(),
376 });
377 }
378 let prg_rom: Box<[u8]> = bytes[cursor..cursor + h.prg_size]
379 .to_vec()
380 .into_boxed_slice();
381 cursor += h.prg_size;
382
383 if bytes.len() < cursor + h.chr_size {
384 return Err(RomError::Truncated {
385 needed: cursor + h.chr_size,
386 got: bytes.len(),
387 });
388 }
389 let chr_rom: Box<[u8]> = bytes[cursor..cursor + h.chr_size]
390 .to_vec()
391 .into_boxed_slice();
392
393 let clean_ines = !h.is_nes2;
425 let (console_type, vs_ppu_type) = if h.mapper_id == 99 || h.mapper_id == 151 {
426 let vs = if h.vs_ppu_type == VsPpuType::None {
427 VsPpuType::Rp2C03
428 } else {
429 h.vs_ppu_type
430 };
431 (ConsoleType::VsSystem, vs)
432 } else if clean_ines && bytes[7] == 0x01 {
433 (ConsoleType::VsSystem, VsPpuType::Rp2C03)
435 } else if clean_ines && bytes[7] == 0x02 {
436 (ConsoleType::Playchoice10, VsPpuType::Rp2C03)
440 } else {
441 (h.console_type, h.vs_ppu_type)
442 };
443
444 let (identity_mapper, identity_submapper) = if h.mapper_id == 30 && !chr_rom.is_empty() {
452 (176, 2)
453 } else {
454 (h.mapper_id, h.submapper)
455 };
456
457 let cart = Cartridge {
458 prg_rom: prg_rom.clone(),
459 chr_rom: chr_rom.clone(),
460 mapper_id: identity_mapper,
461 submapper: identity_submapper,
462 mirroring: h.mirroring,
463 region: h.region,
464 console_type,
465 vs_ppu_type,
466 vs_dual_system: h.is_vs_dual_system(),
469 prg_ram_size: h.prg_ram_window(),
472 chr_ram_size: h.chr_ram_size,
473 has_battery: h.has_battery,
474 has_trainer: h.has_trainer,
475 is_nes2: h.is_nes2,
476 nametable_wiring_bits: bytes[6] & 0x09,
477 };
478
479 let mapper: Box<dyn Mapper> = match h.mapper_id {
480 0 => {
481 let nrom = Nrom::new(prg_rom, chr_rom, h.mirroring)
482 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
483 Box::new(nrom)
484 }
485 1 => {
486 let prg_ram_bytes = if h.prg_ram_window() == 0 {
490 0
491 } else {
492 h.prg_ram_window() as usize
493 };
494 let mmc1 = Mmc1::new(prg_rom, chr_rom, h.mirroring, prg_ram_bytes)
495 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
496 Box::new(mmc1)
497 }
498 2 => {
499 let uxrom = UxRom::new(prg_rom, chr_rom, h.mirroring)
500 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
501 Box::new(uxrom)
502 }
503 3 => {
504 let cnrom = CnRom::new(prg_rom, chr_rom, h.mirroring)
505 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
506 Box::new(cnrom)
507 }
508 4 if h.is_nes2 && h.submapper == 5 => {
511 mmc3_board(Mmc3BoardKind::M4T9552, prg_rom, chr_rom, &h)?
512 }
513 249 => mmc3_board(Mmc3BoardKind::M249, prg_rom, chr_rom, &h)?,
514 4 => {
515 let sub = if h.is_nes2 { h.submapper } else { 0 };
527 let revision = if sub == 4 {
528 Mmc3Revision::Nec
529 } else {
530 Mmc3Revision::Sharp
531 };
532 let variant = match sub {
533 1 => Mmc3Variant::Mmc6,
534 2 => Mmc3Variant::HardwiredMirroring,
535 3 => Mmc3Variant::McAcc,
536 _ => Mmc3Variant::Standard,
537 };
538 let prg_ram_bytes = if h.prg_ram_window() == 0 {
539 0
540 } else {
541 h.prg_ram_window() as usize
542 };
543 let mmc3 = Mmc3::new(prg_rom, chr_rom, h.mirroring, prg_ram_bytes, revision)
544 .map_err(|e| RomError::InvalidConfig(e.to_string()))?
545 .with_variant(variant);
546 Box::new(mmc3)
547 }
548 5 => {
549 let prg_ram_bytes = if h.prg_ram_window() == 0 {
554 0
555 } else {
556 h.prg_ram_window() as usize
557 };
558 let mmc5 = Mmc5::new(prg_rom, chr_rom, h.mirroring, prg_ram_bytes)
559 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
560 Box::new(mmc5)
561 }
562 7 => {
563 let axrom =
564 AxRom::new(prg_rom, chr_rom).map_err(|e| RomError::InvalidConfig(e.to_string()))?;
565 Box::new(axrom)
566 }
567 9 => {
568 let mmc2 = Mmc2::new(prg_rom, chr_rom, h.mirroring)
569 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
570 Box::new(mmc2)
571 }
572 10 => {
573 let mmc4 = Mmc4::new(prg_rom, chr_rom, h.mirroring)
574 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
575 Box::new(mmc4)
576 }
577 11 => {
578 let cd = ColorDreams::new(prg_rom, chr_rom, h.mirroring)
579 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
580 Box::new(cd)
581 }
582 13 => {
583 let cprom = Cprom::new(prg_rom, h.mirroring)
584 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
585 Box::new(cprom)
586 }
587 21 | 23 | 25 => {
591 let m021_vrc4 = Vrc4::new(prg_rom, chr_rom, h.mapper_id, h.submapper, h.mirroring)
600 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
601 Box::new(m021_vrc4)
602 }
603 22 => {
604 let vrc2 = Vrc2::new(prg_rom, chr_rom, 22, h.submapper, h.mirroring)
605 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
606 Box::new(vrc2)
607 }
608 24 | 26 => {
609 let vrc6 = Vrc6::new(prg_rom, chr_rom, h.mapper_id, h.mirroring)
610 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
611 Box::new(vrc6)
612 }
613 34 => {
614 let variant = if h.is_nes2 && h.submapper == 1 {
615 M34Variant::Nina001
616 } else {
617 M34Variant::Bnrom
618 };
619 let m34 = M34::new(prg_rom, chr_rom, h.mirroring, variant)
620 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
621 Box::new(m34)
622 }
623 66 => {
624 let gxrom = GxRom::new(prg_rom, chr_rom, h.mirroring)
625 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
626 Box::new(gxrom)
627 }
628 69 => {
629 let fme7 = Fme7::new(prg_rom, chr_rom, h.mirroring)
630 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
631 Box::new(fme7)
632 }
633 71 => {
634 let has_single_screen = h.is_nes2 && h.submapper == 1;
637 let cam = Camerica::new(prg_rom, h.mirroring, has_single_screen)
638 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
639 Box::new(cam)
640 }
641 75 => {
642 let vrc1 = Vrc1::new(prg_rom, chr_rom, h.mirroring)
643 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
644 Box::new(vrc1)
645 }
646 19 => {
647 let n163 = Namco163::new(prg_rom, chr_rom, h.mirroring)
648 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
649 Box::new(n163)
650 }
651 16 => {
652 let variant = match h.submapper {
661 4 => FcgVariant::Fcg,
662 5 => FcgVariant::Lz93d50_24c02,
663 _ => FcgVariant::Both,
664 };
665 let fcg = BandaiFcg::new(prg_rom, chr_rom, h.mirroring, variant)
666 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
667 Box::new(fcg)
668 }
669 153 => {
670 let fcg = BandaiFcg::new(prg_rom, chr_rom, h.mirroring, FcgVariant::Lz93d50Wram)
673 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
674 Box::new(fcg)
675 }
676 159 => {
677 let fcg = BandaiFcg::new(prg_rom, chr_rom, h.mirroring, FcgVariant::Lz93d50_24c01)
679 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
680 Box::new(fcg)
681 }
682 18 => {
683 let ss = JalecoSs88006::new(prg_rom, chr_rom, h.mirroring)
685 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
686 Box::new(ss)
687 }
688 32 => {
689 let force_one_screen = h.is_nes2 && h.submapper == 1;
697 let m32 = IremG101::new(prg_rom, chr_rom, h.mirroring, force_one_screen)
698 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
699 Box::new(m32)
700 }
701 33 => {
702 let m33 = TaitoTc0190::new(prg_rom, chr_rom, h.mirroring)
707 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
708 Box::new(m33)
709 }
710 48 => {
711 let m48 = TaitoTc0690::new(prg_rom, chr_rom, h.mirroring)
715 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
716 Box::new(m48)
717 }
718 70 => {
719 let m70 = Bandai74::new(prg_rom, chr_rom, h.mirroring)
721 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
722 Box::new(m70)
723 }
724 87 => {
725 let m87 = Jaleco87::new(prg_rom, chr_rom, h.mirroring)
729 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
730 Box::new(m87)
731 }
732 89 => {
733 let m89 = Sunsoft2::new(prg_rom, chr_rom, h.mirroring)
738 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
739 Box::new(m89)
740 }
741 184 => {
742 let m184 = Sunsoft1::new(prg_rom, chr_rom, h.mirroring)
746 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
747 Box::new(m184)
748 }
749 93 => {
750 let m93 = Sunsoft3r::new(prg_rom, h.mirroring)
754 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
755 Box::new(m93)
756 }
757 99 => {
758 let m99 = VsSystem::new(prg_rom, chr_rom, h.mirroring)
764 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
765 Box::new(m99)
766 }
767 152 => {
768 let m152 = Bandai152::new(prg_rom, chr_rom)
772 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
773 Box::new(m152)
774 }
775 73 => {
776 let vrc3 = Vrc3::new(prg_rom, h.mirroring)
779 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
780 Box::new(vrc3)
781 }
782 64 => {
783 let m64 = Rambo1::new(prg_rom, chr_rom, h.mirroring)
787 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
788 Box::new(m64)
789 }
790 65 => {
791 let m65 = IremH3001::new(prg_rom, chr_rom, h.mirroring)
794 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
795 Box::new(m65)
796 }
797 67 => {
798 let m67 = Sunsoft3::new(prg_rom, chr_rom, h.mirroring)
801 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
802 Box::new(m67)
803 }
804 68 => {
805 let m68 = Sunsoft4::new(prg_rom, chr_rom, h.mirroring)
809 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
810 Box::new(m68)
811 }
812 78 => {
813 let variant = match (h.is_nes2, h.submapper) {
824 (true, 1) => M78Variant::CosmoCarrier,
825 (true, 3) => M78Variant::HolyDiver,
826 _ if h.mirroring == Mirroring::FourScreen => M78Variant::HolyDiver,
827 _ => M78Variant::CosmoCarrier,
828 };
829 let m78 = M78::new(prg_rom, chr_rom, variant)
830 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
831 Box::new(m78)
832 }
833 118 => {
834 let prg_ram_bytes = if h.prg_ram_window() == 0 {
838 0
839 } else {
840 h.prg_ram_window() as usize
841 };
842 let m118 = TxSrom::new(prg_rom, chr_rom, h.mirroring, prg_ram_bytes)
843 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
844 Box::new(m118)
845 }
846 119 => {
847 let prg_ram_bytes = if h.prg_ram_window() == 0 {
852 0
853 } else {
854 h.prg_ram_window() as usize
855 };
856 let m119 = Tqrom::new(prg_rom, chr_rom, h.mirroring, prg_ram_bytes)
857 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
858 Box::new(m119)
859 }
860 210 => {
861 let board = if h.is_nes2 && h.submapper == 2 {
868 Namco175Board::N340
869 } else if (h.is_nes2 && h.submapper == 1) || h.has_battery {
870 Namco175Board::N175
871 } else {
872 Namco175Board::N340
873 };
874 let m210 = Namco175::new(prg_rom, chr_rom, h.mirroring, board)
875 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
876 Box::new(m210)
877 }
878 80 => {
879 let m80 = TaitoX1005::new(prg_rom, chr_rom, h.mirroring)
885 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
886 Box::new(m80)
887 }
888 82 => {
889 let m82 = TaitoX1017::new(prg_rom, chr_rom, h.mirroring)
896 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
897 Box::new(m82)
898 }
899 151 => {
900 let m151 = KonamiVs::new(prg_rom, chr_rom, h.mirroring)
905 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
906 Box::new(m151)
907 }
908 88 => {
909 let m88 = Namco118::new(prg_rom, chr_rom, h.mirroring, Namco118Board::M88)
911 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
912 Box::new(m88)
913 }
914 154 => {
915 let m154 = Namco118::new(prg_rom, chr_rom, h.mirroring, Namco118Board::M154)
919 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
920 Box::new(m154)
921 }
922 206 => {
923 let m206 = Namco118::new(prg_rom, chr_rom, h.mirroring, Namco118Board::Dxrom)
925 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
926 Box::new(m206)
927 }
928 85 => {
929 let vrc7 = Vrc7::new(prg_rom, chr_rom, h.mirroring)
939 .map_err(|e| RomError::InvalidConfig(e.to_string()))?;
940 Box::new(vrc7)
941 }
942 38 => Box::new(
947 Bitcorp38::new(prg_rom, chr_rom, h.mirroring)
948 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
949 ),
950 41 => Box::new(
951 Caltron41::new(prg_rom, chr_rom, h.mirroring)
952 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
953 ),
954 79 => Box::new(
955 Nina0379::new(prg_rom, chr_rom, h.mirroring)
956 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
957 ),
958 86 => Box::new(
959 Jaleco86::new(prg_rom, chr_rom, h.mirroring)
960 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
961 ),
962 113 => Box::new(
964 Nina006M113::new(prg_rom, chr_rom)
965 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
966 ),
967 140 => Box::new(
968 Jaleco140::new(prg_rom, chr_rom, h.mirroring)
969 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
970 ),
971 232 => Box::new(
972 Camerica232::new(prg_rom, chr_rom, h.mirroring)
973 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
974 ),
975 240 => Box::new(
976 Cne240::new(prg_rom, chr_rom, h.mirroring)
977 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
978 ),
979 241 => Box::new(
980 Bxrom241::new(prg_rom, chr_rom, h.mirroring)
981 .map_err(|e| RomError::InvalidConfig(e.to_string()))?
982 .with_battery(h.has_battery),
983 ),
984 15 => Box::new(
989 Multicart15::new(prg_rom, &chr_rom)
990 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
991 ),
992 36 => Box::new(
993 Txc36::new(prg_rom, chr_rom, h.mirroring)
994 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
995 ),
996 39 => Box::new(
997 Subor39::new(prg_rom, chr_rom, h.mirroring)
998 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
999 ),
1000 61 => Box::new(
1001 Multicart61::new(prg_rom, &chr_rom)
1002 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1003 ),
1004 62 => Box::new(
1005 Multicart62::new(prg_rom, chr_rom, h.mirroring)
1006 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1007 ),
1008 72 => Box::new(
1009 Jaleco72::new(prg_rom, chr_rom, h.mirroring)
1010 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1011 ),
1012 77 => Box::new(
1013 Irem77::new(prg_rom, chr_rom).map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1014 ),
1015 92 => Box::new(
1016 Jaleco92::new(prg_rom, chr_rom, h.mirroring)
1017 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1018 ),
1019 96 => Box::new(
1020 Bandai96::new(prg_rom, chr_rom, h.mirroring)
1021 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1022 ),
1023 97 => Box::new(
1024 Irem97::new(prg_rom, chr_rom, h.mirroring)
1025 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1026 ),
1027 132 => Box::new(
1028 Txc132::new(prg_rom, chr_rom, h.mirroring)
1029 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1030 ),
1031 133 => Box::new(
1032 Sachen133::new(prg_rom, chr_rom, h.mirroring)
1033 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1034 ),
1035 145 => Box::new(
1036 Sachen145::new(prg_rom, chr_rom, h.mirroring)
1037 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1038 ),
1039 146 => Box::new(
1040 Sachen146::new(prg_rom, chr_rom, h.mirroring)
1041 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1042 ),
1043 147 => Box::new(
1044 Sachen3018M147::new(prg_rom, chr_rom, h.mirroring)
1045 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1046 ),
1047 148 => Box::new(
1048 Sachen148::new(prg_rom, chr_rom, h.mirroring)
1049 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1050 ),
1051 149 => Box::new(
1052 Sachen149::new(prg_rom, chr_rom, h.mirroring)
1053 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1054 ),
1055 150 => Box::new(
1056 Sachen150::new(prg_rom, chr_rom).map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1057 ),
1058 243 => Box::new(
1061 Sachen150::new_on_board(prg_rom, chr_rom, Sa020aBoard::Sa020a)
1062 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1063 ),
1064 180 => Box::new(
1065 Nichibutsu180::new(prg_rom, chr_rom, h.mirroring)
1066 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1067 ),
1068 185 => Box::new(
1069 CnRom185::new(prg_rom, chr_rom, h.mirroring, h.submapper)
1070 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1071 ),
1072 200 => Box::new(
1073 Multicart200::new(prg_rom, chr_rom, h.mirroring)
1074 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1075 ),
1076 201 => Box::new(
1077 Multicart201::new(prg_rom, chr_rom, h.mirroring)
1078 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1079 ),
1080 202 => Box::new(
1081 Multicart202::new(prg_rom, chr_rom, h.mirroring)
1082 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1083 ),
1084 203 => Box::new(
1085 Multicart203::new(prg_rom, chr_rom, h.mirroring)
1086 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1087 ),
1088 212 => Box::new(
1089 Multicart212::new(prg_rom, chr_rom, h.mirroring)
1090 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1091 ),
1092 213 => Box::new(
1093 Multicart213::new(prg_rom, chr_rom, h.mirroring)
1094 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1095 ),
1096 214 => Box::new(
1097 Multicart214::new(prg_rom, chr_rom, h.mirroring)
1098 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1099 ),
1100 29 => Box::new(
1105 Cufrom29::new(prg_rom, &chr_rom, h.mirroring)
1106 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1107 ),
1108 31 => Box::new(
1109 Inl31::new(prg_rom, &chr_rom, h.mirroring)
1110 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1111 ),
1112 58 => Box::new(
1113 Multicart58::new(prg_rom, chr_rom, h.mirroring)
1114 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1115 ),
1116 60 => Box::new(
1117 Multicart60::new(prg_rom, chr_rom, h.mirroring)
1118 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1119 ),
1120 94 => Box::new(
1121 Un1rom94::new(prg_rom, &chr_rom, h.mirroring)
1122 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1123 ),
1124 101 => Box::new(
1125 Jaleco101::new(prg_rom, chr_rom, h.mirroring)
1126 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1127 ),
1128 107 => Box::new(
1129 MagicDragon107::new(prg_rom, chr_rom, h.mirroring)
1130 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1131 ),
1132 111 if !chr_rom.is_empty() => {
1139 return Err(RomError::InvalidConfig(
1140 "mapper 111 with CHR-ROM is the Ninja Ryukenden MMC1 variant, which is not \
1141 supported (GTROM carries CHR-RAM)"
1142 .into(),
1143 ));
1144 }
1145 111 => Box::new(
1147 Gtrom111::new(prg_rom, &chr_rom).map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1148 ),
1149 143 => Box::new(
1150 SachenTca01M143::new(prg_rom, chr_rom, h.mirroring)
1151 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1152 ),
1153 177 => Box::new(
1155 Hengedianzi177::new(prg_rom, &chr_rom)
1156 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1157 ),
1158 179 => Box::new(
1159 Hengedianzi179::new(prg_rom, &chr_rom)
1160 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1161 ),
1162 218 => {
1163 let mirroring = if h.four_screen {
1169 if (bytes[6] & 0x01) != 0 {
1170 Mirroring::SingleScreenB
1171 } else {
1172 Mirroring::SingleScreenA
1173 }
1174 } else {
1175 h.mirroring
1176 };
1177 Box::new(
1178 MagicFloor218::new(prg_rom, &chr_rom, mirroring)
1179 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1180 )
1181 }
1182 231 => Box::new(
1183 Multicart231::new(prg_rom, &chr_rom, h.mirroring)
1184 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1185 ),
1186 234 => Box::new(
1187 Maxi15M234::new(prg_rom, chr_rom, h.mirroring)
1188 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1189 ),
1190 28 => Box::new(
1195 Action53M28::new(prg_rom, &chr_rom, h.mirroring)
1196 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1197 ),
1198 30 if !chr_rom.is_empty() => Box::new(
1208 new_m176(prg_rom, chr_rom, h.mirroring, 2)
1209 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1210 ),
1211 30 => {
1212 let four_screen = h.four_screen;
1218 let vertical = (bytes[6] & 0x01) != 0;
1219 Box::new(
1220 Unrom512M30::new(
1221 prg_rom,
1222 &chr_rom,
1223 four_screen,
1224 vertical,
1225 h.submapper,
1226 h.has_battery,
1227 )
1228 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1229 )
1230 }
1231 63 => Box::new(
1232 Ntdec63::new(prg_rom, &chr_rom, h.mirroring)
1233 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1234 ),
1235 76 => Box::new(
1236 Namcot3446M76::new(prg_rom, chr_rom, h.mirroring)
1237 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1238 ),
1239 174 => Box::new(
1240 Ntdec174::new(prg_rom, chr_rom, h.mirroring)
1241 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1242 ),
1243 225 => Box::new(
1244 Multicart225::new(prg_rom, chr_rom, h.mirroring)
1245 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1246 ),
1247 226 => Box::new(
1248 Multicart226::new(prg_rom, &chr_rom, h.mirroring)
1249 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1250 ),
1251 227 => Box::new(
1252 Multicart227::new(prg_rom, &chr_rom, h.mirroring)
1253 .map_err(|e| RomError::InvalidConfig(e.to_string()))?
1254 .with_battery(h.has_battery),
1255 ),
1256 229 => Box::new(
1257 Multicart229::new(prg_rom, chr_rom, h.mirroring)
1258 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1259 ),
1260 233 => Box::new(
1261 Multicart233::new(prg_rom, &chr_rom, h.mirroring)
1262 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1263 ),
1264 242 => Box::new(
1265 Waixing242::new(prg_rom, &chr_rom, h.mirroring)
1266 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1267 ),
1268 246 => Box::new(
1269 FongShenBang246::new(prg_rom, chr_rom, h.mirroring)
1270 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1271 ),
1272 40 => Box::new(
1277 Ntdec2722M40::new(prg_rom, &chr_rom, h.mirroring)
1278 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1279 ),
1280 81 => Box::new(
1281 Ntdec81::new(prg_rom, chr_rom, h.mirroring)
1282 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1283 ),
1284 95 => Box::new(
1285 Namcot3425M95::new(prg_rom, chr_rom, h.mirroring)
1286 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1287 ),
1288 112 => Box::new(
1289 NtdecAsder112::new(prg_rom, chr_rom, h.mirroring)
1290 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1291 ),
1292 137 => Box::new(
1293 Sachen8259M137::new(prg_rom, chr_rom, h.mirroring)
1294 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1295 ),
1296 156 => Box::new(
1297 Daou156::new(prg_rom, chr_rom, h.mirroring)
1298 .map_err(|e| RomError::InvalidConfig(e.to_string()))?
1299 .with_battery(h.has_battery),
1300 ),
1301 162 => Box::new(
1302 WaixingFs304M162::new(prg_rom, &chr_rom, h.mirroring)
1303 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1304 ),
1305 178 => Box::new(
1306 Waixing178::new(prg_rom, &chr_rom, h.mirroring)
1307 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1308 ),
1309 244 => Box::new(
1310 Decathlon244::new(prg_rom, chr_rom, h.mirroring)
1311 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1312 ),
1313 250 => Box::new(
1314 Nitra250::new(prg_rom, chr_rom, h.mirroring)
1315 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1316 ),
1317 90 => Box::new(
1326 JyAsic::new(prg_rom, chr_rom, h.mirroring, JyBoard::M90)
1327 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1328 ),
1329 209 => Box::new(
1330 JyAsic::new(prg_rom, chr_rom, h.mirroring, JyBoard::M209)
1331 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1332 ),
1333 211 => Box::new(
1334 JyAsic::new(prg_rom, chr_rom, h.mirroring, JyBoard::M211)
1335 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1336 ),
1337 35 => Box::new(
1340 JyAsic::new(prg_rom, chr_rom, h.mirroring, JyBoard::M35)
1341 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1342 ),
1343 12 if h.submapper == 0 => mmc3_board(Mmc3BoardKind::M12, prg_rom, chr_rom, &h)?,
1351 83 => Box::new(
1352 Cony83::new(prg_rom, chr_rom, h.is_nes2.then_some(h.submapper))
1353 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1354 ),
1355 91 => Box::new(
1356 Jy91::new(prg_rom, chr_rom, h.mirroring, h.submapper)
1357 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1358 ),
1359 105 => Box::new(
1360 NesEvent105::new(prg_rom, h.mirroring)
1361 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1362 ),
1363 163 => Box::new(
1364 Nanjing163::new(prg_rom, h.mirroring)
1365 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1366 ),
1367 228 => Box::new(
1368 Action52M228::new(prg_rom, chr_rom)
1369 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1370 ),
1371 37 => mmc3_board(Mmc3BoardKind::M37, prg_rom, chr_rom, &h)?,
1372 45 => mmc3_board(Mmc3BoardKind::M45, prg_rom, chr_rom, &h)?,
1373 47 => mmc3_board(Mmc3BoardKind::M47, prg_rom, chr_rom, &h)?,
1374 74 => mmc3_board(Mmc3BoardKind::M74, prg_rom, chr_rom, &h)?,
1375 121 => mmc3_board(Mmc3BoardKind::M121, prg_rom, chr_rom, &h)?,
1376 191 => mmc3_board(Mmc3BoardKind::M191, prg_rom, chr_rom, &h)?,
1377 192 => mmc3_board(Mmc3BoardKind::M192, prg_rom, chr_rom, &h)?,
1378 194 => mmc3_board(Mmc3BoardKind::M194, prg_rom, chr_rom, &h)?,
1379 195 => mmc3_board(Mmc3BoardKind::M195, prg_rom, chr_rom, &h)?,
1380 44 => Box::new(
1381 new_m44(prg_rom, chr_rom, h.mirroring)
1382 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1383 ),
1384 49 => Box::new(
1385 new_m49(prg_rom, chr_rom, h.mirroring)
1386 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1387 ),
1388 52 => Box::new(
1389 new_m52(prg_rom, chr_rom, h.mirroring)
1390 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1391 ),
1392 115 => Box::new(
1393 new_m115(prg_rom, chr_rom, h.mirroring)
1394 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1395 ),
1396 134 => Box::new(
1397 new_m134(prg_rom, chr_rom, h.mirroring)
1398 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1399 ),
1400 189 => Box::new(
1401 new_m189(prg_rom, chr_rom, h.mirroring)
1402 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1403 ),
1404 205 => Box::new(
1405 new_m205(prg_rom, chr_rom, h.mirroring)
1406 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1407 ),
1408 238 => Box::new(
1409 new_m238(prg_rom, chr_rom, h.mirroring)
1410 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1411 ),
1412 245 => Box::new(
1413 new_m245(prg_rom, chr_rom, h.mirroring)
1414 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1415 ),
1416 348 => Box::new(
1417 new_m348(prg_rom, chr_rom, h.mirroring)
1418 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1419 ),
1420 366 => Box::new(
1421 new_m366(prg_rom, chr_rom, h.mirroring)
1422 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1423 ),
1424 141 => Box::new(
1426 Sachen8259::new(Sachen8259Variant::A, prg_rom, chr_rom, h.mirroring)
1427 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1428 ),
1429 138 => Box::new(
1430 Sachen8259::new(Sachen8259Variant::B, prg_rom, chr_rom, h.mirroring)
1431 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1432 ),
1433 139 => Box::new(
1434 Sachen8259::new(Sachen8259Variant::C, prg_rom, chr_rom, h.mirroring)
1435 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1436 ),
1437 42 => Box::new(
1440 Mapper42::new(prg_rom, chr_rom, h.mirroring)
1441 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1442 ),
1443 50 => Box::new(
1444 Mapper50::new(prg_rom, &chr_rom, h.mirroring)
1445 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1446 ),
1447 46 => Box::new(
1448 new_m46(prg_rom, chr_rom, h.mirroring)
1449 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1450 ),
1451 51 => Box::new(
1452 new_m51(prg_rom, chr_rom, h.mirroring)
1453 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1454 ),
1455 57 => Box::new(
1456 new_m57(prg_rom, chr_rom, h.mirroring)
1457 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1458 ),
1459 104 => Box::new(
1460 new_m104(prg_rom, chr_rom, h.mirroring)
1461 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1462 ),
1463 120 => Box::new(
1464 new_m120(prg_rom, chr_rom, h.mirroring)
1465 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1466 ),
1467 290 => Box::new(
1468 new_m290(prg_rom, chr_rom, h.mirroring)
1469 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1470 ),
1471 301 => Box::new(
1472 new_m301(prg_rom, chr_rom, h.mirroring)
1473 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1474 ),
1475 176 => Box::new(
1480 new_m176(prg_rom, chr_rom, h.mirroring, h.submapper)
1481 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1482 ),
1483 268 => Box::new(
1484 new_m268(prg_rom, chr_rom, h.mirroring)
1485 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1486 ),
1487 513 => Box::new(
1488 new_m513(prg_rom, chr_rom, h.mirroring)
1489 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1490 ),
1491 136 => Box::new(
1492 new_m136(prg_rom, chr_rom, h.mirroring)
1493 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1494 ),
1495 164 => Box::new(
1496 new_m164(prg_rom, chr_rom, h.mirroring)
1497 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1498 ),
1499 253 => Box::new(
1500 new_m253(prg_rom, chr_rom, h.mirroring)
1501 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1502 ),
1503 286 => Box::new(
1504 new_m286(prg_rom, chr_rom, h.mirroring)
1505 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1506 ),
1507 56 => Box::new(
1508 new_m56(prg_rom, chr_rom, h.mirroring)
1509 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1510 ),
1511 142 => Box::new(
1512 new_m142(prg_rom, chr_rom, h.mirroring)
1513 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1514 ),
1515 303 => Box::new(
1516 new_m303(prg_rom, chr_rom, h.mirroring)
1517 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1518 ),
1519 305 => Box::new(
1520 new_m305(prg_rom, chr_rom, h.mirroring)
1521 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1522 ),
1523 306 => Box::new(
1524 new_m306(prg_rom, chr_rom, h.mirroring)
1525 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1526 ),
1527 312 => Box::new(
1528 new_m312(prg_rom, chr_rom, h.mirroring)
1529 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1530 ),
1531 261 => Box::new(
1532 new_m261(prg_rom, chr_rom, h.mirroring)
1533 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1534 ),
1535 289 => Box::new(
1536 new_m289(prg_rom, chr_rom, h.mirroring)
1537 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1538 ),
1539 320 => Box::new(
1540 new_m320(prg_rom, chr_rom, h.mirroring)
1541 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1542 ),
1543 336 => Box::new(
1544 new_m336(prg_rom, chr_rom, h.mirroring)
1545 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1546 ),
1547 349 => Box::new(
1548 new_m349(prg_rom, chr_rom, h.mirroring)
1549 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1550 ),
1551 193 => Box::new(
1555 new_m193(prg_rom, chr_rom, h.mirroring)
1556 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1557 ),
1558 204 => Box::new(
1559 new_m204(prg_rom, chr_rom, h.mirroring)
1560 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1561 ),
1562 221 => Box::new(
1563 new_m221(prg_rom, chr_rom, h.mirroring)
1564 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1565 ),
1566 299 => Box::new(
1567 new_m299(prg_rom, chr_rom, h.mirroring)
1568 .map_err(|e| RomError::InvalidConfig(e.to_string()))?,
1569 ),
1570 other => return Err(RomError::UnsupportedMapper(other)),
1571 };
1572
1573 let mut cart = cart;
1579 if matches!(h.mapper_id, 30 | 111) && !mapper.save_data().is_empty() {
1580 cart.has_battery = true;
1581 }
1582
1583 Ok((cart, mapper))
1584}
1585
1586#[cfg(test)]
1587#[allow(clippy::cast_possible_truncation)]
1588mod tests {
1589 use super::*;
1590 use alloc::{vec, vec::Vec};
1591
1592 fn synth_nrom_rom(prg_kib: usize, chr_kib: usize) -> Vec<u8> {
1593 let mut bytes = Vec::with_capacity(16 + prg_kib * 1024 + chr_kib * 1024);
1594 bytes.extend_from_slice(&header::MAGIC);
1595 bytes.push((prg_kib / 16) as u8); bytes.push((chr_kib / 8) as u8); bytes.push(0x00); bytes.push(0x00); bytes.extend_from_slice(&[0u8; 8]); for i in 0..(prg_kib * 1024) {
1603 bytes.push((i & 0xFF) as u8);
1604 }
1605 for i in 0..(chr_kib * 1024) {
1607 bytes.push(!(i as u8));
1608 }
1609 bytes
1610 }
1611
1612 #[test]
1613 fn version_is_non_empty() {
1614 assert_ne!(version(), "");
1615 }
1616
1617 #[test]
1618 fn parse_synthetic_nrom_16k() {
1619 let rom = synth_nrom_rom(16, 8);
1620 let (cart, mut mapper) = parse(&rom).unwrap();
1621 assert_eq!(cart.mapper_id, 0);
1622 assert_eq!(cart.prg_rom.len(), 16 * 1024);
1623 assert_eq!(cart.chr_rom.len(), 8 * 1024);
1624 assert_eq!(cart.mirroring, Mirroring::Horizontal);
1625 assert_eq!(mapper.cpu_read(0x8000), mapper.cpu_read(0xC000));
1627 }
1628
1629 #[test]
1630 fn parse_synthetic_nrom_32k() {
1631 let rom = synth_nrom_rom(32, 8);
1632 let (cart, _mapper) = parse(&rom).unwrap();
1633 assert_eq!(cart.prg_rom.len(), 32 * 1024);
1634 }
1635
1636 fn synth_m78(flags6_low: u8, nes2_sub: Option<u8>) -> Vec<u8> {
1640 let mut rom = synth_nrom_rom(32, 8);
1641 rom[6] = 0xE0 | (flags6_low & 0x0F); rom[7] = 0x40; if let Some(sub) = nes2_sub {
1644 rom[7] |= 0x08;
1645 rom[8] = sub << 4;
1646 }
1647 rom
1648 }
1649
1650 fn m78_mirroring_with_bit3(rom: &[u8]) -> Mirroring {
1654 let (_cart, mut mapper) = parse(rom).unwrap();
1655 mapper.cpu_write(0x8000, 0x08);
1656 mapper.current_mirroring()
1657 }
1658
1659 #[test]
1660 fn mapper_78_ines_header_selects_wiring_by_alt_nametables_bit() {
1661 assert_eq!(
1667 m78_mirroring_with_bit3(&synth_m78(0x00, None)),
1668 Mirroring::SingleScreenB,
1669 "iNES 1.0, flag clear: Cosmo Carrier wiring"
1670 );
1671 assert_eq!(
1672 m78_mirroring_with_bit3(&synth_m78(0x08, None)),
1673 Mirroring::Vertical,
1674 "iNES 1.0, flag set: Holy Diver wiring"
1675 );
1676 assert_eq!(
1678 m78_mirroring_with_bit3(&synth_m78(0x00, Some(0))),
1679 Mirroring::SingleScreenB,
1680 "NES 2.0 submapper 0, flag clear: Cosmo Carrier wiring"
1681 );
1682 assert_eq!(
1684 m78_mirroring_with_bit3(&synth_m78(0x08, Some(1))),
1685 Mirroring::SingleScreenB,
1686 "submapper 1: Cosmo Carrier"
1687 );
1688 assert_eq!(
1689 m78_mirroring_with_bit3(&synth_m78(0x00, Some(3))),
1690 Mirroring::Vertical,
1691 "submapper 3: Holy Diver"
1692 );
1693 }
1694
1695 #[test]
1696 fn parse_truncated_returns_typed_error() {
1697 let mut rom = synth_nrom_rom(32, 8);
1698 rom.truncate(16 + 100);
1699 let err = parse(&rom).err().expect("must error");
1700 assert!(
1701 matches!(err, RomError::Truncated { .. }),
1702 "expected Truncated, got {err:?}"
1703 );
1704 }
1705
1706 #[test]
1707 fn parse_unsupported_mapper_errors() {
1708 let mut rom = synth_nrom_rom(32, 8);
1709 rom[6] = 0x80;
1713 rom[7] = 0xF0;
1714 let err = parse(&rom).err().expect("must error");
1715 assert!(
1716 matches!(err, RomError::UnsupportedMapper(_)),
1717 "expected UnsupportedMapper, got {err:?}"
1718 );
1719 }
1720
1721 #[test]
1722 fn parse_fds_image_reports_fds_unsupported() {
1723 let mut fwnes = alloc::vec![0u8; 16 + 65500];
1725 fwnes[0..4].copy_from_slice(b"FDS\x1A");
1726 fwnes[4] = 1; assert!(
1728 matches!(parse(&fwnes).err(), Some(RomError::FdsUnsupported)),
1729 "fwNES FDS header must report FdsUnsupported"
1730 );
1731 let mut raw = alloc::vec![0u8; 65500];
1733 raw[0] = 0x01;
1734 raw[1..15].copy_from_slice(b"*NINTENDO-HVC*");
1735 assert!(
1736 matches!(parse(&raw).err(), Some(RomError::FdsUnsupported)),
1737 "raw FDS disk side must report FdsUnsupported"
1738 );
1739 }
1740
1741 #[test]
1742 fn parse_mapper_5_dispatches_to_mmc5() {
1743 let mut rom = synth_nrom_rom(32, 8);
1746 rom[6] = 0x50;
1747 let (cart, mut mapper) = parse(&rom).expect("MMC5 ROM must parse");
1748 assert_eq!(cart.mapper_id, 5);
1749 let _ = mapper.cpu_read(0xE000);
1752 }
1753
1754 #[test]
1755 fn parse_random_bytes_does_not_panic() {
1756 let seeds: [u64; 8] = [1, 17, 99, 12345, 0xDEAD_BEEF, 0xCAFE, 0x55AA, 0xFF];
1758 for &s in &seeds {
1759 let mut state = s;
1760 let mut bytes = Vec::with_capacity(64);
1761 for _ in 0..64 {
1762 state = state
1763 .wrapping_mul(6_364_136_223_846_793_005)
1764 .wrapping_add(1);
1765 bytes.push((state >> 32) as u8);
1766 }
1767 let _ = parse(&bytes);
1769 }
1770 }
1771
1772 #[test]
1773 fn parse_with_trainer_skips_trainer_bytes() {
1774 let mut rom = Vec::new();
1775 rom.extend_from_slice(&header::MAGIC);
1776 rom.push(2); rom.push(1); rom.push(0b0000_0100); rom.push(0);
1780 rom.extend_from_slice(&[0u8; 8]);
1781 rom.extend_from_slice(&[0xAB; 512]); rom.extend_from_slice(&vec![0u8; 32 * 1024]); rom.extend_from_slice(&vec![0u8; 8 * 1024]); let (cart, _) = parse(&rom).unwrap();
1785 assert!(cart.has_trainer);
1786 assert_eq!(cart.prg_rom.len(), 32 * 1024);
1787 }
1788}
1789
1790#[cfg(test)]
1821mod expansion_level_tripwire {
1822 const RE_BLESS: &str = "expansion-audio level constant changed -- re-bless \
1823 `audio_expansion` AND the gitignored `external_real_games` \
1824 (--features commercial-roms) in this same change; see this module's docs";
1825
1826 #[test]
1827 fn expansion_audio_levels_are_pinned() {
1828 assert_eq!(crate::m024_vrc6::VRC6_MIX_SCALE, 650, "VRC6: {RE_BLESS}");
1829 assert_eq!(
1830 crate::m005_mmc5::MMC5_PULSE_SCALE,
1831 650,
1832 "MMC5 pulse: {RE_BLESS}"
1833 );
1834 assert_eq!(crate::m005_mmc5::MMC5_PCM_SCALE, 40, "MMC5 PCM: {RE_BLESS}");
1835 assert_eq!(
1836 crate::m019_namco163::NAMCO163_MIX_SCALE,
1837 261,
1838 "N163: {RE_BLESS}"
1839 );
1840 assert_eq!(
1841 crate::m069_sunsoft_fme7::SUNSOFT5B_MIX_SCALE_NUM,
1842 2549,
1843 "5B numerator: {RE_BLESS}"
1844 );
1845 assert_eq!(
1846 crate::m069_sunsoft_fme7::SUNSOFT5B_MIX_SCALE_DEN,
1847 138,
1848 "5B denominator: {RE_BLESS}"
1849 );
1850 }
1851
1852 #[test]
1856 fn mmc5_mix_bias_stays_the_midpoint_of_its_two_scales() {
1857 assert_eq!(
1858 crate::m005_mmc5::MMC5_MIX_BIAS,
1859 i16::midpoint(
1860 30 * crate::m005_mmc5::MMC5_PULSE_SCALE,
1861 127 * crate::m005_mmc5::MMC5_PCM_SCALE
1862 ),
1863 "MMC5 bias formula: {RE_BLESS}"
1864 );
1865 }
1866}