1#![allow(
30 clippy::bool_to_int_with_if,
31 clippy::cast_lossless,
32 clippy::cast_possible_truncation,
33 clippy::doc_markdown,
34 clippy::match_same_arms,
35 clippy::missing_const_for_fn,
36 clippy::similar_names,
37 clippy::struct_excessive_bools,
38 clippy::too_many_lines,
39 clippy::unreadable_literal
40)]
41
42use crate::cartridge::Mirroring;
43use crate::mapper::{Mapper, MapperCaps, MapperError};
44use alloc::{boxed::Box, vec::Vec};
45use alloc::{format, vec};
46
47const PRG_BANK_16K: usize = 0x4000;
48const PRG_BANK_32K: usize = 0x8000;
49const CHR_BANK_8K: usize = 0x2000;
50const NAMETABLE_SIZE: usize = 0x0400;
51const NAMETABLE_SIZE_U16: u16 = 0x0400;
52
53const SAVE_STATE_VERSION: u8 = 1;
54
55const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
60 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
61 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
62 let physical = mirroring.physical_bank(table);
63 physical * NAMETABLE_SIZE + local
64}
65
66pub struct Sachen133 {
68 prg_rom: Box<[u8]>,
69 chr_rom: Box<[u8]>,
70 vram: Box<[u8]>,
71 prg_bank: u8,
72 chr_bank: u8,
73 mirroring: Mirroring,
74}
75
76impl Sachen133 {
77 pub fn new(
84 prg_rom: Box<[u8]>,
85 chr_rom: Box<[u8]>,
86 mirroring: Mirroring,
87 ) -> Result<Self, MapperError> {
88 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
89 return Err(MapperError::Invalid(format!(
90 "mapper 133 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
91 prg_rom.len()
92 )));
93 }
94 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
95 return Err(MapperError::Invalid(format!(
96 "mapper 133 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
97 chr_rom.len()
98 )));
99 }
100 Ok(Self {
101 prg_rom,
102 chr_rom,
103 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
104 prg_bank: 0,
105 chr_bank: 0,
106 mirroring,
107 })
108 }
109}
110
111impl Mapper for Sachen133 {
112 fn caps(&self) -> MapperCaps {
113 MapperCaps::NONE
114 }
115
116 fn cpu_read(&mut self, addr: u16) -> u8 {
117 if (0x8000..=0xFFFF).contains(&addr) {
118 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
119 let bank = (self.prg_bank as usize) % count;
120 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
121 } else {
122 0
123 }
124 }
125
126 fn cpu_write(&mut self, addr: u16, value: u8) {
127 if (0x4100..=0x5FFF).contains(&addr) && (addr & 0x0100) != 0 {
128 self.prg_bank = (value >> 2) & 0x01;
129 self.chr_bank = value & 0x03;
130 }
131 }
132
133 fn ppu_read(&mut self, addr: u16) -> u8 {
134 let addr = addr & 0x3FFF;
135 match addr {
136 0x0000..=0x1FFF => {
137 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
138 let bank = (self.chr_bank as usize) % count;
139 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
140 }
141 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
142 _ => 0,
143 }
144 }
145
146 fn ppu_write(&mut self, addr: u16, value: u8) {
147 let addr = addr & 0x3FFF;
148 if let 0x2000..=0x3EFF = addr {
149 let off = nametable_offset(addr, self.mirroring);
150 self.vram[off] = value;
151 }
152 }
153
154 fn current_mirroring(&self) -> Mirroring {
155 self.mirroring
156 }
157
158 fn save_state(&self) -> Vec<u8> {
159 let mut out = Vec::with_capacity(3 + self.vram.len());
160 out.push(SAVE_STATE_VERSION);
161 out.push(self.prg_bank);
162 out.push(self.chr_bank);
163 out.extend_from_slice(&self.vram);
164 out
165 }
166
167 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
168 let expected = 3 + self.vram.len();
169 if data.len() != expected {
170 return Err(MapperError::WrongLength {
171 expected,
172 got: data.len(),
173 });
174 }
175 if data[0] != SAVE_STATE_VERSION {
176 return Err(MapperError::UnsupportedVersion(data[0]));
177 }
178 self.prg_bank = data[1];
179 self.chr_bank = data[2];
180 self.vram.copy_from_slice(&data[3..3 + self.vram.len()]);
181 Ok(())
182 }
183}
184
185pub struct Sachen145 {
197 prg_rom: Box<[u8]>,
198 chr_rom: Box<[u8]>,
199 vram: Box<[u8]>,
200 chr_bank: u8,
201 mirroring: Mirroring,
202}
203
204impl Sachen145 {
205 pub fn new(
212 prg_rom: Box<[u8]>,
213 chr_rom: Box<[u8]>,
214 mirroring: Mirroring,
215 ) -> Result<Self, MapperError> {
216 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
220 return Err(MapperError::Invalid(format!(
221 "mapper 145 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
222 prg_rom.len()
223 )));
224 }
225 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
226 return Err(MapperError::Invalid(format!(
227 "mapper 145 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
228 chr_rom.len()
229 )));
230 }
231 Ok(Self {
232 prg_rom,
233 chr_rom,
234 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
235 chr_bank: 0,
236 mirroring,
237 })
238 }
239}
240
241impl Mapper for Sachen145 {
242 fn caps(&self) -> MapperCaps {
243 MapperCaps::NONE
244 }
245
246 fn cpu_read(&mut self, addr: u16) -> u8 {
247 if (0x8000..=0xFFFF).contains(&addr) {
248 self.prg_rom[(addr as usize - 0x8000) % self.prg_rom.len()]
250 } else {
251 0
252 }
253 }
254
255 fn cpu_write(&mut self, addr: u16, value: u8) {
256 if (0x4100..=0x7FFF).contains(&addr) && (addr & 0x4100) == 0x4100 {
259 self.chr_bank = (value >> 7) & 0x01;
260 }
261 }
262
263 fn ppu_read(&mut self, addr: u16) -> u8 {
264 let addr = addr & 0x3FFF;
265 match addr {
266 0x0000..=0x1FFF => {
267 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
268 let bank = (self.chr_bank as usize) % count;
269 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
270 }
271 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
272 _ => 0,
273 }
274 }
275
276 fn ppu_write(&mut self, addr: u16, value: u8) {
277 let addr = addr & 0x3FFF;
278 if let 0x2000..=0x3EFF = addr {
279 let off = nametable_offset(addr, self.mirroring);
280 self.vram[off] = value;
281 }
282 }
283
284 fn current_mirroring(&self) -> Mirroring {
285 self.mirroring
286 }
287
288 fn save_state(&self) -> Vec<u8> {
289 let mut out = Vec::with_capacity(2 + self.vram.len());
290 out.push(SAVE_STATE_VERSION);
291 out.push(self.chr_bank);
292 out.extend_from_slice(&self.vram);
293 out
294 }
295
296 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
297 let expected = 2 + self.vram.len();
298 if data.len() != expected {
299 return Err(MapperError::WrongLength {
300 expected,
301 got: data.len(),
302 });
303 }
304 if data[0] != SAVE_STATE_VERSION {
305 return Err(MapperError::UnsupportedVersion(data[0]));
306 }
307 self.chr_bank = data[1];
308 self.vram.copy_from_slice(&data[2..2 + self.vram.len()]);
309 Ok(())
310 }
311}
312
313pub struct Sachen146 {
326 prg_rom: Box<[u8]>,
327 chr_rom: Box<[u8]>,
328 vram: Box<[u8]>,
329 prg_bank: u8,
330 chr_bank: u8,
331 mirroring: Mirroring,
332}
333
334impl Sachen146 {
335 pub fn new(
342 prg_rom: Box<[u8]>,
343 chr_rom: Box<[u8]>,
344 mirroring: Mirroring,
345 ) -> Result<Self, MapperError> {
346 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
347 return Err(MapperError::Invalid(format!(
348 "mapper 146 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
349 prg_rom.len()
350 )));
351 }
352 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
353 return Err(MapperError::Invalid(format!(
354 "mapper 146 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
355 chr_rom.len()
356 )));
357 }
358 Ok(Self {
359 prg_rom,
360 chr_rom,
361 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
362 prg_bank: 0,
363 chr_bank: 0,
364 mirroring,
365 })
366 }
367
368 const fn apply(&mut self, value: u8) {
369 self.prg_bank = (value >> 3) & 0x01;
370 self.chr_bank = value & 0x07;
371 }
372}
373
374impl Mapper for Sachen146 {
375 fn caps(&self) -> MapperCaps {
376 MapperCaps::NONE
377 }
378
379 fn cpu_read(&mut self, addr: u16) -> u8 {
380 if (0x8000..=0xFFFF).contains(&addr) {
381 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
382 let bank = (self.prg_bank as usize) % count;
383 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
384 } else {
385 0
386 }
387 }
388
389 fn cpu_write(&mut self, addr: u16, value: u8) {
390 if ((0x4100..=0x5FFF).contains(&addr) && (addr & 0x0100) != 0)
392 || (0x6000..=0x7FFF).contains(&addr)
393 {
394 self.apply(value);
395 }
396 }
397
398 fn ppu_read(&mut self, addr: u16) -> u8 {
399 let addr = addr & 0x3FFF;
400 match addr {
401 0x0000..=0x1FFF => {
402 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
403 let bank = (self.chr_bank as usize) % count;
404 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
405 }
406 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
407 _ => 0,
408 }
409 }
410
411 fn ppu_write(&mut self, addr: u16, value: u8) {
412 let addr = addr & 0x3FFF;
413 if let 0x2000..=0x3EFF = addr {
414 let off = nametable_offset(addr, self.mirroring);
415 self.vram[off] = value;
416 }
417 }
418
419 fn current_mirroring(&self) -> Mirroring {
420 self.mirroring
421 }
422
423 fn save_state(&self) -> Vec<u8> {
424 let mut out = Vec::with_capacity(3 + self.vram.len());
425 out.push(SAVE_STATE_VERSION);
426 out.push(self.prg_bank);
427 out.push(self.chr_bank);
428 out.extend_from_slice(&self.vram);
429 out
430 }
431
432 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
433 let expected = 3 + self.vram.len();
434 if data.len() != expected {
435 return Err(MapperError::WrongLength {
436 expected,
437 got: data.len(),
438 });
439 }
440 if data[0] != SAVE_STATE_VERSION {
441 return Err(MapperError::UnsupportedVersion(data[0]));
442 }
443 self.prg_bank = data[1];
444 self.chr_bank = data[2];
445 self.vram.copy_from_slice(&data[3..3 + self.vram.len()]);
446 Ok(())
447 }
448}
449
450#[derive(Clone, Copy)]
457struct Jv001Chip {
458 accumulator: u8,
459 inverter: u8,
460 staging: u8,
461 output: u8,
462 increase: bool,
463 invert: u8,
466}
467
468impl Default for Jv001Chip {
469 fn default() -> Self {
470 Self {
471 accumulator: 0,
472 inverter: 0,
473 staging: 0,
474 output: 0,
475 increase: false,
476 invert: 0xFF,
478 }
479 }
480}
481
482impl Jv001Chip {
483 const fn read(self) -> u8 {
486 ((self.inverter ^ self.invert) & 0xF0) | (self.accumulator & 0x0F)
487 }
488
489 const fn write(&mut self, absolute: u16, value: u8) {
493 if absolute < 0x8000 {
494 match absolute & 0x0103 {
495 0x0100 => {
496 self.accumulator = if self.increase {
497 self.accumulator.wrapping_add(1)
498 } else {
499 (self.accumulator & 0xF0) | ((self.staging ^ self.invert) & 0x0F)
500 };
501 }
502 0x0101 => self.invert = if value & 0x01 != 0 { 0xFF } else { 0x00 },
503 0x0102 => {
504 self.staging = value & 0x0F;
505 self.inverter = value & 0xF0;
506 }
507 0x0103 => self.increase = (value & 0x01) != 0,
508 _ => {}
509 }
510 } else {
511 self.output = (self.inverter & 0xF0) | (self.accumulator & 0x0F);
513 }
514 }
515}
516
517pub struct Sachen3018M147 {
519 prg_rom: Box<[u8]>,
520 chr_rom: Box<[u8]>,
521 vram: Box<[u8]>,
522 chr_is_ram: bool,
523 jv001: Jv001Chip,
524 mirroring: Mirroring,
525}
526
527impl Sachen3018M147 {
528 pub fn new(
535 prg_rom: Box<[u8]>,
536 chr_rom: Box<[u8]>,
537 mirroring: Mirroring,
538 ) -> Result<Self, MapperError> {
539 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
540 return Err(MapperError::Invalid(format!(
541 "mapper 147 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
542 prg_rom.len()
543 )));
544 }
545 let chr_is_ram = chr_rom.is_empty();
546 let chr_rom: Box<[u8]> = if chr_is_ram {
547 vec![0u8; CHR_BANK_8K].into_boxed_slice()
548 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
549 chr_rom
550 } else {
551 return Err(MapperError::Invalid(format!(
552 "mapper 147 CHR-ROM size {} is not a multiple of 8 KiB",
553 chr_rom.len()
554 )));
555 };
556 Ok(Self {
557 prg_rom,
558 chr_rom,
559 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
560 chr_is_ram,
561 jv001: Jv001Chip::default(),
562 mirroring,
563 })
564 }
565
566 const fn prg_bank(&self) -> usize {
568 (((self.jv001.output & 0x20) >> 4) | (self.jv001.output & 0x01)) as usize
569 }
570
571 const fn chr_bank(&self) -> usize {
573 ((self.jv001.output & 0x1E) >> 1) as usize
574 }
575
576 fn read_prg(&self, addr: u16) -> u8 {
577 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
578 let bank = self.prg_bank() % count;
579 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
580 }
581
582 fn chr_offset(&self, addr: u16) -> usize {
583 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
584 let bank = self.chr_bank() % count;
585 bank * CHR_BANK_8K + addr as usize
586 }
587}
588
589impl Mapper for Sachen3018M147 {
590 fn caps(&self) -> MapperCaps {
591 MapperCaps::NONE
592 }
593
594 fn cpu_read_unmapped(&self, addr: u16) -> bool {
595 (matches!(addr, 0x6000..=0x7FFF) && self.sram().is_empty()) || {
598 !((0x4100..=0x5FFF).contains(&addr) && (addr & 0x0103) == 0x0100)
601 && (0x4020..=0x5FFF).contains(&addr)
602 }
603 }
604
605 fn cpu_read(&mut self, addr: u16) -> u8 {
606 match addr {
607 0x4100..=0x5FFF if (addr & 0x0103) == 0x0100 => {
611 let v = self.jv001.read();
612 ((v & 0x3F) << 2) | ((v & 0xC0) >> 6)
613 }
614 0x8000..=0xFFFF => self.read_prg(addr),
615 _ => 0,
616 }
617 }
618
619 fn cpu_write(&mut self, addr: u16, value: u8) {
620 let scramble = |v: u8| ((v & 0x03) << 6) | ((v & 0xFC) >> 2);
624 match addr {
625 0x4100..=0x5FFF => self.jv001.write(addr, scramble(value)),
626 0x8000..=0xFFFF => {
627 let effective = value & self.read_prg(addr);
629 self.jv001.write(addr, scramble(effective));
630 }
631 _ => {}
632 }
633 }
634
635 fn ppu_read(&mut self, addr: u16) -> u8 {
636 let addr = addr & 0x3FFF;
637 match addr {
638 0x0000..=0x1FFF => self.chr_rom[self.chr_offset(addr)],
639 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
640 _ => 0,
641 }
642 }
643
644 fn ppu_write(&mut self, addr: u16, value: u8) {
645 let addr = addr & 0x3FFF;
646 match addr {
647 0x0000..=0x1FFF => {
648 if self.chr_is_ram {
649 let off = self.chr_offset(addr);
650 self.chr_rom[off] = value;
651 }
652 }
653 0x2000..=0x3EFF => {
654 let off = nametable_offset(addr, self.mirroring);
655 self.vram[off] = value;
656 }
657 _ => {}
658 }
659 }
660
661 fn current_mirroring(&self) -> Mirroring {
662 self.mirroring
663 }
664
665 fn save_state(&self) -> Vec<u8> {
666 let chr_extra = if self.chr_is_ram {
667 self.chr_rom.len()
668 } else {
669 0
670 };
671 let mut out = Vec::with_capacity(7 + self.vram.len() + chr_extra);
673 out.push(SAVE_STATE_VERSION);
674 out.push(self.jv001.accumulator);
675 out.push(self.jv001.inverter);
676 out.push(self.jv001.staging);
677 out.push(self.jv001.output);
678 out.push(u8::from(self.jv001.increase));
679 out.push(self.jv001.invert); out.extend_from_slice(&self.vram);
681 if self.chr_is_ram {
682 out.extend_from_slice(&self.chr_rom);
683 }
684 out
685 }
686
687 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
688 let chr_extra = if self.chr_is_ram {
689 self.chr_rom.len()
690 } else {
691 0
692 };
693 let expected = 7 + self.vram.len() + chr_extra;
694 if data.len() != expected {
695 return Err(MapperError::WrongLength {
696 expected,
697 got: data.len(),
698 });
699 }
700 if data[0] != SAVE_STATE_VERSION {
701 return Err(MapperError::UnsupportedVersion(data[0]));
702 }
703 self.jv001.accumulator = data[1];
704 self.jv001.inverter = data[2];
705 self.jv001.staging = data[3];
706 self.jv001.output = data[4];
707 self.jv001.increase = data[5] != 0;
708 self.jv001.invert = data[6]; let mut cursor = 7;
710 self.vram
711 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
712 cursor += self.vram.len();
713 if self.chr_is_ram {
714 self.chr_rom
715 .copy_from_slice(&data[cursor..cursor + self.chr_rom.len()]);
716 }
717 Ok(())
718 }
719}
720
721pub struct Sachen148 {
733 prg_rom: Box<[u8]>,
734 chr_rom: Box<[u8]>,
735 vram: Box<[u8]>,
736 chr_is_ram: bool,
737 prg_bank: u8,
738 chr_bank: u8,
739 mirroring: Mirroring,
740}
741
742impl Sachen148 {
743 pub fn new(
750 prg_rom: Box<[u8]>,
751 chr_rom: Box<[u8]>,
752 mirroring: Mirroring,
753 ) -> Result<Self, MapperError> {
754 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
755 return Err(MapperError::Invalid(format!(
756 "mapper 148 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
757 prg_rom.len()
758 )));
759 }
760 let chr_is_ram = chr_rom.is_empty();
761 let chr_rom: Box<[u8]> = if chr_is_ram {
762 vec![0u8; CHR_BANK_8K].into_boxed_slice()
763 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
764 chr_rom
765 } else {
766 return Err(MapperError::Invalid(format!(
767 "mapper 148 CHR-ROM size {} is not a multiple of 8 KiB",
768 chr_rom.len()
769 )));
770 };
771 Ok(Self {
772 prg_rom,
773 chr_rom,
774 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
775 chr_is_ram,
776 prg_bank: 0,
777 chr_bank: 0,
778 mirroring,
779 })
780 }
781
782 fn read_prg(&self, addr: u16) -> u8 {
783 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
784 let bank = (self.prg_bank as usize) % count;
785 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
786 }
787
788 fn chr_offset(&self, addr: u16) -> usize {
789 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
790 let bank = (self.chr_bank as usize) % count;
791 bank * CHR_BANK_8K + addr as usize
792 }
793}
794
795impl Mapper for Sachen148 {
796 fn caps(&self) -> MapperCaps {
797 MapperCaps::NONE
798 }
799
800 fn cpu_read(&mut self, addr: u16) -> u8 {
801 if (0x8000..=0xFFFF).contains(&addr) {
802 self.read_prg(addr)
803 } else {
804 0
805 }
806 }
807
808 fn cpu_write(&mut self, addr: u16, value: u8) {
809 if (0x8000..=0xFFFF).contains(&addr) {
810 let effective = value & self.read_prg(addr);
812 self.prg_bank = (effective >> 3) & 0x01;
813 self.chr_bank = effective & 0x07;
814 }
815 }
816
817 fn ppu_read(&mut self, addr: u16) -> u8 {
818 let addr = addr & 0x3FFF;
819 match addr {
820 0x0000..=0x1FFF => self.chr_rom[self.chr_offset(addr)],
821 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
822 _ => 0,
823 }
824 }
825
826 fn ppu_write(&mut self, addr: u16, value: u8) {
827 let addr = addr & 0x3FFF;
828 match addr {
829 0x0000..=0x1FFF => {
830 if self.chr_is_ram {
831 let off = self.chr_offset(addr);
832 self.chr_rom[off] = value;
833 }
834 }
835 0x2000..=0x3EFF => {
836 let off = nametable_offset(addr, self.mirroring);
837 self.vram[off] = value;
838 }
839 _ => {}
840 }
841 }
842
843 fn current_mirroring(&self) -> Mirroring {
844 self.mirroring
845 }
846
847 fn save_state(&self) -> Vec<u8> {
848 let chr_extra = if self.chr_is_ram {
849 self.chr_rom.len()
850 } else {
851 0
852 };
853 let mut out = Vec::with_capacity(3 + self.vram.len() + chr_extra);
854 out.push(SAVE_STATE_VERSION);
855 out.push(self.prg_bank);
856 out.push(self.chr_bank);
857 out.extend_from_slice(&self.vram);
858 if self.chr_is_ram {
859 out.extend_from_slice(&self.chr_rom);
860 }
861 out
862 }
863
864 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
865 let chr_extra = if self.chr_is_ram {
866 self.chr_rom.len()
867 } else {
868 0
869 };
870 let expected = 3 + self.vram.len() + chr_extra;
871 if data.len() != expected {
872 return Err(MapperError::WrongLength {
873 expected,
874 got: data.len(),
875 });
876 }
877 if data[0] != SAVE_STATE_VERSION {
878 return Err(MapperError::UnsupportedVersion(data[0]));
879 }
880 self.prg_bank = data[1];
881 self.chr_bank = data[2];
882 let mut cursor = 3;
883 self.vram
884 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
885 cursor += self.vram.len();
886 if self.chr_is_ram {
887 self.chr_rom
888 .copy_from_slice(&data[cursor..cursor + self.chr_rom.len()]);
889 }
890 Ok(())
891 }
892}
893
894pub struct Sachen149 {
905 prg_rom: Box<[u8]>,
906 chr_rom: Box<[u8]>,
907 vram: Box<[u8]>,
908 chr_bank: u8,
909 mirroring: Mirroring,
910}
911
912impl Sachen149 {
913 pub fn new(
920 prg_rom: Box<[u8]>,
921 chr_rom: Box<[u8]>,
922 mirroring: Mirroring,
923 ) -> Result<Self, MapperError> {
924 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
925 return Err(MapperError::Invalid(format!(
926 "mapper 149 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
927 prg_rom.len()
928 )));
929 }
930 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
931 return Err(MapperError::Invalid(format!(
932 "mapper 149 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
933 chr_rom.len()
934 )));
935 }
936 Ok(Self {
937 prg_rom,
938 chr_rom,
939 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
940 chr_bank: 0,
941 mirroring,
942 })
943 }
944
945 fn read_prg(&self, addr: u16) -> u8 {
946 self.prg_rom[addr as usize - 0x8000]
948 }
949}
950
951impl Mapper for Sachen149 {
952 fn caps(&self) -> MapperCaps {
953 MapperCaps::NONE
954 }
955
956 fn cpu_read(&mut self, addr: u16) -> u8 {
957 if (0x8000..=0xFFFF).contains(&addr) {
958 self.read_prg(addr)
959 } else {
960 0
961 }
962 }
963
964 fn cpu_write(&mut self, addr: u16, value: u8) {
965 if (0x8000..=0xFFFF).contains(&addr) {
966 let effective = value & self.read_prg(addr);
968 self.chr_bank = (effective >> 7) & 0x01;
969 }
970 }
971
972 fn ppu_read(&mut self, addr: u16) -> u8 {
973 let addr = addr & 0x3FFF;
974 match addr {
975 0x0000..=0x1FFF => {
976 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
977 let bank = (self.chr_bank as usize) % count;
978 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
979 }
980 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
981 _ => 0,
982 }
983 }
984
985 fn ppu_write(&mut self, addr: u16, value: u8) {
986 let addr = addr & 0x3FFF;
987 if let 0x2000..=0x3EFF = addr {
988 let off = nametable_offset(addr, self.mirroring);
989 self.vram[off] = value;
990 }
991 }
992
993 fn current_mirroring(&self) -> Mirroring {
994 self.mirroring
995 }
996
997 fn save_state(&self) -> Vec<u8> {
998 let mut out = Vec::with_capacity(2 + self.vram.len());
999 out.push(SAVE_STATE_VERSION);
1000 out.push(self.chr_bank);
1001 out.extend_from_slice(&self.vram);
1002 out
1003 }
1004
1005 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1006 let expected = 2 + self.vram.len();
1007 if data.len() != expected {
1008 return Err(MapperError::WrongLength {
1009 expected,
1010 got: data.len(),
1011 });
1012 }
1013 if data[0] != SAVE_STATE_VERSION {
1014 return Err(MapperError::UnsupportedVersion(data[0]));
1015 }
1016 self.chr_bank = data[1];
1017 self.vram.copy_from_slice(&data[2..2 + self.vram.len()]);
1018 Ok(())
1019 }
1020}
1021
1022#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1054pub enum Sa020aBoard {
1055 Sa150,
1057 Sa020a,
1059}
1060
1061impl Sa020aBoard {
1062 const fn ines_id(self) -> u16 {
1064 match self {
1065 Self::Sa150 => 150,
1066 Self::Sa020a => 243,
1067 }
1068 }
1069}
1070
1071pub struct Sachen150 {
1082 prg_rom: Box<[u8]>,
1083 chr_rom: Box<[u8]>,
1084 vram: Box<[u8]>,
1085 chr_is_ram: bool,
1086 current_register: u8,
1087 reg: [u8; 8],
1088 board: Sa020aBoard,
1089}
1090
1091impl Sachen150 {
1092 pub fn new(prg_rom: Box<[u8]>, chr_rom: Box<[u8]>) -> Result<Self, MapperError> {
1099 Self::new_on_board(prg_rom, chr_rom, Sa020aBoard::Sa150)
1100 }
1101
1102 pub fn new_on_board(
1109 prg_rom: Box<[u8]>,
1110 chr_rom: Box<[u8]>,
1111 board: Sa020aBoard,
1112 ) -> Result<Self, MapperError> {
1113 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
1114 return Err(MapperError::Invalid(format!(
1115 "mapper {} PRG-ROM size {} is not a non-zero multiple of 32 KiB",
1116 board.ines_id(),
1117 prg_rom.len()
1118 )));
1119 }
1120 let chr_is_ram = chr_rom.is_empty();
1121 let chr_rom: Box<[u8]> = if chr_is_ram {
1122 vec![0u8; CHR_BANK_8K].into_boxed_slice()
1123 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
1124 chr_rom
1125 } else {
1126 return Err(MapperError::Invalid(format!(
1127 "mapper {} CHR-ROM size {} is not a multiple of 8 KiB",
1128 board.ines_id(),
1129 chr_rom.len()
1130 )));
1131 };
1132 Ok(Self {
1133 prg_rom,
1134 chr_rom,
1135 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
1136 chr_is_ram,
1137 current_register: 0,
1138 reg: [0u8; 8],
1139 board,
1140 })
1141 }
1142
1143 const fn prg_bank(&self) -> u8 {
1149 match self.board {
1150 Sa020aBoard::Sa150 => self.reg[5] | (self.reg[2] & 0x01),
1151 Sa020aBoard::Sa020a => self.reg[5] & 0x03,
1156 }
1157 }
1158
1159 const fn chr_bank(&self) -> u8 {
1160 match self.board {
1161 Sa020aBoard::Sa150 => {
1162 ((self.reg[2] & 0x01) << 3) | ((self.reg[4] & 0x01) << 2) | (self.reg[6] & 0x03)
1163 }
1164 Sa020aBoard::Sa020a => {
1171 (self.reg[2] & 0x01) | ((self.reg[4] & 0x01) << 1) | ((self.reg[6] & 0x03) << 2)
1172 }
1173 }
1174 }
1175
1176 const fn mirror_sel(&self) -> u8 {
1178 (self.reg[7] >> 1) & 0x03
1179 }
1180
1181 const fn write_register(&mut self, addr: u16, value: u8) {
1182 match addr & 0x0101 {
1183 0x0100 => self.current_register = value & 0x07,
1184 0x0101 => self.reg[(self.current_register & 0x07) as usize] = value & 0x07,
1185 _ => {}
1186 }
1187 }
1188
1189 fn read_prg(&self, addr: u16) -> u8 {
1190 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
1191 let bank = (self.prg_bank() as usize) % count;
1192 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
1193 }
1194
1195 fn chr_offset(&self, addr: u16) -> usize {
1196 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
1197 let bank = (self.chr_bank() as usize) % count;
1198 bank * CHR_BANK_8K + addr as usize
1199 }
1200}
1201
1202impl Mapper for Sachen150 {
1203 fn caps(&self) -> MapperCaps {
1204 MapperCaps::NONE
1205 }
1206
1207 fn cpu_read_unmapped(&self, addr: u16) -> bool {
1208 (matches!(addr, 0x6000..=0x7FFF) && self.sram().is_empty()) || {
1211 (0x4020..=0x5FFF).contains(&addr) && (addr & 0x0101) != 0x0101
1214 }
1215 }
1216
1217 fn cpu_read_driven_mask(&self, addr: u16) -> u8 {
1220 if (0x4100..=0x5FFF).contains(&addr) && (addr & 0x0101) == 0x0101 {
1221 0x07
1222 } else {
1223 0xFF
1224 }
1225 }
1226
1227 fn cpu_read(&mut self, addr: u16) -> u8 {
1228 match addr {
1229 0x4100..=0x5FFF if (addr & 0x0101) == 0x0101 => {
1230 self.reg[(self.current_register & 0x07) as usize] & 0x07
1232 }
1233 0x8000..=0xFFFF => self.read_prg(addr),
1234 _ => 0,
1235 }
1236 }
1237
1238 fn cpu_write(&mut self, addr: u16, value: u8) {
1239 match addr {
1240 0x4100..=0x5FFF => self.write_register(addr, value),
1241 0x6000..=0x7FFF => self.write_register(addr.wrapping_add(0x1000), value),
1244 _ => {}
1245 }
1246 }
1247
1248 fn ppu_read(&mut self, addr: u16) -> u8 {
1249 let addr = addr & 0x3FFF;
1250 match addr {
1251 0x0000..=0x1FFF => self.chr_rom[self.chr_offset(addr)],
1252 0x2000..=0x3EFF => self.vram[self.resolve_nametable(addr)],
1253 _ => 0,
1254 }
1255 }
1256
1257 fn ppu_write(&mut self, addr: u16, value: u8) {
1258 let addr = addr & 0x3FFF;
1259 match addr {
1260 0x0000..=0x1FFF => {
1261 if self.chr_is_ram {
1262 let off = self.chr_offset(addr);
1263 self.chr_rom[off] = value;
1264 }
1265 }
1266 0x2000..=0x3EFF => {
1267 let off = self.resolve_nametable(addr);
1268 self.vram[off] = value;
1269 }
1270 _ => {}
1271 }
1272 }
1273
1274 fn current_mirroring(&self) -> Mirroring {
1275 match self.mirror_sel() {
1276 1 => Mirroring::Horizontal,
1277 2 => Mirroring::Vertical,
1278 3 => Mirroring::SingleScreenA,
1279 _ => Mirroring::MapperControlled,
1282 }
1283 }
1284
1285 #[allow(clippy::cast_possible_truncation)]
1286 fn nametable_address(&self, addr: u16) -> u16 {
1287 self.resolve_nametable(addr) as u16
1289 }
1290
1291 fn save_state(&self) -> Vec<u8> {
1292 let chr_extra = if self.chr_is_ram {
1293 self.chr_rom.len()
1294 } else {
1295 0
1296 };
1297 let mut out = Vec::with_capacity(2 + 8 + self.vram.len() + chr_extra);
1298 out.push(SAVE_STATE_VERSION);
1299 out.push(self.current_register);
1300 out.extend_from_slice(&self.reg);
1301 out.extend_from_slice(&self.vram);
1302 if self.chr_is_ram {
1303 out.extend_from_slice(&self.chr_rom);
1304 }
1305 out
1306 }
1307
1308 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1309 let chr_extra = if self.chr_is_ram {
1310 self.chr_rom.len()
1311 } else {
1312 0
1313 };
1314 let expected = 2 + 8 + self.vram.len() + chr_extra;
1315 if data.len() != expected {
1316 return Err(MapperError::WrongLength {
1317 expected,
1318 got: data.len(),
1319 });
1320 }
1321 if data[0] != SAVE_STATE_VERSION {
1322 return Err(MapperError::UnsupportedVersion(data[0]));
1323 }
1324 self.current_register = data[1];
1325 let mut cursor = 2;
1326 self.reg.copy_from_slice(&data[cursor..cursor + 8]);
1327 cursor += 8;
1328 self.vram
1329 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
1330 cursor += self.vram.len();
1331 if self.chr_is_ram {
1332 self.chr_rom
1333 .copy_from_slice(&data[cursor..cursor + self.chr_rom.len()]);
1334 }
1335 Ok(())
1336 }
1337}
1338
1339impl Sachen150 {
1340 const fn resolve_nametable(&self, addr: u16) -> usize {
1344 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
1345 match self.mirror_sel() {
1346 0 => {
1348 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as usize;
1349 let physical = if table == 3 { 1 } else { 0 };
1350 physical * NAMETABLE_SIZE + local
1351 }
1352 1 => nametable_offset(addr, Mirroring::Horizontal),
1353 3 => nametable_offset(addr, Mirroring::SingleScreenA),
1354 _ => nametable_offset(addr, Mirroring::Vertical),
1356 }
1357 }
1358}
1359
1360pub struct SachenTca01M143 {
1362 prg_rom: Box<[u8]>,
1363 chr_rom: Box<[u8]>,
1364 vram: Box<[u8]>,
1365 mirroring: Mirroring,
1366}
1367
1368impl SachenTca01M143 {
1369 pub fn new(
1376 prg_rom: Box<[u8]>,
1377 chr_rom: Box<[u8]>,
1378 mirroring: Mirroring,
1379 ) -> Result<Self, MapperError> {
1380 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
1381 return Err(MapperError::Invalid(format!(
1382 "mapper 143 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
1383 prg_rom.len()
1384 )));
1385 }
1386 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
1387 return Err(MapperError::Invalid(format!(
1388 "mapper 143 CHR-ROM size {} is not a non-zero 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 mirroring,
1397 })
1398 }
1399
1400 fn read_prg(&self, addr: u16) -> u8 {
1403 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
1404 let bank = if addr < 0xC000 { 0 } else { 1 % count };
1405 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
1406 }
1407}
1408
1409impl Mapper for SachenTca01M143 {
1410 fn caps(&self) -> MapperCaps {
1411 MapperCaps::NONE
1412 }
1413
1414 fn cpu_read_unmapped(&self, addr: u16) -> bool {
1421 matches!(addr, 0x6000..=0x7FFF) && self.sram().is_empty()
1422 }
1423
1424 fn cpu_read_driven_mask(&self, addr: u16) -> u8 {
1428 if (0x4100..=0x5FFF).contains(&addr) && addr & 0x0100 != 0 {
1429 0x3F
1430 } else {
1431 0xFF
1432 }
1433 }
1434
1435 fn cpu_read(&mut self, addr: u16) -> u8 {
1436 match addr {
1437 0x4100..=0x5FFF if addr & 0x0100 != 0 => {
1445 ((!addr & 0x3F) as u8) | ((addr >> 8) as u8 & 0xC0)
1446 }
1447 0x4100..=0x5FFF if addr >= 0x5000 => 0xFF,
1448 0x8000..=0xFFFF => self.read_prg(addr),
1449 _ => 0,
1450 }
1451 }
1452
1453 fn cpu_write(&mut self, _addr: u16, _value: u8) {}
1454
1455 fn ppu_read(&mut self, addr: u16) -> u8 {
1456 let addr = addr & 0x3FFF;
1457 match addr {
1458 0x0000..=0x1FFF => {
1459 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
1460 let bank = 0 % count;
1461 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
1462 }
1463 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
1464 _ => 0,
1465 }
1466 }
1467
1468 fn ppu_write(&mut self, addr: u16, value: u8) {
1469 let addr = addr & 0x3FFF;
1470 if let 0x2000..=0x3EFF = addr {
1471 let off = nametable_offset(addr, self.mirroring);
1472 self.vram[off] = value;
1473 }
1474 }
1475
1476 fn current_mirroring(&self) -> Mirroring {
1477 self.mirroring
1478 }
1479
1480 fn save_state(&self) -> Vec<u8> {
1481 let mut out = Vec::with_capacity(1 + self.vram.len());
1482 out.push(SAVE_STATE_VERSION);
1483 out.extend_from_slice(&self.vram);
1484 out
1485 }
1486
1487 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1488 let expected = 1 + self.vram.len();
1489 if data.len() != expected {
1490 return Err(MapperError::WrongLength {
1491 expected,
1492 got: data.len(),
1493 });
1494 }
1495 if data[0] != SAVE_STATE_VERSION {
1496 return Err(MapperError::UnsupportedVersion(data[0]));
1497 }
1498 self.vram.copy_from_slice(&data[1..=self.vram.len()]);
1499 Ok(())
1500 }
1501}
1502
1503#[cfg(test)]
1504#[allow(clippy::cast_possible_truncation)]
1505mod tests {
1506 use super::*;
1507 fn synth_prg_16k(banks: usize) -> Box<[u8]> {
1509 let mut v = vec![0xFFu8; banks * PRG_BANK_16K];
1510 for b in 0..banks {
1511 v[b * PRG_BANK_16K] = b as u8;
1512 }
1513 v.into_boxed_slice()
1514 }
1515
1516 fn synth_prg_32k(banks: usize) -> Box<[u8]> {
1517 let mut v = vec![0xFFu8; banks * PRG_BANK_32K];
1518 for b in 0..banks {
1519 v[b * PRG_BANK_32K] = b as u8;
1520 }
1521 v.into_boxed_slice()
1522 }
1523
1524 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
1525 let mut v = vec![0u8; banks * CHR_BANK_8K];
1526 for b in 0..banks {
1527 v[b * CHR_BANK_8K] = b as u8;
1528 }
1529 v.into_boxed_slice()
1530 }
1531
1532 #[test]
1533 fn m133_register_on_a8() {
1534 let mut m = Sachen133::new(synth_prg_32k(2), synth_chr_8k(4), Mirroring::Vertical).unwrap();
1535 m.cpu_write(0x4100, 0b0000_0111);
1537 assert_eq!(m.cpu_read(0x8000), 1);
1538 assert_eq!(m.ppu_read(0x0000), 3);
1539 m.cpu_write(0x4200, 0b0000_0000);
1541 assert_eq!(m.cpu_read(0x8000), 1);
1542 }
1543
1544 #[test]
1545 fn m145_chr_from_data_bit7() {
1546 let mut m = Sachen145::new(synth_prg_32k(1), synth_chr_8k(2), Mirroring::Vertical).unwrap();
1547 assert_eq!(m.ppu_read(0x0000), 0);
1549 m.cpu_write(0x4100, 0x80);
1551 assert_eq!(m.ppu_read(0x0000), 1);
1552 m.cpu_write(0x6100, 0x00);
1554 assert_eq!(m.ppu_read(0x0000), 0);
1555 assert_eq!(m.cpu_read(0x8000), 0);
1557 }
1558
1559 #[test]
1560 fn m146_like_nina03() {
1561 let mut m = Sachen146::new(synth_prg_32k(2), synth_chr_8k(8), Mirroring::Vertical).unwrap();
1562 m.cpu_write(0x4100, 0b0000_1101);
1564 assert_eq!(m.cpu_read(0x8000), 1);
1565 assert_eq!(m.ppu_read(0x0000), 5);
1566 m.cpu_write(0x6000, 0b0000_0010); assert_eq!(m.cpu_read(0x8000), 0);
1569 assert_eq!(m.ppu_read(0x0000), 2);
1570 }
1571
1572 #[test]
1573 fn m147_jv001_protection_read_and_bank_decode() {
1574 let mut m =
1579 Sachen3018M147::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
1580 m.cpu_write(0x4101, 0x01);
1584 m.cpu_write(0x4102, 0x14);
1588 m.cpu_write(0x4100, 0x00);
1591 assert_eq!(m.cpu_read(0x4100), 0x14);
1594 m.cpu_write(0x8000, 0xFF); assert_eq!(m.cpu_read(0x8000), 1); assert_eq!(m.ppu_read(0x0000), 2); }
1603
1604 #[test]
1605 fn m147_save_state_round_trip() {
1606 let mut m =
1607 Sachen3018M147::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
1608 m.cpu_write(0x4101, 0x01);
1609 m.cpu_write(0x4102, 0x14);
1610 m.cpu_write(0x4100, 0x00);
1611 m.cpu_write(0x8000, 0x00); let prg = m.cpu_read(0x8000);
1613 let chr = m.ppu_read(0x0000);
1614 let blob = m.save_state();
1615 let mut m2 =
1616 Sachen3018M147::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
1617 m2.load_state(&blob).unwrap();
1618 assert_eq!(m2.cpu_read(0x8000), prg);
1619 assert_eq!(m2.ppu_read(0x0000), chr);
1620 }
1621
1622 #[test]
1623 fn m148_latch_selects_prg_and_chr_with_conflict() {
1624 let mut m =
1626 Sachen148::new(synth_prg_32k(2), synth_chr_8k(8), Mirroring::Horizontal).unwrap();
1627 m.cpu_write(0x8001, 0b0000_1101);
1630 assert_eq!(m.cpu_read(0x8000), 1);
1631 assert_eq!(m.ppu_read(0x0000), 5);
1632 }
1633
1634 #[test]
1635 fn m149_chr_bit_in_bit7() {
1636 let mut m = Sachen149::new(synth_prg_32k(1), synth_chr_8k(2), Mirroring::Vertical).unwrap();
1637 m.cpu_write(0x8001, 0x80);
1639 assert_eq!(m.ppu_read(0x0000), 1);
1640 assert_eq!(m.cpu_read(0x8000), 0);
1642 m.cpu_write(0x8001, 0x00);
1644 assert_eq!(m.ppu_read(0x0000), 0);
1645 }
1646
1647 #[test]
1648 fn m150_register_protocol_and_banking() {
1649 let mut m = Sachen150::new(synth_prg_32k(4), synth_chr_8k(8)).unwrap();
1650 m.cpu_write(0x4100, 5); m.cpu_write(0x4101, 2); assert_eq!(m.cpu_read(0x8000), 2);
1654 m.cpu_write(0x4100, 6);
1657 m.cpu_write(0x4101, 0b001);
1658 m.cpu_write(0x4100, 4);
1659 m.cpu_write(0x4101, 1);
1660 assert_eq!(m.ppu_read(0x0000), 5);
1661 m.cpu_write(0x4100, 6);
1663 assert_eq!(m.cpu_read(0x4101), 0b001);
1664 }
1665
1666 #[test]
1667 fn m150_mirroring_modes() {
1668 let mut m = Sachen150::new(synth_prg_32k(1), synth_chr_8k(1)).unwrap();
1669 m.cpu_write(0x4100, 7);
1671 m.cpu_write(0x4101, 1 << 1); assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
1673 m.cpu_write(0x4101, 2 << 1); assert_eq!(m.current_mirroring(), Mirroring::Vertical);
1675 m.cpu_write(0x4101, 3 << 1); assert_eq!(m.current_mirroring(), Mirroring::SingleScreenA);
1677 m.cpu_write(0x4101, 0);
1679 assert_eq!(m.current_mirroring(), Mirroring::MapperControlled);
1680 m.ppu_write(0x2000, 0xAA);
1682 m.ppu_write(0x2C00, 0xBB);
1683 assert_eq!(m.ppu_read(0x2000), 0xAA);
1684 assert_eq!(m.ppu_read(0x2C00), 0xBB);
1685 assert_eq!(m.ppu_read(0x2400), 0xAA);
1687 }
1688
1689 #[test]
1690 fn m143_protection_read_and_nrom() {
1691 let mut m =
1692 SachenTca01M143::new(synth_prg_16k(1), synth_chr_8k(1), Mirroring::Vertical).unwrap();
1693 let addr = 0x4100u16;
1696 assert_eq!(
1697 m.cpu_read(addr),
1698 ((!addr & 0x3F) as u8) | ((addr >> 8) as u8 & 0xC0)
1699 );
1700 assert_eq!(m.cpu_read(0x5000), 0xFF);
1702 assert!(!m.cpu_read_unmapped(0x4100));
1703 assert_eq!(m.cpu_read(0x8000), 0);
1705 assert_eq!(m.cpu_read(0xC000), 0);
1706 }
1707
1708 #[test]
1709 fn m143_save_state_round_trip() {
1710 let mut m =
1711 SachenTca01M143::new(synth_prg_16k(1), synth_chr_8k(1), Mirroring::Vertical).unwrap();
1712 m.ppu_write(0x2000, 0x33);
1713 let blob = m.save_state();
1714 let mut m2 =
1715 SachenTca01M143::new(synth_prg_16k(1), synth_chr_8k(1), Mirroring::Vertical).unwrap();
1716 m2.load_state(&blob).unwrap();
1717 assert_eq!(m2.ppu_read(0x2000), 0x33);
1718 }
1719
1720 fn m243() -> Sachen150 {
1725 Sachen150::new_on_board(synth_prg_32k(4), synth_chr_8k(16), Sa020aBoard::Sa020a).unwrap()
1726 }
1727
1728 fn set_reg(m: &mut Sachen150, index: u8, value: u8) {
1730 m.cpu_write(0x4100, index);
1731 m.cpu_write(0x4101, value);
1732 }
1733
1734 #[test]
1735 fn m243_prg_bank_is_r5_alone() {
1736 let mut m = m243();
1739 for bank in 0..4u8 {
1740 set_reg(&mut m, 5, bank);
1741 assert_eq!(
1742 m.cpu_read(0x8000),
1743 bank,
1744 "R5={bank} selects 32 KiB bank {bank}"
1745 );
1746 }
1747 set_reg(&mut m, 5, 2);
1749 set_reg(&mut m, 2, 1);
1750 assert_eq!(m.cpu_read(0x8000), 2, "R2 does not feed PRG on the SA-020A");
1751 }
1752
1753 #[test]
1754 fn m243_chr_bank_assembles_a13_a14_a15_a16() {
1755 let cases = [
1757 (0u8, 0u8, 0u8, 0u8),
1758 (1, 0, 0, 1),
1759 (0, 1, 0, 2),
1760 (1, 1, 0, 3),
1761 (0, 0, 1, 4),
1762 (1, 0, 1, 5),
1763 (0, 0, 2, 8),
1764 (1, 1, 3, 15),
1765 ];
1766 for (r2, r4, r6, expect) in cases {
1767 let mut m = m243();
1768 set_reg(&mut m, 2, r2);
1769 set_reg(&mut m, 4, r4);
1770 set_reg(&mut m, 6, r6);
1771 assert_eq!(
1772 m.ppu_read(0x0000),
1773 expect,
1774 "R2={r2} R4={r4} R6={r6} should select CHR bank {expect}"
1775 );
1776 }
1777 }
1778
1779 #[test]
1780 fn m243_and_m150_decode_the_same_registers_differently() {
1781 let mut a = m243();
1785 let mut b = Sachen150::new(synth_prg_32k(4), synth_chr_8k(16)).unwrap();
1786 for m in [&mut a, &mut b] {
1787 set_reg(m, 2, 1);
1788 }
1789 assert_eq!(a.ppu_read(0x0000), 1, "SA-020A: R2 is CHR A13");
1790 assert_eq!(b.ppu_read(0x0000), 8, "SA-150: R2 is the CHR MSB");
1791 assert_ne!(a.ppu_read(0x0000), b.ppu_read(0x0000));
1792 }
1793
1794 #[test]
1795 fn m243_mirroring_modes_including_the_vertically_flipped_l() {
1796 let mut m = m243();
1797 set_reg(&mut m, 7, 1 << 1);
1799 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
1800 set_reg(&mut m, 7, 2 << 1);
1801 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
1802 set_reg(&mut m, 7, 3 << 1);
1803 assert_eq!(m.current_mirroring(), Mirroring::SingleScreenA);
1804
1805 set_reg(&mut m, 7, 0);
1808 assert_eq!(m.current_mirroring(), Mirroring::MapperControlled);
1809 m.ppu_write(0x2000, 0xA1);
1810 m.ppu_write(0x2C00, 0xB2);
1811 assert_eq!(m.ppu_read(0x2000), 0xA1);
1812 assert_eq!(m.ppu_read(0x2400), 0xA1, "table 1 shares page 0");
1813 assert_eq!(m.ppu_read(0x2800), 0xA1, "table 2 shares page 0");
1814 assert_eq!(m.ppu_read(0x2C00), 0xB2, "table 3 is the unique page");
1815 }
1816
1817 #[test]
1818 fn m243_registers_read_back_and_round_trip_through_a_save_state() {
1819 let mut m = m243();
1820 set_reg(&mut m, 6, 3);
1821 assert_eq!(
1822 m.cpu_read(0x4101),
1823 3,
1824 "the data port reads the selected register"
1825 );
1826 set_reg(&mut m, 2, 1);
1827 set_reg(&mut m, 7, 2 << 1);
1828 let blob = m.save_state();
1829
1830 let mut m2 = m243();
1831 m2.load_state(&blob).unwrap();
1832 assert_eq!(m2.ppu_read(0x0000), m.ppu_read(0x0000));
1833 assert_eq!(m2.current_mirroring(), Mirroring::Vertical);
1834 }
1835}