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_8K: usize = 0x2000;
45const PRG_BANK_16K: usize = 0x4000;
46const CHR_BANK_2K: usize = 0x0800;
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 CHR_BANK_1K: usize = 0x0400;
65
66const fn byte_to_mirroring(b: u8, fallback: Mirroring) -> Mirroring {
67 match b {
68 0 => Mirroring::Horizontal,
69 1 => Mirroring::Vertical,
70 2 => Mirroring::SingleScreenA,
71 3 => Mirroring::SingleScreenB,
72 4 => Mirroring::FourScreen,
73 5 => Mirroring::MapperControlled,
74 _ => fallback,
75 }
76}
77
78const fn mirroring_to_byte(m: Mirroring) -> u8 {
79 match m {
80 Mirroring::Horizontal => 0,
81 Mirroring::Vertical => 1,
82 Mirroring::SingleScreenA => 2,
83 Mirroring::SingleScreenB => 3,
84 Mirroring::FourScreen => 4,
85 Mirroring::MapperControlled => 5,
86 }
87}
88
89pub struct Ntdec63 {
91 prg_rom: Box<[u8]>,
92 chr_ram: Box<[u8]>,
93 vram: Box<[u8]>,
94 prg_bank: u8,
95 prg32_mode: bool,
96 horizontal_mirroring: bool,
97}
98
99impl Ntdec63 {
100 pub fn new(
107 prg_rom: Box<[u8]>,
108 _chr_rom: &[u8],
109 _mirroring: Mirroring,
110 ) -> Result<Self, MapperError> {
111 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
112 return Err(MapperError::Invalid(format!(
113 "mapper 63 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
114 prg_rom.len()
115 )));
116 }
117 Ok(Self {
118 prg_rom,
119 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
120 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
121 prg_bank: 0,
122 prg32_mode: true,
123 horizontal_mirroring: false,
124 })
125 }
126
127 fn read_prg(&self, bank16: usize, addr: u16) -> u8 {
128 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
129 let bank = bank16 % count;
130 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
131 }
132}
133
134impl Mapper for Ntdec63 {
135 fn caps(&self) -> MapperCaps {
136 MapperCaps::NONE
137 }
138
139 fn cpu_read(&mut self, addr: u16) -> u8 {
140 match addr {
141 0x8000..=0xBFFF => {
142 let base = (self.prg_bank as usize) & if self.prg32_mode { !1 } else { !0 };
143 self.read_prg(base, addr)
144 }
145 0xC000..=0xFFFF => {
146 let base = self.prg_bank as usize;
147 let bank = if self.prg32_mode {
148 (base & !1) | 1
149 } else {
150 base
151 };
152 self.read_prg(bank, addr)
153 }
154 _ => 0,
155 }
156 }
157
158 fn cpu_write(&mut self, addr: u16, _value: u8) {
159 if (0x8000..=0xFFFF).contains(&addr) {
160 self.prg_bank = ((addr >> 2) & 0x3F) as u8;
161 self.prg32_mode = (addr & 0x02) == 0;
162 self.horizontal_mirroring = (addr & 0x01) != 0;
163 }
164 }
165
166 fn ppu_read(&mut self, addr: u16) -> u8 {
167 let addr = addr & 0x3FFF;
168 match addr {
169 0x0000..=0x1FFF => self.chr_ram[addr as usize],
170 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
171 _ => 0,
172 }
173 }
174
175 fn ppu_write(&mut self, addr: u16, value: u8) {
176 let addr = addr & 0x3FFF;
177 match addr {
178 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
179 0x2000..=0x3EFF => {
180 let off = nametable_offset(addr, self.current_mirroring());
181 self.vram[off] = value;
182 }
183 _ => {}
184 }
185 }
186
187 fn current_mirroring(&self) -> Mirroring {
188 if self.horizontal_mirroring {
189 Mirroring::Horizontal
190 } else {
191 Mirroring::Vertical
192 }
193 }
194
195 fn save_state(&self) -> Vec<u8> {
196 let mut out = Vec::with_capacity(4 + self.vram.len() + self.chr_ram.len());
197 out.push(SAVE_STATE_VERSION);
198 out.push(self.prg_bank);
199 out.push(u8::from(self.prg32_mode));
200 out.push(u8::from(self.horizontal_mirroring));
201 out.extend_from_slice(&self.vram);
202 out.extend_from_slice(&self.chr_ram);
203 out
204 }
205
206 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
207 let expected = 4 + self.vram.len() + self.chr_ram.len();
208 if data.len() != expected {
209 return Err(MapperError::WrongLength {
210 expected,
211 got: data.len(),
212 });
213 }
214 if data[0] != SAVE_STATE_VERSION {
215 return Err(MapperError::UnsupportedVersion(data[0]));
216 }
217 self.prg_bank = data[1];
218 self.prg32_mode = data[2] != 0;
219 self.horizontal_mirroring = data[3] != 0;
220 let mut cursor = 4;
221 self.vram
222 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
223 cursor += self.vram.len();
224 self.chr_ram
225 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
226 Ok(())
227 }
228}
229
230pub struct Ntdec174 {
232 prg_rom: Box<[u8]>,
233 chr_rom: Box<[u8]>,
234 vram: Box<[u8]>,
235 prg_bank: u8,
236 prg16_mode: bool,
237 chr_bank: u8,
238 horizontal_mirroring: bool,
239}
240
241impl Ntdec174 {
242 pub fn new(
249 prg_rom: Box<[u8]>,
250 chr_rom: Box<[u8]>,
251 _mirroring: Mirroring,
252 ) -> Result<Self, MapperError> {
253 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
254 return Err(MapperError::Invalid(format!(
255 "mapper 174 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
256 prg_rom.len()
257 )));
258 }
259 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
260 return Err(MapperError::Invalid(format!(
261 "mapper 174 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
262 chr_rom.len()
263 )));
264 }
265 Ok(Self {
266 prg_rom,
267 chr_rom,
268 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
269 prg_bank: 0,
270 prg16_mode: false,
271 chr_bank: 0,
272 horizontal_mirroring: false,
273 })
274 }
275
276 fn read_prg(&self, bank16: usize, addr: u16) -> u8 {
277 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
278 let bank = bank16 % count;
279 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
280 }
281}
282
283impl Mapper for Ntdec174 {
284 fn caps(&self) -> MapperCaps {
285 MapperCaps::NONE
286 }
287
288 fn cpu_read(&mut self, addr: u16) -> u8 {
289 match addr {
290 0x8000..=0xBFFF => {
291 let base = if self.prg16_mode {
292 self.prg_bank as usize
293 } else {
294 (self.prg_bank as usize) & !1
295 };
296 self.read_prg(base, addr)
297 }
298 0xC000..=0xFFFF => {
299 let bank = if self.prg16_mode {
300 self.prg_bank as usize
301 } else {
302 ((self.prg_bank as usize) & !1) | 1
303 };
304 self.read_prg(bank, addr)
305 }
306 _ => 0,
307 }
308 }
309
310 fn cpu_write(&mut self, addr: u16, _value: u8) {
311 if (0x8000..=0xFFFF).contains(&addr) {
312 self.prg_bank = ((addr >> 4) & 0x07) as u8;
313 self.prg16_mode = (addr & 0x80) != 0;
314 self.chr_bank = ((addr >> 1) & 0x07) as u8;
315 self.horizontal_mirroring = (addr & 0x01) != 0;
316 }
317 }
318
319 fn ppu_read(&mut self, addr: u16) -> u8 {
320 let addr = addr & 0x3FFF;
321 match addr {
322 0x0000..=0x1FFF => {
323 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
324 let bank = (self.chr_bank as usize) % count;
325 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
326 }
327 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
328 _ => 0,
329 }
330 }
331
332 fn ppu_write(&mut self, addr: u16, value: u8) {
333 let addr = addr & 0x3FFF;
334 if let 0x2000..=0x3EFF = addr {
335 let off = nametable_offset(addr, self.current_mirroring());
336 self.vram[off] = value;
337 }
338 }
339
340 fn current_mirroring(&self) -> Mirroring {
341 if self.horizontal_mirroring {
342 Mirroring::Horizontal
343 } else {
344 Mirroring::Vertical
345 }
346 }
347
348 fn save_state(&self) -> Vec<u8> {
349 let mut out = Vec::with_capacity(5 + self.vram.len());
350 out.push(SAVE_STATE_VERSION);
351 out.push(self.prg_bank);
352 out.push(u8::from(self.prg16_mode));
353 out.push(self.chr_bank);
354 out.push(u8::from(self.horizontal_mirroring));
355 out.extend_from_slice(&self.vram);
356 out
357 }
358
359 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
360 let expected = 5 + self.vram.len();
361 if data.len() != expected {
362 return Err(MapperError::WrongLength {
363 expected,
364 got: data.len(),
365 });
366 }
367 if data[0] != SAVE_STATE_VERSION {
368 return Err(MapperError::UnsupportedVersion(data[0]));
369 }
370 self.prg_bank = data[1];
371 self.prg16_mode = data[2] != 0;
372 self.chr_bank = data[3];
373 self.horizontal_mirroring = data[4] != 0;
374 self.vram.copy_from_slice(&data[5..5 + self.vram.len()]);
375 Ok(())
376 }
377}
378
379pub struct Ntdec2722M40 {
381 prg_rom: Box<[u8]>,
382 chr_ram: Box<[u8]>,
383 vram: Box<[u8]>,
384 switch_bank: u8,
385 irq_enabled: bool,
386 irq_counter: u16,
387 irq_pending: bool,
388 mirroring: Mirroring,
389}
390
391impl Ntdec2722M40 {
392 pub fn new(
399 prg_rom: Box<[u8]>,
400 _chr_rom: &[u8],
401 mirroring: Mirroring,
402 ) -> Result<Self, MapperError> {
403 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
404 return Err(MapperError::Invalid(format!(
405 "mapper 40 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
406 prg_rom.len()
407 )));
408 }
409 Ok(Self {
410 prg_rom,
411 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
412 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
413 switch_bank: 0,
414 irq_enabled: false,
415 irq_counter: 0,
416 irq_pending: false,
417 mirroring,
418 })
419 }
420
421 fn read_prg(&self, bank: usize, addr: u16) -> u8 {
422 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
423 let bank = bank % count;
424 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
425 }
426}
427
428impl Mapper for Ntdec2722M40 {
429 fn caps(&self) -> MapperCaps {
430 MapperCaps::CYCLE_IRQ
431 }
432
433 fn cpu_read(&mut self, addr: u16) -> u8 {
434 match addr {
435 0x6000..=0x7FFF => self.read_prg(6, addr),
436 0x8000..=0x9FFF => self.read_prg(4, addr),
437 0xA000..=0xBFFF => self.read_prg(5, addr),
438 0xC000..=0xDFFF => self.read_prg(self.switch_bank as usize, addr),
439 0xE000..=0xFFFF => self.read_prg(7, addr),
440 _ => 0,
441 }
442 }
443
444 fn cpu_read_unmapped(&self, addr: u16) -> bool {
445 (0x4020..=0x5FFF).contains(&addr)
447 }
448
449 fn cpu_write(&mut self, addr: u16, value: u8) {
450 match addr {
451 0x8000..=0x9FFF => {
452 self.irq_enabled = false;
454 self.irq_pending = false;
455 self.irq_counter = 0;
456 }
457 0xA000..=0xBFFF => self.irq_enabled = true,
458 0xE000..=0xFFFF => self.switch_bank = value & 0x07,
459 _ => {}
460 }
461 }
462
463 fn ppu_read(&mut self, addr: u16) -> u8 {
464 let addr = addr & 0x3FFF;
465 match addr {
466 0x0000..=0x1FFF => self.chr_ram[addr as usize],
467 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
468 _ => 0,
469 }
470 }
471
472 fn ppu_write(&mut self, addr: u16, value: u8) {
473 let addr = addr & 0x3FFF;
474 match addr {
475 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
476 0x2000..=0x3EFF => {
477 let off = nametable_offset(addr, self.mirroring);
478 self.vram[off] = value;
479 }
480 _ => {}
481 }
482 }
483
484 fn notify_cpu_cycle(&mut self) {
485 if !self.irq_enabled {
486 return;
487 }
488 if self.irq_counter >= 0x1000 {
490 self.irq_pending = true;
491 } else {
492 self.irq_counter += 1;
493 if self.irq_counter >= 0x1000 {
494 self.irq_pending = true;
495 }
496 }
497 }
498
499 fn irq_pending(&self) -> bool {
500 self.irq_pending
501 }
502
503 fn irq_acknowledge(&mut self) {
504 self.irq_pending = false;
505 }
506
507 fn current_mirroring(&self) -> Mirroring {
508 self.mirroring
509 }
510
511 fn save_state(&self) -> Vec<u8> {
512 let mut out = Vec::with_capacity(6 + self.vram.len() + self.chr_ram.len());
513 out.push(SAVE_STATE_VERSION);
514 out.push(self.switch_bank);
515 out.push(u8::from(self.irq_enabled));
516 out.push((self.irq_counter & 0xFF) as u8);
517 out.push((self.irq_counter >> 8) as u8);
518 out.push(u8::from(self.irq_pending));
519 out.extend_from_slice(&self.vram);
520 out.extend_from_slice(&self.chr_ram);
521 out
522 }
523
524 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
525 let expected = 6 + self.vram.len() + self.chr_ram.len();
526 if data.len() != expected {
527 return Err(MapperError::WrongLength {
528 expected,
529 got: data.len(),
530 });
531 }
532 if data[0] != SAVE_STATE_VERSION {
533 return Err(MapperError::UnsupportedVersion(data[0]));
534 }
535 self.switch_bank = data[1];
536 self.irq_enabled = data[2] != 0;
537 self.irq_counter = u16::from(data[3]) | (u16::from(data[4]) << 8);
538 self.irq_pending = data[5] != 0;
539 let mut cursor = 6;
540 self.vram
541 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
542 cursor += self.vram.len();
543 self.chr_ram
544 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
545 Ok(())
546 }
547}
548
549pub struct Ntdec81 {
560 prg_rom: Box<[u8]>,
561 chr_rom: Box<[u8]>,
562 vram: Box<[u8]>,
563 prg_bank: u8,
564 chr_bank: u8,
565 mirroring: Mirroring,
566}
567
568impl Ntdec81 {
569 pub fn new(
576 prg_rom: Box<[u8]>,
577 chr_rom: Box<[u8]>,
578 mirroring: Mirroring,
579 ) -> Result<Self, MapperError> {
580 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
581 return Err(MapperError::Invalid(format!(
582 "mapper 81 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
583 prg_rom.len()
584 )));
585 }
586 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
587 return Err(MapperError::Invalid(format!(
588 "mapper 81 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
589 chr_rom.len()
590 )));
591 }
592 Ok(Self {
593 prg_rom,
594 chr_rom,
595 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
596 prg_bank: 0,
597 chr_bank: 0,
598 mirroring,
599 })
600 }
601}
602
603impl Mapper for Ntdec81 {
604 fn caps(&self) -> MapperCaps {
605 MapperCaps::NONE
606 }
607
608 fn cpu_read(&mut self, addr: u16) -> u8 {
609 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
610 match addr {
611 0x8000..=0xBFFF => {
612 let bank = (self.prg_bank as usize) % count;
613 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
614 }
615 0xC000..=0xFFFF => {
616 let last = count - 1;
617 self.prg_rom[last * PRG_BANK_16K + (addr as usize & 0x3FFF)]
618 }
619 _ => 0,
620 }
621 }
622
623 fn cpu_write(&mut self, addr: u16, value: u8) {
624 if (0x8000..=0xFFFF).contains(&addr) {
625 self.prg_bank = (value >> 2) & 0x03;
626 self.chr_bank = value & 0x03;
627 }
628 }
629
630 fn ppu_read(&mut self, addr: u16) -> u8 {
631 let addr = addr & 0x3FFF;
632 match addr {
633 0x0000..=0x1FFF => {
634 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
635 let bank = (self.chr_bank as usize) % count;
636 self.chr_rom[bank * CHR_BANK_8K + (addr as usize & 0x1FFF)]
637 }
638 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
639 _ => 0,
640 }
641 }
642
643 fn ppu_write(&mut self, addr: u16, value: u8) {
644 let addr = addr & 0x3FFF;
645 if (0x2000..=0x3EFF).contains(&addr) {
646 let off = nametable_offset(addr, self.mirroring);
647 self.vram[off] = value;
648 }
649 }
650
651 fn current_mirroring(&self) -> Mirroring {
652 self.mirroring
653 }
654
655 fn save_state(&self) -> Vec<u8> {
656 let mut out = Vec::with_capacity(3 + self.vram.len());
657 out.push(SAVE_STATE_VERSION);
658 out.push(self.prg_bank);
659 out.push(self.chr_bank);
660 out.extend_from_slice(&self.vram);
661 out
662 }
663
664 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
665 let expected = 3 + self.vram.len();
666 if data.len() != expected {
667 return Err(MapperError::WrongLength {
668 expected,
669 got: data.len(),
670 });
671 }
672 if data[0] != SAVE_STATE_VERSION {
673 return Err(MapperError::UnsupportedVersion(data[0]));
674 }
675 self.prg_bank = data[1];
676 self.chr_bank = data[2];
677 self.vram.copy_from_slice(&data[3..3 + self.vram.len()]);
678 Ok(())
679 }
680}
681
682pub struct NtdecAsder112 {
684 prg_rom: Box<[u8]>,
685 chr_rom: Box<[u8]>,
686 vram: Box<[u8]>,
687 reg_index: u8,
688 prg_banks: [u8; 2],
689 chr_regs: [u8; 6],
691 horizontal_mirroring: bool,
692}
693
694impl NtdecAsder112 {
695 pub fn new(
702 prg_rom: Box<[u8]>,
703 chr_rom: Box<[u8]>,
704 mirroring: Mirroring,
705 ) -> Result<Self, MapperError> {
706 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
707 return Err(MapperError::Invalid(format!(
708 "mapper 112 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
709 prg_rom.len()
710 )));
711 }
712 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_1K) {
713 return Err(MapperError::Invalid(format!(
714 "mapper 112 CHR-ROM size {} is not a non-zero multiple of 1 KiB",
715 chr_rom.len()
716 )));
717 }
718 Ok(Self {
719 prg_rom,
720 chr_rom,
721 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
722 reg_index: 0,
723 prg_banks: [0, 1],
724 chr_regs: [0; 6],
725 horizontal_mirroring: mirroring == Mirroring::Horizontal,
726 })
727 }
728
729 fn read_prg(&self, bank: usize, addr: u16) -> u8 {
730 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
731 let bank = bank % count;
732 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
733 }
734
735 fn read_chr(&self, addr: u16) -> u8 {
736 let count1k = (self.chr_rom.len() / CHR_BANK_1K).max(1);
737 let bank1k = match addr {
738 0x0000..=0x07FF => (self.chr_regs[0] as usize & !1) + ((addr as usize >> 10) & 1),
739 0x0800..=0x0FFF => (self.chr_regs[1] as usize & !1) + ((addr as usize >> 10) & 1),
740 0x1000..=0x13FF => self.chr_regs[2] as usize,
741 0x1400..=0x17FF => self.chr_regs[3] as usize,
742 0x1800..=0x1BFF => self.chr_regs[4] as usize,
743 _ => self.chr_regs[5] as usize,
744 };
745 let bank = bank1k % count1k;
746 self.chr_rom[bank * CHR_BANK_1K + (addr as usize & 0x03FF)]
747 }
748}
749
750impl Mapper for NtdecAsder112 {
751 fn caps(&self) -> MapperCaps {
752 MapperCaps::NONE
753 }
754
755 fn cpu_read(&mut self, addr: u16) -> u8 {
756 let last = (self.prg_rom.len() / PRG_BANK_8K).max(1) - 1;
757 match addr {
758 0x8000..=0x9FFF => self.read_prg(self.prg_banks[0] as usize, addr),
759 0xA000..=0xBFFF => self.read_prg(self.prg_banks[1] as usize, addr),
760 0xC000..=0xDFFF => self.read_prg(last - 1, addr),
761 0xE000..=0xFFFF => self.read_prg(last, addr),
762 _ => 0,
763 }
764 }
765
766 fn cpu_write(&mut self, addr: u16, value: u8) {
767 match addr & 0xE001 {
768 0x8000 => self.reg_index = value & 0x07,
769 0xA000 => match self.reg_index {
770 0 => self.prg_banks[0] = value,
771 1 => self.prg_banks[1] = value,
772 2 => self.chr_regs[0] = value,
773 3 => self.chr_regs[1] = value,
774 4 => self.chr_regs[2] = value,
775 5 => self.chr_regs[3] = value,
776 6 => self.chr_regs[4] = value,
777 _ => self.chr_regs[5] = value,
778 },
779 0xE000 => self.horizontal_mirroring = (value & 0x01) != 0,
780 _ => {}
781 }
782 }
783
784 fn ppu_read(&mut self, addr: u16) -> u8 {
785 let addr = addr & 0x3FFF;
786 match addr {
787 0x0000..=0x1FFF => self.read_chr(addr),
788 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
789 _ => 0,
790 }
791 }
792
793 fn ppu_write(&mut self, addr: u16, value: u8) {
794 let addr = addr & 0x3FFF;
795 if (0x2000..=0x3EFF).contains(&addr) {
796 let off = nametable_offset(addr, self.current_mirroring());
797 self.vram[off] = value;
798 }
799 }
800
801 fn current_mirroring(&self) -> Mirroring {
802 if self.horizontal_mirroring {
803 Mirroring::Horizontal
804 } else {
805 Mirroring::Vertical
806 }
807 }
808
809 fn save_state(&self) -> Vec<u8> {
810 let mut out = Vec::with_capacity(11 + self.vram.len());
811 out.push(SAVE_STATE_VERSION);
812 out.push(self.reg_index);
813 out.extend_from_slice(&self.prg_banks);
814 out.extend_from_slice(&self.chr_regs);
815 out.push(u8::from(self.horizontal_mirroring));
816 out.extend_from_slice(&self.vram);
817 out
818 }
819
820 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
821 let expected = 11 + self.vram.len();
822 if data.len() != expected {
823 return Err(MapperError::WrongLength {
824 expected,
825 got: data.len(),
826 });
827 }
828 if data[0] != SAVE_STATE_VERSION {
829 return Err(MapperError::UnsupportedVersion(data[0]));
830 }
831 self.reg_index = data[1];
832 self.prg_banks.copy_from_slice(&data[2..4]);
833 self.chr_regs.copy_from_slice(&data[4..10]);
834 self.horizontal_mirroring = data[10] != 0;
835 self.vram.copy_from_slice(&data[11..11 + self.vram.len()]);
836 Ok(())
837 }
838}
839
840fn check_prg(prg: &[u8], id: u16) -> Result<(), MapperError> {
842 if prg.is_empty() || !prg.len().is_multiple_of(PRG_BANK_8K) {
843 return Err(MapperError::Invalid(format!(
844 "mapper {id} PRG-ROM size {} is not a non-zero multiple of 8 KiB",
845 prg.len()
846 )));
847 }
848 Ok(())
849}
850
851fn chr_or_ram(chr_rom: Box<[u8]>) -> (Box<[u8]>, bool) {
854 if chr_rom.is_empty() {
855 (vec![0u8; CHR_BANK_8K].into_boxed_slice(), true)
856 } else {
857 (chr_rom, false)
858 }
859}
860
861pub struct NtdecTc112 {
876 prg_rom: Box<[u8]>,
877 chr: Box<[u8]>,
878 chr_is_ram: bool,
879 vram: Box<[u8]>,
880 mirroring: Mirroring,
881 prg0: usize,
883 chr2: [usize; 4],
885}
886
887impl NtdecTc112 {
888 fn new(
889 prg_rom: Box<[u8]>,
890 chr_rom: Box<[u8]>,
891 mirroring: Mirroring,
892 ) -> Result<Self, MapperError> {
893 check_prg(&prg_rom, 193)?;
894 let (chr, chr_is_ram) = chr_or_ram(chr_rom);
895 Ok(Self {
896 prg_rom,
897 chr,
898 chr_is_ram,
899 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
900 mirroring,
901 prg0: 0,
902 chr2: [0; 4],
903 })
904 }
905
906 fn prg_count_8k(&self) -> usize {
907 (self.prg_rom.len() / PRG_BANK_8K).max(1)
908 }
909
910 fn chr_count_2k(&self) -> usize {
911 (self.chr.len() / CHR_BANK_2K).max(1)
912 }
913}
914
915impl Mapper for NtdecTc112 {
916 fn caps(&self) -> MapperCaps {
917 MapperCaps::NONE
918 }
919
920 fn cpu_read(&mut self, addr: u16) -> u8 {
921 match addr {
922 0x8000..=0xFFFF => {
924 let count = self.prg_count_8k();
925 let slot = (addr as usize - 0x8000) / PRG_BANK_8K;
926 let bank = match slot {
927 0 => self.prg0 % count,
928 _ => count.saturating_sub(4 - slot) % count, };
932 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
933 }
934 _ => 0,
935 }
936 }
937
938 fn cpu_write(&mut self, addr: u16, value: u8) {
939 if (0x6000..=0x7FFF).contains(&addr) {
940 match addr & 0x03 {
941 0 => {
942 self.chr2[0] = (value >> 1) as usize;
944 self.chr2[1] = (value >> 1) as usize + 1;
945 }
946 1 => self.chr2[2] = (value >> 1) as usize,
947 2 => self.chr2[3] = (value >> 1) as usize,
948 _ => self.prg0 = value as usize,
949 }
950 }
951 }
952
953 fn ppu_read(&mut self, addr: u16) -> u8 {
954 let addr = addr & 0x3FFF;
955 match addr {
956 0x0000..=0x1FFF => {
957 if self.chr_is_ram {
958 return self.chr[addr as usize & (CHR_BANK_8K - 1)];
959 }
960 let slot = (addr as usize) / CHR_BANK_2K;
961 let count = self.chr_count_2k();
962 let bank = self.chr2[slot] % count;
963 self.chr[bank * CHR_BANK_2K + (addr as usize & (CHR_BANK_2K - 1))]
964 }
965 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
966 _ => 0,
967 }
968 }
969
970 fn ppu_write(&mut self, addr: u16, value: u8) {
971 let addr = addr & 0x3FFF;
972 match addr {
973 0x0000..=0x1FFF if self.chr_is_ram => {
974 self.chr[addr as usize & (CHR_BANK_8K - 1)] = value;
975 }
976 0x2000..=0x3EFF => {
977 let off = nametable_offset(addr, self.mirroring);
978 self.vram[off] = value;
979 }
980 _ => {}
981 }
982 }
983
984 fn current_mirroring(&self) -> Mirroring {
985 self.mirroring
986 }
987
988 fn save_state(&self) -> Vec<u8> {
989 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
990 let mut out = Vec::with_capacity(1 + 4 + 16 + 1 + self.vram.len() + chr_ram);
991 out.push(SAVE_STATE_VERSION);
992 out.extend_from_slice(&(self.prg0 as u32).to_le_bytes());
993 for c in &self.chr2 {
994 out.extend_from_slice(&(*c as u32).to_le_bytes());
995 }
996 out.push(mirroring_to_byte(self.mirroring));
997 out.extend_from_slice(&self.vram);
998 if self.chr_is_ram {
999 out.extend_from_slice(&self.chr);
1000 }
1001 out
1002 }
1003
1004 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1005 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
1006 let expected = 1 + 4 + 16 + 1 + self.vram.len() + chr_ram;
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 let rd = |c: usize| {
1017 u32::from_le_bytes([data[c], data[c + 1], data[c + 2], data[c + 3]]) as usize
1018 };
1019 let mut c = 1;
1020 self.prg0 = rd(c);
1021 c += 4;
1022 for s in &mut self.chr2 {
1023 *s = rd(c);
1024 c += 4;
1025 }
1026 self.mirroring = byte_to_mirroring(data[c], self.mirroring);
1027 c += 1;
1028 self.vram.copy_from_slice(&data[c..c + self.vram.len()]);
1029 c += self.vram.len();
1030 if self.chr_is_ram {
1031 self.chr.copy_from_slice(&data[c..c + self.chr.len()]);
1032 }
1033 Ok(())
1034 }
1035}
1036
1037pub fn new_m193(
1042 prg_rom: Box<[u8]>,
1043 chr_rom: Box<[u8]>,
1044 mirroring: Mirroring,
1045) -> Result<NtdecTc112, MapperError> {
1046 NtdecTc112::new(prg_rom, chr_rom, mirroring)
1047}
1048
1049pub struct NtdecN625092 {
1063 prg_rom: Box<[u8]>,
1064 chr: Box<[u8]>,
1065 chr_is_ram: bool,
1066 vram: Box<[u8]>,
1067 mirroring: Mirroring,
1068 mode: u16,
1069 prg_reg: u8,
1070 prg0: usize,
1072 prg1: usize,
1073}
1074
1075impl NtdecN625092 {
1076 fn new(
1077 prg_rom: Box<[u8]>,
1078 chr_rom: Box<[u8]>,
1079 mirroring: Mirroring,
1080 ) -> Result<Self, MapperError> {
1081 check_prg(&prg_rom, 221)?;
1082 if prg_rom.len() < PRG_BANK_16K {
1083 return Err(MapperError::Invalid(format!(
1084 "mapper 221 PRG-ROM size {} is smaller than one 16 KiB bank",
1085 prg_rom.len()
1086 )));
1087 }
1088 let (chr, chr_is_ram) = chr_or_ram(chr_rom);
1089 let mut m = Self {
1090 prg_rom,
1091 chr,
1092 chr_is_ram,
1093 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
1094 mirroring,
1095 mode: 0,
1096 prg_reg: 0,
1097 prg0: 0,
1098 prg1: 0,
1099 };
1100 m.update_state();
1101 Ok(m)
1102 }
1103
1104 fn prg_count_16k(&self) -> usize {
1105 (self.prg_rom.len() / PRG_BANK_16K).max(1)
1106 }
1107
1108 fn update_state(&mut self) {
1109 let outer = ((self.mode & 0xFC) >> 2) as usize;
1110 let reg = self.prg_reg as usize;
1111 if self.mode & 0x02 != 0 {
1112 if self.mode & 0x0100 != 0 {
1113 self.prg0 = outer | reg;
1115 self.prg1 = outer | 0x07;
1116 } else {
1117 let b = outer | (reg & 0x06);
1120 self.prg0 = b;
1121 self.prg1 = b | 1;
1122 }
1123 } else {
1124 self.prg0 = outer | reg;
1126 self.prg1 = outer | reg;
1127 }
1128 self.mirroring = if self.mode & 0x01 != 0 {
1129 Mirroring::Horizontal
1130 } else {
1131 Mirroring::Vertical
1132 };
1133 }
1134
1135 fn prg_byte(&self, slot16: usize, addr: u16) -> u8 {
1136 let count = self.prg_count_16k();
1137 let bank = slot16 % count;
1138 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
1139 }
1140}
1141
1142impl Mapper for NtdecN625092 {
1143 fn caps(&self) -> MapperCaps {
1144 MapperCaps::NONE
1145 }
1146
1147 fn cpu_read(&mut self, addr: u16) -> u8 {
1148 match addr {
1149 0x8000..=0xBFFF => self.prg_byte(self.prg0, addr),
1150 0xC000..=0xFFFF => self.prg_byte(self.prg1, addr),
1151 _ => 0,
1152 }
1153 }
1154
1155 fn cpu_write(&mut self, addr: u16, _value: u8) {
1156 match addr & 0xC000 {
1157 0x8000 => {
1158 self.mode = addr;
1159 self.update_state();
1160 }
1161 0xC000 => {
1162 self.prg_reg = (addr & 0x07) as u8;
1163 self.update_state();
1164 }
1165 _ => {}
1166 }
1167 }
1168
1169 fn ppu_read(&mut self, addr: u16) -> u8 {
1170 let addr = addr & 0x3FFF;
1171 match addr {
1172 0x0000..=0x1FFF => self.chr[addr as usize & (CHR_BANK_8K - 1)],
1173 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
1174 _ => 0,
1175 }
1176 }
1177
1178 fn ppu_write(&mut self, addr: u16, value: u8) {
1179 let addr = addr & 0x3FFF;
1180 match addr {
1181 0x0000..=0x1FFF if self.chr_is_ram => {
1182 self.chr[addr as usize & (CHR_BANK_8K - 1)] = value;
1183 }
1184 0x2000..=0x3EFF => {
1185 let off = nametable_offset(addr, self.mirroring);
1186 self.vram[off] = value;
1187 }
1188 _ => {}
1189 }
1190 }
1191
1192 fn current_mirroring(&self) -> Mirroring {
1193 self.mirroring
1194 }
1195
1196 fn save_state(&self) -> Vec<u8> {
1197 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
1198 let mut out = Vec::with_capacity(1 + 2 + 1 + 8 + 1 + self.vram.len() + chr_ram);
1199 out.push(SAVE_STATE_VERSION);
1200 out.extend_from_slice(&self.mode.to_le_bytes());
1201 out.push(self.prg_reg);
1202 out.extend_from_slice(&(self.prg0 as u32).to_le_bytes());
1203 out.extend_from_slice(&(self.prg1 as u32).to_le_bytes());
1204 out.push(mirroring_to_byte(self.mirroring));
1205 out.extend_from_slice(&self.vram);
1206 if self.chr_is_ram {
1207 out.extend_from_slice(&self.chr);
1208 }
1209 out
1210 }
1211
1212 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
1213 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
1214 let expected = 1 + 2 + 1 + 8 + 1 + self.vram.len() + chr_ram;
1215 if data.len() != expected {
1216 return Err(MapperError::WrongLength {
1217 expected,
1218 got: data.len(),
1219 });
1220 }
1221 if data[0] != SAVE_STATE_VERSION {
1222 return Err(MapperError::UnsupportedVersion(data[0]));
1223 }
1224 let rd = |c: usize| {
1225 u32::from_le_bytes([data[c], data[c + 1], data[c + 2], data[c + 3]]) as usize
1226 };
1227 self.mode = u16::from_le_bytes([data[1], data[2]]);
1228 self.prg_reg = data[3];
1229 self.prg0 = rd(4);
1230 self.prg1 = rd(8);
1231 let mut c = 12;
1232 self.mirroring = byte_to_mirroring(data[c], self.mirroring);
1233 c += 1;
1234 self.vram.copy_from_slice(&data[c..c + self.vram.len()]);
1235 c += self.vram.len();
1236 if self.chr_is_ram {
1237 self.chr.copy_from_slice(&data[c..c + self.chr.len()]);
1238 }
1239 Ok(())
1240 }
1241}
1242
1243pub fn new_m221(
1248 prg_rom: Box<[u8]>,
1249 chr_rom: Box<[u8]>,
1250 mirroring: Mirroring,
1251) -> Result<NtdecN625092, MapperError> {
1252 NtdecN625092::new(prg_rom, chr_rom, mirroring)
1253}
1254
1255#[cfg(test)]
1266#[allow(clippy::cast_possible_truncation)]
1267mod tests {
1268 use super::*;
1269 fn synth_chr_1k(banks: usize) -> Box<[u8]> {
1271 let mut v = vec![0u8; banks * CHR_BANK_1K];
1272 for b in 0..banks {
1273 v[b * CHR_BANK_1K] = b as u8;
1274 }
1275 v.into_boxed_slice()
1276 }
1277
1278 fn synth_prg_16k(banks: usize) -> Box<[u8]> {
1279 let mut v = vec![0xFFu8; banks * PRG_BANK_16K];
1280 for b in 0..banks {
1281 v[b * PRG_BANK_16K] = b as u8;
1282 }
1283 v.into_boxed_slice()
1284 }
1285
1286 fn synth_prg_8k(banks: usize) -> Box<[u8]> {
1287 let mut v = vec![0xFFu8; banks * PRG_BANK_8K];
1288 for b in 0..banks {
1289 v[b * PRG_BANK_8K] = b as u8;
1290 }
1291 v.into_boxed_slice()
1292 }
1293
1294 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
1295 let mut v = vec![0u8; banks * CHR_BANK_8K];
1296 for b in 0..banks {
1297 v[b * CHR_BANK_8K] = b as u8;
1298 }
1299 v.into_boxed_slice()
1300 }
1301
1302 #[test]
1303 fn m63_address_decoded_bank_and_mode() {
1304 let mut m = Ntdec63::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
1305 m.cpu_write(0x8008, 0);
1308 assert_eq!(m.cpu_read(0x8000), 2);
1309 assert_eq!(m.cpu_read(0xC000), 3); assert_eq!(m.current_mirroring(), Mirroring::Vertical);
1311 }
1312
1313 #[test]
1314 fn m63_save_state_round_trip() {
1315 let mut m = Ntdec63::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
1316 m.cpu_write(0x8009, 0); m.ppu_write(0x0010, 0x12);
1318 let blob = m.save_state();
1319 let mut m2 = Ntdec63::new(synth_prg_16k(16), &[], Mirroring::Vertical).unwrap();
1320 m2.load_state(&blob).unwrap();
1321 assert_eq!(m2.current_mirroring(), Mirroring::Horizontal);
1322 assert_eq!(m2.ppu_read(0x0010), 0x12);
1323 }
1324
1325 #[test]
1326 fn m174_address_decoded_prg_chr_mirror() {
1327 let mut m = Ntdec174::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
1328 m.cpu_write(0x8020, 0);
1331 assert_eq!(m.cpu_read(0x8000), 2);
1332 assert_eq!(m.cpu_read(0xC000), 3);
1333 assert_eq!(m.ppu_read(0x0000), 0);
1334 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
1335 }
1336
1337 #[test]
1338 fn m174_save_state_round_trip() {
1339 let mut m = Ntdec174::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
1340 m.cpu_write(0x80A3, 0); let blob = m.save_state();
1342 let mut m2 = Ntdec174::new(synth_prg_16k(8), synth_chr_8k(8), Mirroring::Vertical).unwrap();
1343 m2.load_state(&blob).unwrap();
1344 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
1345 assert_eq!(m2.current_mirroring(), m.current_mirroring());
1346 }
1347
1348 #[test]
1349 fn m40_fixed_layout_and_switchable_window() {
1350 let mut m = Ntdec2722M40::new(synth_prg_8k(8), &[], Mirroring::Vertical).unwrap();
1351 assert_eq!(m.cpu_read(0x8000), 4);
1353 assert_eq!(m.cpu_read(0xA000), 5);
1354 assert_eq!(m.cpu_read(0xE000), 7);
1355 m.cpu_write(0xE000, 3);
1357 assert_eq!(m.cpu_read(0xC000), 3);
1358 }
1359
1360 #[test]
1361 fn m40_irq_fires_after_enable() {
1362 let mut m = Ntdec2722M40::new(synth_prg_8k(8), &[], Mirroring::Vertical).unwrap();
1363 assert!(!m.irq_pending());
1364 m.cpu_write(0xA000, 0); for _ in 0..0x1000 {
1366 m.notify_cpu_cycle();
1367 }
1368 assert!(m.irq_pending());
1369 m.cpu_write(0x8000, 0); assert!(!m.irq_pending());
1371 }
1372
1373 #[test]
1374 fn m40_save_state_round_trip() {
1375 let mut m = Ntdec2722M40::new(synth_prg_8k(8), &[], Mirroring::Vertical).unwrap();
1376 m.cpu_write(0xE000, 2);
1377 m.cpu_write(0xA000, 0);
1378 m.notify_cpu_cycle();
1379 m.ppu_write(0x0005, 0x9A);
1380 let blob = m.save_state();
1381 let mut m2 = Ntdec2722M40::new(synth_prg_8k(8), &[], Mirroring::Vertical).unwrap();
1382 m2.load_state(&blob).unwrap();
1383 assert_eq!(m2.cpu_read(0xC000), 2);
1384 assert_eq!(m2.ppu_read(0x0005), 0x9A);
1385 }
1386
1387 #[test]
1388 fn m81_prg_and_chr_select() {
1389 let mut m = Ntdec81::new(synth_prg_16k(8), synth_chr_8k(4), Mirroring::Vertical).unwrap();
1390 m.cpu_write(0x8000, 0x09);
1392 assert_eq!(m.cpu_read(0x8000), 2);
1393 assert_eq!(m.cpu_read(0xC000), 7);
1395 assert_eq!(m.ppu_read(0x0000), 1);
1396 }
1397
1398 #[test]
1399 fn m81_save_state_round_trip() {
1400 let mut m = Ntdec81::new(synth_prg_16k(8), synth_chr_8k(4), Mirroring::Vertical).unwrap();
1401 m.cpu_write(0x8000, 0x06);
1402 let blob = m.save_state();
1403 let mut m2 = Ntdec81::new(synth_prg_16k(8), synth_chr_8k(4), Mirroring::Vertical).unwrap();
1404 m2.load_state(&blob).unwrap();
1405 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
1406 assert_eq!(m2.ppu_read(0x0000), m.ppu_read(0x0000));
1407 }
1408
1409 #[test]
1410 fn m112_indexed_prg_chr_and_mirroring() {
1411 let mut m =
1412 NtdecAsder112::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
1413 m.cpu_write(0x8000, 0);
1415 m.cpu_write(0xA000, 3);
1416 assert_eq!(m.cpu_read(0x8000), 3);
1417 m.cpu_write(0x8000, 1);
1419 m.cpu_write(0xA000, 2);
1420 assert_eq!(m.cpu_read(0xA000), 2);
1421 m.cpu_write(0xE000, 1);
1423 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
1424 assert_eq!(m.cpu_read(0xE000), 7);
1426 }
1427
1428 #[test]
1429 fn m112_save_state_round_trip() {
1430 let mut m =
1431 NtdecAsder112::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
1432 m.cpu_write(0x8000, 0);
1433 m.cpu_write(0xA000, 5);
1434 m.cpu_write(0xE000, 1);
1435 let blob = m.save_state();
1436 let mut m2 =
1437 NtdecAsder112::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
1438 m2.load_state(&blob).unwrap();
1439 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
1440 assert_eq!(m2.current_mirroring(), Mirroring::Horizontal);
1441 }
1442
1443 fn prg(banks_8k: usize) -> Box<[u8]> {
1444 let mut v = vec![0u8; banks_8k * PRG_BANK_8K];
1446 for (i, b) in v.chunks_mut(PRG_BANK_8K).enumerate() {
1447 b.fill(i as u8);
1448 }
1449 v.into_boxed_slice()
1450 }
1451
1452 fn chr(banks_8k: usize) -> Box<[u8]> {
1453 let mut v = vec![0u8; banks_8k * CHR_BANK_8K];
1454 for (i, b) in v.chunks_mut(CHR_BANK_8K).enumerate() {
1455 b.fill(i as u8);
1456 }
1457 v.into_boxed_slice()
1458 }
1459
1460 #[test]
1461 fn m193_last_three_prg_windows_are_fixed() {
1462 let mut m = new_m193(prg(8), chr(4), Mirroring::Vertical).unwrap();
1465 assert_eq!(m.cpu_read(0x8000), 0);
1467 assert_eq!(m.cpu_read(0xA000), 5, "$A000 fixed to last-3");
1468 assert_eq!(m.cpu_read(0xC000), 6, "$C000 fixed to last-3");
1469 assert_eq!(m.cpu_read(0xE000), 7, "$E000 fixed to last-3");
1470 m.cpu_write(0x6003, 3);
1472 assert_eq!(m.cpu_read(0x8000), 3);
1473 assert_eq!(m.cpu_read(0xE000), 7);
1475 }
1476
1477 #[test]
1478 fn m193_chr_registers_select_2k_windows() {
1479 let mut m = new_m193(prg(2), chr(4), Mirroring::Vertical).unwrap();
1480 m.cpu_write(0x6000, 4);
1482 assert_eq!(m.ppu_read(0x0000), 0); m.cpu_write(0x6001, 8); assert_eq!(m.ppu_read(0x1000), 1);
1488 m.cpu_write(0x6002, 12); assert_eq!(m.ppu_read(0x1800), 1);
1490 }
1491
1492 #[test]
1493 fn m193_chr_ram_when_no_chr_rom() {
1494 let mut m = new_m193(prg(2), Box::new([]), Mirroring::Vertical).unwrap();
1495 m.ppu_write(0x0123, 0xAB);
1496 assert_eq!(m.ppu_read(0x0123), 0xAB);
1497 }
1498
1499 #[test]
1500 fn m221_nrom_mode_mirrors_both_windows() {
1501 let mut m = new_m221(prg(16), chr(1), Mirroring::Vertical).unwrap();
1502 m.cpu_write(0x8000, 0);
1504 m.cpu_write(0xC003, 0); assert_eq!(m.cpu_read(0x8000), 6, "16k page 3 -> 8k bank 6");
1507 assert_eq!(m.cpu_read(0xC000), 6);
1508 }
1509
1510 #[test]
1511 fn m221_unrom_nrom256_subcase() {
1512 let mut m = new_m221(prg(16), chr(1), Mirroring::Vertical).unwrap();
1513 m.cpu_write(0x8102, 0);
1516 m.cpu_write(0xC002, 0); assert_eq!(m.cpu_read(0x8000), 4, "16k page 2 -> 8k bank 4");
1519 assert_eq!(m.cpu_read(0xC000), 14, "16k page 7 -> 8k bank 14");
1520 }
1521
1522 #[test]
1523 fn m221_mirroring_bit() {
1524 let mut m = new_m221(prg(4), chr(1), Mirroring::Horizontal).unwrap();
1525 m.cpu_write(0x8001, 0); assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
1527 m.cpu_write(0x8000, 0); assert_eq!(m.current_mirroring(), Mirroring::Vertical);
1529 }
1530
1531 #[test]
1532 fn m193_m221_save_load_round_trip() {
1533 let mut a = new_m193(prg(8), chr(4), Mirroring::Vertical).unwrap();
1535 a.cpu_write(0x6003, 5);
1536 a.cpu_write(0x6000, 6);
1537 let s = a.save_state();
1538 let mut b = new_m193(prg(8), chr(4), Mirroring::Vertical).unwrap();
1539 b.load_state(&s).unwrap();
1540 assert_eq!(a.cpu_read(0x8000), b.cpu_read(0x8000));
1541 assert_eq!(a.ppu_read(0x0000), b.ppu_read(0x0000));
1542
1543 let mut a = new_m221(prg(16), chr(1), Mirroring::Vertical).unwrap();
1545 a.cpu_write(0x8102, 0);
1546 a.cpu_write(0xC002, 0);
1547 let s = a.save_state();
1548 let mut b = new_m221(prg(16), chr(1), Mirroring::Horizontal).unwrap();
1549 b.load_state(&s).unwrap();
1550 assert_eq!(a.cpu_read(0x8000), b.cpu_read(0x8000));
1551 assert_eq!(a.cpu_read(0xC000), b.cpu_read(0xC000));
1552 }
1553
1554 #[test]
1555 fn m193_m221_bad_prg_size_is_rejected() {
1556 assert!(
1558 new_m193(
1559 vec![0u8; 100].into_boxed_slice(),
1560 chr(1),
1561 Mirroring::Vertical
1562 )
1563 .is_err()
1564 );
1565 assert!(
1566 new_m221(
1567 vec![0u8; 100].into_boxed_slice(),
1568 chr(1),
1569 Mirroring::Vertical
1570 )
1571 .is_err()
1572 );
1573 }
1574}