1#![allow(
27 clippy::bool_to_int_with_if,
28 clippy::cast_lossless,
29 clippy::cast_possible_truncation,
30 clippy::doc_markdown,
31 clippy::match_same_arms,
32 clippy::missing_const_for_fn,
33 clippy::similar_names,
34 clippy::struct_excessive_bools,
35 clippy::too_many_lines,
36 clippy::unreadable_literal
37)]
38
39use crate::cartridge::Mirroring;
40use crate::mapper::{Mapper, MapperCaps, MapperError};
41use alloc::{boxed::Box, vec::Vec};
42use alloc::{format, vec};
43
44const PRG_BANK_16K: usize = 0x4000;
45const PRG_BANK_32K: usize = 0x8000;
46const PRG_BANK_8K: usize = 0x2000;
47const CHR_BANK_8K: usize = 0x2000;
48const NAMETABLE_SIZE: usize = 0x0400;
49const NAMETABLE_SIZE_U16: u16 = 0x0400;
50
51const SAVE_STATE_VERSION: u8 = 1;
52
53const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
58 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
59 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
60 let physical = mirroring.physical_bank(table);
61 physical * NAMETABLE_SIZE + local
62}
63
64const fn byte_to_mirroring(b: u8, fallback: Mirroring) -> Mirroring {
79 match b {
80 0 => Mirroring::Horizontal,
81 1 => Mirroring::Vertical,
82 2 => Mirroring::SingleScreenA,
83 3 => Mirroring::SingleScreenB,
84 4 => Mirroring::FourScreen,
85 5 => Mirroring::MapperControlled,
86 _ => fallback,
87 }
88}
89
90fn check_prg(prg: &[u8], id: u16) -> Result<(), MapperError> {
92 if prg.is_empty() || !prg.len().is_multiple_of(PRG_BANK_8K) {
93 return Err(MapperError::Invalid(format!(
94 "mapper {id} PRG-ROM size {} is not a non-zero multiple of 8 KiB",
95 prg.len()
96 )));
97 }
98 Ok(())
99}
100
101fn chr_or_ram(chr_rom: Box<[u8]>) -> (Box<[u8]>, bool) {
104 if chr_rom.is_empty() {
105 (vec![0u8; CHR_BANK_8K].into_boxed_slice(), true)
106 } else {
107 (chr_rom, false)
108 }
109}
110
111const fn mirroring_to_byte(m: Mirroring) -> u8 {
112 match m {
113 Mirroring::Horizontal => 0,
114 Mirroring::Vertical => 1,
115 Mirroring::SingleScreenA => 2,
116 Mirroring::SingleScreenB => 3,
117 Mirroring::FourScreen => 4,
118 Mirroring::MapperControlled => 5,
119 }
120}
121
122pub struct Multicart15 {
124 prg_rom: Box<[u8]>,
125 chr_ram: Box<[u8]>,
126 prg_ram: Box<[u8]>,
135 vram: Box<[u8]>,
136 mode: u8,
137 prg_bank: u8,
138 half: u8,
139 horizontal_mirroring: bool,
140}
141
142impl Multicart15 {
143 pub fn new(prg_rom: Box<[u8]>, chr_rom: &[u8]) -> Result<Self, MapperError> {
150 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
151 return Err(MapperError::Invalid(format!(
152 "mapper 15 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
153 prg_rom.len()
154 )));
155 }
156 if !chr_rom.is_empty() {
157 return Err(MapperError::Invalid(format!(
158 "mapper 15 uses 8 KiB CHR-RAM; got {} bytes of CHR-ROM",
159 chr_rom.len()
160 )));
161 }
162 Ok(Self {
163 prg_rom,
164 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
165 prg_ram: vec![0u8; PRG_BANK_8K].into_boxed_slice(),
166 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
167 mode: 0,
168 prg_bank: 0,
169 half: 0,
170 horizontal_mirroring: false,
171 })
172 }
173
174 fn prg_count_16k(&self) -> usize {
175 (self.prg_rom.len() / PRG_BANK_16K).max(1)
176 }
177
178 fn read_16k(&self, bank: usize, off: usize) -> u8 {
179 let bank = bank % self.prg_count_16k();
180 self.prg_rom[bank * PRG_BANK_16K + off]
181 }
182
183 fn read_8k(&self, bank: usize, off: usize) -> u8 {
184 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
185 let bank = bank % count;
186 self.prg_rom[bank * PRG_BANK_8K + off]
187 }
188}
189
190impl Mapper for Multicart15 {
191 fn sram(&self) -> &[u8] {
193 &self.prg_ram
194 }
195 fn sram_mut(&mut self) -> &mut [u8] {
196 &mut self.prg_ram
197 }
198
199 fn caps(&self) -> MapperCaps {
200 MapperCaps::NONE
201 }
202
203 fn cpu_read(&mut self, addr: u16) -> u8 {
204 if (0x6000..=0x7FFF).contains(&addr) {
205 return self.prg_ram[(addr as usize) - 0x6000];
206 }
207 if !(0x8000..=0xFFFF).contains(&addr) {
208 return 0;
209 }
210 let win = (addr as usize) - 0x8000; let bank = self.prg_bank as usize;
212 match self.mode {
213 0 => {
214 if win < PRG_BANK_16K {
215 self.read_16k(bank, win)
216 } else {
217 self.read_16k(bank | 1, win - PRG_BANK_16K)
218 }
219 }
220 1 => {
221 if win < PRG_BANK_16K {
222 self.read_16k(bank, win)
223 } else {
224 self.read_16k(bank | 7, win - PRG_BANK_16K)
225 }
226 }
227 2 => {
228 let off = win & (PRG_BANK_8K - 1);
230 self.read_8k((bank << 1) | (self.half as usize), off)
231 }
232 _ => {
234 let off = win & (PRG_BANK_16K - 1);
235 self.read_16k(bank, off)
236 }
237 }
238 }
239
240 fn cpu_write(&mut self, addr: u16, value: u8) {
241 if (0x6000..=0x7FFF).contains(&addr) {
242 self.prg_ram[(addr as usize) - 0x6000] = value;
243 return;
244 }
245 if (0x8000..=0xFFFF).contains(&addr) {
246 self.mode = (addr & 0x03) as u8;
247 self.horizontal_mirroring = (value & 0x40) != 0;
248 self.prg_bank = value & 0x3F;
249 self.half = u8::from((value & 0x80) != 0);
250 }
251 }
252
253 fn ppu_read(&mut self, addr: u16) -> u8 {
254 let addr = addr & 0x3FFF;
255 match addr {
256 0x0000..=0x1FFF => self.chr_ram[addr as usize],
257 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
258 _ => 0,
259 }
260 }
261
262 fn ppu_write(&mut self, addr: u16, value: u8) {
263 let addr = addr & 0x3FFF;
264 match addr {
265 0x0000..=0x1FFF => {
266 self.chr_ram[addr as usize] = value;
287 }
288 0x2000..=0x3EFF => {
289 let off = nametable_offset(addr, self.current_mirroring());
290 self.vram[off] = value;
291 }
292 _ => {}
293 }
294 }
295
296 fn current_mirroring(&self) -> Mirroring {
297 if self.horizontal_mirroring {
298 Mirroring::Horizontal
299 } else {
300 Mirroring::Vertical
301 }
302 }
303
304 fn save_state(&self) -> Vec<u8> {
305 let mut out =
311 Vec::with_capacity(5 + self.vram.len() + self.chr_ram.len() + self.prg_ram.len());
312 out.push(SAVE_STATE_VERSION);
313 out.push(self.mode);
314 out.push(self.prg_bank);
315 out.push(self.half);
316 out.push(u8::from(self.horizontal_mirroring));
317 out.extend_from_slice(&self.vram);
318 out.extend_from_slice(&self.chr_ram);
319 out.extend_from_slice(&self.prg_ram);
320 out
321 }
322
323 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
324 let with_ram = 5 + self.vram.len() + self.chr_ram.len() + self.prg_ram.len();
329 if data.len() != with_ram {
330 return Err(MapperError::WrongLength {
331 expected: with_ram,
332 got: data.len(),
333 });
334 }
335 if data[0] != SAVE_STATE_VERSION {
336 return Err(MapperError::UnsupportedVersion(data[0]));
337 }
338 self.mode = data[1];
339 self.prg_bank = data[2];
340 self.half = data[3];
341 self.horizontal_mirroring = data[4] != 0;
342 let mut cursor = 5;
343 self.vram
344 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
345 cursor += self.vram.len();
346 self.chr_ram
347 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
348 cursor += self.chr_ram.len();
349 self.prg_ram
350 .copy_from_slice(&data[cursor..cursor + self.prg_ram.len()]);
351 Ok(())
352 }
353}
354
355pub struct Multicart61 {
357 prg_rom: Box<[u8]>,
358 chr_ram: Box<[u8]>,
359 vram: Box<[u8]>,
360 prg_page: u8,
361 prg_16k_mode: bool,
362 horizontal_mirroring: bool,
363}
364
365impl Multicart61 {
366 pub fn new(prg_rom: Box<[u8]>, chr_rom: &[u8]) -> Result<Self, MapperError> {
373 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
374 return Err(MapperError::Invalid(format!(
375 "mapper 61 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
376 prg_rom.len()
377 )));
378 }
379 if !chr_rom.is_empty() {
380 return Err(MapperError::Invalid(format!(
381 "mapper 61 uses 8 KiB CHR-RAM; got {} bytes of CHR-ROM",
382 chr_rom.len()
383 )));
384 }
385 Ok(Self {
386 prg_rom,
387 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
388 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
389 prg_page: 0,
390 prg_16k_mode: false,
391 horizontal_mirroring: false,
392 })
393 }
394}
395
396impl Mapper for Multicart61 {
397 fn caps(&self) -> MapperCaps {
398 MapperCaps::NONE
399 }
400
401 fn cpu_read(&mut self, addr: u16) -> u8 {
402 if !(0x8000..=0xFFFF).contains(&addr) {
403 return 0;
404 }
405 let win = (addr as usize) - 0x8000;
406 if self.prg_16k_mode {
407 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
408 let bank = (self.prg_page as usize) % count;
409 let off = win & (PRG_BANK_16K - 1);
410 self.prg_rom[bank * PRG_BANK_16K + off]
411 } else {
412 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
413 let bank = ((self.prg_page >> 1) as usize) % count;
414 self.prg_rom[(bank * PRG_BANK_32K + win) % self.prg_rom.len()]
419 }
420 }
421
422 fn cpu_write(&mut self, addr: u16, _value: u8) {
423 if (0x8000..=0xFFFF).contains(&addr) {
424 let lo = (addr & 0x0F) as u8;
425 let hi = ((addr >> 5) & 0x01) as u8;
426 self.prg_page = (lo << 1) | hi;
427 self.prg_16k_mode = (addr & 0x10) != 0;
428 self.horizontal_mirroring = (addr & 0x80) != 0;
429 }
430 }
431
432 fn ppu_read(&mut self, addr: u16) -> u8 {
433 let addr = addr & 0x3FFF;
434 match addr {
435 0x0000..=0x1FFF => self.chr_ram[addr as usize],
436 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
437 _ => 0,
438 }
439 }
440
441 fn ppu_write(&mut self, addr: u16, value: u8) {
442 let addr = addr & 0x3FFF;
443 match addr {
444 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
445 0x2000..=0x3EFF => {
446 let off = nametable_offset(addr, self.current_mirroring());
447 self.vram[off] = value;
448 }
449 _ => {}
450 }
451 }
452
453 fn current_mirroring(&self) -> Mirroring {
454 if self.horizontal_mirroring {
455 Mirroring::Horizontal
456 } else {
457 Mirroring::Vertical
458 }
459 }
460
461 fn save_state(&self) -> Vec<u8> {
462 let mut out = Vec::with_capacity(4 + self.vram.len() + self.chr_ram.len());
463 out.push(SAVE_STATE_VERSION);
464 out.push(self.prg_page);
465 out.push(u8::from(self.prg_16k_mode));
466 out.push(u8::from(self.horizontal_mirroring));
467 out.extend_from_slice(&self.vram);
468 out.extend_from_slice(&self.chr_ram);
469 out
470 }
471
472 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
473 let expected = 4 + self.vram.len() + self.chr_ram.len();
474 if data.len() != expected {
475 return Err(MapperError::WrongLength {
476 expected,
477 got: data.len(),
478 });
479 }
480 if data[0] != SAVE_STATE_VERSION {
481 return Err(MapperError::UnsupportedVersion(data[0]));
482 }
483 self.prg_page = data[1];
484 self.prg_16k_mode = data[2] != 0;
485 self.horizontal_mirroring = data[3] != 0;
486 let mut cursor = 4;
487 self.vram
488 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
489 cursor += self.vram.len();
490 self.chr_ram
491 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
492 Ok(())
493 }
494}
495
496pub struct Multicart62 {
511 prg_rom: Box<[u8]>,
512 chr_rom: Box<[u8]>,
513 vram: Box<[u8]>,
514 prg_page: u8,
515 chr_bank: u8,
516 prg_16k_mode: bool,
517 horizontal_mirroring: bool,
518}
519
520impl Multicart62 {
521 pub fn new(
528 prg_rom: Box<[u8]>,
529 chr_rom: Box<[u8]>,
530 mirroring: Mirroring,
531 ) -> Result<Self, MapperError> {
532 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
533 return Err(MapperError::Invalid(format!(
534 "mapper 62 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
535 prg_rom.len()
536 )));
537 }
538 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
539 return Err(MapperError::Invalid(format!(
540 "mapper 62 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
541 chr_rom.len()
542 )));
543 }
544 Ok(Self {
545 prg_rom,
546 chr_rom,
547 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
548 prg_page: 0,
549 chr_bank: 0,
550 prg_16k_mode: false,
552 horizontal_mirroring: mirroring == Mirroring::Horizontal,
553 })
554 }
555}
556
557impl Mapper for Multicart62 {
558 fn caps(&self) -> MapperCaps {
559 MapperCaps::NONE
560 }
561
562 fn cpu_read(&mut self, addr: u16) -> u8 {
563 if !(0x8000..=0xFFFF).contains(&addr) {
564 return 0;
565 }
566 let win = (addr as usize) - 0x8000;
567 if self.prg_16k_mode {
568 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
569 let bank = (self.prg_page as usize) % count;
570 let off = win & (PRG_BANK_16K - 1);
571 self.prg_rom[bank * PRG_BANK_16K + off]
572 } else {
573 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
574 let bank = ((self.prg_page >> 1) as usize) % count;
575 self.prg_rom[(bank * PRG_BANK_32K + win) % self.prg_rom.len()]
577 }
578 }
579
580 fn cpu_write(&mut self, addr: u16, value: u8) {
581 if (0x8000..=0xFFFF).contains(&addr) {
582 let page_lo = ((addr & 0x3F00) >> 8) as u8;
583 let page_hi = (addr & 0x40) as u8;
584 self.prg_page = page_lo | page_hi;
585 let chr_hi = ((addr & 0x1F) as u8) << 2;
586 self.chr_bank = chr_hi | (value & 0x03);
587 self.prg_16k_mode = (addr & 0x20) != 0;
588 self.horizontal_mirroring = (addr & 0x80) != 0;
589 }
590 }
591
592 fn ppu_read(&mut self, addr: u16) -> u8 {
593 let addr = addr & 0x3FFF;
594 match addr {
595 0x0000..=0x1FFF => {
596 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
597 let bank = (self.chr_bank as usize) % count;
598 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
599 }
600 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
601 _ => 0,
602 }
603 }
604
605 fn ppu_write(&mut self, addr: u16, value: u8) {
606 let addr = addr & 0x3FFF;
607 if let 0x2000..=0x3EFF = addr {
608 let off = nametable_offset(addr, self.current_mirroring());
609 self.vram[off] = value;
610 }
611 }
612
613 fn current_mirroring(&self) -> Mirroring {
614 if self.horizontal_mirroring {
615 Mirroring::Horizontal
616 } else {
617 Mirroring::Vertical
618 }
619 }
620
621 fn save_state(&self) -> Vec<u8> {
622 let mut out = Vec::with_capacity(5 + self.vram.len());
623 out.push(SAVE_STATE_VERSION);
624 out.push(self.prg_page);
625 out.push(self.chr_bank);
626 out.push(u8::from(self.prg_16k_mode));
627 out.push(u8::from(self.horizontal_mirroring));
628 out.extend_from_slice(&self.vram);
629 out
630 }
631
632 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
633 let expected = 5 + self.vram.len();
634 if data.len() != expected {
635 return Err(MapperError::WrongLength {
636 expected,
637 got: data.len(),
638 });
639 }
640 if data[0] != SAVE_STATE_VERSION {
641 return Err(MapperError::UnsupportedVersion(data[0]));
642 }
643 self.prg_page = data[1];
644 self.chr_bank = data[2];
645 self.prg_16k_mode = data[3] != 0;
646 self.horizontal_mirroring = data[4] != 0;
647 self.vram.copy_from_slice(&data[5..5 + self.vram.len()]);
648 Ok(())
649 }
650}
651
652pub struct Multicart200 {
654 prg_rom: Box<[u8]>,
655 chr: Box<[u8]>,
656 vram: Box<[u8]>,
657 chr_is_ram: bool,
658 bank: u8,
659 horizontal_mirroring: bool,
660}
661
662impl Multicart200 {
663 pub fn new(
670 prg_rom: Box<[u8]>,
671 chr_rom: Box<[u8]>,
672 mirroring: Mirroring,
673 ) -> Result<Self, MapperError> {
674 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
675 return Err(MapperError::Invalid(format!(
676 "mapper 200 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
677 prg_rom.len()
678 )));
679 }
680 let chr_is_ram = chr_rom.is_empty();
681 let chr: Box<[u8]> = if chr_is_ram {
682 vec![0u8; CHR_BANK_8K].into_boxed_slice()
683 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
684 chr_rom
685 } else {
686 return Err(MapperError::Invalid(format!(
687 "mapper 200 CHR-ROM size {} is not a multiple of 8 KiB",
688 chr_rom.len()
689 )));
690 };
691 Ok(Self {
692 prg_rom,
693 chr,
694 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
695 chr_is_ram,
696 bank: 0,
697 horizontal_mirroring: mirroring == Mirroring::Horizontal,
698 })
699 }
700}
701
702impl Mapper for Multicart200 {
703 fn caps(&self) -> MapperCaps {
704 MapperCaps::NONE
705 }
706
707 fn cpu_read(&mut self, addr: u16) -> u8 {
708 if (0x8000..=0xFFFF).contains(&addr) {
709 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
711 let bank = (self.bank as usize) % count;
712 let off = (addr as usize - 0x8000) & (PRG_BANK_16K - 1);
713 self.prg_rom[bank * PRG_BANK_16K + off]
714 } else {
715 0
716 }
717 }
718
719 fn cpu_write(&mut self, addr: u16, _value: u8) {
720 if (0x8000..=0xFFFF).contains(&addr) {
721 self.bank = (addr & 0x07) as u8;
722 self.horizontal_mirroring = (addr & 0x08) != 0;
723 }
724 }
725
726 fn ppu_read(&mut self, addr: u16) -> u8 {
727 let addr = addr & 0x3FFF;
728 match addr {
729 0x0000..=0x1FFF => {
730 let count = (self.chr.len() / CHR_BANK_8K).max(1);
731 let bank = (self.bank as usize) % count;
732 self.chr[bank * CHR_BANK_8K + addr as usize]
733 }
734 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
735 _ => 0,
736 }
737 }
738
739 fn ppu_write(&mut self, addr: u16, value: u8) {
740 let addr = addr & 0x3FFF;
741 match addr {
742 0x0000..=0x1FFF => {
743 if self.chr_is_ram {
744 let count = (self.chr.len() / CHR_BANK_8K).max(1);
745 let bank = (self.bank as usize) % count;
746 self.chr[bank * CHR_BANK_8K + addr as usize] = value;
747 }
748 }
749 0x2000..=0x3EFF => {
750 let off = nametable_offset(addr, self.current_mirroring());
751 self.vram[off] = value;
752 }
753 _ => {}
754 }
755 }
756
757 fn current_mirroring(&self) -> Mirroring {
758 if self.horizontal_mirroring {
759 Mirroring::Horizontal
760 } else {
761 Mirroring::Vertical
762 }
763 }
764
765 fn save_state(&self) -> Vec<u8> {
766 let chr_extra = if self.chr_is_ram { self.chr.len() } else { 0 };
767 let mut out = Vec::with_capacity(3 + self.vram.len() + chr_extra);
768 out.push(SAVE_STATE_VERSION);
769 out.push(self.bank);
770 out.push(u8::from(self.horizontal_mirroring));
771 out.extend_from_slice(&self.vram);
772 if self.chr_is_ram {
773 out.extend_from_slice(&self.chr);
774 }
775 out
776 }
777
778 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
779 let chr_extra = if self.chr_is_ram { self.chr.len() } else { 0 };
780 let expected = 3 + self.vram.len() + chr_extra;
781 if data.len() != expected {
782 return Err(MapperError::WrongLength {
783 expected,
784 got: data.len(),
785 });
786 }
787 if data[0] != SAVE_STATE_VERSION {
788 return Err(MapperError::UnsupportedVersion(data[0]));
789 }
790 self.bank = data[1];
791 self.horizontal_mirroring = data[2] != 0;
792 let mut cursor = 3;
793 self.vram
794 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
795 cursor += self.vram.len();
796 if self.chr_is_ram {
797 self.chr
798 .copy_from_slice(&data[cursor..cursor + self.chr.len()]);
799 }
800 Ok(())
801 }
802}
803
804pub struct Multicart201 {
816 prg_rom: Box<[u8]>,
817 chr_rom: Box<[u8]>,
818 vram: Box<[u8]>,
819 chr_is_ram: bool,
820 prg_bank: u8,
821 chr_bank: u8,
822 mirroring: Mirroring,
823}
824
825impl Multicart201 {
826 pub fn new(
833 prg_rom: Box<[u8]>,
834 chr_rom: Box<[u8]>,
835 mirroring: Mirroring,
836 ) -> Result<Self, MapperError> {
837 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
838 return Err(MapperError::Invalid(format!(
839 "mapper 201 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
840 prg_rom.len()
841 )));
842 }
843 let chr_is_ram = chr_rom.is_empty();
844 let chr_rom: Box<[u8]> = if chr_is_ram {
845 vec![0u8; CHR_BANK_8K].into_boxed_slice()
846 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
847 chr_rom
848 } else {
849 return Err(MapperError::Invalid(format!(
850 "mapper 201 CHR-ROM size {} is not a multiple of 8 KiB",
851 chr_rom.len()
852 )));
853 };
854 Ok(Self {
855 prg_rom,
856 chr_rom,
857 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
858 chr_is_ram,
859 prg_bank: 0,
860 chr_bank: 0,
861 mirroring,
862 })
863 }
864
865 fn chr_offset(&self, addr: u16) -> usize {
866 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
867 let bank = (self.chr_bank as usize) % count;
868 bank * CHR_BANK_8K + addr as usize
869 }
870}
871
872impl Mapper for Multicart201 {
873 fn caps(&self) -> MapperCaps {
874 MapperCaps::NONE
875 }
876
877 fn cpu_read(&mut self, addr: u16) -> u8 {
878 if (0x8000..=0xFFFF).contains(&addr) {
879 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
880 let bank = (self.prg_bank as usize) % count;
881 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
882 } else {
883 0
884 }
885 }
886
887 fn cpu_write(&mut self, addr: u16, _value: u8) {
888 if (0x8000..=0xFFFF).contains(&addr) {
889 self.prg_bank = (addr & 0x03) as u8;
890 self.chr_bank = (addr & 0x07) as u8;
891 }
892 }
893
894 fn ppu_read(&mut self, addr: u16) -> u8 {
895 let addr = addr & 0x3FFF;
896 match addr {
897 0x0000..=0x1FFF => self.chr_rom[self.chr_offset(addr)],
898 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
899 _ => 0,
900 }
901 }
902
903 fn ppu_write(&mut self, addr: u16, value: u8) {
904 let addr = addr & 0x3FFF;
905 match addr {
906 0x0000..=0x1FFF => {
907 if self.chr_is_ram {
908 let off = self.chr_offset(addr);
909 self.chr_rom[off] = value;
910 }
911 }
912 0x2000..=0x3EFF => {
913 let off = nametable_offset(addr, self.mirroring);
914 self.vram[off] = value;
915 }
916 _ => {}
917 }
918 }
919
920 fn current_mirroring(&self) -> Mirroring {
921 self.mirroring
922 }
923
924 fn save_state(&self) -> Vec<u8> {
925 let chr_extra = if self.chr_is_ram {
926 self.chr_rom.len()
927 } else {
928 0
929 };
930 let mut out = Vec::with_capacity(3 + self.vram.len() + chr_extra);
931 out.push(SAVE_STATE_VERSION);
932 out.push(self.prg_bank);
933 out.push(self.chr_bank);
934 out.extend_from_slice(&self.vram);
935 if self.chr_is_ram {
936 out.extend_from_slice(&self.chr_rom);
937 }
938 out
939 }
940
941 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
942 let chr_extra = if self.chr_is_ram {
943 self.chr_rom.len()
944 } else {
945 0
946 };
947 let expected = 3 + self.vram.len() + chr_extra;
948 if data.len() != expected {
949 return Err(MapperError::WrongLength {
950 expected,
951 got: data.len(),
952 });
953 }
954 if data[0] != SAVE_STATE_VERSION {
955 return Err(MapperError::UnsupportedVersion(data[0]));
956 }
957 self.prg_bank = data[1];
958 self.chr_bank = data[2];
959 let mut cursor = 3;
960 self.vram
961 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
962 cursor += self.vram.len();
963 if self.chr_is_ram {
964 self.chr_rom
965 .copy_from_slice(&data[cursor..cursor + self.chr_rom.len()]);
966 }
967 Ok(())
968 }
969}
970
971pub struct Multicart202 {
990 prg_rom: Box<[u8]>,
991 chr_rom: Box<[u8]>,
992 vram: Box<[u8]>,
993 chr_is_ram: bool,
994 page: u8,
995 prg_32k_mode: bool,
996 horizontal_mirroring: bool,
997}
998
999impl Multicart202 {
1000 pub fn new(
1007 prg_rom: Box<[u8]>,
1008 chr_rom: Box<[u8]>,
1009 mirroring: Mirroring,
1010 ) -> Result<Self, MapperError> {
1011 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
1012 return Err(MapperError::Invalid(format!(
1013 "mapper 202 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
1014 prg_rom.len()
1015 )));
1016 }
1017 let chr_is_ram = chr_rom.is_empty();
1018 let chr_rom: Box<[u8]> = if chr_is_ram {
1019 vec![0u8; CHR_BANK_8K].into_boxed_slice()
1020 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
1021 chr_rom
1022 } else {
1023 return Err(MapperError::Invalid(format!(
1024 "mapper 202 CHR-ROM size {} is not a multiple of 8 KiB",
1025 chr_rom.len()
1026 )));
1027 };
1028 Ok(Self {
1029 prg_rom,
1030 chr_rom,
1031 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
1032 chr_is_ram,
1033 page: 0,
1034 prg_32k_mode: false,
1035 horizontal_mirroring: mirroring == Mirroring::Horizontal,
1036 })
1037 }
1038
1039 fn chr_offset(&self, addr: u16) -> usize {
1040 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
1041 let bank = (self.page as usize) % count;
1042 bank * CHR_BANK_8K + addr as usize
1043 }
1044}
1045
1046impl Mapper for Multicart202 {
1047 fn caps(&self) -> MapperCaps {
1048 MapperCaps::NONE
1049 }
1050
1051 fn cpu_read(&mut self, addr: u16) -> u8 {
1052 match addr {
1053 0x8000..=0xFFFF => {
1054 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
1055 if self.prg_32k_mode {
1056 let lo16 = ((self.page >> 1) << 1) as usize % count;
1058 let off = addr as usize - 0x8000;
1059 self.prg_rom[(lo16 * PRG_BANK_16K + off) % self.prg_rom.len()]
1065 } else {
1066 let bank = (self.page as usize) % count;
1068 let off = (addr as usize - 0x8000) & (PRG_BANK_16K - 1);
1069 self.prg_rom[bank * PRG_BANK_16K + off]
1070 }
1071 }
1072 _ => 0,
1073 }
1074 }
1075
1076 fn cpu_write(&mut self, addr: u16, _value: u8) {
1077 if (0x8000..=0xFFFF).contains(&addr) {
1078 self.prg_32k_mode = (addr & 0x09) == 0x09;
1080 self.page = ((addr >> 1) & 0x07) as u8;
1081 self.horizontal_mirroring = (addr & 0x01) != 0;
1082 }
1083 }
1084
1085 fn ppu_read(&mut self, addr: u16) -> u8 {
1086 let addr = addr & 0x3FFF;
1087 match addr {
1088 0x0000..=0x1FFF => self.chr_rom[self.chr_offset(addr)],
1089 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
1090 _ => 0,
1091 }
1092 }
1093
1094 fn ppu_write(&mut self, addr: u16, value: u8) {
1095 let addr = addr & 0x3FFF;
1096 match addr {
1097 0x0000..=0x1FFF => {
1098 if self.chr_is_ram {
1099 let off = self.chr_offset(addr);
1100 self.chr_rom[off] = value;
1101 }
1102 }
1103 0x2000..=0x3EFF => {
1104 let off = nametable_offset(addr, self.current_mirroring());
1105 self.vram[off] = value;
1106 }
1107 _ => {}
1108 }
1109 }
1110
1111 fn current_mirroring(&self) -> Mirroring {
1112 if self.horizontal_mirroring {
1113 Mirroring::Horizontal
1114 } else {
1115 Mirroring::Vertical
1116 }
1117 }
1118
1119 fn save_state(&self) -> Vec<u8> {
1120 let chr_extra = if self.chr_is_ram {
1121 self.chr_rom.len()
1122 } else {
1123 0
1124 };
1125 let mut out = Vec::with_capacity(4 + self.vram.len() + chr_extra);
1126 out.push(SAVE_STATE_VERSION);
1127 out.push(self.page);
1128 out.push(u8::from(self.prg_32k_mode));
1129 out.push(u8::from(self.horizontal_mirroring));
1130 out.extend_from_slice(&self.vram);
1131 if self.chr_is_ram {
1132 out.extend_from_slice(&self.chr_rom);
1133 }
1134 out
1135 }
1136
1137 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1138 let chr_extra = if self.chr_is_ram {
1139 self.chr_rom.len()
1140 } else {
1141 0
1142 };
1143 let expected = 4 + self.vram.len() + chr_extra;
1144 if data.len() != expected {
1145 return Err(MapperError::WrongLength {
1146 expected,
1147 got: data.len(),
1148 });
1149 }
1150 if data[0] != SAVE_STATE_VERSION {
1151 return Err(MapperError::UnsupportedVersion(data[0]));
1152 }
1153 self.page = data[1];
1154 self.prg_32k_mode = data[2] != 0;
1155 self.horizontal_mirroring = data[3] != 0;
1156 let mut cursor = 4;
1157 self.vram
1158 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
1159 cursor += self.vram.len();
1160 if self.chr_is_ram {
1161 self.chr_rom
1162 .copy_from_slice(&data[cursor..cursor + self.chr_rom.len()]);
1163 }
1164 Ok(())
1165 }
1166}
1167
1168pub struct Multicart203 {
1180 prg_rom: Box<[u8]>,
1181 chr_rom: Box<[u8]>,
1182 vram: Box<[u8]>,
1183 chr_is_ram: bool,
1184 prg_bank: u8,
1185 chr_bank: u8,
1186 mirroring: Mirroring,
1187}
1188
1189impl Multicart203 {
1190 pub fn new(
1197 prg_rom: Box<[u8]>,
1198 chr_rom: Box<[u8]>,
1199 mirroring: Mirroring,
1200 ) -> Result<Self, MapperError> {
1201 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
1202 return Err(MapperError::Invalid(format!(
1203 "mapper 203 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
1204 prg_rom.len()
1205 )));
1206 }
1207 let chr_is_ram = chr_rom.is_empty();
1208 let chr_rom: Box<[u8]> = if chr_is_ram {
1209 vec![0u8; CHR_BANK_8K].into_boxed_slice()
1210 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
1211 chr_rom
1212 } else {
1213 return Err(MapperError::Invalid(format!(
1214 "mapper 203 CHR-ROM size {} is not a multiple of 8 KiB",
1215 chr_rom.len()
1216 )));
1217 };
1218 Ok(Self {
1219 prg_rom,
1220 chr_rom,
1221 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
1222 chr_is_ram,
1223 prg_bank: 0,
1224 chr_bank: 0,
1225 mirroring,
1226 })
1227 }
1228
1229 fn chr_offset(&self, addr: u16) -> usize {
1230 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
1231 let bank = (self.chr_bank as usize) % count;
1232 bank * CHR_BANK_8K + addr as usize
1233 }
1234}
1235
1236impl Mapper for Multicart203 {
1237 fn caps(&self) -> MapperCaps {
1238 MapperCaps::NONE
1239 }
1240
1241 fn cpu_read(&mut self, addr: u16) -> u8 {
1242 if (0x8000..=0xFFFF).contains(&addr) {
1243 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
1245 let bank = (self.prg_bank as usize) % count;
1246 let off = (addr as usize - 0x8000) & (PRG_BANK_16K - 1);
1247 self.prg_rom[bank * PRG_BANK_16K + off]
1248 } else {
1249 0
1250 }
1251 }
1252
1253 fn cpu_write(&mut self, addr: u16, value: u8) {
1254 if (0x8000..=0xFFFF).contains(&addr) {
1255 self.prg_bank = (value >> 2) & 0x3F;
1256 self.chr_bank = value & 0x03;
1257 }
1258 }
1259
1260 fn ppu_read(&mut self, addr: u16) -> u8 {
1261 let addr = addr & 0x3FFF;
1262 match addr {
1263 0x0000..=0x1FFF => self.chr_rom[self.chr_offset(addr)],
1264 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
1265 _ => 0,
1266 }
1267 }
1268
1269 fn ppu_write(&mut self, addr: u16, value: u8) {
1270 let addr = addr & 0x3FFF;
1271 match addr {
1272 0x0000..=0x1FFF => {
1273 if self.chr_is_ram {
1274 let off = self.chr_offset(addr);
1275 self.chr_rom[off] = value;
1276 }
1277 }
1278 0x2000..=0x3EFF => {
1279 let off = nametable_offset(addr, self.mirroring);
1280 self.vram[off] = value;
1281 }
1282 _ => {}
1283 }
1284 }
1285
1286 fn current_mirroring(&self) -> Mirroring {
1287 self.mirroring
1288 }
1289
1290 fn save_state(&self) -> Vec<u8> {
1291 let chr_extra = if self.chr_is_ram {
1292 self.chr_rom.len()
1293 } else {
1294 0
1295 };
1296 let mut out = Vec::with_capacity(3 + self.vram.len() + chr_extra);
1297 out.push(SAVE_STATE_VERSION);
1298 out.push(self.prg_bank);
1299 out.push(self.chr_bank);
1300 out.extend_from_slice(&self.vram);
1301 if self.chr_is_ram {
1302 out.extend_from_slice(&self.chr_rom);
1303 }
1304 out
1305 }
1306
1307 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1308 let chr_extra = if self.chr_is_ram {
1309 self.chr_rom.len()
1310 } else {
1311 0
1312 };
1313 let expected = 3 + self.vram.len() + chr_extra;
1314 if data.len() != expected {
1315 return Err(MapperError::WrongLength {
1316 expected,
1317 got: data.len(),
1318 });
1319 }
1320 if data[0] != SAVE_STATE_VERSION {
1321 return Err(MapperError::UnsupportedVersion(data[0]));
1322 }
1323 self.prg_bank = data[1];
1324 self.chr_bank = data[2];
1325 let mut cursor = 3;
1326 self.vram
1327 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
1328 cursor += self.vram.len();
1329 if self.chr_is_ram {
1330 self.chr_rom
1331 .copy_from_slice(&data[cursor..cursor + self.chr_rom.len()]);
1332 }
1333 Ok(())
1334 }
1335}
1336
1337pub struct Multicart212 {
1354 prg_rom: Box<[u8]>,
1355 chr_rom: Box<[u8]>,
1356 vram: Box<[u8]>,
1357 chr_is_ram: bool,
1358 page: u8,
1359 prg_32k_mode: bool,
1360 horizontal_mirroring: bool,
1361}
1362
1363impl Multicart212 {
1364 pub fn new(
1371 prg_rom: Box<[u8]>,
1372 chr_rom: Box<[u8]>,
1373 mirroring: Mirroring,
1374 ) -> Result<Self, MapperError> {
1375 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
1376 return Err(MapperError::Invalid(format!(
1377 "mapper 212 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
1378 prg_rom.len()
1379 )));
1380 }
1381 let chr_is_ram = chr_rom.is_empty();
1382 let chr_rom: Box<[u8]> = if chr_is_ram {
1383 vec![0u8; CHR_BANK_8K].into_boxed_slice()
1384 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
1385 chr_rom
1386 } else {
1387 return Err(MapperError::Invalid(format!(
1388 "mapper 212 CHR-ROM size {} is not a multiple of 8 KiB",
1389 chr_rom.len()
1390 )));
1391 };
1392 Ok(Self {
1393 prg_rom,
1394 chr_rom,
1395 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
1396 chr_is_ram,
1397 page: 0,
1398 prg_32k_mode: false,
1399 horizontal_mirroring: mirroring == Mirroring::Horizontal,
1400 })
1401 }
1402
1403 fn chr_offset(&self, addr: u16) -> usize {
1404 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
1405 let bank = (self.page as usize) % count;
1406 bank * CHR_BANK_8K + addr as usize
1407 }
1408}
1409
1410impl Mapper for Multicart212 {
1411 fn caps(&self) -> MapperCaps {
1412 MapperCaps::NONE
1413 }
1414
1415 fn cpu_read_unmapped(&self, addr: u16) -> bool {
1416 (0x4020..=0x5FFF).contains(&addr)
1419 }
1420
1421 fn cpu_read(&mut self, addr: u16) -> u8 {
1422 match addr {
1423 0x6000..=0x7FFF => {
1424 if (addr & 0x0010) == 0 { 0x80 } else { 0x00 }
1426 }
1427 0x8000..=0xFFFF => {
1428 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
1429 if self.prg_32k_mode {
1430 let lo16 = ((self.page >> 1) << 1) as usize % count;
1431 let off = addr as usize - 0x8000;
1432 self.prg_rom[(lo16 * PRG_BANK_16K + off) % self.prg_rom.len()]
1435 } else {
1436 let bank = (self.page as usize) % count;
1437 let off = (addr as usize - 0x8000) & (PRG_BANK_16K - 1);
1438 self.prg_rom[bank * PRG_BANK_16K + off]
1439 }
1440 }
1441 _ => 0,
1442 }
1443 }
1444
1445 fn cpu_write(&mut self, addr: u16, _value: u8) {
1446 if (0x8000..=0xFFFF).contains(&addr) {
1447 self.prg_32k_mode = (addr & 0x4000) != 0;
1448 self.page = (addr & 0x07) as u8;
1449 self.horizontal_mirroring = (addr & 0x0008) != 0;
1450 }
1451 }
1452
1453 fn ppu_read(&mut self, addr: u16) -> u8 {
1454 let addr = addr & 0x3FFF;
1455 match addr {
1456 0x0000..=0x1FFF => self.chr_rom[self.chr_offset(addr)],
1457 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
1458 _ => 0,
1459 }
1460 }
1461
1462 fn ppu_write(&mut self, addr: u16, value: u8) {
1463 let addr = addr & 0x3FFF;
1464 match addr {
1465 0x0000..=0x1FFF => {
1466 if self.chr_is_ram {
1467 let off = self.chr_offset(addr);
1468 self.chr_rom[off] = value;
1469 }
1470 }
1471 0x2000..=0x3EFF => {
1472 let off = nametable_offset(addr, self.current_mirroring());
1473 self.vram[off] = value;
1474 }
1475 _ => {}
1476 }
1477 }
1478
1479 fn current_mirroring(&self) -> Mirroring {
1480 if self.horizontal_mirroring {
1481 Mirroring::Horizontal
1482 } else {
1483 Mirroring::Vertical
1484 }
1485 }
1486
1487 fn save_state(&self) -> Vec<u8> {
1488 let chr_extra = if self.chr_is_ram {
1489 self.chr_rom.len()
1490 } else {
1491 0
1492 };
1493 let mut out = Vec::with_capacity(4 + self.vram.len() + chr_extra);
1494 out.push(SAVE_STATE_VERSION);
1495 out.push(self.page);
1496 out.push(u8::from(self.prg_32k_mode));
1497 out.push(u8::from(self.horizontal_mirroring));
1498 out.extend_from_slice(&self.vram);
1499 if self.chr_is_ram {
1500 out.extend_from_slice(&self.chr_rom);
1501 }
1502 out
1503 }
1504
1505 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1506 let chr_extra = if self.chr_is_ram {
1507 self.chr_rom.len()
1508 } else {
1509 0
1510 };
1511 let expected = 4 + self.vram.len() + chr_extra;
1512 if data.len() != expected {
1513 return Err(MapperError::WrongLength {
1514 expected,
1515 got: data.len(),
1516 });
1517 }
1518 if data[0] != SAVE_STATE_VERSION {
1519 return Err(MapperError::UnsupportedVersion(data[0]));
1520 }
1521 self.page = data[1];
1522 self.prg_32k_mode = data[2] != 0;
1523 self.horizontal_mirroring = data[3] != 0;
1524 let mut cursor = 4;
1525 self.vram
1526 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
1527 cursor += self.vram.len();
1528 if self.chr_is_ram {
1529 self.chr_rom
1530 .copy_from_slice(&data[cursor..cursor + self.chr_rom.len()]);
1531 }
1532 Ok(())
1533 }
1534}
1535
1536pub struct Multicart213 {
1547 prg_rom: Box<[u8]>,
1548 chr_rom: Box<[u8]>,
1549 vram: Box<[u8]>,
1550 chr_is_ram: bool,
1551 prg_bank: u8,
1552 chr_bank: u8,
1553 mirroring: Mirroring,
1554}
1555
1556impl Multicart213 {
1557 pub fn new(
1564 prg_rom: Box<[u8]>,
1565 chr_rom: Box<[u8]>,
1566 mirroring: Mirroring,
1567 ) -> Result<Self, MapperError> {
1568 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
1569 return Err(MapperError::Invalid(format!(
1570 "mapper 213 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
1571 prg_rom.len()
1572 )));
1573 }
1574 let chr_is_ram = chr_rom.is_empty();
1575 let chr_rom: Box<[u8]> = if chr_is_ram {
1576 vec![0u8; CHR_BANK_8K].into_boxed_slice()
1577 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
1578 chr_rom
1579 } else {
1580 return Err(MapperError::Invalid(format!(
1581 "mapper 213 CHR-ROM size {} is not a multiple of 8 KiB",
1582 chr_rom.len()
1583 )));
1584 };
1585 Ok(Self {
1586 prg_rom,
1587 chr_rom,
1588 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
1589 chr_is_ram,
1590 prg_bank: 0,
1591 chr_bank: 0,
1592 mirroring,
1593 })
1594 }
1595
1596 fn chr_offset(&self, addr: u16) -> usize {
1597 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
1598 let bank = (self.chr_bank as usize) % count;
1599 bank * CHR_BANK_8K + addr as usize
1600 }
1601}
1602
1603impl Mapper for Multicart213 {
1604 fn caps(&self) -> MapperCaps {
1605 MapperCaps::NONE
1606 }
1607
1608 fn cpu_read(&mut self, addr: u16) -> u8 {
1609 if (0x8000..=0xFFFF).contains(&addr) {
1610 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
1611 let bank = (self.prg_bank as usize) % count;
1612 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
1613 } else {
1614 0
1615 }
1616 }
1617
1618 fn cpu_write(&mut self, addr: u16, _value: u8) {
1619 if (0x8000..=0xFFFF).contains(&addr) {
1620 self.chr_bank = ((addr >> 3) & 0x07) as u8;
1621 self.prg_bank = ((addr >> 1) & 0x03) as u8;
1622 }
1623 }
1624
1625 fn ppu_read(&mut self, addr: u16) -> u8 {
1626 let addr = addr & 0x3FFF;
1627 match addr {
1628 0x0000..=0x1FFF => self.chr_rom[self.chr_offset(addr)],
1629 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
1630 _ => 0,
1631 }
1632 }
1633
1634 fn ppu_write(&mut self, addr: u16, value: u8) {
1635 let addr = addr & 0x3FFF;
1636 match addr {
1637 0x0000..=0x1FFF => {
1638 if self.chr_is_ram {
1639 let off = self.chr_offset(addr);
1640 self.chr_rom[off] = value;
1641 }
1642 }
1643 0x2000..=0x3EFF => {
1644 let off = nametable_offset(addr, self.mirroring);
1645 self.vram[off] = value;
1646 }
1647 _ => {}
1648 }
1649 }
1650
1651 fn current_mirroring(&self) -> Mirroring {
1652 self.mirroring
1653 }
1654
1655 fn save_state(&self) -> Vec<u8> {
1656 let chr_extra = if self.chr_is_ram {
1657 self.chr_rom.len()
1658 } else {
1659 0
1660 };
1661 let mut out = Vec::with_capacity(3 + self.vram.len() + chr_extra);
1662 out.push(SAVE_STATE_VERSION);
1663 out.push(self.prg_bank);
1664 out.push(self.chr_bank);
1665 out.extend_from_slice(&self.vram);
1666 if self.chr_is_ram {
1667 out.extend_from_slice(&self.chr_rom);
1668 }
1669 out
1670 }
1671
1672 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1673 let chr_extra = if self.chr_is_ram {
1674 self.chr_rom.len()
1675 } else {
1676 0
1677 };
1678 let expected = 3 + self.vram.len() + chr_extra;
1679 if data.len() != expected {
1680 return Err(MapperError::WrongLength {
1681 expected,
1682 got: data.len(),
1683 });
1684 }
1685 if data[0] != SAVE_STATE_VERSION {
1686 return Err(MapperError::UnsupportedVersion(data[0]));
1687 }
1688 self.prg_bank = data[1];
1689 self.chr_bank = data[2];
1690 let mut cursor = 3;
1691 self.vram
1692 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
1693 cursor += self.vram.len();
1694 if self.chr_is_ram {
1695 self.chr_rom
1696 .copy_from_slice(&data[cursor..cursor + self.chr_rom.len()]);
1697 }
1698 Ok(())
1699 }
1700}
1701
1702pub struct Multicart214 {
1713 prg_rom: Box<[u8]>,
1714 chr_rom: Box<[u8]>,
1715 vram: Box<[u8]>,
1716 chr_is_ram: bool,
1717 prg_bank: u8,
1718 chr_bank: u8,
1719 mirroring: Mirroring,
1720}
1721
1722impl Multicart214 {
1723 pub fn new(
1730 prg_rom: Box<[u8]>,
1731 chr_rom: Box<[u8]>,
1732 mirroring: Mirroring,
1733 ) -> Result<Self, MapperError> {
1734 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
1735 return Err(MapperError::Invalid(format!(
1736 "mapper 214 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
1737 prg_rom.len()
1738 )));
1739 }
1740 let chr_is_ram = chr_rom.is_empty();
1741 let chr_rom: Box<[u8]> = if chr_is_ram {
1742 vec![0u8; CHR_BANK_8K].into_boxed_slice()
1743 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
1744 chr_rom
1745 } else {
1746 return Err(MapperError::Invalid(format!(
1747 "mapper 214 CHR-ROM size {} is not a multiple of 8 KiB",
1748 chr_rom.len()
1749 )));
1750 };
1751 Ok(Self {
1752 prg_rom,
1753 chr_rom,
1754 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
1755 chr_is_ram,
1756 prg_bank: 0,
1757 chr_bank: 0,
1758 mirroring,
1759 })
1760 }
1761
1762 fn chr_offset(&self, addr: u16) -> usize {
1763 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
1764 let bank = (self.chr_bank as usize) % count;
1765 bank * CHR_BANK_8K + addr as usize
1766 }
1767}
1768
1769impl Mapper for Multicart214 {
1770 fn caps(&self) -> MapperCaps {
1771 MapperCaps::NONE
1772 }
1773
1774 fn cpu_read(&mut self, addr: u16) -> u8 {
1775 if (0x8000..=0xFFFF).contains(&addr) {
1776 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
1778 let bank = (self.prg_bank as usize) % count;
1779 let off = (addr as usize - 0x8000) & (PRG_BANK_16K - 1);
1780 self.prg_rom[bank * PRG_BANK_16K + off]
1781 } else {
1782 0
1783 }
1784 }
1785
1786 fn cpu_write(&mut self, addr: u16, _value: u8) {
1787 if (0x8000..=0xFFFF).contains(&addr) {
1788 self.chr_bank = (addr & 0x03) as u8;
1789 self.prg_bank = ((addr >> 2) & 0x03) as u8;
1790 }
1791 }
1792
1793 fn ppu_read(&mut self, addr: u16) -> u8 {
1794 let addr = addr & 0x3FFF;
1795 match addr {
1796 0x0000..=0x1FFF => self.chr_rom[self.chr_offset(addr)],
1797 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
1798 _ => 0,
1799 }
1800 }
1801
1802 fn ppu_write(&mut self, addr: u16, value: u8) {
1803 let addr = addr & 0x3FFF;
1804 match addr {
1805 0x0000..=0x1FFF => {
1806 if self.chr_is_ram {
1807 let off = self.chr_offset(addr);
1808 self.chr_rom[off] = value;
1809 }
1810 }
1811 0x2000..=0x3EFF => {
1812 let off = nametable_offset(addr, self.mirroring);
1813 self.vram[off] = value;
1814 }
1815 _ => {}
1816 }
1817 }
1818
1819 fn current_mirroring(&self) -> Mirroring {
1820 self.mirroring
1821 }
1822
1823 fn save_state(&self) -> Vec<u8> {
1824 let chr_extra = if self.chr_is_ram {
1825 self.chr_rom.len()
1826 } else {
1827 0
1828 };
1829 let mut out = Vec::with_capacity(3 + self.vram.len() + chr_extra);
1830 out.push(SAVE_STATE_VERSION);
1831 out.push(self.prg_bank);
1832 out.push(self.chr_bank);
1833 out.extend_from_slice(&self.vram);
1834 if self.chr_is_ram {
1835 out.extend_from_slice(&self.chr_rom);
1836 }
1837 out
1838 }
1839
1840 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1841 let chr_extra = if self.chr_is_ram {
1842 self.chr_rom.len()
1843 } else {
1844 0
1845 };
1846 let expected = 3 + self.vram.len() + chr_extra;
1847 if data.len() != expected {
1848 return Err(MapperError::WrongLength {
1849 expected,
1850 got: data.len(),
1851 });
1852 }
1853 if data[0] != SAVE_STATE_VERSION {
1854 return Err(MapperError::UnsupportedVersion(data[0]));
1855 }
1856 self.prg_bank = data[1];
1857 self.chr_bank = data[2];
1858 let mut cursor = 3;
1859 self.vram
1860 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
1861 cursor += self.vram.len();
1862 if self.chr_is_ram {
1863 self.chr_rom
1864 .copy_from_slice(&data[cursor..cursor + self.chr_rom.len()]);
1865 }
1866 Ok(())
1867 }
1868}
1869
1870pub struct Multicart58 {
1872 prg_rom: Box<[u8]>,
1873 chr_rom: Box<[u8]>,
1874 vram: Box<[u8]>,
1875 prg_bank: u8,
1876 prg32_mode: bool,
1877 chr_bank: u8,
1878 horizontal_mirroring: bool,
1879}
1880
1881impl Multicart58 {
1882 pub fn new(
1889 prg_rom: Box<[u8]>,
1890 chr_rom: Box<[u8]>,
1891 mirroring: Mirroring,
1892 ) -> Result<Self, MapperError> {
1893 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
1894 return Err(MapperError::Invalid(format!(
1895 "mapper 58 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
1896 prg_rom.len()
1897 )));
1898 }
1899 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
1900 return Err(MapperError::Invalid(format!(
1901 "mapper 58 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
1902 chr_rom.len()
1903 )));
1904 }
1905 let horizontal_mirroring = mirroring == Mirroring::Horizontal;
1906 Ok(Self {
1907 prg_rom,
1908 chr_rom,
1909 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
1910 prg_bank: 0,
1911 prg32_mode: false,
1912 chr_bank: 0,
1913 horizontal_mirroring,
1914 })
1915 }
1916}
1917
1918impl Mapper for Multicart58 {
1919 fn caps(&self) -> MapperCaps {
1920 MapperCaps::NONE
1921 }
1922
1923 fn cpu_read(&mut self, addr: u16) -> u8 {
1924 if !(0x8000..=0xFFFF).contains(&addr) {
1925 return 0;
1926 }
1927 if self.prg32_mode {
1928 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
1929 let bank = (((self.prg_bank & 0x06) >> 1) as usize) % count;
1930 self.prg_rom[(bank * PRG_BANK_32K + (addr as usize - 0x8000)) % self.prg_rom.len()]
1933 } else {
1934 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
1935 let bank = (self.prg_bank as usize) % count;
1936 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
1937 }
1938 }
1939
1940 fn cpu_write(&mut self, addr: u16, _value: u8) {
1941 if (0x8000..=0xFFFF).contains(&addr) {
1942 self.prg_bank = (addr & 0x07) as u8;
1943 self.prg32_mode = (addr & 0x40) == 0;
1944 self.chr_bank = ((addr >> 3) & 0x07) as u8;
1945 self.horizontal_mirroring = (addr & 0x80) != 0;
1946 }
1947 }
1948
1949 fn ppu_read(&mut self, addr: u16) -> u8 {
1950 let addr = addr & 0x3FFF;
1951 match addr {
1952 0x0000..=0x1FFF => {
1953 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
1954 let bank = (self.chr_bank as usize) % count;
1955 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
1956 }
1957 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
1958 _ => 0,
1959 }
1960 }
1961
1962 fn ppu_write(&mut self, addr: u16, value: u8) {
1963 let addr = addr & 0x3FFF;
1964 if let 0x2000..=0x3EFF = addr {
1965 let off = nametable_offset(addr, self.current_mirroring());
1966 self.vram[off] = value;
1967 }
1968 }
1969
1970 fn current_mirroring(&self) -> Mirroring {
1971 if self.horizontal_mirroring {
1972 Mirroring::Horizontal
1973 } else {
1974 Mirroring::Vertical
1975 }
1976 }
1977
1978 fn save_state(&self) -> Vec<u8> {
1979 let mut out = Vec::with_capacity(5 + self.vram.len());
1980 out.push(SAVE_STATE_VERSION);
1981 out.push(self.prg_bank);
1982 out.push(u8::from(self.prg32_mode));
1983 out.push(self.chr_bank);
1984 out.push(u8::from(self.horizontal_mirroring));
1985 out.extend_from_slice(&self.vram);
1986 out
1987 }
1988
1989 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1990 let expected = 5 + self.vram.len();
1991 if data.len() != expected {
1992 return Err(MapperError::WrongLength {
1993 expected,
1994 got: data.len(),
1995 });
1996 }
1997 if data[0] != SAVE_STATE_VERSION {
1998 return Err(MapperError::UnsupportedVersion(data[0]));
1999 }
2000 self.prg_bank = data[1];
2001 self.prg32_mode = data[2] != 0;
2002 self.chr_bank = data[3];
2003 self.horizontal_mirroring = data[4] != 0;
2004 self.vram.copy_from_slice(&data[5..5 + self.vram.len()]);
2005 Ok(())
2006 }
2007}
2008
2009pub struct Multicart60 {
2021 prg_rom: Box<[u8]>,
2022 chr_rom: Box<[u8]>,
2023 vram: Box<[u8]>,
2024 bank: u8,
2025 mirroring: Mirroring,
2026}
2027
2028impl Multicart60 {
2029 pub fn new(
2036 prg_rom: Box<[u8]>,
2037 chr_rom: Box<[u8]>,
2038 mirroring: Mirroring,
2039 ) -> Result<Self, MapperError> {
2040 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
2041 return Err(MapperError::Invalid(format!(
2042 "mapper 60 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
2043 prg_rom.len()
2044 )));
2045 }
2046 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
2047 return Err(MapperError::Invalid(format!(
2048 "mapper 60 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
2049 chr_rom.len()
2050 )));
2051 }
2052 Ok(Self {
2053 prg_rom,
2054 chr_rom,
2055 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
2056 bank: 0,
2058 mirroring,
2059 })
2060 }
2061}
2062
2063impl Mapper for Multicart60 {
2064 fn caps(&self) -> MapperCaps {
2065 MapperCaps::NONE
2066 }
2067
2068 fn cpu_read(&mut self, addr: u16) -> u8 {
2069 if (0x8000..=0xFFFF).contains(&addr) {
2070 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
2073 let bank = (self.bank as usize) % count;
2074 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
2075 } else {
2076 0
2077 }
2078 }
2079
2080 fn cpu_write(&mut self, _addr: u16, _value: u8) {}
2081
2082 fn ppu_read(&mut self, addr: u16) -> u8 {
2083 let addr = addr & 0x3FFF;
2084 match addr {
2085 0x0000..=0x1FFF => {
2086 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
2087 let bank = (self.bank as usize) % count;
2088 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
2089 }
2090 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
2091 _ => 0,
2092 }
2093 }
2094
2095 fn ppu_write(&mut self, addr: u16, value: u8) {
2096 let addr = addr & 0x3FFF;
2097 if let 0x2000..=0x3EFF = addr {
2098 let off = nametable_offset(addr, self.mirroring);
2099 self.vram[off] = value;
2100 }
2101 }
2102
2103 fn current_mirroring(&self) -> Mirroring {
2104 self.mirroring
2105 }
2106
2107 fn save_state(&self) -> Vec<u8> {
2108 let mut out = Vec::with_capacity(2 + self.vram.len());
2109 out.push(SAVE_STATE_VERSION);
2110 out.push(self.bank);
2111 out.extend_from_slice(&self.vram);
2112 out
2113 }
2114
2115 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
2116 let expected = 2 + self.vram.len();
2117 if data.len() != expected {
2118 return Err(MapperError::WrongLength {
2119 expected,
2120 got: data.len(),
2121 });
2122 }
2123 if data[0] != SAVE_STATE_VERSION {
2124 return Err(MapperError::UnsupportedVersion(data[0]));
2125 }
2126 self.bank = data[1];
2127 self.vram.copy_from_slice(&data[2..2 + self.vram.len()]);
2128 Ok(())
2129 }
2130}
2131
2132pub struct Multicart231 {
2146 prg_rom: Box<[u8]>,
2147 chr_ram: Box<[u8]>,
2148 vram: Box<[u8]>,
2149 prg_bank0: u8,
2150 prg_bank1: u8,
2151 horizontal_mirroring: bool,
2152}
2153
2154impl Multicart231 {
2155 pub fn new(
2162 prg_rom: Box<[u8]>,
2163 _chr_rom: &[u8],
2164 mirroring: Mirroring,
2165 ) -> Result<Self, MapperError> {
2166 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
2167 return Err(MapperError::Invalid(format!(
2168 "mapper 231 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
2169 prg_rom.len()
2170 )));
2171 }
2172 let horizontal_mirroring = mirroring == Mirroring::Horizontal;
2173 Ok(Self {
2174 prg_rom,
2175 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
2176 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
2177 prg_bank0: 0,
2178 prg_bank1: 0,
2179 horizontal_mirroring,
2180 })
2181 }
2182
2183 fn read_prg(&self, bank: u8, addr: u16) -> u8 {
2184 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
2185 let bank = (bank as usize) % count;
2186 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
2187 }
2188}
2189
2190impl Mapper for Multicart231 {
2191 fn caps(&self) -> MapperCaps {
2192 MapperCaps::NONE
2193 }
2194
2195 fn cpu_read(&mut self, addr: u16) -> u8 {
2196 match addr {
2197 0x8000..=0xBFFF => self.read_prg(self.prg_bank0, addr),
2198 0xC000..=0xFFFF => self.read_prg(self.prg_bank1, addr),
2199 _ => 0,
2200 }
2201 }
2202
2203 fn cpu_write(&mut self, addr: u16, _value: u8) {
2204 if (0x8000..=0xFFFF).contains(&addr) {
2205 let prg_bank = ((((addr >> 5) & 0x01) | (addr & 0x1E)) & 0xFF) as u8;
2206 self.prg_bank0 = prg_bank & 0x1E;
2207 self.prg_bank1 = prg_bank;
2208 self.horizontal_mirroring = (addr & 0x80) != 0;
2209 }
2210 }
2211
2212 fn ppu_read(&mut self, addr: u16) -> u8 {
2213 let addr = addr & 0x3FFF;
2214 match addr {
2215 0x0000..=0x1FFF => self.chr_ram[addr as usize],
2216 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
2217 _ => 0,
2218 }
2219 }
2220
2221 fn ppu_write(&mut self, addr: u16, value: u8) {
2222 let addr = addr & 0x3FFF;
2223 match addr {
2224 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
2225 0x2000..=0x3EFF => {
2226 let off = nametable_offset(addr, self.current_mirroring());
2227 self.vram[off] = value;
2228 }
2229 _ => {}
2230 }
2231 }
2232
2233 fn current_mirroring(&self) -> Mirroring {
2234 if self.horizontal_mirroring {
2235 Mirroring::Horizontal
2236 } else {
2237 Mirroring::Vertical
2238 }
2239 }
2240
2241 fn save_state(&self) -> Vec<u8> {
2242 let mut out = Vec::with_capacity(4 + self.vram.len() + self.chr_ram.len());
2243 out.push(SAVE_STATE_VERSION);
2244 out.push(self.prg_bank0);
2245 out.push(self.prg_bank1);
2246 out.push(u8::from(self.horizontal_mirroring));
2247 out.extend_from_slice(&self.vram);
2248 out.extend_from_slice(&self.chr_ram);
2249 out
2250 }
2251
2252 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
2253 let expected = 4 + self.vram.len() + self.chr_ram.len();
2254 if data.len() != expected {
2255 return Err(MapperError::WrongLength {
2256 expected,
2257 got: data.len(),
2258 });
2259 }
2260 if data[0] != SAVE_STATE_VERSION {
2261 return Err(MapperError::UnsupportedVersion(data[0]));
2262 }
2263 self.prg_bank0 = data[1];
2264 self.prg_bank1 = data[2];
2265 self.horizontal_mirroring = data[3] != 0;
2266 let mut cursor = 4;
2267 self.vram
2268 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
2269 cursor += self.vram.len();
2270 self.chr_ram
2271 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
2272 Ok(())
2273 }
2274}
2275
2276pub struct Maxi15M234 {
2278 prg_rom: Box<[u8]>,
2279 chr_rom: Box<[u8]>,
2280 vram: Box<[u8]>,
2281 reg0: u8,
2282 reg1: u8,
2283}
2284
2285impl Maxi15M234 {
2286 pub fn new(
2293 prg_rom: Box<[u8]>,
2294 chr_rom: Box<[u8]>,
2295 _mirroring: Mirroring,
2296 ) -> Result<Self, MapperError> {
2297 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
2298 return Err(MapperError::Invalid(format!(
2299 "mapper 234 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
2300 prg_rom.len()
2301 )));
2302 }
2303 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
2304 return Err(MapperError::Invalid(format!(
2305 "mapper 234 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
2306 chr_rom.len()
2307 )));
2308 }
2309 Ok(Self {
2310 prg_rom,
2311 chr_rom,
2312 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
2313 reg0: 0,
2314 reg1: 0,
2315 })
2316 }
2317
2318 const fn update_state(&mut self) {
2319 if (self.reg0 & 0x40) != 0 {
2320 self.reg1 &= 0x71;
2322 } else {
2323 self.reg1 &= 0x31;
2325 }
2326 }
2327
2328 fn latch_access(&mut self, addr: u16, value: u8) {
2329 if (0xFF80..=0xFF9F).contains(&addr) {
2330 if self.reg0.trailing_zeros() >= 6 {
2332 self.reg0 = value;
2333 self.update_state();
2334 }
2335 } else if (0xFFE8..=0xFFF8).contains(&addr) {
2336 self.reg1 = value & 0x71;
2337 self.update_state();
2338 }
2339 }
2340
2341 const fn prg_bank(&self) -> u8 {
2342 if (self.reg0 & 0x40) != 0 {
2343 (self.reg0 & 0x0E) | (self.reg1 & 0x01)
2344 } else {
2345 self.reg0 & 0x0F
2346 }
2347 }
2348
2349 const fn chr_bank(&self) -> u8 {
2350 if (self.reg0 & 0x40) != 0 {
2351 ((self.reg0 << 2) & 0x38) | ((self.reg1 >> 4) & 0x07)
2352 } else {
2353 ((self.reg0 << 2) & 0x3C) | ((self.reg1 >> 4) & 0x03)
2354 }
2355 }
2356}
2357
2358impl Mapper for Maxi15M234 {
2359 fn caps(&self) -> MapperCaps {
2360 MapperCaps::NONE
2361 }
2362
2363 fn cpu_read(&mut self, addr: u16) -> u8 {
2364 if (0x8000..=0xFFFF).contains(&addr) {
2365 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
2366 let bank = (self.prg_bank() as usize) % count;
2367 let value = self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)];
2368 self.latch_access(addr, value);
2371 value
2372 } else {
2373 0
2374 }
2375 }
2376
2377 fn cpu_write(&mut self, addr: u16, value: u8) {
2378 if (0x8000..=0xFFFF).contains(&addr) {
2379 self.latch_access(addr, value);
2380 }
2381 }
2382
2383 fn ppu_read(&mut self, addr: u16) -> u8 {
2384 let addr = addr & 0x3FFF;
2385 match addr {
2386 0x0000..=0x1FFF => {
2387 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
2388 let bank = (self.chr_bank() as usize) % count;
2389 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
2390 }
2391 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
2392 _ => 0,
2393 }
2394 }
2395
2396 fn ppu_write(&mut self, addr: u16, value: u8) {
2397 let addr = addr & 0x3FFF;
2398 if let 0x2000..=0x3EFF = addr {
2399 let off = nametable_offset(addr, self.current_mirroring());
2400 self.vram[off] = value;
2401 }
2402 }
2403
2404 fn current_mirroring(&self) -> Mirroring {
2405 if (self.reg0 & 0x80) != 0 {
2406 Mirroring::Horizontal
2407 } else {
2408 Mirroring::Vertical
2409 }
2410 }
2411
2412 fn save_state(&self) -> Vec<u8> {
2413 let mut out = Vec::with_capacity(3 + self.vram.len());
2414 out.push(SAVE_STATE_VERSION);
2415 out.push(self.reg0);
2416 out.push(self.reg1);
2417 out.extend_from_slice(&self.vram);
2418 out
2419 }
2420
2421 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
2422 let expected = 3 + self.vram.len();
2423 if data.len() != expected {
2424 return Err(MapperError::WrongLength {
2425 expected,
2426 got: data.len(),
2427 });
2428 }
2429 if data[0] != SAVE_STATE_VERSION {
2430 return Err(MapperError::UnsupportedVersion(data[0]));
2431 }
2432 self.reg0 = data[1];
2433 self.reg1 = data[2];
2434 self.vram.copy_from_slice(&data[3..3 + self.vram.len()]);
2435 Ok(())
2436 }
2437}
2438
2439pub struct Multicart225 {
2441 prg_rom: Box<[u8]>,
2442 chr_rom: Box<[u8]>,
2443 vram: Box<[u8]>,
2444 scratch: [u8; 4],
2446 prg_bank: u8,
2447 prg16_mode: bool,
2448 chr_bank: u8,
2449 horizontal_mirroring: bool,
2450}
2451
2452impl Multicart225 {
2453 pub fn new(
2460 prg_rom: Box<[u8]>,
2461 chr_rom: Box<[u8]>,
2462 _mirroring: Mirroring,
2463 ) -> Result<Self, MapperError> {
2464 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
2465 return Err(MapperError::Invalid(format!(
2466 "mapper 225 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
2467 prg_rom.len()
2468 )));
2469 }
2470 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
2471 return Err(MapperError::Invalid(format!(
2472 "mapper 225 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
2473 chr_rom.len()
2474 )));
2475 }
2476 Ok(Self {
2477 prg_rom,
2478 chr_rom,
2479 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
2480 scratch: [0; 4],
2481 prg_bank: 0,
2482 prg16_mode: false,
2483 chr_bank: 0,
2484 horizontal_mirroring: false,
2485 })
2486 }
2487
2488 fn read_prg(&self, bank16: usize, addr: u16) -> u8 {
2489 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
2490 let bank = bank16 % count;
2491 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
2492 }
2493}
2494
2495impl Mapper for Multicart225 {
2496 fn caps(&self) -> MapperCaps {
2497 MapperCaps::NONE
2498 }
2499
2500 fn cpu_read_unmapped(&self, addr: u16) -> bool {
2506 (0x4020..=0x57FF).contains(&addr)
2508 || (matches!(addr, 0x6000..=0x7FFF) && self.sram().is_empty())
2509 }
2510
2511 fn cpu_read(&mut self, addr: u16) -> u8 {
2512 match addr {
2513 0x5800..=0x5FFF => self.scratch[(addr & 0x03) as usize] & 0x0F,
2514 0x8000..=0xBFFF => {
2515 let base = if self.prg16_mode {
2516 self.prg_bank as usize
2517 } else {
2518 (self.prg_bank as usize) & !1
2519 };
2520 self.read_prg(base, addr)
2521 }
2522 0xC000..=0xFFFF => {
2523 let bank = if self.prg16_mode {
2524 self.prg_bank as usize
2525 } else {
2526 ((self.prg_bank as usize) & !1) | 1
2527 };
2528 self.read_prg(bank, addr)
2529 }
2530 _ => 0,
2531 }
2532 }
2533
2534 fn cpu_write(&mut self, addr: u16, value: u8) {
2535 match addr {
2536 0x5800..=0x5FFF => self.scratch[(addr & 0x03) as usize] = value & 0x0F,
2537 0x8000..=0xFFFF => {
2538 let high = ((addr >> 14) & 0x01) as u8;
2544 self.prg16_mode = (addr & 0x1000) != 0;
2545 self.prg_bank = (high << 6) | (((addr >> 6) & 0x3F) as u8);
2546 self.chr_bank = (high << 6) | ((addr & 0x3F) as u8);
2547 self.horizontal_mirroring = (addr & 0x2000) != 0;
2548 }
2549 _ => {}
2550 }
2551 }
2552
2553 fn ppu_read(&mut self, addr: u16) -> u8 {
2554 let addr = addr & 0x3FFF;
2555 match addr {
2556 0x0000..=0x1FFF => {
2557 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
2558 let bank = (self.chr_bank as usize) % count;
2559 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
2560 }
2561 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
2562 _ => 0,
2563 }
2564 }
2565
2566 fn ppu_write(&mut self, addr: u16, value: u8) {
2567 let addr = addr & 0x3FFF;
2568 if let 0x2000..=0x3EFF = addr {
2569 let off = nametable_offset(addr, self.current_mirroring());
2570 self.vram[off] = value;
2571 }
2572 }
2573
2574 fn current_mirroring(&self) -> Mirroring {
2575 if self.horizontal_mirroring {
2576 Mirroring::Horizontal
2577 } else {
2578 Mirroring::Vertical
2579 }
2580 }
2581
2582 fn save_state(&self) -> Vec<u8> {
2583 let mut out = Vec::with_capacity(4 + 4 + self.vram.len());
2584 out.push(SAVE_STATE_VERSION);
2585 out.push(self.prg_bank);
2586 out.push(u8::from(self.prg16_mode));
2587 out.push(self.chr_bank);
2588 out.push(u8::from(self.horizontal_mirroring));
2589 out.extend_from_slice(&self.scratch);
2590 out.extend_from_slice(&self.vram);
2591 out
2592 }
2593
2594 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
2595 let expected = 5 + 4 + self.vram.len();
2596 if data.len() != expected {
2597 return Err(MapperError::WrongLength {
2598 expected,
2599 got: data.len(),
2600 });
2601 }
2602 if data[0] != SAVE_STATE_VERSION {
2603 return Err(MapperError::UnsupportedVersion(data[0]));
2604 }
2605 self.prg_bank = data[1];
2606 self.prg16_mode = data[2] != 0;
2607 self.chr_bank = data[3];
2608 self.horizontal_mirroring = data[4] != 0;
2609 self.scratch.copy_from_slice(&data[5..9]);
2610 self.vram.copy_from_slice(&data[9..9 + self.vram.len()]);
2611 Ok(())
2612 }
2613}
2614
2615pub struct Multicart226 {
2634 prg_rom: Box<[u8]>,
2635 chr_ram: Box<[u8]>,
2636 vram: Box<[u8]>,
2637 reg0: u8,
2638 reg1: u8,
2639}
2640
2641impl Multicart226 {
2642 pub fn new(
2649 prg_rom: Box<[u8]>,
2650 _chr_rom: &[u8],
2651 _mirroring: Mirroring,
2652 ) -> Result<Self, MapperError> {
2653 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
2654 return Err(MapperError::Invalid(format!(
2655 "mapper 226 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
2656 prg_rom.len()
2657 )));
2658 }
2659 Ok(Self {
2660 prg_rom,
2661 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
2662 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
2663 reg0: 0,
2664 reg1: 0,
2665 })
2666 }
2667
2668 const fn prg_bank(&self) -> usize {
2671 let low = (self.reg0 & 0x1F) as usize;
2672 let bit5 = ((self.reg0 >> 7) & 0x01) as usize;
2673 let high = (self.reg1 & 0x01) as usize;
2674 (high << 6) | (bit5 << 5) | low
2675 }
2676
2677 const fn is_16k(&self) -> bool {
2680 (self.reg0 & 0x20) != 0
2681 }
2682
2683 fn read_prg(&self, bank16: usize, addr: u16) -> u8 {
2684 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
2685 let bank = bank16 % count;
2686 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
2687 }
2688}
2689
2690impl Mapper for Multicart226 {
2691 fn caps(&self) -> MapperCaps {
2692 MapperCaps::NONE
2693 }
2694
2695 fn cpu_read(&mut self, addr: u16) -> u8 {
2696 if (0x8000..=0xFFFF).contains(&addr) {
2697 let bank = self.prg_bank();
2698 if self.is_16k() {
2699 self.read_prg(bank, addr)
2701 } else {
2702 let base = bank & !1;
2705 let bank16 = base | usize::from(addr >= 0xC000);
2706 self.read_prg(bank16, addr)
2707 }
2708 } else {
2709 0
2710 }
2711 }
2712
2713 fn cpu_write(&mut self, addr: u16, value: u8) {
2714 match addr {
2715 0x8000..=0xFFFF if (addr & 0x01) == 0 => self.reg0 = value,
2716 0x8000..=0xFFFF => self.reg1 = value,
2717 _ => {}
2718 }
2719 }
2720
2721 fn ppu_read(&mut self, addr: u16) -> u8 {
2722 let addr = addr & 0x3FFF;
2723 match addr {
2724 0x0000..=0x1FFF => self.chr_ram[addr as usize],
2725 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
2726 _ => 0,
2727 }
2728 }
2729
2730 fn ppu_write(&mut self, addr: u16, value: u8) {
2731 let addr = addr & 0x3FFF;
2732 match addr {
2733 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
2734 0x2000..=0x3EFF => {
2735 let off = nametable_offset(addr, self.current_mirroring());
2736 self.vram[off] = value;
2737 }
2738 _ => {}
2739 }
2740 }
2741
2742 fn reset(&mut self) {
2745 self.reg0 = 0;
2746 self.reg1 = 0;
2747 }
2748
2749 fn current_mirroring(&self) -> Mirroring {
2750 if (self.reg0 & 0x40) != 0 {
2752 Mirroring::Vertical
2753 } else {
2754 Mirroring::Horizontal
2755 }
2756 }
2757
2758 fn save_state(&self) -> Vec<u8> {
2759 let mut out = Vec::with_capacity(3 + self.vram.len() + self.chr_ram.len());
2760 out.push(SAVE_STATE_VERSION);
2761 out.push(self.reg0);
2762 out.push(self.reg1);
2763 out.extend_from_slice(&self.vram);
2764 out.extend_from_slice(&self.chr_ram);
2765 out
2766 }
2767
2768 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
2769 let expected = 3 + self.vram.len() + self.chr_ram.len();
2770 if data.len() != expected {
2771 return Err(MapperError::WrongLength {
2772 expected,
2773 got: data.len(),
2774 });
2775 }
2776 if data[0] != SAVE_STATE_VERSION {
2777 return Err(MapperError::UnsupportedVersion(data[0]));
2778 }
2779 self.reg0 = data[1];
2780 self.reg1 = data[2];
2781 let mut cursor = 3;
2782 self.vram
2783 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
2784 cursor += self.vram.len();
2785 self.chr_ram
2786 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
2787 Ok(())
2788 }
2789}
2790
2791#[allow(clippy::struct_excessive_bools)] pub struct Multicart227 {
2810 prg_rom: Box<[u8]>,
2811 chr_ram: Box<[u8]>,
2812 vram: Box<[u8]>,
2813 prg_bank: u8,
2814 s_flag: bool,
2815 l_flag: bool,
2816 prg_mode: bool,
2817 horizontal_mirroring: bool,
2818 wram: Box<[u8]>,
2823}
2824
2825impl Multicart227 {
2826 pub fn new(
2833 prg_rom: Box<[u8]>,
2834 _chr_rom: &[u8],
2835 _mirroring: Mirroring,
2836 ) -> Result<Self, MapperError> {
2837 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
2838 return Err(MapperError::Invalid(format!(
2839 "mapper 227 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
2840 prg_rom.len()
2841 )));
2842 }
2843 Ok(Self {
2844 prg_rom,
2845 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
2846 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
2847 prg_bank: 0,
2848 s_flag: false,
2849 l_flag: false,
2850 prg_mode: false,
2851 horizontal_mirroring: false,
2852 wram: Box::new([]),
2853 })
2854 }
2855
2856 #[must_use]
2859 pub fn with_battery(mut self, battery: bool) -> Self {
2860 self.wram = if battery {
2861 vec![0u8; 0x2000].into_boxed_slice()
2862 } else {
2863 Box::new([])
2864 };
2865 self
2866 }
2867
2868 fn read_prg(&self, bank16: usize, addr: u16) -> u8 {
2869 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
2870 let bank = bank16 % count;
2871 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
2872 }
2873
2874 const fn prg_pages(&self) -> (usize, usize) {
2877 let b = self.prg_bank as usize;
2878 if self.prg_mode {
2879 if self.s_flag {
2880 (b & 0xFE, (b & 0xFE) | 1) } else {
2882 (b, b) }
2884 } else {
2885 let lo = if self.s_flag { b & 0x3E } else { b };
2886 let hi = if self.l_flag { b | 0x07 } else { b & 0x38 };
2887 (lo, hi)
2888 }
2889 }
2890}
2891
2892impl Mapper for Multicart227 {
2893 fn caps(&self) -> MapperCaps {
2894 MapperCaps::NONE
2895 }
2896
2897 fn sram(&self) -> &[u8] {
2898 &self.wram
2899 }
2900 fn sram_mut(&mut self) -> &mut [u8] {
2901 &mut self.wram
2902 }
2903
2904 fn cpu_read(&mut self, addr: u16) -> u8 {
2905 let (p0, p1) = self.prg_pages();
2906 match addr {
2907 0x6000..=0x7FFF if !self.wram.is_empty() => self.wram[usize::from(addr - 0x6000)],
2908 0x8000..=0xBFFF => self.read_prg(p0, addr),
2909 0xC000..=0xFFFF => self.read_prg(p1, addr),
2910 _ => 0,
2911 }
2912 }
2913
2914 fn cpu_write(&mut self, addr: u16, value: u8) {
2915 if (0x6000..=0x7FFF).contains(&addr) && !self.wram.is_empty() {
2916 self.wram[usize::from(addr - 0x6000)] = value;
2917 return;
2918 }
2919 if (0x8000..=0xFFFF).contains(&addr) {
2920 let low = ((addr >> 2) & 0x1F) as u8;
2921 let high = ((addr & 0x100) >> 3) as u8; self.prg_bank = low | high;
2923 self.s_flag = (addr & 0x01) != 0;
2924 self.prg_mode = ((addr >> 7) & 0x01) != 0;
2925 self.l_flag = ((addr >> 9) & 0x01) != 0;
2926 self.horizontal_mirroring = (addr & 0x02) != 0;
2927 }
2928 }
2929
2930 fn ppu_read(&mut self, addr: u16) -> u8 {
2931 let addr = addr & 0x3FFF;
2932 match addr {
2933 0x0000..=0x1FFF => self.chr_ram[addr as usize],
2934 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
2935 _ => 0,
2936 }
2937 }
2938
2939 fn ppu_write(&mut self, addr: u16, value: u8) {
2940 let addr = addr & 0x3FFF;
2941 match addr {
2942 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
2943 0x2000..=0x3EFF => {
2944 let off = nametable_offset(addr, self.current_mirroring());
2945 self.vram[off] = value;
2946 }
2947 _ => {}
2948 }
2949 }
2950
2951 fn current_mirroring(&self) -> Mirroring {
2952 if self.horizontal_mirroring {
2953 Mirroring::Horizontal
2954 } else {
2955 Mirroring::Vertical
2956 }
2957 }
2958
2959 fn save_state(&self) -> Vec<u8> {
2960 let mut out = Vec::with_capacity(6 + self.vram.len() + self.chr_ram.len());
2961 out.push(SAVE_STATE_VERSION);
2962 out.push(self.prg_bank);
2963 out.push(u8::from(self.s_flag));
2964 out.push(u8::from(self.l_flag));
2965 out.push(u8::from(self.prg_mode));
2966 out.push(u8::from(self.horizontal_mirroring));
2967 out.extend_from_slice(&self.vram);
2968 out.extend_from_slice(&self.chr_ram);
2969 out.extend_from_slice(&self.wram);
2972 out
2973 }
2974
2975 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
2976 let expected = 6 + self.vram.len() + self.chr_ram.len() + self.wram.len();
2981 if data.len() != expected {
2982 return Err(MapperError::WrongLength {
2983 expected,
2984 got: data.len(),
2985 });
2986 }
2987 if data[0] != SAVE_STATE_VERSION {
2988 return Err(MapperError::UnsupportedVersion(data[0]));
2989 }
2990 self.prg_bank = data[1];
2991 self.s_flag = data[2] != 0;
2992 self.l_flag = data[3] != 0;
2993 self.prg_mode = data[4] != 0;
2994 self.horizontal_mirroring = data[5] != 0;
2995 let mut cursor = 6;
2996 self.vram
2997 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
2998 cursor += self.vram.len();
2999 self.chr_ram
3000 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
3001 cursor += self.chr_ram.len();
3002 self.wram.copy_from_slice(&data[cursor..]);
3003 Ok(())
3004 }
3005}
3006
3007pub struct Multicart229 {
3020 prg_rom: Box<[u8]>,
3021 chr_rom: Box<[u8]>,
3022 vram: Box<[u8]>,
3023 addr_latch: u8,
3025 chr_bank: u8,
3026 horizontal_mirroring: bool,
3027}
3028
3029impl Multicart229 {
3030 pub fn new(
3037 prg_rom: Box<[u8]>,
3038 chr_rom: Box<[u8]>,
3039 _mirroring: Mirroring,
3040 ) -> Result<Self, MapperError> {
3041 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
3042 return Err(MapperError::Invalid(format!(
3043 "mapper 229 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
3044 prg_rom.len()
3045 )));
3046 }
3047 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
3048 return Err(MapperError::Invalid(format!(
3049 "mapper 229 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
3050 chr_rom.len()
3051 )));
3052 }
3053 Ok(Self {
3054 prg_rom,
3055 chr_rom,
3056 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
3057 addr_latch: 0,
3058 chr_bank: 0,
3059 horizontal_mirroring: false,
3060 })
3061 }
3062
3063 const fn is_menu(&self) -> bool {
3065 (self.addr_latch & 0x1E) == 0
3066 }
3067
3068 fn read_prg(&self, bank16: usize, addr: u16) -> u8 {
3069 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
3070 let bank = bank16 % count;
3071 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
3072 }
3073}
3074
3075impl Mapper for Multicart229 {
3076 fn caps(&self) -> MapperCaps {
3077 MapperCaps::NONE
3078 }
3079
3080 fn cpu_read(&mut self, addr: u16) -> u8 {
3081 if !(0x8000..=0xFFFF).contains(&addr) {
3082 return 0;
3083 }
3084 if self.is_menu() {
3085 let bank16 = usize::from(addr >= 0xC000);
3087 self.read_prg(bank16, addr)
3088 } else {
3089 let bank = (self.addr_latch & 0x1F) as usize;
3091 self.read_prg(bank, addr)
3092 }
3093 }
3094
3095 fn cpu_write(&mut self, addr: u16, _value: u8) {
3096 if (0x8000..=0xFFFF).contains(&addr) {
3097 self.addr_latch = (addr & 0x3F) as u8;
3098 self.chr_bank = (addr & 0x0F) as u8;
3099 self.horizontal_mirroring = (addr & 0x20) != 0;
3100 }
3101 }
3102
3103 fn ppu_read(&mut self, addr: u16) -> u8 {
3104 let addr = addr & 0x3FFF;
3105 match addr {
3106 0x0000..=0x1FFF => {
3107 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
3108 let bank = (self.chr_bank as usize) % count;
3109 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
3110 }
3111 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
3112 _ => 0,
3113 }
3114 }
3115
3116 fn ppu_write(&mut self, addr: u16, value: u8) {
3117 let addr = addr & 0x3FFF;
3118 if let 0x2000..=0x3EFF = addr {
3119 let off = nametable_offset(addr, self.current_mirroring());
3120 self.vram[off] = value;
3121 }
3122 }
3123
3124 fn current_mirroring(&self) -> Mirroring {
3125 if self.horizontal_mirroring {
3126 Mirroring::Horizontal
3127 } else {
3128 Mirroring::Vertical
3129 }
3130 }
3131
3132 fn save_state(&self) -> Vec<u8> {
3133 let mut out = Vec::with_capacity(4 + self.vram.len());
3134 out.push(SAVE_STATE_VERSION);
3135 out.push(self.addr_latch);
3136 out.push(self.chr_bank);
3137 out.push(u8::from(self.horizontal_mirroring));
3138 out.extend_from_slice(&self.vram);
3139 out
3140 }
3141
3142 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
3143 let expected = 4 + self.vram.len();
3144 if data.len() != expected {
3145 return Err(MapperError::WrongLength {
3146 expected,
3147 got: data.len(),
3148 });
3149 }
3150 if data[0] != SAVE_STATE_VERSION {
3151 return Err(MapperError::UnsupportedVersion(data[0]));
3152 }
3153 self.addr_latch = data[1];
3154 self.chr_bank = data[2];
3155 self.horizontal_mirroring = data[3] != 0;
3156 self.vram.copy_from_slice(&data[4..4 + self.vram.len()]);
3157 Ok(())
3158 }
3159}
3160
3161pub struct Multicart233 {
3175 prg_rom: Box<[u8]>,
3176 chr_ram: Box<[u8]>,
3177 vram: Box<[u8]>,
3178 outer_block: u8,
3180 prg_bank: u8,
3181 mode_16k: bool,
3184 mirror_mode: u8,
3185}
3186
3187impl Multicart233 {
3188 pub fn new(
3195 prg_rom: Box<[u8]>,
3196 _chr_rom: &[u8],
3197 _mirroring: Mirroring,
3198 ) -> Result<Self, MapperError> {
3199 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
3200 return Err(MapperError::Invalid(format!(
3201 "mapper 233 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
3202 prg_rom.len()
3203 )));
3204 }
3205 Ok(Self {
3206 prg_rom,
3207 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
3208 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
3209 outer_block: 0,
3210 prg_bank: 0,
3211 mode_16k: false,
3212 mirror_mode: 0,
3213 })
3214 }
3215
3216 fn read_prg(&self, bank16: usize, addr: u16) -> u8 {
3217 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
3218 let bank = bank16 % count;
3219 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
3220 }
3221}
3222
3223impl Mapper for Multicart233 {
3224 fn caps(&self) -> MapperCaps {
3225 MapperCaps::NONE
3226 }
3227
3228 fn cpu_read(&mut self, addr: u16) -> u8 {
3229 let bank16 = (self.prg_bank as usize) | ((self.outer_block as usize) << 5);
3235 match addr {
3236 0x8000..=0xBFFF => {
3237 let b = if self.mode_16k { bank16 } else { bank16 & !1 };
3238 self.read_prg(b, addr)
3239 }
3240 0xC000..=0xFFFF => {
3241 let b = if self.mode_16k {
3242 bank16
3243 } else {
3244 (bank16 & !1) | 1
3245 };
3246 self.read_prg(b, addr)
3247 }
3248 _ => 0,
3249 }
3250 }
3251
3252 fn cpu_write(&mut self, _addr: u16, value: u8) {
3253 self.prg_bank = value & 0x1F;
3259 self.mode_16k = (value & 0x20) != 0;
3260 self.mirror_mode = (value >> 6) & 0x03;
3261 }
3262
3263 fn ppu_read(&mut self, addr: u16) -> u8 {
3264 let addr = addr & 0x3FFF;
3265 match addr {
3266 0x0000..=0x1FFF => self.chr_ram[addr as usize],
3267 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
3268 _ => 0,
3269 }
3270 }
3271
3272 fn ppu_write(&mut self, addr: u16, value: u8) {
3273 let addr = addr & 0x3FFF;
3274 match addr {
3275 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
3276 0x2000..=0x3EFF => {
3277 let off = nametable_offset(addr, self.current_mirroring());
3278 self.vram[off] = value;
3279 }
3280 _ => {}
3281 }
3282 }
3283
3284 fn current_mirroring(&self) -> Mirroring {
3285 match self.mirror_mode {
3286 0 => Mirroring::SingleScreenA,
3287 1 => Mirroring::Vertical,
3288 2 => Mirroring::Horizontal,
3289 _ => Mirroring::SingleScreenB,
3290 }
3291 }
3292
3293 fn save_state(&self) -> Vec<u8> {
3294 let mut out = Vec::with_capacity(5 + self.vram.len() + self.chr_ram.len());
3295 out.push(SAVE_STATE_VERSION);
3296 out.push(self.outer_block);
3297 out.push(self.prg_bank);
3298 out.push(u8::from(self.mode_16k));
3299 out.push(self.mirror_mode);
3300 out.extend_from_slice(&self.vram);
3301 out.extend_from_slice(&self.chr_ram);
3302 out
3303 }
3304
3305 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
3306 let expected = 5 + self.vram.len() + self.chr_ram.len();
3307 if data.len() != expected {
3308 return Err(MapperError::WrongLength {
3309 expected,
3310 got: data.len(),
3311 });
3312 }
3313 if data[0] != SAVE_STATE_VERSION {
3314 return Err(MapperError::UnsupportedVersion(data[0]));
3315 }
3316 self.outer_block = data[1];
3317 self.prg_bank = data[2];
3318 self.mode_16k = data[3] != 0;
3319 self.mirror_mode = data[4] & 0x03;
3323 let mut cursor = 5;
3324 self.vram
3325 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
3326 cursor += self.vram.len();
3327 self.chr_ram
3328 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
3329 Ok(())
3330 }
3331}
3332
3333#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3335pub enum DiscreteBoard {
3336 M46,
3338 M51,
3340 M57,
3342 M104,
3344 M120,
3346 M290,
3348 M301,
3350}
3351
3352pub struct DiscreteMapper {
3354 board: DiscreteBoard,
3355 prg_rom: Box<[u8]>,
3356 chr: Box<[u8]>,
3357 chr_is_ram: bool,
3358 vram: Box<[u8]>,
3359 reg0: u8,
3360 reg1: u8,
3361 last_addr: u16,
3363 mirroring: Mirroring,
3364}
3365
3366impl DiscreteMapper {
3367 fn new(
3368 board: DiscreteBoard,
3369 prg_rom: Box<[u8]>,
3370 chr_rom: Box<[u8]>,
3371 mirroring: Mirroring,
3372 id: u16,
3373 ) -> Result<Self, MapperError> {
3374 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
3375 return Err(MapperError::Invalid(format!(
3376 "mapper {id} PRG-ROM size {} is not a non-zero multiple of 8 KiB",
3377 prg_rom.len()
3378 )));
3379 }
3380 let chr_is_ram = chr_rom.is_empty();
3381 let chr: Box<[u8]> = if chr_is_ram {
3382 vec![0u8; CHR_BANK_8K].into_boxed_slice()
3383 } else {
3384 chr_rom
3385 };
3386 Ok(Self {
3387 board,
3388 prg_rom,
3389 chr,
3390 chr_is_ram,
3391 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
3392 reg0: 0,
3393 reg1: 0,
3394 last_addr: 0,
3395 mirroring,
3396 })
3397 }
3398
3399 fn prg_16k(&self, bank: usize, addr: u16) -> u8 {
3400 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
3401 let bank = bank % count;
3402 self.prg_rom[(bank * PRG_BANK_16K + (addr as usize & 0x3FFF)) % self.prg_rom.len()]
3408 }
3409
3410 fn prg_32k(&self, bank: usize, addr: u16) -> u8 {
3411 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
3412 let bank = bank % count;
3413 self.prg_rom[(bank * PRG_BANK_32K + (addr as usize & 0x7FFF)) % self.prg_rom.len()]
3418 }
3419
3420 fn prg_8k(&self, bank: usize, addr: u16) -> u8 {
3421 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
3422 let bank = bank % count;
3423 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
3424 }
3425
3426 fn chr_8k_bank(&self) -> usize {
3427 match self.board {
3428 DiscreteBoard::M46 => {
3429 ((self.reg0 as usize & 0xF0) >> 1) | ((self.reg1 as usize & 0x70) >> 4)
3430 }
3431 DiscreteBoard::M57 => {
3432 ((self.reg0 as usize & 0x40) >> 3) | ((self.reg0 | self.reg1) as usize & 0x07)
3433 }
3434 _ => 0,
3435 }
3436 }
3437}
3438
3439impl Mapper for DiscreteMapper {
3440 fn caps(&self) -> MapperCaps {
3441 MapperCaps::NONE
3442 }
3443
3444 fn cpu_read(&mut self, addr: u16) -> u8 {
3445 match self.board {
3446 DiscreteBoard::M46 => {
3447 if (0x8000..=0xFFFF).contains(&addr) {
3448 let bank = ((self.reg0 as usize & 0x0F) << 1) | (self.reg1 as usize & 0x01);
3449 self.prg_32k(bank, addr)
3450 } else {
3451 0
3452 }
3453 }
3454 DiscreteBoard::M51 => {
3455 let bank4 = (self.reg0 as usize) << 2;
3456 match addr {
3457 0x6000..=0x7FFF => {
3458 let b = if self.reg1 & 0x01 != 0 {
3459 0x23 | bank4
3460 } else {
3461 0x2F | bank4
3462 };
3463 self.prg_8k(b, addr)
3464 }
3465 0x8000..=0xBFFF if self.reg1 & 0x01 != 0 => self.prg_16k(bank4 >> 1, addr),
3466 0xC000..=0xFFFF if self.reg1 & 0x01 != 0 => {
3467 self.prg_16k((bank4 >> 1) | 1, addr)
3468 }
3469 0x8000..=0xBFFF => {
3470 self.prg_16k((bank4 >> 1) | (self.reg1 as usize >> 1 & 0x01), addr)
3471 }
3472 0xC000..=0xFFFF => self.prg_16k((bank4 >> 1) | 0x07, addr),
3473 _ => 0,
3474 }
3475 }
3476 DiscreteBoard::M57 => {
3477 if (0x8000..=0xFFFF).contains(&addr) {
3478 if self.reg1 & 0x10 != 0 {
3479 let b = ((self.reg1 as usize >> 5) & 0x06) >> 1;
3480 self.prg_32k(b, addr)
3481 } else {
3482 let b16 = (self.reg1 as usize >> 5) & 0x07;
3483 self.prg_16k(b16, addr)
3484 }
3485 } else {
3486 0
3487 }
3488 }
3489 DiscreteBoard::M104 => match addr {
3490 0x8000..=0xBFFF => self.prg_16k(self.reg0 as usize, addr),
3491 0xC000..=0xFFFF => {
3492 let high = (self.reg1 as usize & 0x70) | 0x0F;
3493 self.prg_16k(high, addr)
3494 }
3495 _ => 0,
3496 },
3497 DiscreteBoard::M120 => match addr {
3498 0x6000..=0x7FFF => self.prg_8k(self.reg0 as usize, addr),
3499 0x8000..=0xFFFF => {
3500 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
3501 let slot = (addr as usize - 0x8000) / PRG_BANK_8K;
3502 let base = count.saturating_sub(4);
3503 self.prg_8k(base + slot, addr)
3504 }
3505 _ => 0,
3506 },
3507 DiscreteBoard::M290 => {
3508 if (0x8000..=0xFFFF).contains(&addr) {
3509 self.prg_16k(self.reg0 as usize, addr)
3510 } else {
3511 0
3512 }
3513 }
3514 DiscreteBoard::M301 => {
3515 if (0x8000..=0xFFFF).contains(&addr) {
3516 let a = self.last_addr;
3517 let inner = (a >> 2) & 0x07;
3518 let outer128 = (a >> 5) & 0x03;
3519 let outer256 = (a >> 7) & 0x01;
3523 let outer512 = (a >> 8) & 0x01;
3524 let bank16 = (outer512 << 6) | (outer256 << 5) | (outer128 << 3) | inner;
3525 self.prg_16k(bank16 as usize, addr)
3526 } else {
3527 0
3528 }
3529 }
3530 }
3531 }
3532
3533 fn cpu_read_unmapped(&self, addr: u16) -> bool {
3534 match self.board {
3535 DiscreteBoard::M51 | DiscreteBoard::M120 => (0x4020..=0x5FFF).contains(&addr),
3536 _ => (0x4020..=0x7FFF).contains(&addr),
3537 }
3538 }
3539
3540 fn cpu_write(&mut self, addr: u16, value: u8) {
3541 match self.board {
3542 DiscreteBoard::M46 => {
3543 if addr < 0x8000 {
3544 self.reg0 = value;
3545 } else {
3546 self.reg1 = value;
3547 }
3548 }
3549 DiscreteBoard::M51 => match addr {
3550 0x6000..=0x7FFF => self.reg1 = ((value >> 3) & 0x02) | ((value >> 1) & 0x01),
3551 0xC000..=0xDFFF => {
3552 self.reg0 = value & 0x0F;
3553 self.reg1 = ((value >> 3) & 0x02) | (self.reg1 & 0x01);
3554 }
3555 0x8000..=0xFFFF => self.reg0 = value & 0x0F,
3556 _ => {}
3557 },
3558 DiscreteBoard::M57 => match addr & 0x8800 {
3559 0x8000 => self.reg0 = value,
3560 0x8800 => self.reg1 = value,
3561 _ => {}
3562 },
3563 DiscreteBoard::M104 => {
3564 if addr >= 0xC000 {
3565 self.reg0 = (self.reg0 & 0xF0) | (value & 0x0F);
3566 } else if (0x8000..=0x9FFF).contains(&addr) && value & 0x08 != 0 {
3567 self.reg0 = (self.reg0 & 0x0F) | ((value << 4) & 0x70);
3568 self.reg1 = value;
3569 }
3570 }
3571 DiscreteBoard::M120 => {
3572 if addr == 0x41FF {
3573 self.reg0 = value;
3574 }
3575 }
3576 DiscreteBoard::M290 => {
3577 if (0x8000..=0xFFFF).contains(&addr) {
3578 let prg = ((addr >> 10) & 0x1E) as u8;
3579 let chr = (((addr & 0x0300) >> 5) | (addr & 0x07)) as u8;
3580 self.reg0 = if addr & 0x80 != 0 {
3581 prg | ((addr >> 6) & 1) as u8
3582 } else {
3583 prg & 0xFE
3584 };
3585 self.reg1 = chr;
3586 self.mirroring = if addr & 0x400 != 0 {
3587 Mirroring::Horizontal
3588 } else {
3589 Mirroring::Vertical
3590 };
3591 }
3592 }
3593 DiscreteBoard::M301 => {
3594 if (0x8000..=0xFFFF).contains(&addr) {
3595 self.last_addr = addr;
3596 self.mirroring = if addr & 0x02 != 0 {
3597 Mirroring::Horizontal
3598 } else {
3599 Mirroring::Vertical
3600 };
3601 }
3602 }
3603 }
3604 }
3605
3606 fn ppu_read(&mut self, addr: u16) -> u8 {
3607 let addr = addr & 0x3FFF;
3608 match addr {
3609 0x0000..=0x1FFF => {
3610 if self.chr_is_ram {
3611 return self.chr[addr as usize & (self.chr.len() - 1)];
3612 }
3613 let count = (self.chr.len() / CHR_BANK_8K).max(1);
3614 let bank = self.chr_8k_bank() % count;
3615 self.chr[bank * CHR_BANK_8K + (addr as usize & 0x1FFF)]
3616 }
3617 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
3618 _ => 0,
3619 }
3620 }
3621
3622 fn ppu_write(&mut self, addr: u16, value: u8) {
3623 let addr = addr & 0x3FFF;
3624 match addr {
3625 0x0000..=0x1FFF if self.chr_is_ram => {
3626 let off = addr as usize & (self.chr.len() - 1);
3627 self.chr[off] = value;
3628 }
3629 0x2000..=0x3EFF => {
3630 let off = nametable_offset(addr, self.mirroring);
3631 self.vram[off] = value;
3632 }
3633 _ => {}
3634 }
3635 }
3636
3637 fn current_mirroring(&self) -> Mirroring {
3638 self.mirroring
3639 }
3640
3641 fn save_state(&self) -> Vec<u8> {
3642 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
3643 let mut out = Vec::with_capacity(6 + self.vram.len() + chr_ram);
3644 out.push(SAVE_STATE_VERSION);
3645 out.push(self.reg0);
3646 out.push(self.reg1);
3647 out.push((self.last_addr & 0xFF) as u8);
3648 out.push((self.last_addr >> 8) as u8);
3649 out.push(mirroring_to_byte(self.mirroring));
3650 out.extend_from_slice(&self.vram);
3651 if self.chr_is_ram {
3652 out.extend_from_slice(&self.chr);
3653 }
3654 out
3655 }
3656
3657 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
3658 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
3659 let expected = 6 + self.vram.len() + chr_ram;
3660 if data.len() != expected {
3661 return Err(MapperError::WrongLength {
3662 expected,
3663 got: data.len(),
3664 });
3665 }
3666 if data[0] != SAVE_STATE_VERSION {
3667 return Err(MapperError::UnsupportedVersion(data[0]));
3668 }
3669 self.reg0 = data[1];
3670 self.reg1 = data[2];
3671 self.last_addr = u16::from(data[3]) | (u16::from(data[4]) << 8);
3672 self.mirroring = byte_to_mirroring(data[5], self.mirroring);
3673 let mut cursor = 6;
3674 self.vram
3675 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
3676 cursor += self.vram.len();
3677 if self.chr_is_ram {
3678 self.chr
3679 .copy_from_slice(&data[cursor..cursor + self.chr.len()]);
3680 }
3681 Ok(())
3682 }
3683}
3684
3685macro_rules! discrete_ctor {
3686 ($fn_name:ident, $board:expr, $id:expr, $doc:expr) => {
3687 #[doc = $doc]
3688 pub fn $fn_name(
3692 prg_rom: Box<[u8]>,
3693 chr_rom: Box<[u8]>,
3694 mirroring: Mirroring,
3695 ) -> Result<DiscreteMapper, MapperError> {
3696 DiscreteMapper::new($board, prg_rom, chr_rom, mirroring, $id)
3697 }
3698 };
3699}
3700
3701discrete_ctor!(
3702 new_m46,
3703 DiscreteBoard::M46,
3704 46,
3705 "Mapper 46 (Color Dreams Rumble Station 15-in-1)."
3706);
3707discrete_ctor!(
3708 new_m51,
3709 DiscreteBoard::M51,
3710 51,
3711 "Mapper 51 (BMC 11-in-1 multicart)."
3712);
3713discrete_ctor!(
3714 new_m57,
3715 DiscreteBoard::M57,
3716 57,
3717 "Mapper 57 (BMC GK 6-in-1 multicart)."
3718);
3719discrete_ctor!(
3720 new_m104,
3721 DiscreteBoard::M104,
3722 104,
3723 "Mapper 104 (Codemasters Golden Five / Pegasus 5-in-1)."
3724);
3725discrete_ctor!(
3726 new_m120,
3727 DiscreteBoard::M120,
3728 120,
3729 "Mapper 120 (Tobidase Daisakusen FDS-conversion protection)."
3730);
3731discrete_ctor!(
3732 new_m290,
3733 DiscreteBoard::M290,
3734 290,
3735 "Mapper 290 (NTDEC Asder BMC-NTD-03)."
3736);
3737discrete_ctor!(
3738 new_m301,
3739 DiscreteBoard::M301,
3740 301,
3741 "Mapper 301 (BMC-8157 address-as-data multicart)."
3742);
3743
3744pub struct Bmc204 {
3746 prg_rom: Box<[u8]>,
3747 chr: Box<[u8]>,
3748 chr_is_ram: bool,
3749 vram: Box<[u8]>,
3750 mirroring: Mirroring,
3751 prg0: usize,
3753 prg1: usize,
3754 chr8: usize,
3756}
3757
3758impl Bmc204 {
3759 fn new(
3760 prg_rom: Box<[u8]>,
3761 chr_rom: Box<[u8]>,
3762 mirroring: Mirroring,
3763 ) -> Result<Self, MapperError> {
3764 check_prg(&prg_rom, 204)?;
3765 if prg_rom.len() < PRG_BANK_16K {
3766 return Err(MapperError::Invalid(format!(
3767 "mapper 204 PRG-ROM size {} is smaller than one 16 KiB bank",
3768 prg_rom.len()
3769 )));
3770 }
3771 let (chr, chr_is_ram) = chr_or_ram(chr_rom);
3772 let mut m = Self {
3773 prg_rom,
3774 chr,
3775 chr_is_ram,
3776 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
3777 mirroring,
3778 prg0: 0,
3779 prg1: 0,
3780 chr8: 0,
3781 };
3782 m.write_addr(0x8000);
3784 Ok(m)
3785 }
3786
3787 fn prg_count_16k(&self) -> usize {
3788 (self.prg_rom.len() / PRG_BANK_16K).max(1)
3789 }
3790
3791 fn chr_count_8k(&self) -> usize {
3792 (self.chr.len() / CHR_BANK_8K).max(1)
3793 }
3794
3795 fn write_addr(&mut self, addr: u16) {
3796 let bit_mask = (addr & 0x06) as usize;
3797 let page = bit_mask
3798 + if bit_mask == 0x06 {
3799 0
3800 } else {
3801 (addr & 0x01) as usize
3802 };
3803 self.prg0 = page;
3804 self.prg1 = bit_mask
3805 + if bit_mask == 0x06 {
3806 1
3807 } else {
3808 (addr & 0x01) as usize
3809 };
3810 self.chr8 = page;
3811 self.mirroring = if addr & 0x10 != 0 {
3812 Mirroring::Horizontal
3813 } else {
3814 Mirroring::Vertical
3815 };
3816 }
3817
3818 fn prg_byte(&self, slot16: usize, addr: u16) -> u8 {
3819 let count = self.prg_count_16k();
3820 let bank = slot16 % count;
3821 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
3822 }
3823}
3824
3825impl Mapper for Bmc204 {
3826 fn caps(&self) -> MapperCaps {
3827 MapperCaps::NONE
3828 }
3829
3830 fn cpu_read(&mut self, addr: u16) -> u8 {
3831 match addr {
3832 0x8000..=0xBFFF => self.prg_byte(self.prg0, addr),
3833 0xC000..=0xFFFF => self.prg_byte(self.prg1, addr),
3834 _ => 0,
3835 }
3836 }
3837
3838 fn cpu_write(&mut self, addr: u16, _value: u8) {
3839 if addr >= 0x8000 {
3840 self.write_addr(addr);
3841 }
3842 }
3843
3844 fn ppu_read(&mut self, addr: u16) -> u8 {
3845 let addr = addr & 0x3FFF;
3846 match addr {
3847 0x0000..=0x1FFF => {
3848 if self.chr_is_ram {
3849 return self.chr[addr as usize & (CHR_BANK_8K - 1)];
3850 }
3851 let bank = self.chr8 % self.chr_count_8k();
3852 self.chr[bank * CHR_BANK_8K + (addr as usize & 0x1FFF)]
3853 }
3854 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
3855 _ => 0,
3856 }
3857 }
3858
3859 fn ppu_write(&mut self, addr: u16, value: u8) {
3860 let addr = addr & 0x3FFF;
3861 match addr {
3862 0x0000..=0x1FFF if self.chr_is_ram => {
3863 self.chr[addr as usize & (CHR_BANK_8K - 1)] = value;
3864 }
3865 0x2000..=0x3EFF => {
3866 let off = nametable_offset(addr, self.mirroring);
3867 self.vram[off] = value;
3868 }
3869 _ => {}
3870 }
3871 }
3872
3873 fn current_mirroring(&self) -> Mirroring {
3874 self.mirroring
3875 }
3876
3877 fn save_state(&self) -> Vec<u8> {
3878 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
3879 let mut out = Vec::with_capacity(1 + 12 + 1 + self.vram.len() + chr_ram);
3880 out.push(SAVE_STATE_VERSION);
3881 out.extend_from_slice(&(self.prg0 as u32).to_le_bytes());
3882 out.extend_from_slice(&(self.prg1 as u32).to_le_bytes());
3883 out.extend_from_slice(&(self.chr8 as u32).to_le_bytes());
3884 out.push(mirroring_to_byte(self.mirroring));
3885 out.extend_from_slice(&self.vram);
3886 if self.chr_is_ram {
3887 out.extend_from_slice(&self.chr);
3888 }
3889 out
3890 }
3891
3892 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
3893 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
3894 let expected = 1 + 12 + 1 + self.vram.len() + chr_ram;
3895 if data.len() != expected {
3896 return Err(MapperError::WrongLength {
3897 expected,
3898 got: data.len(),
3899 });
3900 }
3901 if data[0] != SAVE_STATE_VERSION {
3902 return Err(MapperError::UnsupportedVersion(data[0]));
3903 }
3904 let rd = |c: usize| {
3905 u32::from_le_bytes([data[c], data[c + 1], data[c + 2], data[c + 3]]) as usize
3906 };
3907 self.prg0 = rd(1);
3908 self.prg1 = rd(5);
3909 self.chr8 = rd(9);
3910 let mut c = 13;
3911 self.mirroring = byte_to_mirroring(data[c], self.mirroring);
3912 c += 1;
3913 self.vram.copy_from_slice(&data[c..c + self.vram.len()]);
3914 c += self.vram.len();
3915 if self.chr_is_ram {
3916 self.chr.copy_from_slice(&data[c..c + self.chr.len()]);
3917 }
3918 Ok(())
3919 }
3920}
3921
3922pub fn new_m204(
3927 prg_rom: Box<[u8]>,
3928 chr_rom: Box<[u8]>,
3929 mirroring: Mirroring,
3930) -> Result<Bmc204, MapperError> {
3931 Bmc204::new(prg_rom, chr_rom, mirroring)
3932}
3933
3934pub struct Bmc11160 {
3949 prg_rom: Box<[u8]>,
3950 chr: Box<[u8]>,
3951 chr_is_ram: bool,
3952 vram: Box<[u8]>,
3953 mirroring: Mirroring,
3954 prg32: usize,
3956 chr8: usize,
3958}
3959
3960impl Bmc11160 {
3961 fn new(
3962 prg_rom: Box<[u8]>,
3963 chr_rom: Box<[u8]>,
3964 mirroring: Mirroring,
3965 ) -> Result<Self, MapperError> {
3966 check_prg(&prg_rom, 299)?;
3967 if prg_rom.len() < PRG_BANK_32K {
3968 return Err(MapperError::Invalid(format!(
3969 "mapper 299 PRG-ROM size {} is smaller than one 32 KiB bank",
3970 prg_rom.len()
3971 )));
3972 }
3973 let (chr, chr_is_ram) = chr_or_ram(chr_rom);
3974 let mut m = Self {
3975 prg_rom,
3976 chr,
3977 chr_is_ram,
3978 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
3979 mirroring,
3980 prg32: 0,
3981 chr8: 0,
3982 };
3983 m.write_reg(0);
3985 Ok(m)
3986 }
3987
3988 fn prg_count_32k(&self) -> usize {
3989 (self.prg_rom.len() / PRG_BANK_32K).max(1)
3990 }
3991
3992 fn chr_count_8k(&self) -> usize {
3993 (self.chr.len() / CHR_BANK_8K).max(1)
3994 }
3995
3996 fn write_reg(&mut self, value: u8) {
3997 let bank = ((value >> 4) & 0x07) as usize;
3998 self.prg32 = bank;
3999 self.chr8 = (bank << 2) | (value as usize & 0x03);
4000 self.mirroring = if value & 0x80 != 0 {
4001 Mirroring::Vertical
4002 } else {
4003 Mirroring::Horizontal
4004 };
4005 }
4006}
4007
4008impl Mapper for Bmc11160 {
4009 fn caps(&self) -> MapperCaps {
4010 MapperCaps::NONE
4011 }
4012
4013 fn cpu_read(&mut self, addr: u16) -> u8 {
4014 match addr {
4015 0x8000..=0xFFFF => {
4016 let count = self.prg_count_32k();
4017 let bank = self.prg32 % count;
4018 self.prg_rom[bank * PRG_BANK_32K + (addr as usize & 0x7FFF)]
4019 }
4020 _ => 0,
4021 }
4022 }
4023
4024 fn cpu_write(&mut self, addr: u16, value: u8) {
4025 if addr >= 0x8000 {
4026 self.write_reg(value);
4027 }
4028 }
4029
4030 fn ppu_read(&mut self, addr: u16) -> u8 {
4031 let addr = addr & 0x3FFF;
4032 match addr {
4033 0x0000..=0x1FFF => {
4034 if self.chr_is_ram {
4035 return self.chr[addr as usize & (CHR_BANK_8K - 1)];
4036 }
4037 let bank = self.chr8 % self.chr_count_8k();
4038 self.chr[bank * CHR_BANK_8K + (addr as usize & 0x1FFF)]
4039 }
4040 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
4041 _ => 0,
4042 }
4043 }
4044
4045 fn ppu_write(&mut self, addr: u16, value: u8) {
4046 let addr = addr & 0x3FFF;
4047 match addr {
4048 0x0000..=0x1FFF if self.chr_is_ram => {
4049 self.chr[addr as usize & (CHR_BANK_8K - 1)] = value;
4050 }
4051 0x2000..=0x3EFF => {
4052 let off = nametable_offset(addr, self.mirroring);
4053 self.vram[off] = value;
4054 }
4055 _ => {}
4056 }
4057 }
4058
4059 fn current_mirroring(&self) -> Mirroring {
4060 self.mirroring
4061 }
4062
4063 fn save_state(&self) -> Vec<u8> {
4064 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
4065 let mut out = Vec::with_capacity(1 + 8 + 1 + self.vram.len() + chr_ram);
4066 out.push(SAVE_STATE_VERSION);
4067 out.extend_from_slice(&(self.prg32 as u32).to_le_bytes());
4068 out.extend_from_slice(&(self.chr8 as u32).to_le_bytes());
4069 out.push(mirroring_to_byte(self.mirroring));
4070 out.extend_from_slice(&self.vram);
4071 if self.chr_is_ram {
4072 out.extend_from_slice(&self.chr);
4073 }
4074 out
4075 }
4076
4077 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
4078 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
4079 let expected = 1 + 8 + 1 + self.vram.len() + chr_ram;
4080 if data.len() != expected {
4081 return Err(MapperError::WrongLength {
4082 expected,
4083 got: data.len(),
4084 });
4085 }
4086 if data[0] != SAVE_STATE_VERSION {
4087 return Err(MapperError::UnsupportedVersion(data[0]));
4088 }
4089 let rd = |c: usize| {
4090 u32::from_le_bytes([data[c], data[c + 1], data[c + 2], data[c + 3]]) as usize
4091 };
4092 self.prg32 = rd(1);
4093 self.chr8 = rd(5);
4094 let mut c = 9;
4095 self.mirroring = byte_to_mirroring(data[c], self.mirroring);
4096 c += 1;
4097 self.vram.copy_from_slice(&data[c..c + self.vram.len()]);
4098 c += self.vram.len();
4099 if self.chr_is_ram {
4100 self.chr.copy_from_slice(&data[c..c + self.chr.len()]);
4101 }
4102 Ok(())
4103 }
4104}
4105
4106pub fn new_m299(
4111 prg_rom: Box<[u8]>,
4112 chr_rom: Box<[u8]>,
4113 mirroring: Mirroring,
4114) -> Result<Bmc11160, MapperError> {
4115 Bmc11160::new(prg_rom, chr_rom, mirroring)
4116}
4117
4118#[cfg(test)]
4119#[allow(clippy::cast_possible_truncation)]
4120mod tests {
4121 use super::*;
4122 fn synth_prg_8k(banks: usize) -> Box<[u8]> {
4124 let mut v = vec![0xFFu8; banks * PRG_BANK_8K];
4125 for b in 0..banks {
4126 v[b * PRG_BANK_8K] = b as u8;
4127 }
4128 v.into_boxed_slice()
4129 }
4130
4131 fn synth_prg_32k(banks: usize) -> Box<[u8]> {
4134 let mut v = vec![0xFFu8; banks * PRG_BANK_32K];
4135 for b in 0..banks {
4136 v[b * PRG_BANK_32K] = b as u8;
4137 }
4138 v.into_boxed_slice()
4139 }
4140
4141 fn synth_prg_16k(banks: usize) -> Box<[u8]> {
4142 let mut v = vec![0xFFu8; banks * PRG_BANK_16K];
4143 for b in 0..banks {
4144 v[b * PRG_BANK_16K] = b as u8;
4145 }
4146 v.into_boxed_slice()
4147 }
4148
4149 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
4150 let mut v = vec![0u8; banks * CHR_BANK_8K];
4151 for b in 0..banks {
4152 v[b * CHR_BANK_8K] = b as u8;
4153 }
4154 v.into_boxed_slice()
4155 }
4156
4157 #[test]
4158 fn m15_mode0_two_16k_halves() {
4159 let mut m = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4161 m.cpu_write(0x8000, 0b0000_0010);
4163 assert_eq!(m.cpu_read(0x8000), 2); assert_eq!(m.cpu_read(0xC000), 3); assert_eq!(m.current_mirroring(), Mirroring::Vertical);
4166 }
4167
4168 #[test]
4169 fn m15_mirroring_bit() {
4170 let mut m = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4171 m.cpu_write(0x8000, 0b0100_0000); assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4173 }
4174
4175 #[test]
4176 fn m15_mode3_single_bank_mirrored() {
4177 let mut m = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4178 m.cpu_write(0x8003, 0b0000_0101);
4180 assert_eq!(m.cpu_read(0x8000), 5);
4181 assert_eq!(m.cpu_read(0xC000), 5); }
4183
4184 #[test]
4195 fn m15_chr_ram_is_writable_in_every_mode() {
4196 let mut m = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4197 for (mode_addr, value) in [
4198 (0x8000u16, 0xABu8),
4199 (0x8001, 0xCD),
4200 (0x8002, 0xEF),
4201 (0x8003, 0x42),
4202 ] {
4203 m.cpu_write(mode_addr, 0);
4204 m.ppu_write(0x0000, value);
4205 assert_eq!(
4206 m.ppu_read(0x0000),
4207 value,
4208 "mapper-15 hacks require CHR-RAM writes to land in mode {}",
4209 mode_addr & 0x03
4210 );
4211 }
4212 }
4213
4214 #[test]
4220 fn m15_prg_ram_round_trips_through_a_save_state() {
4221 let mut m = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4222 m.cpu_write(0x6000, 0x11);
4223 m.cpu_write(0x7FFF, 0x22);
4224 m.ppu_write(0x0000, 0x33);
4225 let blob = m.save_state();
4226
4227 let mut restored = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4228 restored.load_state(&blob).expect("same-shape state loads");
4229 assert_eq!(restored.cpu_read(0x6000), 0x11);
4230 assert_eq!(restored.cpu_read(0x7FFF), 0x22);
4231 assert_eq!(restored.ppu_read(0x0000), 0x33);
4232 }
4233
4234 #[test]
4237 fn m15_provides_prg_ram_the_board_lacks() {
4238 let mut m = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4239 m.cpu_write(0x6000, 0x5A);
4240 m.cpu_write(0x7FFF, 0xA5);
4241 assert_eq!(m.cpu_read(0x6000), 0x5A);
4242 assert_eq!(m.cpu_read(0x7FFF), 0xA5);
4243 let before = m.cpu_read(0x8000);
4245 m.cpu_write(0x6001, 0xFF);
4246 assert_eq!(
4247 m.cpu_read(0x8000),
4248 before,
4249 "the $6000-$7FFF window is RAM, not the mapper's address/data latch"
4250 );
4251 }
4252
4253 #[test]
4254 fn m61_16k_mode_address_decode() {
4255 let mut m = Multicart61::new(synth_prg_16k(16), &[]).unwrap();
4257 m.cpu_write(0x8093, 0x00);
4260 assert_eq!(m.cpu_read(0x8000), 6);
4261 assert_eq!(m.cpu_read(0xC000), 6); assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4263 }
4264
4265 #[test]
4266 fn m61_32k_mode() {
4267 let mut m = Multicart61::new(synth_prg_16k(16), &[]).unwrap();
4268 m.cpu_write(0x8002, 0x00);
4271 assert_eq!(m.cpu_read(0x8000), 4);
4273 assert_eq!(m.cpu_read(0xC000), 5);
4274 }
4275
4276 #[test]
4277 fn m62_address_and_data_decode() {
4278 let mut m =
4279 Multicart62::new(synth_prg_16k(8), synth_chr_8k(256), Mirroring::Vertical).unwrap();
4280 let addr = 0x8000 | (0x01 << 8) | 0x20 | 0x80 | 0x05;
4284 m.cpu_write(addr, 0x02); assert_eq!(m.cpu_read(0x8000), 1); assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4287 assert_eq!(m.ppu_read(0x0000), 22);
4289 }
4290
4291 #[test]
4292 fn m15_save_state_round_trips_mirroring() {
4293 let mut m = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4294 m.cpu_write(0x8001, 0b0100_0101);
4295 let blob = m.save_state();
4296 let mut m2 = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4297 m2.load_state(&blob).unwrap();
4298 assert_eq!(m2.current_mirroring(), m.current_mirroring());
4299 }
4300
4301 #[test]
4302 fn m200_address_latch() {
4303 let mut m =
4304 Multicart200::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4305 m.cpu_write(0x8000 | 0x0B, 0x00); assert_eq!(m.cpu_read(0x8000), 3);
4308 assert_eq!(m.cpu_read(0xC000), 3);
4310 assert_eq!(m.ppu_read(0x0000), 3);
4311 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4312 m.cpu_write(0x8000 | 0x02, 0x00);
4314 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
4315 assert_eq!(m.cpu_read(0x8000), 2);
4316 }
4317
4318 #[test]
4319 fn m201_address_drives_prg_and_chr() {
4320 let mut m =
4321 Multicart201::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4322 m.cpu_write(0x8000 | 0x03, 0x00);
4324 assert_eq!(m.cpu_read(0x8000), 3);
4325 assert_eq!(m.ppu_read(0x0000), 3);
4326 m.cpu_write(0x8000 | 0x05, 0x00);
4328 assert_eq!(m.cpu_read(0x8000), 1);
4329 assert_eq!(m.ppu_read(0x0000), 5);
4330 }
4331
4332 #[test]
4333 fn m202_16k_mode() {
4334 let mut m =
4335 Multicart202::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4336 m.cpu_write(0x8000 | 0b0110, 0x00);
4340 assert_eq!(m.cpu_read(0x8000), 3);
4341 assert_eq!(m.cpu_read(0xC000), 3); assert_eq!(m.ppu_read(0x0000), 3);
4343 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
4344 }
4345
4346 #[test]
4347 fn m202_32k_mode_and_mirroring() {
4348 let mut m =
4349 Multicart202::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4350 m.cpu_write(0x8000 | 0x09, 0x00);
4356 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4357 assert_eq!(m.cpu_read(0x8000), 4);
4359 assert_eq!(m.cpu_read(0xC000), 5);
4360 }
4361
4362 #[test]
4363 fn m203_data_latch() {
4364 let mut m =
4365 Multicart203::new(synth_prg_16k(8), synth_chr_8k(4), Mirroring::Vertical).unwrap();
4366 m.cpu_write(0x8000, 0x0E);
4369 assert_eq!(m.cpu_read(0x8000), 3);
4370 assert_eq!(m.cpu_read(0xC000), 3); assert_eq!(m.ppu_read(0x0000), 2);
4372 }
4373
4374 #[test]
4375 fn m212_16k_mode_and_protection_read() {
4376 let mut m =
4377 Multicart212::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4378 m.cpu_write(0x8000 | 0x0B, 0x00); assert_eq!(m.cpu_read(0x8000), 3);
4381 assert_eq!(m.cpu_read(0xC000), 3);
4382 assert_eq!(m.ppu_read(0x0000), 3);
4383 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4384 assert_eq!(m.cpu_read(0x6000) & 0x80, 0x80);
4386 assert_eq!(m.cpu_read(0x6010), 0x00);
4387 }
4388
4389 #[test]
4390 fn m212_32k_mode() {
4391 let mut m =
4392 Multicart212::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4393 m.cpu_write(0xC000 | 0x04, 0x00); assert_eq!(m.cpu_read(0x8000), 4);
4396 assert_eq!(m.cpu_read(0xC000), 5);
4397 }
4398
4399 #[test]
4400 fn m213_address_latch() {
4401 let mut m =
4402 Multicart213::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4403 m.cpu_write(0x8000 | 0x2C, 0x00);
4406 assert_eq!(m.ppu_read(0x0000), 5);
4407 assert_eq!(m.cpu_read(0x8000), 2);
4408 }
4409
4410 #[test]
4411 fn m214_address_latch() {
4412 let mut m =
4413 Multicart214::new(synth_prg_16k(8), synth_chr_8k(4), Mirroring::Vertical).unwrap();
4414 m.cpu_write(0x8000 | 0x09, 0x00);
4417 assert_eq!(m.cpu_read(0x8000), 2);
4418 assert_eq!(m.cpu_read(0xC000), 2); assert_eq!(m.ppu_read(0x0000), 1);
4420 }
4421
4422 #[test]
4423 fn m58_address_decoded_banking() {
4424 let mut m =
4425 Multicart58::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4426 let addr = 0x8000 | (1 << 6) | (0b010 << 3) | 0b011;
4428 m.cpu_write(addr, 0x00);
4429 assert_eq!(m.cpu_read(0x8000), 3); assert_eq!(m.ppu_read(0x0000), 2); let addr32 = 0x8000 | 0b110;
4434 m.cpu_write(addr32, 0x00);
4435 assert_eq!(m.cpu_read(0x8000), 6);
4437 }
4438
4439 #[test]
4440 fn m58_save_state_round_trip() {
4441 let mut m =
4442 Multicart58::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4443 let addr = 0x8000 | (1 << 7) | (1 << 6) | (0b001 << 3) | 0b010;
4444 m.cpu_write(addr, 0x00);
4445 let blob = m.save_state();
4446 let mut m2 =
4447 Multicart58::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4448 m2.load_state(&blob).unwrap();
4449 assert_eq!(m2.cpu_read(0x8000), 2);
4450 assert_eq!(m2.ppu_read(0x0000), 1);
4451 assert_eq!(m2.current_mirroring(), Mirroring::Horizontal);
4452 }
4453
4454 #[test]
4455 fn m60_power_on_bank_zero() {
4456 let mut m =
4457 Multicart60::new(synth_prg_16k(4), synth_chr_8k(4), Mirroring::Vertical).unwrap();
4458 assert_eq!(m.cpu_read(0x8000), 0);
4459 assert_eq!(m.ppu_read(0x0000), 0);
4460 m.cpu_write(0x8000, 0xFF);
4462 assert_eq!(m.cpu_read(0x8000), 0);
4463 }
4464
4465 #[test]
4466 fn m60_save_state_round_trip() {
4467 let mut m =
4468 Multicart60::new(synth_prg_16k(4), synth_chr_8k(4), Mirroring::Vertical).unwrap();
4469 m.ppu_write(0x2000, 0x44);
4470 let blob = m.save_state();
4471 let mut m2 =
4472 Multicart60::new(synth_prg_16k(4), synth_chr_8k(4), Mirroring::Vertical).unwrap();
4473 m2.load_state(&blob).unwrap();
4474 assert_eq!(m2.ppu_read(0x2000), 0x44);
4475 }
4476
4477 #[test]
4478 fn m231_dual_bank_address_decoded() {
4479 let mut m = Multicart231::new(synth_prg_16k(32), &[], Mirroring::Vertical).unwrap();
4480 let addr = 0x8000 | 0x14;
4484 m.cpu_write(addr, 0x00);
4485 assert_eq!(m.cpu_read(0x8000), 20);
4486 assert_eq!(m.cpu_read(0xC000), 20);
4487 let addr2 = 0x8000 | 0x20 | 0x14; m.cpu_write(addr2, 0x00);
4490 assert_eq!(m.cpu_read(0x8000), 20);
4492 assert_eq!(m.cpu_read(0xC000), 21);
4493 }
4494
4495 #[test]
4496 fn m231_save_state_round_trip() {
4497 let mut m = Multicart231::new(synth_prg_16k(32), &[], Mirroring::Vertical).unwrap();
4498 m.cpu_write(0x8000 | 0x80 | 0x04, 0x00); m.ppu_write(0x0008, 0x66);
4500 let blob = m.save_state();
4501 let mut m2 = Multicart231::new(synth_prg_16k(32), &[], Mirroring::Vertical).unwrap();
4502 m2.load_state(&blob).unwrap();
4503 assert_eq!(m2.current_mirroring(), Mirroring::Horizontal);
4504 assert_eq!(m2.ppu_read(0x0008), 0x66);
4505 }
4506
4507 #[test]
4508 fn m234_cnrom_mode_banks() {
4509 let mut m =
4510 Maxi15M234::new(synth_prg_32k(8), synth_chr_8k(16), Mirroring::Vertical).unwrap();
4511 m.cpu_write(0xFF80, 0x05);
4515 assert_eq!(m.cpu_read(0x8000), 5);
4516 assert_eq!(m.ppu_read(0x0000), 20 % 16);
4518 m.cpu_write(0xFFE8, 0x10);
4520 assert_eq!(m.ppu_read(0x0000), 0x15 % 16);
4522 }
4523
4524 #[test]
4525 fn m234_save_state_round_trip() {
4526 let mut m =
4527 Maxi15M234::new(synth_prg_32k(8), synth_chr_8k(16), Mirroring::Vertical).unwrap();
4528 m.cpu_write(0xFF80, 0x83); m.cpu_write(0xFFE8, 0x20);
4530 let blob = m.save_state();
4531 let mut m2 =
4532 Maxi15M234::new(synth_prg_32k(8), synth_chr_8k(16), Mirroring::Vertical).unwrap();
4533 m2.load_state(&blob).unwrap();
4534 assert_eq!(m2.cpu_read(0x8000), 3);
4535 assert_eq!(m2.current_mirroring(), Mirroring::Horizontal);
4536 }
4537
4538 #[test]
4539 fn m225_address_decoded_and_scratch_ram() {
4540 let mut m =
4541 Multicart225::new(synth_prg_16k(16), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4542 m.cpu_write(0x8080, 0);
4546 assert_eq!(m.cpu_read(0x8000), 2);
4547 assert_eq!(m.cpu_read(0xC000), 3);
4548 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
4549 m.cpu_write(0x5800, 0xA9);
4551 assert_eq!(m.cpu_read(0x5800), 0x09);
4552 }
4553
4554 #[test]
4555 fn m225_save_state_round_trip() {
4556 let mut m =
4557 Multicart225::new(synth_prg_16k(16), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4558 m.cpu_write(0x9180, 0); m.cpu_write(0x5803, 0x05);
4560 let blob = m.save_state();
4561 let mut m2 =
4562 Multicart225::new(synth_prg_16k(16), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4563 m2.load_state(&blob).unwrap();
4564 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
4565 assert_eq!(m2.cpu_read(0x5803), 0x05);
4566 }
4567
4568 #[test]
4569 fn m226_two_regs_select_prg_and_mirror() {
4570 let mut m = Multicart226::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
4571 m.cpu_write(0x8000, 0x23);
4574 m.cpu_write(0x8001, 0x00);
4576 assert_eq!(m.cpu_read(0x8000), 3);
4578 assert_eq!(m.cpu_read(0xC000), 3);
4579 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4580 }
4581
4582 #[test]
4583 fn m226_reg0_layout_is_pmop_pppp() {
4584 let mut m = Multicart226::new(synth_prg_16k(128), &[], Mirroring::Vertical).unwrap();
4589 m.cpu_write(0x8000, 0xA3);
4591 assert_eq!(m.cpu_read(0x8000), 35);
4592 assert_eq!(m.cpu_read(0xC000), 35);
4593 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4594 m.cpu_write(0x8000, 0x23);
4596 assert_eq!(m.cpu_read(0x8000), 3);
4597 m.cpu_write(0x8000, 0x83);
4599 assert_eq!(m.cpu_read(0x8000), 34);
4600 assert_eq!(m.cpu_read(0xC000), 35);
4601 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4602 m.cpu_write(0x8000, 0x45);
4604 m.cpu_write(0x8001, 0x01);
4605 assert_eq!(m.cpu_read(0x8000), 68);
4606 assert_eq!(m.cpu_read(0xC000), 69);
4607 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
4608 m.reset();
4610 assert_eq!(m.cpu_read(0x8000), 0);
4611 assert_eq!(m.cpu_read(0xC000), 1);
4612 }
4613
4614 #[test]
4615 fn m226_save_state_round_trip() {
4616 let mut m = Multicart226::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
4617 m.cpu_write(0x8000, 0x85); m.cpu_write(0x8001, 0x00);
4619 m.ppu_write(0x0001, 0x66);
4620 let blob = m.save_state();
4621 let mut m2 = Multicart226::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
4622 m2.load_state(&blob).unwrap();
4623 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
4624 assert_eq!(m2.ppu_read(0x0001), 0x66);
4625 }
4626
4627 #[test]
4628 fn m227_address_decoded_bank() {
4629 let mut m = Multicart227::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
4630 m.cpu_write(0x8008, 0);
4634 assert_eq!(m.cpu_read(0x8000), 2);
4635 assert_eq!(m.cpu_read(0xC000), 0);
4636 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
4637 m.cpu_write(0x8208, 0);
4640 assert_eq!(m.cpu_read(0x8000), 2);
4641 assert_eq!(m.cpu_read(0xC000), 7);
4642 m.cpu_write(0x8088, 0);
4645 assert_eq!(m.cpu_read(0x8000), 2);
4646 assert_eq!(m.cpu_read(0xC000), 2);
4647 }
4648
4649 #[test]
4650 fn m227_save_state_round_trip() {
4651 let mut m = Multicart227::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
4652 m.cpu_write(0x808B, 0); m.ppu_write(0x0002, 0x33);
4654 let blob = m.save_state();
4655 let mut m2 = Multicart227::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
4656 m2.load_state(&blob).unwrap();
4657 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
4658 assert_eq!(m2.ppu_read(0x0002), 0x33);
4659 }
4660
4661 #[test]
4662 fn m229_menu_bank_and_game_bank() {
4663 let mut m =
4664 Multicart229::new(synth_prg_16k(16), synth_chr_8k(16), Mirroring::Vertical).unwrap();
4665 m.cpu_write(0x8000, 0);
4667 assert_eq!(m.cpu_read(0x8000), 0);
4668 assert_eq!(m.cpu_read(0xC000), 1);
4669 m.cpu_write(0x8003, 0);
4671 assert_eq!(m.cpu_read(0x8000), 3);
4672 assert_eq!(m.cpu_read(0xC000), 3);
4673 }
4674
4675 #[test]
4676 fn m229_save_state_round_trip() {
4677 let mut m =
4678 Multicart229::new(synth_prg_16k(16), synth_chr_8k(16), Mirroring::Vertical).unwrap();
4679 m.cpu_write(0x8025, 0); let blob = m.save_state();
4681 let mut m2 =
4682 Multicart229::new(synth_prg_16k(16), synth_chr_8k(16), Mirroring::Vertical).unwrap();
4683 m2.load_state(&blob).unwrap();
4684 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
4685 assert_eq!(m2.current_mirroring(), m.current_mirroring());
4686 }
4687
4688 #[test]
4689 fn m233_bank_and_mirror_modes() {
4690 let mut m = Multicart233::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
4691 m.cpu_write(0x8000, 0xA5);
4695 assert_eq!(m.cpu_read(0x8000), 5);
4696 assert_eq!(m.cpu_read(0xC000), 5);
4697 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4698 m.cpu_write(0x8000, 0x05);
4701 assert_eq!(m.cpu_read(0x8000), 4);
4702 assert_eq!(m.cpu_read(0xC000), 5);
4703 }
4704
4705 #[test]
4706 fn m233_save_state_round_trip() {
4707 let mut m = Multicart233::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
4708 m.cpu_write(0x8000, 0x26); m.ppu_write(0x0004, 0x88);
4710 let blob = m.save_state();
4711 let mut m2 = Multicart233::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
4712 m2.load_state(&blob).unwrap();
4713 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
4714 assert_eq!(m2.ppu_read(0x0004), 0x88);
4715 }
4716
4717 #[test]
4718 fn m46_outer_inner_prg() {
4719 let mut m = new_m46(synth_prg_32k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
4720 m.cpu_write(0x6000, 0x02); m.cpu_write(0x8000, 0x01); assert_eq!(m.cpu_read(0x8000), 5);
4724 }
4725
4726 #[test]
4727 fn m104_golden_five_blocks() {
4728 let mut m = new_m104(synth_prg_16k(32), synth_chr_8k(1), Mirroring::Vertical).unwrap();
4729 m.cpu_write(0x8000, 0x08 | 0x02); m.cpu_write(0xC000, 0x05);
4731 assert_eq!(m.cpu_read(0x8000), 37 % 32); assert_eq!(m.cpu_read(0xC000), 47 % 32); }
4734
4735 #[test]
4736 fn m290_address_decoded_mirror() {
4737 let mut m = new_m290(synth_prg_16k(16), synth_chr_8k(16), Mirroring::Vertical).unwrap();
4738 m.cpu_write(0x8400, 0); assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4740 }
4741
4742 #[test]
4743 fn m301_address_as_data_mirror() {
4744 let mut m = new_m301(synth_prg_16k(16), synth_chr_8k(1), Mirroring::Vertical).unwrap();
4745 m.cpu_write(0x8002, 0); assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4747 }
4748
4749 #[test]
4750 fn discrete_save_state_round_trip() {
4751 let mut m = new_m51(synth_prg_8k(64), Box::new([]), Mirroring::Vertical).unwrap();
4752 m.cpu_write(0x6000, 0x10);
4753 m.cpu_write(0xC000, 0x05);
4754 m.ppu_write(0x0020, 0x7F);
4755 let blob = m.save_state();
4756 let mut m2 = new_m51(synth_prg_8k(64), Box::new([]), Mirroring::Vertical).unwrap();
4757 m2.load_state(&blob).unwrap();
4758 assert_eq!(m2.ppu_read(0x0020), 0x7F);
4759 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
4760 }
4761
4762 fn prg(banks_8k: usize) -> Box<[u8]> {
4763 let mut v = vec![0u8; banks_8k * PRG_BANK_8K];
4765 for (i, b) in v.chunks_mut(PRG_BANK_8K).enumerate() {
4766 b.fill(i as u8);
4767 }
4768 v.into_boxed_slice()
4769 }
4770
4771 fn chr(banks_8k: usize) -> Box<[u8]> {
4772 let mut v = vec![0u8; banks_8k * CHR_BANK_8K];
4773 for (i, b) in v.chunks_mut(CHR_BANK_8K).enumerate() {
4774 b.fill(i as u8);
4775 }
4776 v.into_boxed_slice()
4777 }
4778
4779 #[test]
4780 fn m299_load_state_rejects_truncated_and_bad_version() {
4781 let m = new_m299(prg(8), chr(8), Mirroring::Horizontal).unwrap();
4782 let mut s = m.save_state();
4783 let mut t = m.save_state();
4785 t.pop();
4786 let mut m2 = new_m299(prg(8), chr(8), Mirroring::Horizontal).unwrap();
4787 assert!(matches!(
4788 m2.load_state(&t),
4789 Err(MapperError::WrongLength { .. })
4790 ));
4791 s[0] = 0xFF;
4793 assert!(matches!(
4794 m2.load_state(&s),
4795 Err(MapperError::UnsupportedVersion(0xFF))
4796 ));
4797 }
4798
4799 #[test]
4800 fn m204_address_decode_selects_prg_and_chr() {
4801 let mut m = new_m204(prg(16), chr(8), Mirroring::Vertical).unwrap();
4802 m.cpu_write(0x8000, 0);
4804 assert_eq!(m.cpu_read(0x8000), 0);
4805 assert_eq!(m.cpu_read(0xC000), 0); m.cpu_write(0x8007, 0);
4808 assert_eq!(m.cpu_read(0x8000), 12, "16k page 6 -> 8k bank 12");
4809 }
4810
4811 #[test]
4812 fn m204_distinct_halves_in_bitmask6_mode() {
4813 let mut m = new_m204(prg(16), chr(8), Mirroring::Vertical).unwrap();
4814 m.cpu_write(0x8006, 0); assert_eq!(m.cpu_read(0x8000), 12);
4817 assert_eq!(m.cpu_read(0xC000), 14);
4819 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
4820 }
4821
4822 #[test]
4823 fn m204_mirroring_bit() {
4824 let mut m = new_m204(prg(4), chr(2), Mirroring::Vertical).unwrap();
4825 m.cpu_write(0x8010, 0);
4826 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4827 m.cpu_write(0x8000, 0);
4828 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
4829 }
4830
4831 #[test]
4832 fn m299_value_decode_selects_prg_chr_mirror() {
4833 let mut m = new_m299(prg(8 * 4), chr(32), Mirroring::Horizontal).unwrap();
4834 m.cpu_write(0x8000, 0x10);
4837 assert_eq!(m.cpu_read(0x8000), 4, "32k bank 1 -> 8k bank 4");
4838 assert_eq!(m.ppu_read(0x0000), 4, "chr 8k bank 4");
4839 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
4840 }
4841
4842 #[test]
4843 fn m299_chr_low_bits_and_mirror() {
4844 let mut m = new_m299(prg(8 * 2), chr(16), Mirroring::Horizontal).unwrap();
4845 m.cpu_write(0xFFFF, 0x83);
4847 assert_eq!(m.cpu_read(0x8000), 0, "32k bank 0");
4848 assert_eq!(m.ppu_read(0x0000), 3, "chr 8k bank 3");
4849 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
4850 }
4851
4852 #[test]
4853 fn m204_m299_save_load_round_trip() {
4854 let mut a = new_m204(prg(16), chr(8), Mirroring::Vertical).unwrap();
4856 a.cpu_write(0x8006, 0);
4857 let s = a.save_state();
4858 let mut b = new_m204(prg(16), chr(8), Mirroring::Horizontal).unwrap();
4859 b.load_state(&s).unwrap();
4860 assert_eq!(a.cpu_read(0xC000), b.cpu_read(0xC000));
4861 assert_eq!(a.current_mirroring(), b.current_mirroring());
4862
4863 let mut a = new_m299(prg(8 * 4), chr(32), Mirroring::Horizontal).unwrap();
4865 a.cpu_write(0x8000, 0x91);
4866 let s = a.save_state();
4867 let mut b = new_m299(prg(8 * 4), chr(32), Mirroring::Horizontal).unwrap();
4868 b.load_state(&s).unwrap();
4869 assert_eq!(a.cpu_read(0x8000), b.cpu_read(0x8000));
4870 assert_eq!(a.ppu_read(0x0000), b.ppu_read(0x0000));
4871 assert_eq!(a.current_mirroring(), b.current_mirroring());
4872 }
4873
4874 #[test]
4875 fn m204_m299_bad_prg_size_is_rejected() {
4876 assert!(
4878 new_m204(
4879 vec![0u8; 100].into_boxed_slice(),
4880 chr(1),
4881 Mirroring::Vertical
4882 )
4883 .is_err()
4884 );
4885 assert!(
4886 new_m299(
4887 vec![0u8; 100].into_boxed_slice(),
4888 chr(1),
4889 Mirroring::Vertical
4890 )
4891 .is_err()
4892 );
4893 }
4894
4895 #[test]
4896 fn m15_refuses_a_pre_prg_ram_save_state() {
4897 let mut m = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4901 m.cpu_write(0x6000, 0x5A);
4902 let current = m.save_state();
4903
4904 let legacy = current[..current.len() - 0x2000].to_vec();
4906 let mut m2 = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4907 m2.cpu_write(0x6000, 0xFF);
4908 assert!(matches!(
4909 m2.load_state(&legacy),
4910 Err(MapperError::WrongLength { .. })
4911 ));
4912
4913 let mut m3 = Multicart15::new(synth_prg_16k(8), &[]).unwrap();
4915 m3.load_state(¤t).unwrap();
4916 assert_eq!(m3.cpu_read(0x6000), 0x5A);
4917
4918 let mut bad = current.clone();
4920 bad.pop();
4921 assert!(m3.load_state(&bad).is_err());
4922 }
4923}