1#![allow(
25 clippy::bool_to_int_with_if,
26 clippy::cast_lossless,
27 clippy::cast_possible_truncation,
28 clippy::doc_markdown,
29 clippy::match_same_arms,
30 clippy::missing_const_for_fn,
31 clippy::similar_names,
32 clippy::struct_excessive_bools,
33 clippy::too_many_lines,
34 clippy::unreadable_literal
35)]
36
37use crate::cartridge::Mirroring;
38use crate::mapper::{Mapper, MapperCaps, MapperError};
39use alloc::{boxed::Box, vec::Vec};
40use alloc::{format, vec};
41
42const PRG_BANK_8K: usize = 0x2000;
43const PRG_BANK_16K: usize = 0x4000;
44const PRG_BANK_32K: usize = 0x8000;
45const CHR_BANK_2K: usize = 0x0800;
46const CHR_BANK_8K: usize = 0x2000;
47const NAMETABLE_SIZE: usize = 0x0400;
48const NAMETABLE_SIZE_U16: u16 = 0x0400;
49
50const SAVE_STATE_VERSION: u8 = 1;
51
52const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
57 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
58 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
59 let physical = mirroring.physical_bank(table);
60 physical * NAMETABLE_SIZE + local
61}
62
63const CHR_BANK_1K: usize = 0x0400;
64
65const fn byte_to_mirroring(b: u8, fallback: Mirroring) -> Mirroring {
66 match b {
67 0 => Mirroring::Horizontal,
68 1 => Mirroring::Vertical,
69 2 => Mirroring::SingleScreenA,
70 3 => Mirroring::SingleScreenB,
71 4 => Mirroring::FourScreen,
72 5 => Mirroring::MapperControlled,
73 _ => fallback,
74 }
75}
76
77fn check_prg(prg: &[u8], id: u16) -> Result<(), MapperError> {
79 if prg.is_empty() || !prg.len().is_multiple_of(PRG_BANK_8K) {
80 return Err(MapperError::Invalid(format!(
81 "mapper {id} PRG-ROM size {} is not a non-zero multiple of 8 KiB",
82 prg.len()
83 )));
84 }
85 Ok(())
86}
87
88const fn mirroring_to_byte(m: Mirroring) -> u8 {
89 match m {
90 Mirroring::Horizontal => 0,
91 Mirroring::Vertical => 1,
92 Mirroring::SingleScreenA => 2,
93 Mirroring::SingleScreenB => 3,
94 Mirroring::FourScreen => 4,
95 Mirroring::MapperControlled => 5,
96 }
97}
98
99pub struct Waixing242 {
101 prg_rom: Box<[u8]>,
102 chr_ram: Box<[u8]>,
103 vram: Box<[u8]>,
104 prg_ram: Box<[u8]>,
106 prg_bank: u8,
107 horizontal_mirroring: bool,
108}
109
110impl Waixing242 {
111 pub fn new(
118 prg_rom: Box<[u8]>,
119 _chr_rom: &[u8],
120 _mirroring: Mirroring,
121 ) -> Result<Self, MapperError> {
122 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
123 return Err(MapperError::Invalid(format!(
124 "mapper 242 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
125 prg_rom.len()
126 )));
127 }
128 Ok(Self {
129 prg_rom,
130 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
131 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
132 prg_ram: vec![0u8; PRG_BANK_8K].into_boxed_slice(),
133 prg_bank: 0,
134 horizontal_mirroring: false,
135 })
136 }
137}
138
139impl Mapper for Waixing242 {
140 fn sram(&self) -> &[u8] {
141 &self.prg_ram
142 }
143 fn sram_mut(&mut self) -> &mut [u8] {
144 &mut self.prg_ram
145 }
146 fn caps(&self) -> MapperCaps {
147 MapperCaps::NONE
148 }
149
150 fn cpu_read(&mut self, addr: u16) -> u8 {
154 if (0x6000..=0x7FFF).contains(&addr) {
155 self.prg_ram[(addr - 0x6000) as usize]
156 } else if (0x8000..=0xFFFF).contains(&addr) {
157 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
158 let bank = (self.prg_bank as usize) % count;
159 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
160 } else {
161 0
162 }
163 }
164
165 fn cpu_write(&mut self, addr: u16, value: u8) {
166 if (0x6000..=0x7FFF).contains(&addr) {
167 self.prg_ram[(addr - 0x6000) as usize] = value;
168 } else if (0x8000..=0xFFFF).contains(&addr) {
169 let inner = ((addr >> 2) & 0x07) as u8;
175 let outer = ((addr >> 5) & 0x03) as u8;
176 self.prg_bank = (outer << 2) | (inner >> 1);
179 self.horizontal_mirroring = (addr & 0x02) != 0;
180 }
181 }
182
183 fn ppu_read(&mut self, addr: u16) -> u8 {
184 let addr = addr & 0x3FFF;
185 match addr {
186 0x0000..=0x1FFF => self.chr_ram[addr as usize],
187 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
188 _ => 0,
189 }
190 }
191
192 fn ppu_write(&mut self, addr: u16, value: u8) {
193 let addr = addr & 0x3FFF;
194 match addr {
195 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
196 0x2000..=0x3EFF => {
197 let off = nametable_offset(addr, self.current_mirroring());
198 self.vram[off] = value;
199 }
200 _ => {}
201 }
202 }
203
204 fn current_mirroring(&self) -> Mirroring {
205 if self.horizontal_mirroring {
206 Mirroring::Horizontal
207 } else {
208 Mirroring::Vertical
209 }
210 }
211
212 fn save_state(&self) -> Vec<u8> {
213 let mut out =
214 Vec::with_capacity(3 + self.prg_ram.len() + self.vram.len() + self.chr_ram.len());
215 out.push(SAVE_STATE_VERSION);
216 out.push(self.prg_bank);
217 out.push(u8::from(self.horizontal_mirroring));
218 out.extend_from_slice(&self.prg_ram);
219 out.extend_from_slice(&self.vram);
220 out.extend_from_slice(&self.chr_ram);
221 out
222 }
223
224 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
225 let expected = 3 + self.prg_ram.len() + self.vram.len() + self.chr_ram.len();
226 if data.len() != expected {
227 return Err(MapperError::WrongLength {
228 expected,
229 got: data.len(),
230 });
231 }
232 if data[0] != SAVE_STATE_VERSION {
233 return Err(MapperError::UnsupportedVersion(data[0]));
234 }
235 self.prg_bank = data[1];
236 self.horizontal_mirroring = data[2] != 0;
237 let mut cursor = 3;
238 self.prg_ram
239 .copy_from_slice(&data[cursor..cursor + self.prg_ram.len()]);
240 cursor += self.prg_ram.len();
241 self.vram
242 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
243 cursor += self.vram.len();
244 self.chr_ram
245 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
246 Ok(())
247 }
248}
249
250pub struct WaixingFs304M162 {
252 prg_rom: Box<[u8]>,
253 chr_ram: Box<[u8]>,
254 vram: Box<[u8]>,
255 prg_ram: Box<[u8]>,
258 regs: [u8; 4],
259 mirroring: Mirroring,
260}
261
262impl WaixingFs304M162 {
263 pub fn new(
270 prg_rom: Box<[u8]>,
271 _chr_rom: &[u8],
272 mirroring: Mirroring,
273 ) -> Result<Self, MapperError> {
274 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
275 return Err(MapperError::Invalid(format!(
276 "mapper 162 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
277 prg_rom.len()
278 )));
279 }
280 Ok(Self {
281 prg_rom,
282 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
283 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
284 prg_ram: vec![0u8; PRG_BANK_8K].into_boxed_slice(),
285 regs: [0; 4],
286 mirroring,
287 })
288 }
289
290 const fn prg_bank(&self) -> usize {
291 let r0 = self.regs[0] as usize;
292 let r1 = self.regs[1] as usize;
293 let r2 = self.regs[2] as usize;
294 let r3 = self.regs[3] as usize;
295 let a = (r3 >> 2) & 1; let b = r3 & 1; let a16 = if a == 0 { 1 } else { (r0 >> 1) & 1 };
298 let a15 = if b == 0 {
299 (r1 >> 1) & 1
300 } else if a == 0 {
301 1
302 } else {
303 r0 & 1
304 };
305 let a17 = (r0 >> 2) & 1;
306 let a18 = (r0 >> 3) & 1;
307 let a19 = r2 & 1;
308 let a20 = (r2 >> 1) & 1;
309 a15 | (a16 << 1) | (a17 << 2) | (a18 << 3) | (a19 << 4) | (a20 << 5)
310 }
311}
312
313impl Mapper for WaixingFs304M162 {
314 fn sram(&self) -> &[u8] {
315 &self.prg_ram
316 }
317 fn sram_mut(&mut self) -> &mut [u8] {
318 &mut self.prg_ram
319 }
320 fn caps(&self) -> MapperCaps {
321 MapperCaps::NONE
322 }
323
324 fn cpu_read(&mut self, addr: u16) -> u8 {
325 match addr {
326 0x6000..=0x7FFF => self.prg_ram[(addr - 0x6000) as usize],
327 0x8000..=0xFFFF => {
328 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
329 let bank = self.prg_bank() % count;
330 self.prg_rom[bank * PRG_BANK_32K + (addr as usize & 0x7FFF)]
331 }
332 _ => 0,
333 }
334 }
335
336 fn cpu_read_unmapped(&self, addr: u16) -> bool {
337 (matches!(addr, 0x6000..=0x7FFF) && self.sram().is_empty()) || {
340 (0x4020..=0x5FFF).contains(&addr)
344 }
345 }
346
347 fn cpu_write(&mut self, addr: u16, value: u8) {
348 if (0x6000..=0x7FFF).contains(&addr) {
349 self.prg_ram[(addr - 0x6000) as usize] = value;
350 } else if (0x5000..=0x5FFF).contains(&addr) {
351 let idx = ((addr >> 8) & 0x03) as usize;
352 self.regs[idx] = value;
353 }
354 }
355
356 fn ppu_read(&mut self, addr: u16) -> u8 {
357 let addr = addr & 0x3FFF;
358 match addr {
359 0x0000..=0x1FFF => self.chr_ram[addr as usize],
360 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
361 _ => 0,
362 }
363 }
364
365 fn ppu_write(&mut self, addr: u16, value: u8) {
366 let addr = addr & 0x3FFF;
367 match addr {
368 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
369 0x2000..=0x3EFF => {
370 let off = nametable_offset(addr, self.mirroring);
371 self.vram[off] = value;
372 }
373 _ => {}
374 }
375 }
376
377 fn current_mirroring(&self) -> Mirroring {
378 self.mirroring
379 }
380
381 fn save_state(&self) -> Vec<u8> {
382 let mut out =
383 Vec::with_capacity(5 + self.vram.len() + self.chr_ram.len() + self.prg_ram.len());
384 out.push(SAVE_STATE_VERSION);
385 out.extend_from_slice(&self.regs);
386 out.extend_from_slice(&self.vram);
387 out.extend_from_slice(&self.chr_ram);
388 out.extend_from_slice(&self.prg_ram);
389 out
390 }
391
392 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
393 let expected = 5 + self.vram.len() + self.chr_ram.len() + self.prg_ram.len();
394 if data.len() != expected {
395 return Err(MapperError::WrongLength {
396 expected,
397 got: data.len(),
398 });
399 }
400 if data[0] != SAVE_STATE_VERSION {
401 return Err(MapperError::UnsupportedVersion(data[0]));
402 }
403 self.regs.copy_from_slice(&data[1..5]);
404 let mut cursor = 5;
405 self.vram
406 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
407 cursor += self.vram.len();
408 self.chr_ram
409 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
410 cursor += self.chr_ram.len();
411 self.prg_ram
412 .copy_from_slice(&data[cursor..cursor + self.prg_ram.len()]);
413 Ok(())
414 }
415}
416
417pub struct Waixing178 {
439 prg_rom: Box<[u8]>,
440 chr_ram: Box<[u8]>,
441 vram: Box<[u8]>,
442 prg_ram: Box<[u8]>,
443 regs: [u8; 4],
444}
445
446impl Waixing178 {
447 pub fn new(
454 prg_rom: Box<[u8]>,
455 _chr_rom: &[u8],
456 _mirroring: Mirroring,
457 ) -> Result<Self, MapperError> {
458 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
459 return Err(MapperError::Invalid(format!(
460 "mapper 178 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
461 prg_rom.len()
462 )));
463 }
464 Ok(Self {
465 prg_rom,
466 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
467 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
468 prg_ram: vec![0u8; PRG_BANK_8K].into_boxed_slice(),
469 regs: [0; 4],
470 })
471 }
472
473 const fn prg_base16(&self) -> usize {
476 ((self.regs[2] as usize) << 3) | (self.regs[1] as usize & 0x07)
477 }
478
479 const fn prg_mode(&self) -> u8 {
481 (self.regs[0] >> 1) & 0x03
482 }
483
484 fn read_prg(&self, bank16: usize, addr: u16) -> u8 {
485 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
486 let bank = bank16 % count;
487 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
488 }
489}
490
491impl Mapper for Waixing178 {
492 fn sram(&self) -> &[u8] {
493 &self.prg_ram
494 }
495 fn sram_mut(&mut self) -> &mut [u8] {
496 &mut self.prg_ram
497 }
498 fn caps(&self) -> MapperCaps {
499 MapperCaps::NONE
500 }
501
502 fn cpu_read(&mut self, addr: u16) -> u8 {
503 match addr {
504 0x6000..=0x7FFF => self.prg_ram[(addr - 0x6000) as usize],
505 0x8000..=0xBFFF => {
506 let base = self.prg_base16();
507 let bank = if self.prg_mode() == 0 {
510 base & !1
511 } else {
512 base
513 };
514 self.read_prg(bank, addr)
515 }
516 0xC000..=0xFFFF => {
517 let base = self.prg_base16();
518 let bank = match self.prg_mode() {
519 0 => (base & !1) | 1,
521 1 => (base & !0x07) | 0x07,
524 2 => base,
526 _ => base | 1,
528 };
529 self.read_prg(bank, addr)
530 }
531 _ => 0,
532 }
533 }
534
535 fn cpu_read_unmapped(&self, addr: u16) -> bool {
536 (matches!(addr, 0x6000..=0x7FFF) && self.sram().is_empty()) || {
539 (0x4020..=0x5FFF).contains(&addr)
542 }
543 }
544
545 fn cpu_write(&mut self, addr: u16, value: u8) {
546 match addr {
547 0x4800..=0x4803 => self.regs[(addr - 0x4800) as usize] = value,
548 0x6000..=0x7FFF => self.prg_ram[(addr - 0x6000) as usize] = value,
549 _ => {}
550 }
551 }
552
553 fn ppu_read(&mut self, addr: u16) -> u8 {
554 let addr = addr & 0x3FFF;
555 match addr {
556 0x0000..=0x1FFF => self.chr_ram[addr as usize],
557 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
558 _ => 0,
559 }
560 }
561
562 fn ppu_write(&mut self, addr: u16, value: u8) {
563 let addr = addr & 0x3FFF;
564 match addr {
565 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
566 0x2000..=0x3EFF => {
567 let off = nametable_offset(addr, self.current_mirroring());
568 self.vram[off] = value;
569 }
570 _ => {}
571 }
572 }
573
574 fn current_mirroring(&self) -> Mirroring {
575 if (self.regs[0] & 0x01) != 0 {
577 Mirroring::Horizontal
578 } else {
579 Mirroring::Vertical
580 }
581 }
582
583 fn save_state(&self) -> Vec<u8> {
584 let mut out =
585 Vec::with_capacity(5 + self.prg_ram.len() + self.vram.len() + self.chr_ram.len());
586 out.push(SAVE_STATE_VERSION);
587 out.extend_from_slice(&self.regs);
588 out.extend_from_slice(&self.prg_ram);
589 out.extend_from_slice(&self.vram);
590 out.extend_from_slice(&self.chr_ram);
591 out
592 }
593
594 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
595 let expected = 5 + self.prg_ram.len() + self.vram.len() + self.chr_ram.len();
596 if data.len() != expected {
597 return Err(MapperError::WrongLength {
598 expected,
599 got: data.len(),
600 });
601 }
602 if data[0] != SAVE_STATE_VERSION {
603 return Err(MapperError::UnsupportedVersion(data[0]));
604 }
605 self.regs.copy_from_slice(&data[1..5]);
606 let mut cursor = 5;
607 self.prg_ram
608 .copy_from_slice(&data[cursor..cursor + self.prg_ram.len()]);
609 cursor += self.prg_ram.len();
610 self.vram
611 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
612 cursor += self.vram.len();
613 self.chr_ram
614 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
615 Ok(())
616 }
617}
618
619pub struct Waixing253 {
621 prg_rom: Box<[u8]>,
622 chr: Box<[u8]>,
623 chr_is_ram: bool,
628 chr_ram: Box<[u8]>,
629 vram: Box<[u8]>,
630 mirroring: Mirroring,
631 prg_count_8k: usize,
632 chr_count_1k: usize,
633 prg: [u8; 2],
634 chr_low: [u8; 8],
635 chr_high: [u8; 8],
636 force_chr_rom: bool,
637 irq_reload: u8,
638 irq_counter: u8,
639 irq_enabled: bool,
640 irq_scaler: u16,
641 irq_pending: bool,
642}
643
644impl Waixing253 {
645 const SAVE_LEN: usize = 2 + 8 + 8 + 1 + 1 + 1 + 1 + 2 + 1 + 1;
646
647 fn new(
648 prg_rom: Box<[u8]>,
649 chr_rom: Box<[u8]>,
650 mirroring: Mirroring,
651 ) -> Result<Self, MapperError> {
652 check_prg(&prg_rom, 253)?;
653 let chr_is_ram = chr_rom.is_empty();
654 let chr: Box<[u8]> = if chr_is_ram {
655 vec![0u8; CHR_BANK_8K].into_boxed_slice()
659 } else {
660 if !chr_rom.len().is_multiple_of(CHR_BANK_1K) {
661 return Err(MapperError::Invalid(format!(
662 "mapper 253 CHR-ROM size {} is not a multiple of 1 KiB",
663 chr_rom.len()
664 )));
665 }
666 chr_rom
667 };
668 let prg_count_8k = prg_rom.len() / PRG_BANK_8K;
669 let chr_count_1k = (chr.len() / CHR_BANK_1K).max(1);
670 Ok(Self {
671 prg_rom,
672 chr,
673 chr_is_ram,
674 chr_ram: vec![0u8; CHR_BANK_2K].into_boxed_slice(), vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
676 mirroring,
677 prg_count_8k,
678 chr_count_1k,
679 prg: [0; 2],
680 chr_low: [0; 8],
681 chr_high: [0; 8],
682 force_chr_rom: false,
683 irq_reload: 0,
684 irq_counter: 0,
685 irq_enabled: false,
686 irq_scaler: 0,
687 irq_pending: false,
688 })
689 }
690
691 fn prg_bank(&self, slot: usize) -> usize {
692 let count = self.prg_count_8k;
693 match slot {
694 0 => self.prg[0] as usize % count,
695 1 => self.prg[1] as usize % count,
696 2 => count.saturating_sub(2),
697 _ => count - 1,
698 }
699 }
700}
701
702impl Mapper for Waixing253 {
703 fn caps(&self) -> MapperCaps {
704 MapperCaps::CYCLE_IRQ
705 }
706
707 fn cpu_read(&mut self, addr: u16) -> u8 {
708 match addr {
709 0x8000..=0xFFFF => {
710 let slot = (addr as usize - 0x8000) / PRG_BANK_8K;
711 let bank = self.prg_bank(slot);
712 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
713 }
714 _ => 0,
715 }
716 }
717
718 fn cpu_write(&mut self, addr: u16, value: u8) {
719 if (0xB000..=0xE00C).contains(&addr) {
720 let slot = ((((addr & 0x08) | (addr >> 8)) >> 3) as usize).wrapping_add(2) & 0x07;
721 let shift = (addr & 0x04) as u8;
722 let lo = (self.chr_low[slot] & (0xF0u8 >> shift)) | (value << shift);
723 self.chr_low[slot] = lo;
724 if slot == 0 {
725 if lo == 0xC8 {
726 self.force_chr_rom = false;
727 } else if lo == 0x88 {
728 self.force_chr_rom = true;
729 }
730 }
731 if shift != 0 {
732 self.chr_high[slot] = value >> 4;
733 }
734 } else {
735 match addr {
736 0x8010 => self.prg[0] = value,
737 0xA010 => self.prg[1] = value,
738 0x9400 => {
739 self.mirroring = match value & 0x03 {
740 0 => Mirroring::Vertical,
741 1 => Mirroring::Horizontal,
742 2 => Mirroring::SingleScreenA,
743 _ => Mirroring::SingleScreenB,
744 };
745 }
746 0xF000 => {
747 self.irq_reload = (self.irq_reload & 0xF0) | (value & 0x0F);
748 self.irq_pending = false;
749 }
750 0xF004 => {
751 self.irq_reload = (self.irq_reload & 0x0F) | (value << 4);
752 self.irq_pending = false;
753 }
754 0xF008 => {
755 self.irq_counter = self.irq_reload;
756 self.irq_enabled = value & 0x02 != 0;
757 self.irq_scaler = 0;
758 self.irq_pending = false;
759 }
760 _ => {}
761 }
762 }
763 }
764
765 fn ppu_read(&mut self, addr: u16) -> u8 {
766 let addr = addr & 0x3FFF;
767 match addr {
768 0x0000..=0x1FFF => {
769 let slot = (addr as usize) / CHR_BANK_1K;
770 let lo = self.chr_low[slot];
771 if (lo == 4 || lo == 5) && !self.force_chr_rom {
772 let page = (lo as usize & 0x01) * CHR_BANK_1K;
773 return self.chr_ram
774 [(page + (addr as usize & 0x3FF)) & (self.chr_ram.len() - 1)];
775 }
776 let page =
777 (lo as usize | ((self.chr_high[slot] as usize) << 8)) % self.chr_count_1k;
778 self.chr[page * CHR_BANK_1K + (addr as usize & 0x3FF)]
779 }
780 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
781 _ => 0,
782 }
783 }
784
785 fn ppu_write(&mut self, addr: u16, value: u8) {
786 let addr = addr & 0x3FFF;
787 match addr {
788 0x0000..=0x1FFF => {
789 let slot = (addr as usize) / CHR_BANK_1K;
790 let lo = self.chr_low[slot];
791 if (lo == 4 || lo == 5) && !self.force_chr_rom {
792 let page = (lo as usize & 0x01) * CHR_BANK_1K;
793 let off = (page + (addr as usize & 0x3FF)) & (self.chr_ram.len() - 1);
794 self.chr_ram[off] = value;
795 } else if self.chr_is_ram {
796 let page =
800 (lo as usize | ((self.chr_high[slot] as usize) << 8)) % self.chr_count_1k;
801 self.chr[page * CHR_BANK_1K + (addr as usize & 0x3FF)] = value;
802 }
803 }
804 0x2000..=0x3EFF => {
805 let off = nametable_offset(addr, self.mirroring);
806 self.vram[off] = value;
807 }
808 _ => {}
809 }
810 }
811
812 fn notify_cpu_cycle(&mut self) {
813 if self.irq_enabled {
814 self.irq_scaler += 1;
815 if self.irq_scaler >= 114 {
816 self.irq_scaler = 0;
817 self.irq_counter = self.irq_counter.wrapping_add(1);
818 if self.irq_counter == 0 {
819 self.irq_counter = self.irq_reload;
820 self.irq_pending = true;
821 }
822 }
823 }
824 }
825
826 fn irq_pending(&self) -> bool {
827 self.irq_pending
828 }
829
830 fn irq_acknowledge(&mut self) {
831 self.irq_pending = false;
832 }
833
834 fn current_mirroring(&self) -> Mirroring {
835 self.mirroring
836 }
837
838 fn save_state(&self) -> Vec<u8> {
839 let chr_ram_main = if self.chr_is_ram { self.chr.len() } else { 0 };
841 let mut out = Vec::with_capacity(
842 1 + Self::SAVE_LEN + self.vram.len() + self.chr_ram.len() + chr_ram_main,
843 );
844 out.push(SAVE_STATE_VERSION);
845 out.extend_from_slice(&self.prg);
846 out.extend_from_slice(&self.chr_low);
847 out.extend_from_slice(&self.chr_high);
848 out.push(u8::from(self.force_chr_rom));
849 out.push(self.irq_reload);
850 out.push(self.irq_counter);
851 out.push(u8::from(self.irq_enabled));
852 out.extend_from_slice(&self.irq_scaler.to_le_bytes());
853 out.push(u8::from(self.irq_pending));
854 out.push(mirroring_to_byte(self.mirroring));
855 out.extend_from_slice(&self.vram);
856 out.extend_from_slice(&self.chr_ram);
857 if self.chr_is_ram {
858 out.extend_from_slice(&self.chr);
859 }
860 out
861 }
862
863 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
864 let chr_ram_main = if self.chr_is_ram { self.chr.len() } else { 0 };
865 let expected = 1 + Self::SAVE_LEN + self.vram.len() + self.chr_ram.len() + chr_ram_main;
866 if data.len() != expected {
867 return Err(MapperError::WrongLength {
868 expected,
869 got: data.len(),
870 });
871 }
872 if data[0] != SAVE_STATE_VERSION {
873 return Err(MapperError::UnsupportedVersion(data[0]));
874 }
875 let mut c = 1;
876 self.prg.copy_from_slice(&data[c..c + 2]);
877 c += 2;
878 self.chr_low.copy_from_slice(&data[c..c + 8]);
879 c += 8;
880 self.chr_high.copy_from_slice(&data[c..c + 8]);
881 c += 8;
882 self.force_chr_rom = data[c] != 0;
883 self.irq_reload = data[c + 1];
884 self.irq_counter = data[c + 2];
885 self.irq_enabled = data[c + 3] != 0;
886 self.irq_scaler = u16::from_le_bytes([data[c + 4], data[c + 5]]);
887 self.irq_pending = data[c + 6] != 0;
888 self.mirroring = byte_to_mirroring(data[c + 7], self.mirroring);
889 c += 8;
890 self.vram.copy_from_slice(&data[c..c + self.vram.len()]);
891 c += self.vram.len();
892 self.chr_ram
893 .copy_from_slice(&data[c..c + self.chr_ram.len()]);
894 c += self.chr_ram.len();
895 if self.chr_is_ram {
896 self.chr.copy_from_slice(&data[c..c + self.chr.len()]);
897 }
898 Ok(())
899 }
900}
901
902pub fn new_m253(
907 prg_rom: Box<[u8]>,
908 chr_rom: Box<[u8]>,
909 mirroring: Mirroring,
910) -> Result<Waixing253, MapperError> {
911 Waixing253::new(prg_rom, chr_rom, mirroring)
912}
913
914#[cfg(test)]
915#[allow(clippy::cast_possible_truncation)]
916mod tests {
917 use super::*;
918 fn synth_chr_1k(banks: usize) -> Box<[u8]> {
920 let mut v = vec![0u8; banks * CHR_BANK_1K];
921 for b in 0..banks {
922 v[b * CHR_BANK_1K] = b as u8;
923 }
924 v.into_boxed_slice()
925 }
926
927 fn synth_prg_32k(banks: usize) -> Box<[u8]> {
928 let mut v = vec![0xFFu8; banks * PRG_BANK_32K];
929 for b in 0..banks {
930 v[b * PRG_BANK_32K] = b as u8;
931 }
932 v.into_boxed_slice()
933 }
934
935 fn synth_prg_16k(banks: usize) -> Box<[u8]> {
936 let mut v = vec![0xFFu8; banks * PRG_BANK_16K];
937 for b in 0..banks {
938 v[b * PRG_BANK_16K] = b as u8;
939 }
940 v.into_boxed_slice()
941 }
942
943 fn synth_prg_8k(banks: usize) -> Box<[u8]> {
944 let mut v = vec![0xFFu8; banks * PRG_BANK_8K];
945 for b in 0..banks {
946 v[b * PRG_BANK_8K] = b as u8;
947 }
948 v.into_boxed_slice()
949 }
950
951 #[test]
952 fn m242_address_decoded_32k_and_mirror() {
953 let mut m = Waixing242::new(synth_prg_32k(16), &[], Mirroring::Vertical).unwrap();
954 m.cpu_write(0x8018, 0);
956 assert_eq!(m.cpu_read(0x8000), 3);
957 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
958 m.cpu_write(0x801A, 0);
960 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
961 }
962
963 #[test]
964 fn m242_save_state_round_trip() {
965 let mut m = Waixing242::new(synth_prg_32k(16), &[], Mirroring::Vertical).unwrap();
966 m.cpu_write(0x8028, 0); m.ppu_write(0x0006, 0x44);
968 let blob = m.save_state();
969 let mut m2 = Waixing242::new(synth_prg_32k(16), &[], Mirroring::Vertical).unwrap();
970 m2.load_state(&blob).unwrap();
971 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
972 assert_eq!(m2.ppu_read(0x0006), 0x44);
973 }
974
975 #[test]
976 fn m162_regs_compose_prg() {
977 let mut m = WaixingFs304M162::new(synth_prg_32k(64), &[], Mirroring::Vertical).unwrap();
978 assert_eq!(m.cpu_read(0x8000), 2);
980 m.cpu_write(0x5300, 0x04);
982 m.cpu_write(0x5000, 0x02); assert_eq!(m.cpu_read(0x8000), 2); m.cpu_write(0x5100, 0x02); assert_eq!(m.cpu_read(0x8000), 3);
986 m.cpu_write(0x5000, 0x00); assert_eq!(m.cpu_read(0x8000), 1);
988 m.cpu_write(0x5000, 0x0C); m.cpu_write(0x5200, 0x03); assert_eq!(m.cpu_read(0x8000), 1 + 12 + 48);
992 }
993
994 #[test]
995 fn m162_save_state_round_trip() {
996 let mut m = WaixingFs304M162::new(synth_prg_32k(8), &[], Mirroring::Vertical).unwrap();
997 m.cpu_write(0x5000, 6);
998 m.ppu_write(0x0012, 0x44);
999 let blob = m.save_state();
1000 let mut m2 = WaixingFs304M162::new(synth_prg_32k(8), &[], Mirroring::Vertical).unwrap();
1001 m2.load_state(&blob).unwrap();
1002 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
1003 assert_eq!(m2.ppu_read(0x0012), 0x44);
1004 }
1005
1006 #[test]
1007 fn m178_prg_mode_and_work_ram() {
1008 let mut m = Waixing178::new(synth_prg_16k(8), &[], Mirroring::Vertical).unwrap();
1009 m.cpu_write(0x4800, 0x02);
1011 m.cpu_write(0x4801, 0x03);
1012 m.cpu_write(0x4802, 0x00);
1013 assert_eq!(m.cpu_read(0x8000), 3); assert_eq!(m.cpu_read(0xC000), 7); m.cpu_write(0x4800, 0x00);
1017 m.cpu_write(0x4801, 0x02); assert_eq!(m.cpu_read(0x8000), 2);
1019 assert_eq!(m.cpu_read(0xC000), 3);
1020 m.cpu_write(0x6000, 0x77);
1022 assert_eq!(m.cpu_read(0x6000), 0x77);
1023 m.cpu_write(0x4800, 0x01);
1025 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
1026 m.cpu_write(0x4800, 0x00);
1027 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
1028 }
1029
1030 #[test]
1031 fn m178_save_state_round_trip() {
1032 let mut m = Waixing178::new(synth_prg_16k(8), &[], Mirroring::Vertical).unwrap();
1033 m.cpu_write(0x4800, 0x02);
1034 m.cpu_write(0x4801, 0x02);
1035 m.cpu_write(0x6010, 0x55);
1036 let blob = m.save_state();
1037 let mut m2 = Waixing178::new(synth_prg_16k(8), &[], Mirroring::Vertical).unwrap();
1038 m2.load_state(&blob).unwrap();
1039 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
1040 assert_eq!(m2.cpu_read(0x6010), 0x55);
1041 }
1042
1043 #[test]
1044 fn waixing253_prg_and_scaled_irq() {
1045 let mut m = new_m253(synth_prg_8k(16), synth_chr_1k(64), Mirroring::Vertical).unwrap();
1046 m.cpu_write(0x8010, 4); m.cpu_write(0xA010, 6); assert_eq!(m.cpu_read(0x8000), 4);
1049 assert_eq!(m.cpu_read(0xA000), 6);
1050 assert_eq!(m.cpu_read(0xE000), 15); m.cpu_write(0xF000, 0x0E); m.cpu_write(0xF008, 0x02); let mut fired = false;
1056 for _ in 0..(256 * 115) {
1057 m.notify_cpu_cycle();
1058 if m.irq_pending() {
1059 fired = true;
1060 break;
1061 }
1062 }
1063 assert!(fired);
1064 }
1065
1066 #[test]
1067 fn waixing253_chr_ram_escape_and_round_trip() {
1068 let mut m = new_m253(synth_prg_8k(16), synth_chr_1k(64), Mirroring::Vertical).unwrap();
1069 m.cpu_write(0xB000, 0x04); m.ppu_write(0x0000, 0x5E);
1072 assert_eq!(m.ppu_read(0x0000), 0x5E);
1073 let blob = m.save_state();
1074 let mut m2 = new_m253(synth_prg_8k(16), synth_chr_1k(64), Mirroring::Vertical).unwrap();
1075 m2.load_state(&blob).unwrap();
1076 assert_eq!(m2.ppu_read(0x0000), 0x5E);
1077 }
1078
1079 #[test]
1080 fn waixing253_chr_ram_variant_writable_and_round_trips() {
1081 let mut m = new_m253(synth_prg_8k(16), Box::new([]), Mirroring::Vertical).unwrap();
1085 m.ppu_write(0x0000, 0xA5);
1087 m.ppu_write(0x0123, 0x3C);
1088 assert_eq!(m.ppu_read(0x0000), 0xA5);
1089 assert_eq!(m.ppu_read(0x0123), 0x3C);
1090 let blob = m.save_state();
1092 let mut m2 = new_m253(synth_prg_8k(16), Box::new([]), Mirroring::Vertical).unwrap();
1093 m2.load_state(&blob).unwrap();
1094 assert_eq!(m2.ppu_read(0x0000), 0xA5);
1095 assert_eq!(m2.ppu_read(0x0123), 0x3C);
1096 }
1097
1098 #[test]
1099 fn waixing253_chr_rom_not_writable() {
1100 let mut m = new_m253(synth_prg_8k(16), synth_chr_1k(64), Mirroring::Vertical).unwrap();
1102 let before = m.ppu_read(0x0010);
1103 m.ppu_write(0x0010, before.wrapping_add(1));
1104 assert_eq!(m.ppu_read(0x0010), before, "CHR-ROM must not be mutable");
1105 }
1106}