1#![allow(
20 clippy::bool_to_int_with_if,
21 clippy::cast_lossless,
22 clippy::cast_possible_truncation,
23 clippy::doc_markdown,
24 clippy::match_same_arms,
25 clippy::missing_const_for_fn,
26 clippy::similar_names,
27 clippy::struct_excessive_bools,
28 clippy::too_many_lines,
29 clippy::unreadable_literal
30)]
31
32use crate::cartridge::Mirroring;
33use crate::mapper::{Mapper, MapperCaps, MapperError};
34use alloc::{boxed::Box, vec::Vec};
35use alloc::{format, vec};
36
37const PRG_BANK_16K: usize = 0x4000;
38const PRG_BANK_32K: usize = 0x8000;
39const CHR_BANK_8K: usize = 0x2000;
40const NAMETABLE_SIZE: usize = 0x0400;
41const NAMETABLE_SIZE_U16: u16 = 0x0400;
42
43const SAVE_STATE_VERSION: u8 = 1;
44
45const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
46 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
47 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
48 let physical = mirroring.physical_bank(table);
49 physical * NAMETABLE_SIZE + local
50}
51
52pub struct Jaleco86 {
54 prg_rom: Box<[u8]>,
55 chr_rom: Box<[u8]>,
56 vram: Box<[u8]>,
57 prg_bank: u8,
58 chr_bank: u8,
59 mirroring: Mirroring,
60}
61
62impl Jaleco86 {
63 pub fn new(
70 prg_rom: Box<[u8]>,
71 chr_rom: Box<[u8]>,
72 mirroring: Mirroring,
73 ) -> Result<Self, MapperError> {
74 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
75 return Err(MapperError::Invalid(format!(
76 "mapper 86 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
77 prg_rom.len()
78 )));
79 }
80 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
81 return Err(MapperError::Invalid(format!(
82 "mapper 86 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
83 chr_rom.len()
84 )));
85 }
86 Ok(Self {
87 prg_rom,
88 chr_rom,
89 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
90 prg_bank: 0,
91 chr_bank: 0,
92 mirroring,
93 })
94 }
95}
96
97impl Mapper for Jaleco86 {
98 fn caps(&self) -> MapperCaps {
99 MapperCaps::NONE
100 }
101
102 fn cpu_read(&mut self, addr: u16) -> u8 {
103 if (0x8000..=0xFFFF).contains(&addr) {
104 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
105 let bank = (self.prg_bank as usize) % count;
106 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
107 } else {
108 0
109 }
110 }
111
112 fn cpu_write(&mut self, addr: u16, value: u8) {
113 if (0x6000..=0x6FFF).contains(&addr) {
115 self.prg_bank = (value >> 4) & 0x03;
116 self.chr_bank = (value & 0x03) | ((value >> 4) & 0x04);
117 }
118 }
119
120 fn ppu_read(&mut self, addr: u16) -> u8 {
121 let addr = addr & 0x3FFF;
122 match addr {
123 0x0000..=0x1FFF => {
124 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
125 let bank = (self.chr_bank as usize) % count;
126 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
127 }
128 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
129 _ => 0,
130 }
131 }
132
133 fn ppu_write(&mut self, addr: u16, value: u8) {
134 let addr = addr & 0x3FFF;
135 if let 0x2000..=0x3EFF = addr {
136 let off = nametable_offset(addr, self.mirroring);
137 self.vram[off] = value;
138 }
139 }
140
141 fn current_mirroring(&self) -> Mirroring {
142 self.mirroring
143 }
144
145 fn save_state(&self) -> Vec<u8> {
146 let mut out = Vec::with_capacity(3 + self.vram.len());
147 out.push(SAVE_STATE_VERSION);
148 out.push(self.prg_bank);
149 out.push(self.chr_bank);
150 out.extend_from_slice(&self.vram);
151 out
152 }
153
154 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
155 let expected = 3 + self.vram.len();
156 if data.len() != expected {
157 return Err(MapperError::WrongLength {
158 expected,
159 got: data.len(),
160 });
161 }
162 if data[0] != SAVE_STATE_VERSION {
163 return Err(MapperError::UnsupportedVersion(data[0]));
164 }
165 self.prg_bank = data[1];
166 self.chr_bank = data[2];
167 self.vram.copy_from_slice(&data[3..3 + self.vram.len()]);
168 Ok(())
169 }
170}
171
172pub struct Jaleco140 {
183 prg_rom: Box<[u8]>,
184 chr_rom: Box<[u8]>,
185 vram: Box<[u8]>,
186 prg_bank: u8,
187 chr_bank: u8,
188 mirroring: Mirroring,
189}
190
191impl Jaleco140 {
192 pub fn new(
199 prg_rom: Box<[u8]>,
200 chr_rom: Box<[u8]>,
201 mirroring: Mirroring,
202 ) -> Result<Self, MapperError> {
203 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
204 return Err(MapperError::Invalid(format!(
205 "mapper 140 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
206 prg_rom.len()
207 )));
208 }
209 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
210 return Err(MapperError::Invalid(format!(
211 "mapper 140 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
212 chr_rom.len()
213 )));
214 }
215 Ok(Self {
216 prg_rom,
217 chr_rom,
218 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
219 prg_bank: 0,
220 chr_bank: 0,
221 mirroring,
222 })
223 }
224}
225
226impl Mapper for Jaleco140 {
227 fn caps(&self) -> MapperCaps {
228 MapperCaps::NONE
229 }
230
231 fn cpu_read(&mut self, addr: u16) -> u8 {
232 if (0x8000..=0xFFFF).contains(&addr) {
233 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
234 let bank = (self.prg_bank as usize) % count;
235 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
236 } else {
237 0
238 }
239 }
240
241 fn cpu_write(&mut self, addr: u16, value: u8) {
242 if (0x6000..=0x7FFF).contains(&addr) {
243 self.prg_bank = (value >> 4) & 0x03;
244 self.chr_bank = value & 0x0F;
245 }
246 }
247
248 fn ppu_read(&mut self, addr: u16) -> u8 {
249 let addr = addr & 0x3FFF;
250 match addr {
251 0x0000..=0x1FFF => {
252 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
253 let bank = (self.chr_bank as usize) % count;
254 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
255 }
256 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
257 _ => 0,
258 }
259 }
260
261 fn ppu_write(&mut self, addr: u16, value: u8) {
262 let addr = addr & 0x3FFF;
263 if let 0x2000..=0x3EFF = addr {
264 let off = nametable_offset(addr, self.mirroring);
265 self.vram[off] = value;
266 }
267 }
268
269 fn current_mirroring(&self) -> Mirroring {
270 self.mirroring
271 }
272
273 fn save_state(&self) -> Vec<u8> {
274 let mut out = Vec::with_capacity(3 + self.vram.len());
275 out.push(SAVE_STATE_VERSION);
276 out.push(self.prg_bank);
277 out.push(self.chr_bank);
278 out.extend_from_slice(&self.vram);
279 out
280 }
281
282 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
283 let expected = 3 + self.vram.len();
284 if data.len() != expected {
285 return Err(MapperError::WrongLength {
286 expected,
287 got: data.len(),
288 });
289 }
290 if data[0] != SAVE_STATE_VERSION {
291 return Err(MapperError::UnsupportedVersion(data[0]));
292 }
293 self.prg_bank = data[1];
294 self.chr_bank = data[2];
295 self.vram.copy_from_slice(&data[3..3 + self.vram.len()]);
296 Ok(())
297 }
298}
299
300#[allow(clippy::struct_excessive_bools)]
305struct JalecoLatch {
306 prg_rom: Box<[u8]>,
307 chr: Box<[u8]>,
308 vram: Box<[u8]>,
309 chr_is_ram: bool,
310 prg_bank: u8,
311 chr_bank: u8,
312 prev_prg_strobe: bool,
313 prev_chr_strobe: bool,
314 mirroring: Mirroring,
315 prg_field_mask: u8,
317 switchable_high: bool,
323}
324
325impl JalecoLatch {
326 fn new(
327 prg_rom: Box<[u8]>,
328 chr_rom: Box<[u8]>,
329 mirroring: Mirroring,
330 prg_field_mask: u8,
331 switchable_high: bool,
332 id: u16,
333 ) -> Result<Self, MapperError> {
334 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
335 return Err(MapperError::Invalid(format!(
336 "mapper {id} PRG-ROM size {} is not a non-zero multiple of 16 KiB",
337 prg_rom.len()
338 )));
339 }
340 let chr_is_ram = chr_rom.is_empty();
341 let chr: Box<[u8]> = if chr_is_ram {
342 vec![0u8; CHR_BANK_8K].into_boxed_slice()
343 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
344 chr_rom
345 } else {
346 return Err(MapperError::Invalid(format!(
347 "mapper {id} CHR-ROM size {} is not a multiple of 8 KiB",
348 chr_rom.len()
349 )));
350 };
351 Ok(Self {
352 prg_rom,
353 chr,
354 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
355 chr_is_ram,
356 prg_bank: 0,
357 chr_bank: 0,
358 prev_prg_strobe: false,
359 prev_chr_strobe: false,
360 mirroring,
361 prg_field_mask,
362 switchable_high,
363 })
364 }
365
366 fn prg_count_16k(&self) -> usize {
367 (self.prg_rom.len() / PRG_BANK_16K).max(1)
368 }
369
370 fn read_prg_bank(&self, bank: usize, off: usize) -> u8 {
371 let bank = bank % self.prg_count_16k();
372 self.prg_rom[bank * PRG_BANK_16K + off]
373 }
374
375 fn chr_offset(&self, addr: u16) -> usize {
376 let count = (self.chr.len() / CHR_BANK_8K).max(1);
377 let bank = (self.chr_bank as usize) % count;
378 bank * CHR_BANK_8K + addr as usize
379 }
380
381 fn cpu_read(&self, addr: u16) -> u8 {
382 let last = self.prg_count_16k() - 1;
383 match addr {
384 0x8000..=0xBFFF => {
385 let bank = if self.switchable_high {
388 0
389 } else {
390 self.prg_bank as usize
391 };
392 self.read_prg_bank(bank, addr as usize - 0x8000)
393 }
394 0xC000..=0xFFFF => {
395 let bank = if self.switchable_high {
397 self.prg_bank as usize
398 } else {
399 last
400 };
401 self.read_prg_bank(bank, addr as usize - 0xC000)
402 }
403 _ => 0,
404 }
405 }
406
407 fn cpu_write(&mut self, addr: u16, value: u8) {
408 if (0x8000..=0xFFFF).contains(&addr) {
409 let effective = value & self.cpu_read(addr);
411 let prg_strobe = (effective & 0x80) != 0;
412 let chr_strobe = (effective & 0x40) != 0;
413 if prg_strobe && !self.prev_prg_strobe {
414 self.prg_bank = effective & self.prg_field_mask;
415 }
416 if chr_strobe && !self.prev_chr_strobe {
417 self.chr_bank = effective & 0x0F;
418 }
419 self.prev_prg_strobe = prg_strobe;
420 self.prev_chr_strobe = chr_strobe;
421 }
422 }
423
424 fn ppu_read(&self, addr: u16) -> u8 {
425 let addr = addr & 0x3FFF;
426 match addr {
427 0x0000..=0x1FFF => self.chr[self.chr_offset(addr)],
428 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
429 _ => 0,
430 }
431 }
432
433 fn ppu_write(&mut self, addr: u16, value: u8) {
434 let addr = addr & 0x3FFF;
435 match addr {
436 0x0000..=0x1FFF => {
437 if self.chr_is_ram {
438 let off = self.chr_offset(addr);
439 self.chr[off] = value;
440 }
441 }
442 0x2000..=0x3EFF => {
443 let off = nametable_offset(addr, self.mirroring);
444 self.vram[off] = value;
445 }
446 _ => {}
447 }
448 }
449
450 fn save_state(&self) -> Vec<u8> {
451 let chr_extra = if self.chr_is_ram { self.chr.len() } else { 0 };
452 let mut out = Vec::with_capacity(5 + self.vram.len() + chr_extra);
453 out.push(SAVE_STATE_VERSION);
454 out.push(self.prg_bank);
455 out.push(self.chr_bank);
456 out.push(u8::from(self.prev_prg_strobe));
457 out.push(u8::from(self.prev_chr_strobe));
458 out.extend_from_slice(&self.vram);
459 if self.chr_is_ram {
460 out.extend_from_slice(&self.chr);
461 }
462 out
463 }
464
465 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
466 let chr_extra = if self.chr_is_ram { self.chr.len() } else { 0 };
467 let expected = 5 + self.vram.len() + chr_extra;
468 if data.len() != expected {
469 return Err(MapperError::WrongLength {
470 expected,
471 got: data.len(),
472 });
473 }
474 if data[0] != SAVE_STATE_VERSION {
475 return Err(MapperError::UnsupportedVersion(data[0]));
476 }
477 self.prg_bank = data[1];
478 self.chr_bank = data[2];
479 self.prev_prg_strobe = data[3] != 0;
480 self.prev_chr_strobe = data[4] != 0;
481 let mut cursor = 5;
482 self.vram
483 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
484 cursor += self.vram.len();
485 if self.chr_is_ram {
486 self.chr
487 .copy_from_slice(&data[cursor..cursor + self.chr.len()]);
488 }
489 Ok(())
490 }
491}
492
493pub struct Jaleco72 {
495 inner: JalecoLatch,
496}
497
498impl Jaleco72 {
499 pub fn new(
506 prg_rom: Box<[u8]>,
507 chr_rom: Box<[u8]>,
508 mirroring: Mirroring,
509 ) -> Result<Self, MapperError> {
510 Ok(Self {
511 inner: JalecoLatch::new(prg_rom, chr_rom, mirroring, 0x0F, false, 72)?,
512 })
513 }
514}
515
516impl Mapper for Jaleco72 {
517 fn caps(&self) -> MapperCaps {
518 MapperCaps::NONE
519 }
520
521 fn cpu_read(&mut self, addr: u16) -> u8 {
522 self.inner.cpu_read(addr)
523 }
524
525 fn cpu_write(&mut self, addr: u16, value: u8) {
526 self.inner.cpu_write(addr, value);
527 }
528
529 fn ppu_read(&mut self, addr: u16) -> u8 {
530 self.inner.ppu_read(addr)
531 }
532
533 fn ppu_write(&mut self, addr: u16, value: u8) {
534 self.inner.ppu_write(addr, value);
535 }
536
537 fn current_mirroring(&self) -> Mirroring {
538 self.inner.mirroring
539 }
540
541 fn save_state(&self) -> Vec<u8> {
542 self.inner.save_state()
543 }
544
545 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
546 self.inner.load_state(data)
547 }
548}
549
550pub struct Jaleco92 {
552 inner: JalecoLatch,
553}
554
555impl Jaleco92 {
556 pub fn new(
563 prg_rom: Box<[u8]>,
564 chr_rom: Box<[u8]>,
565 mirroring: Mirroring,
566 ) -> Result<Self, MapperError> {
567 Ok(Self {
568 inner: JalecoLatch::new(prg_rom, chr_rom, mirroring, 0x1F, true, 92)?,
569 })
570 }
571}
572
573impl Mapper for Jaleco92 {
574 fn caps(&self) -> MapperCaps {
575 MapperCaps::NONE
576 }
577
578 fn cpu_read(&mut self, addr: u16) -> u8 {
579 self.inner.cpu_read(addr)
580 }
581
582 fn cpu_write(&mut self, addr: u16, value: u8) {
583 self.inner.cpu_write(addr, value);
584 }
585
586 fn ppu_read(&mut self, addr: u16) -> u8 {
587 self.inner.ppu_read(addr)
588 }
589
590 fn ppu_write(&mut self, addr: u16, value: u8) {
591 self.inner.ppu_write(addr, value);
592 }
593
594 fn current_mirroring(&self) -> Mirroring {
595 self.inner.mirroring
596 }
597
598 fn save_state(&self) -> Vec<u8> {
599 self.inner.save_state()
600 }
601
602 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
603 self.inner.load_state(data)
604 }
605}
606
607pub struct Jaleco101 {
609 prg_rom: Box<[u8]>,
610 chr_rom: Box<[u8]>,
611 vram: Box<[u8]>,
612 chr_bank: u8,
613 mirroring: Mirroring,
614}
615
616impl Jaleco101 {
617 pub fn new(
624 prg_rom: Box<[u8]>,
625 chr_rom: Box<[u8]>,
626 mirroring: Mirroring,
627 ) -> Result<Self, MapperError> {
628 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
629 return Err(MapperError::Invalid(format!(
630 "mapper 101 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
631 prg_rom.len()
632 )));
633 }
634 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
635 return Err(MapperError::Invalid(format!(
636 "mapper 101 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
637 chr_rom.len()
638 )));
639 }
640 Ok(Self {
641 prg_rom,
642 chr_rom,
643 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
644 chr_bank: 0,
645 mirroring,
646 })
647 }
648}
649
650impl Mapper for Jaleco101 {
651 fn caps(&self) -> MapperCaps {
652 MapperCaps::NONE
653 }
654
655 fn cpu_read(&mut self, addr: u16) -> u8 {
656 if (0x8000..=0xFFFF).contains(&addr) {
657 self.prg_rom[addr as usize - 0x8000]
658 } else {
659 0
660 }
661 }
662
663 fn cpu_write(&mut self, addr: u16, value: u8) {
664 if (0x6000..=0x7FFF).contains(&addr) {
666 self.chr_bank = value;
667 }
668 }
669
670 fn ppu_read(&mut self, addr: u16) -> u8 {
671 let addr = addr & 0x3FFF;
672 match addr {
673 0x0000..=0x1FFF => {
674 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
675 let bank = (self.chr_bank as usize) % count;
676 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
677 }
678 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
679 _ => 0,
680 }
681 }
682
683 fn ppu_write(&mut self, addr: u16, value: u8) {
684 let addr = addr & 0x3FFF;
685 if let 0x2000..=0x3EFF = addr {
686 let off = nametable_offset(addr, self.mirroring);
687 self.vram[off] = value;
688 }
689 }
690
691 fn current_mirroring(&self) -> Mirroring {
692 self.mirroring
693 }
694
695 fn save_state(&self) -> Vec<u8> {
696 let mut out = Vec::with_capacity(2 + self.vram.len());
697 out.push(SAVE_STATE_VERSION);
698 out.push(self.chr_bank);
699 out.extend_from_slice(&self.vram);
700 out
701 }
702
703 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
704 let expected = 2 + self.vram.len();
705 if data.len() != expected {
706 return Err(MapperError::WrongLength {
707 expected,
708 got: data.len(),
709 });
710 }
711 if data[0] != SAVE_STATE_VERSION {
712 return Err(MapperError::UnsupportedVersion(data[0]));
713 }
714 self.chr_bank = data[1];
715 self.vram.copy_from_slice(&data[2..2 + self.vram.len()]);
716 Ok(())
717 }
718}
719
720#[cfg(test)]
721#[allow(clippy::cast_possible_truncation)]
722mod tests {
723 use super::*;
724
725 fn synth_prg_32k(banks: usize) -> Box<[u8]> {
726 let mut v = vec![0xFFu8; banks * PRG_BANK_32K];
727 for b in 0..banks {
728 v[b * PRG_BANK_32K] = b as u8;
729 }
730 v.into_boxed_slice()
731 }
732
733 fn synth_prg_16k(banks: usize) -> Box<[u8]> {
735 let mut v = vec![0xFFu8; banks * PRG_BANK_16K];
736 for b in 0..banks {
737 v[b * PRG_BANK_16K] = b as u8;
738 }
739 v.into_boxed_slice()
740 }
741
742 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
743 let mut v = vec![0u8; banks * CHR_BANK_8K];
744 for b in 0..banks {
745 v[b * CHR_BANK_8K] = b as u8;
746 }
747 v.into_boxed_slice()
748 }
749
750 #[test]
751 fn m86_latch_selects_prg_and_chr() {
752 let mut m = Jaleco86::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
753 m.cpu_write(0x6000, 0b0011_0010);
757 assert_eq!(m.cpu_read(0x8000), 3);
758 assert_eq!(m.ppu_read(0x0000), 2);
759 m.cpu_write(0x6000, 0b0100_0001);
761 assert_eq!(m.ppu_read(0x0000), 5);
762 m.cpu_write(0x7000, 0xFF);
764 assert_eq!(m.ppu_read(0x0000), 5);
765 }
766
767 #[test]
768 fn m140_latch_selects_prg_and_chr() {
769 let mut m =
770 Jaleco140::new(synth_prg_32k(4), synth_chr_8k(16), Mirroring::Vertical).unwrap();
771 m.cpu_write(0x6FFF, 0b0010_1010); assert_eq!(m.cpu_read(0x8000), 2);
774 assert_eq!(m.ppu_read(0x0000), 10);
775 m.cpu_write(0x7FFF, 0b0001_0011); assert_eq!(m.cpu_read(0x8000), 1);
778 assert_eq!(m.ppu_read(0x0000), 3);
779 }
780
781 #[test]
782 fn m72_strobe_latches_on_rising_edge() {
783 let mut m = Jaleco72::new(synth_prg_16k(8), synth_chr_8k(16), Mirroring::Vertical).unwrap();
784 m.cpu_write(0x8001, 0b1000_0011);
787 assert_eq!(m.cpu_read(0x8000), 3);
788 assert_eq!(m.cpu_read(0xC000), 7);
790 m.cpu_write(0x8001, 0b0100_0101);
792 assert_eq!(m.ppu_read(0x0000), 5);
793 }
794
795 #[test]
796 fn m72_no_relatch_without_falling_edge() {
797 let mut m = Jaleco72::new(synth_prg_16k(8), synth_chr_8k(16), Mirroring::Vertical).unwrap();
798 m.cpu_write(0x8001, 0b1000_0011); assert_eq!(m.cpu_read(0x8000), 3);
800 m.cpu_write(0x8001, 0b1000_0101);
802 assert_eq!(m.cpu_read(0x8000), 3);
803 m.cpu_write(0x8001, 0b0000_0000);
805 m.cpu_write(0x8001, 0b1000_0101);
806 assert_eq!(m.cpu_read(0x8000), 5);
807 }
808
809 #[test]
810 fn m92_uses_5bit_prg_field() {
811 let mut m =
812 Jaleco92::new(synth_prg_16k(32), synth_chr_8k(16), Mirroring::Vertical).unwrap();
813 m.cpu_write(0x8001, 0b1001_0001);
815 assert_eq!(m.cpu_read(0x8000), 0);
818 assert_eq!(m.cpu_read(0xC000), 17);
819 }
820
821 #[test]
822 fn m72_save_state_round_trips_strobe_state() {
823 let mut j = Jaleco72::new(synth_prg_16k(8), synth_chr_8k(16), Mirroring::Vertical).unwrap();
824 j.cpu_write(0x8001, 0b1100_0011); let blob = j.save_state();
826 let mut j2 =
827 Jaleco72::new(synth_prg_16k(8), synth_chr_8k(16), Mirroring::Vertical).unwrap();
828 j2.load_state(&blob).unwrap();
829 assert_eq!(j2.cpu_read(0x8000), 3);
830 assert_eq!(j2.ppu_read(0x0000), 3);
831 j2.cpu_write(0x8001, 0b1100_0101);
834 assert_eq!(j2.cpu_read(0x8000), 3);
835 }
836
837 #[test]
838 fn m101_chr_latch_in_6000_window() {
839 let mut m =
840 Jaleco101::new(synth_prg_32k(1), synth_chr_8k(8), Mirroring::Horizontal).unwrap();
841 m.cpu_write(0x6000, 5);
842 assert_eq!(m.ppu_read(0x0000), 5);
843 assert_eq!(m.cpu_read(0x8000), 0);
845 }
846
847 #[test]
848 fn m101_save_state_round_trip() {
849 let mut m =
850 Jaleco101::new(synth_prg_32k(1), synth_chr_8k(8), Mirroring::Horizontal).unwrap();
851 m.cpu_write(0x7FFF, 6);
852 let blob = m.save_state();
853 let mut m2 =
854 Jaleco101::new(synth_prg_32k(1), synth_chr_8k(8), Mirroring::Horizontal).unwrap();
855 m2.load_state(&blob).unwrap();
856 assert_eq!(m2.ppu_read(0x0000), 6);
857 }
858}