1#![allow(
31 clippy::cast_possible_truncation,
32 clippy::cast_lossless,
33 clippy::missing_const_for_fn,
34 clippy::doc_markdown,
35 clippy::match_same_arms
36)]
37
38use crate::cartridge::Mirroring;
39use crate::m004_mmc3::{Mmc3, Mmc3Revision};
40use crate::mapper::{Mapper, MapperCaps, MapperDebugInfo, MapperError};
41use alloc::string::ToString;
42use alloc::{boxed::Box, format, vec, vec::Vec};
43
44const PRG_BANK_8K: usize = 0x2000;
45const CHR_BANK_1K: usize = 0x0400;
46const WRAM_8K: usize = 0x2000;
47
48const SAVE_STATE_VERSION: u8 = 1;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum Board {
54 M12,
58 M37,
61 M45,
64 M47,
67 M74,
70 M121,
73 M191,
76 M192,
79 M194,
82 M195,
85 M4T9552,
89 M249,
92}
93
94impl Board {
95 const fn id(self) -> u16 {
96 match self {
97 Self::M12 => 12,
98 Self::M37 => 37,
99 Self::M45 => 45,
100 Self::M47 => 47,
101 Self::M74 => 74,
102 Self::M121 => 121,
103 Self::M191 => 191,
104 Self::M192 => 192,
105 Self::M194 => 194,
106 Self::M195 => 195,
107 Self::M4T9552 => 4,
108 Self::M249 => 249,
109 }
110 }
111
112 const fn name(self) -> &'static str {
113 match self {
114 Self::M12 => "SL-5020B (12)",
115 Self::M37 => "SMB+Tetris+NWC (37)",
116 Self::M45 => "GA23C (45)",
117 Self::M47 => "Spike V'Ball+NWC (47)",
118 Self::M74 => "Waixing 43-393 (74)",
119 Self::M121 => "Kasheng A9711/A9713 (121)",
120 Self::M191 => "Waixing MMC3 CHR-RAM (191)",
121 Self::M192 => "Waixing FS308 (192)",
122 Self::M194 => "Waixing MMC3 CHR-RAM (194)",
123 Self::M195 => "Waixing FS303 (195)",
124 Self::M4T9552 => "MMC3 + T9552 (4.5)",
125 Self::M249 => "Waixing T9552 (249)",
126 }
127 }
128
129 const fn overlay_bytes(self) -> usize {
131 match self {
132 Self::M74 | Self::M191 | Self::M194 => 2 * CHR_BANK_1K,
133 Self::M192 => 4 * CHR_BANK_1K,
134 Self::M195 => 8 * CHR_BANK_1K,
136 _ => 0,
137 }
138 }
139
140 const fn default_wram(self) -> usize {
143 match self {
144 Self::M74
145 | Self::M191
146 | Self::M192
147 | Self::M194
148 | Self::M195
149 | Self::M4T9552
150 | Self::M249 => WRAM_8K,
151 _ => 0,
152 }
153 }
154}
155
156const T9552_PRG: [[u8; 4]; 4] = [
160 [16, 17, 15, 14],
161 [17, 16, 14, 15],
162 [14, 15, 16, 17],
163 [15, 14, 17, 16],
164];
165
166const T9552_CHR: [[u8; 6]; 8] = [
168 [15, 12, 16, 17, 14, 13],
169 [14, 15, 13, 12, 17, 16],
170 [12, 13, 14, 15, 16, 17],
171 [16, 14, 12, 13, 17, 15],
172 [15, 13, 17, 16, 12, 14],
173 [14, 12, 15, 16, 17, 13],
174 [13, 16, 14, 15, 12, 17],
175 [12, 15, 16, 17, 13, 14],
176];
177
178fn t9552_scramble<const N: usize>(
183 bank: usize,
184 lsb_line: u8,
185 table: &[[u8; N]],
186 current: usize,
187 file: usize,
188) -> usize {
189 let mut out = bank;
193 for &line in &table[2] {
194 out &= !(1 << (line - lsb_line));
195 }
196 for &line in &table[2] {
197 if bank & (1 << (line - lsb_line)) != 0 {
198 let row = table[current].iter().position(|&l| l == line).unwrap_or(0);
199 out |= 1 << (table[file][row] - lsb_line);
200 }
201 }
202 out
203}
204
205fn mirror_bank(bank: usize, count: usize) -> usize {
223 if count == 0 {
224 return 0;
225 }
226 let mut bank = bank & (count.next_power_of_two() - 1);
227 while bank >= count {
228 let mut size = count;
232 while size + size.isolate_lowest_one() <= bank {
233 size += size.isolate_lowest_one();
234 }
235 bank -= size.isolate_lowest_one();
236 }
237 bank
238}
239
240enum Chr {
242 Rom(usize),
243 Ram(usize),
244}
245
246const M195_POWER_ON_MODE: u8 = 0x80;
248
249const M45_RESET_REGS: [u8; 4] = [0x00, 0x00, 0x0F, 0x00];
261
262const M121_PROTECTION: [u8; 4] = [0x83, 0x83, 0x42, 0x00];
264
265pub struct Mmc3Board {
268 board: Board,
269 core: Mmc3,
270 prg_rom: Box<[u8]>,
271 chr: Box<[u8]>,
273 chr_is_ram: bool,
274 chr_ram: Box<[u8]>,
276 wram: Box<[u8]>,
278 regs: [u8; 4],
286 index: u8,
289 locked: bool,
291 prg_override: [Option<u8>; 3],
293 sticky: Option<u8>,
295 dip: u8,
297}
298
299impl Mmc3Board {
300 pub fn new(
312 board: Board,
313 prg_rom: Box<[u8]>,
314 chr_rom: Box<[u8]>,
315 mirroring: Mirroring,
316 wram_bytes: usize,
317 chr_ram_bytes: usize,
318 ) -> Result<Self, MapperError> {
319 let id = board.id();
320 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
321 return Err(MapperError::Invalid(format!(
322 "mapper {id} PRG-ROM size {} is not a non-zero multiple of 8 KiB",
323 prg_rom.len()
324 )));
325 }
326 if !chr_rom.len().is_multiple_of(CHR_BANK_1K) {
327 return Err(MapperError::Invalid(format!(
328 "mapper {id} CHR-ROM size {} is not a multiple of 1 KiB",
329 chr_rom.len()
330 )));
331 }
332 let chr_is_ram = chr_rom.is_empty();
333 let chr = if chr_is_ram {
334 vec![0u8; 8 * CHR_BANK_1K].into_boxed_slice()
335 } else {
336 chr_rom
337 };
338 let overlay = if board.overlay_bytes() == 0 {
339 0
340 } else if chr_ram_bytes >= CHR_BANK_1K {
341 chr_ram_bytes
342 } else {
343 board.overlay_bytes()
344 };
345 let wram = if wram_bytes > 0 {
346 wram_bytes
347 } else {
348 board.default_wram()
349 };
350 let wram = if board == Board::M195 && wram_bytes == 0 {
352 0
353 } else if board == Board::M195 {
354 0x1000
355 } else {
356 wram
357 };
358 let revision = if board == Board::M12 {
362 Mmc3Revision::Nec
363 } else {
364 Mmc3Revision::Sharp
365 };
366 let mut this = Self {
367 board,
368 core: Mmc3::register_core(mirroring, revision),
369 prg_rom,
370 chr,
371 chr_is_ram,
372 chr_ram: vec![0u8; overlay].into_boxed_slice(),
373 wram: vec![0u8; wram].into_boxed_slice(),
374 regs: if board == Board::M45 {
375 M45_RESET_REGS
376 } else {
377 [0; 4]
378 },
379 index: 0,
380 locked: false,
381 prg_override: [None; 3],
382 sticky: None,
383 dip: 0,
384 };
385 if board == Board::M195 {
386 this.regs[0] = M195_POWER_ON_MODE;
387 }
388 Ok(this)
389 }
390
391 pub fn set_dip(&mut self, dip: u8) {
394 self.dip = dip & 0x07;
395 }
396
397 fn t9552_file_column(&self) -> usize {
399 if self.board == Board::M249 { 0 } else { 2 }
400 }
401
402 fn is_a9713(&self) -> bool {
404 self.prg_rom.len() > 256 * 1024
405 }
406
407 fn prg_bank(&self, addr: u16) -> usize {
409 let raw = self.core.prg_bank_raw(addr) as usize;
410 let r = |i: usize| self.regs[i] as usize;
411 match self.board {
412 Board::M12 | Board::M74 | Board::M191 | Board::M192 | Board::M194 | Board::M195 => raw,
413 Board::M4T9552 | Board::M249 => t9552_scramble(
414 raw,
415 13,
416 &T9552_PRG,
417 usize::from(self.regs[0] & 0x03),
418 self.t9552_file_column(),
419 ),
420 Board::M37 => {
421 let q = r(0);
424 let (q0, q1, q2) = (q & 1, (q >> 1) & 1, (q >> 2) & 1);
425 let m16 = (raw >> 3) & 1;
426 let a16 = (q0 & q1) | (q2 & m16);
427 (raw & 0x07) | (a16 << 3) | (q2 << 4)
428 }
429 Board::M45 => {
430 let and = !r(3) & 0x3F;
434 ((raw & and) | (r(1) & 0x3F)) | (r(1) & 0xC0) | ((r(2) & 0xC0) << 2)
435 }
436 Board::M47 => (raw & 0x0F) | ((r(0) & 1) << 4),
437 Board::M121 => {
438 let slot = match addr & 0xE000 {
439 0xA000 => Some(0),
440 0xC000 => Some(1),
441 0xE000 => Some(2),
442 _ => None,
443 };
444 let inner = slot
445 .and_then(|s| self.prg_override[s])
446 .map_or(raw & 0x1F, |b| b as usize & 0x1F);
447 if self.is_a9713() {
448 inner | ((r(0) & 1) << 5)
449 } else {
450 inner
451 }
452 }
453 }
454 }
455
456 fn chr_target(&self, addr: u16) -> Chr {
458 let addr = addr & 0x1FFF;
459 let raw = self.core.chr_bank_1k(addr);
460 let within = addr as usize & (CHR_BANK_1K - 1);
461 let r = |i: usize| self.regs[i] as usize;
462 let rom = |bank: usize| Chr::Rom(bank * CHR_BANK_1K + within);
463 let ram = |bank: usize| Chr::Ram(bank * CHR_BANK_1K + within);
464 match self.board {
465 Board::M12 => {
466 let a18 = if addr & 0x1000 == 0 {
467 r(0) & 1
468 } else {
469 (r(0) >> 4) & 1
470 };
471 rom((raw & 0xFF) | (a18 << 8))
472 }
473 Board::M37 => rom((raw & 0x7F) | (((r(0) >> 2) & 1) << 7)),
474 Board::M45 if self.chr_is_ram => Chr::Rom(usize::from(addr & 0x1FFF)),
479 Board::M45 => {
480 let c = r(2) & 0x0F;
481 let mask = if c >= 7 { 0xFF >> (15 - c) } else { 0 };
482 rom((raw & mask) | r(0) | ((r(2) & 0xF0) << 4))
483 }
484 Board::M47 => rom((raw & 0x7F) | ((r(0) & 1) << 7)),
485 Board::M121 => {
486 if self.is_a9713() {
487 rom((raw & 0xFF) | ((r(0) & 1) << 8))
488 } else {
489 let a12 = usize::from(addr & 0x1000 != 0);
491 let a18 = if r(3) & 0x80 != 0 { a12 } else { a12 ^ 1 };
492 rom((raw & 0xFF) | (a18 << 8))
493 }
494 }
495 Board::M4T9552 | Board::M249 => rom(t9552_scramble(
496 raw,
497 10,
498 &T9552_CHR,
499 usize::from(self.regs[0] & 0x07),
500 self.t9552_file_column(),
501 )),
502 Board::M74 if matches!(raw, 8 | 9) => ram(raw & 1),
503 Board::M191 if raw & 0x80 != 0 => ram(raw & 1),
504 Board::M192 if matches!(raw, 8..=11) => ram(raw & 3),
505 Board::M194 if raw <= 1 => ram(raw & 1),
506 Board::M195 if m195_ram_banks(self.regs[0]).is_some_and(|b| b.contains(&raw)) => {
507 ram(raw & 7)
509 }
510 _ => rom(raw),
511 }
512 }
513
514 fn chr_rom_offset(&self, off: usize) -> usize {
517 mirror_bank(off / CHR_BANK_1K, self.chr.len() / CHR_BANK_1K) * CHR_BANK_1K
518 + (off & (CHR_BANK_1K - 1))
519 }
520
521 fn read_chr(&self, addr: u16) -> u8 {
522 match self.chr_target(addr) {
523 Chr::Rom(off) => self.chr[self.chr_rom_offset(off)],
524 Chr::Ram(off) => self.chr_ram[off % self.chr_ram.len()],
525 }
526 }
527
528 fn m121_apply(&mut self) {
531 let v = reverse_low_six(self.regs[1]);
532 match self.regs[2] & 0x3F {
533 0x26 => {
534 self.prg_override[2] = Some(v);
535 self.sticky = Some(2);
536 }
537 0x28 => {
538 self.prg_override[1] = Some(v);
539 self.sticky = Some(1);
540 }
541 0x2A => {
542 self.prg_override[0] = Some(v);
543 self.sticky = Some(0);
544 }
545 0x2C => {
546 if v != 0 {
547 self.prg_override[2] = Some(v);
548 }
549 self.sticky = None;
550 }
551 0x2F => self.sticky = None,
552 0x20 | 0x29 | 0x2B | 0x3C | 0x3F => {
553 self.prg_override[2] = Some(v);
554 self.sticky = None;
555 }
556 _ => {
557 self.prg_override = [None; 3];
558 self.sticky = None;
559 }
560 }
561 }
562
563 fn write_high(&mut self, addr: u16, value: u8) {
564 if self.board == Board::M121 {
565 match addr & 0xE003 {
566 0x8000 | 0x8002 => {
567 if !self.is_a9713() {
568 self.regs[3] = value;
569 }
570 self.core.cpu_write(addr, value);
571 }
572 0x8001 => {
573 self.regs[1] = value;
574 if let Some(slot) = self.sticky {
575 self.prg_override[slot as usize] = Some(reverse_low_six(value));
576 }
577 self.core.cpu_write(addr, value);
578 }
579 0x8003 => {
580 self.regs[2] = value;
581 self.m121_apply();
582 self.core.cpu_write(0x8000, value);
584 }
585 _ => self.core.cpu_write(addr, value),
586 }
587 return;
588 }
589 self.core.cpu_write(addr, value);
590 }
591}
592
593fn m195_ram_banks(mode: u8) -> Option<core::ops::RangeInclusive<usize>> {
596 if mode & 0x10 != 0 {
597 return None;
598 }
599 match mode & 0xCA {
600 0x80 => Some(0x28..=0x2B),
601 0x82 => Some(0x00..=0x03),
602 0x88 => Some(0x4C..=0x4F),
603 0x8A => Some(0x64..=0x67),
604 0xC0 => Some(0x46..=0x47),
605 0xC2 => Some(0x7C..=0x7D),
606 0xC8 => Some(0x0A..=0x0B),
607 _ => None,
608 }
609}
610
611const fn reverse_low_six(v: u8) -> u8 {
613 (v & 0xC0) | ((v & 0x3F).reverse_bits() >> 2)
614}
615
616impl Mapper for Mmc3Board {
617 fn sram(&self) -> &[u8] {
618 &self.wram
619 }
620
621 fn sram_mut(&mut self) -> &mut [u8] {
622 &mut self.wram
623 }
624
625 fn caps(&self) -> MapperCaps {
626 MapperCaps::CYCLE_IRQ
627 }
628
629 fn reset(&mut self) {
630 match self.board {
631 Board::M37 => self.regs[0] = 0,
633 Board::M45 => {
635 self.regs = M45_RESET_REGS;
636 self.index = 0;
637 self.locked = false;
638 }
639 _ => {}
640 }
641 }
642
643 fn cpu_read_unmapped(&self, addr: u16) -> bool {
644 match (self.board, addr) {
645 (Board::M12, 0x4020..=0x5FFF) => addr & 0xE100 != 0x4100,
646 (Board::M45 | Board::M121, 0x5000..=0x5FFF) => false,
647 (Board::M195, 0x5000..=0x5FFF) => self.wram.is_empty(),
648 (Board::M195, 0x6000..=0x7FFF) => true,
649 (_, 0x4020..=0x5FFF) => true,
650 (_, 0x6000..=0x7FFF) => self.wram.is_empty() || !self.core.prg_ram_enabled(),
651 _ => false,
652 }
653 }
654
655 fn cpu_read_driven_mask(&self, addr: u16) -> u8 {
656 match (self.board, addr) {
657 (Board::M12, _) => 0x01,
659 (Board::M45, 0x5000..=0x5FFF) => 0x01,
660 _ => 0xFF,
661 }
662 }
663
664 fn cpu_read(&mut self, addr: u16) -> u8 {
665 match addr {
666 0x8000..=0xFFFF => {
667 let bank = mirror_bank(self.prg_bank(addr), self.prg_rom.len() / PRG_BANK_8K);
668 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
669 }
670 0x4020..=0x5FFF if self.board == Board::M12 => 0,
674 0x5000..=0x5FFF if self.board == Board::M45 => {
675 u8::from((addr >> (4 + self.dip)) & 1 != 0)
677 }
678 0x5000..=0x5FFF if self.board == Board::M121 => {
679 M121_PROTECTION[usize::from(self.index & 3)]
680 }
681 0x5000..=0x5FFF if self.board == Board::M195 && !self.wram.is_empty() => {
682 self.wram[usize::from(addr & 0x0FFF) % self.wram.len()]
683 }
684 0x6000..=0x7FFF
685 if self.board != Board::M195
686 && !self.wram.is_empty()
687 && self.core.prg_ram_enabled() =>
688 {
689 self.wram[usize::from(addr - 0x6000) % self.wram.len()]
690 }
691 _ => 0,
692 }
693 }
694
695 fn cpu_write(&mut self, addr: u16, value: u8) {
696 match (self.board, addr) {
697 (_, 0x8000..=0xFFFF) => self.write_high(addr, value),
698 (Board::M12, 0x4020..=0x5FFF) if addr & 0xE100 == 0x4100 => self.regs[0] = value,
699 (Board::M37, 0x6000..=0x7FFF) if self.core.prg_ram_writable() => {
700 self.regs[0] = value & 0x07;
701 }
702 (Board::M47, 0x6000..=0x7FFF) if self.core.prg_ram_writable() => {
703 self.regs[0] = value & 0x01;
704 }
705 (Board::M45, 0x6000..=0x7FFF) => {
706 match addr & 0xF001 {
709 0x6000 if !self.locked => {
710 self.regs[usize::from(self.index)] = value;
711 if self.index == 3 && value & 0x40 != 0 {
712 self.locked = true;
713 }
714 self.index = (self.index + 1) & 3;
715 }
716 0x6001 => {
717 self.regs = M45_RESET_REGS;
718 self.index = 0;
719 self.locked = false;
720 }
721 _ => {}
722 }
723 if !self.wram.is_empty() && self.core.prg_ram_writable() {
724 let len = self.wram.len();
725 self.wram[usize::from(addr - 0x6000) % len] = value;
726 }
727 }
728 (Board::M4T9552 | Board::M249, 0x5000..=0x5FFF) => self.regs[0] = value,
729 (Board::M121, 0x5000..=0x5FFF) => {
730 self.index = value & 0x03;
731 if addr & 0xF180 == 0x5180 && self.is_a9713() {
732 self.regs[0] = value >> 7;
733 }
734 }
735 (Board::M195, 0x5000..=0x5FFF) if !self.wram.is_empty() => {
736 let len = self.wram.len();
737 self.wram[usize::from(addr & 0x0FFF) % len] = value;
738 }
739 (Board::M195, _) => {}
740 (_, 0x6000..=0x7FFF) if !self.wram.is_empty() && self.core.prg_ram_writable() => {
741 let len = self.wram.len();
742 self.wram[usize::from(addr - 0x6000) % len] = value;
743 }
744 _ => {}
745 }
746 }
747
748 fn chr_phys(&self, addr: u16) -> Option<u32> {
749 match self.chr_target(addr) {
750 Chr::Rom(off) if !self.chr_is_ram => u32::try_from(self.chr_rom_offset(off)).ok(),
751 _ => None,
752 }
753 }
754
755 fn ppu_read(&mut self, addr: u16) -> u8 {
756 let addr = addr & 0x3FFF;
757 if addr < 0x2000 {
758 self.read_chr(addr)
759 } else {
760 self.core.ppu_read(addr)
761 }
762 }
763
764 fn ppu_write(&mut self, addr: u16, value: u8) {
765 let addr = addr & 0x3FFF;
766 if addr >= 0x2000 {
767 self.core.ppu_write(addr, value);
768 return;
769 }
770 match self.chr_target(addr) {
771 Chr::Ram(off) => {
772 let len = self.chr_ram.len();
773 self.chr_ram[off % len] = value;
774 }
775 Chr::Rom(off) if self.chr_is_ram => {
776 let off = self.chr_rom_offset(off);
777 self.chr[off] = value;
778 }
779 Chr::Rom(_) => {
780 if self.board == Board::M195 {
784 let bank = self.core.chr_bank_1k(addr);
785 if bank & 0x80 != 0 {
786 self.regs[0] = bank as u8;
787 }
788 }
789 }
790 }
791 }
792
793 fn nametable_address(&self, addr: u16) -> u16 {
794 self.core.nametable_address(addr)
795 }
796
797 fn current_mirroring(&self) -> Mirroring {
798 self.core.current_mirroring()
799 }
800
801 fn notify_a12(&mut self, level: bool) {
802 self.core.notify_a12(level);
803 }
804
805 fn notify_a12_at_sub_dot(&mut self, level: bool, sub_dot: u8) {
806 self.core.notify_a12_at_sub_dot(level, sub_dot);
807 }
808
809 fn notify_cpu_cycle(&mut self) {
810 self.core.notify_cpu_cycle();
811 }
812
813 fn irq_pending(&self) -> bool {
814 self.core.irq_pending()
815 }
816
817 fn irq_acknowledge(&mut self) {
818 self.core.irq_acknowledge();
819 }
820
821 fn debug_info(&self) -> MapperDebugInfo {
822 let mut info = self.core.debug_info();
823 info.mapper_id = self.board.id();
824 info.name = self.board.name().to_string();
825 for (i, r) in self.regs.iter().enumerate() {
826 info.extra.push((format!("board{i}"), format!("{r:#04x}")));
827 }
828 if !self.chr_ram.is_empty() {
829 for slot in 0u16..8 {
830 let is_ram = matches!(self.chr_target(slot * 0x400), Chr::Ram(_));
831 info.chr_banks.push((
832 format!("slot{slot}"),
833 if is_ram { "RAM" } else { "ROM" }.to_string(),
834 ));
835 }
836 }
837 info
838 }
839
840 fn save_state(&self) -> Vec<u8> {
841 let core = self.core.save_state();
842 let chr = if self.chr_is_ram { self.chr.len() } else { 0 };
843 let mut out = Vec::with_capacity(16 + core.len() + chr + self.chr_ram.len());
844 out.push(SAVE_STATE_VERSION);
845 out.push(self.board.id() as u8);
846 out.extend_from_slice(&self.regs);
847 out.push(self.index);
848 out.push(u8::from(self.locked));
849 for o in self.prg_override {
850 out.push(u8::from(o.is_some()));
851 out.push(o.unwrap_or(0));
852 }
853 out.push(self.sticky.unwrap_or(0xFF));
854 out.push(self.dip);
855 out.extend_from_slice(&(core.len() as u32).to_le_bytes());
856 out.extend_from_slice(&core);
857 if self.chr_is_ram {
858 out.extend_from_slice(&self.chr);
859 }
860 out.extend_from_slice(&self.chr_ram);
861 out.extend_from_slice(&self.wram);
862 out
863 }
864
865 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
866 const HEAD: usize = 2 + 4 + 2 + 6 + 2 + 4;
867 if data.len() < HEAD {
868 return Err(MapperError::WrongLength {
869 expected: HEAD,
870 got: data.len(),
871 });
872 }
873 if data[0] != SAVE_STATE_VERSION {
874 return Err(MapperError::UnsupportedVersion(data[0]));
875 }
876 if u16::from(data[1]) != self.board.id() & 0xFF {
877 return Err(MapperError::Invalid(format!(
878 "state is for mapper {}, this board is mapper {}",
879 data[1],
880 self.board.id()
881 )));
882 }
883 let core_len = u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
884 let chr = if self.chr_is_ram { self.chr.len() } else { 0 };
885 let expected = HEAD
889 .checked_add(core_len)
890 .and_then(|n| n.checked_add(chr + self.chr_ram.len() + self.wram.len()));
891 if expected != Some(data.len()) {
892 return Err(MapperError::WrongLength {
893 expected: expected.unwrap_or(usize::MAX),
894 got: data.len(),
895 });
896 }
897 let sticky = match data[14] {
898 0xFF => None,
899 s @ 0..=2 => Some(s),
900 s => {
901 return Err(MapperError::Invalid(format!(
902 "override slot {s} out of range"
903 )));
904 }
905 };
906 let mut cur = HEAD;
907 self.core.load_state(&data[cur..cur + core_len])?;
908 cur += core_len;
909 self.regs.copy_from_slice(&data[2..6]);
910 self.index = data[6] & 3;
911 self.locked = data[7] != 0;
912 for (i, o) in self.prg_override.iter_mut().enumerate() {
913 *o = (data[8 + 2 * i] != 0).then_some(data[9 + 2 * i]);
914 }
915 self.sticky = sticky;
916 self.dip = data[15] & 7;
917 if self.chr_is_ram {
918 self.chr.copy_from_slice(&data[cur..cur + chr]);
919 cur += chr;
920 }
921 let n = self.chr_ram.len();
922 self.chr_ram.copy_from_slice(&data[cur..cur + n]);
923 cur += n;
924 self.wram.copy_from_slice(&data[cur..]);
925 Ok(())
926 }
927}
928
929#[cfg(test)]
930#[path = "mmc3_boards_tests.rs"]
931mod tests;