1#![allow(
29 clippy::cast_possible_truncation,
30 clippy::cast_lossless,
31 clippy::match_same_arms,
32 clippy::doc_markdown,
33 clippy::similar_names,
34 clippy::too_many_lines,
35 clippy::missing_const_for_fn,
36 clippy::struct_excessive_bools,
37 clippy::bool_to_int_with_if,
38 clippy::unreadable_literal
39)]
40
41use crate::cartridge::Mirroring;
42use crate::mapper::{Mapper, MapperCaps, MapperError};
43use alloc::{boxed::Box, format, vec, vec::Vec};
44
45const PRG_BANK_8K: usize = 0x2000;
46const PRG_BANK_16K: usize = 0x4000;
47const PRG_BANK_2K: usize = 0x0800;
48const CHR_BANK_1K: usize = 0x0400;
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
66const fn mirroring_to_byte(m: Mirroring) -> u8 {
67 match m {
68 Mirroring::Horizontal => 0,
69 Mirroring::Vertical => 1,
70 Mirroring::SingleScreenA => 2,
71 Mirroring::SingleScreenB => 3,
72 Mirroring::FourScreen => 4,
73 Mirroring::MapperControlled => 5,
74 }
75}
76
77const fn byte_to_mirroring(b: u8, fallback: Mirroring) -> Mirroring {
78 match b {
79 0 => Mirroring::Horizontal,
80 1 => Mirroring::Vertical,
81 2 => Mirroring::SingleScreenA,
82 3 => Mirroring::SingleScreenB,
83 4 => Mirroring::FourScreen,
84 5 => Mirroring::MapperControlled,
85 _ => fallback,
86 }
87}
88
89fn check_prg(prg: &[u8], id: u16) -> Result<(), MapperError> {
91 if prg.is_empty() || !prg.len().is_multiple_of(PRG_BANK_8K) {
92 return Err(MapperError::Invalid(format!(
93 "mapper {id} PRG-ROM size {} is not a non-zero multiple of 8 KiB",
94 prg.len()
95 )));
96 }
97 Ok(())
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum KaiserBoard {
103 M56,
105 M142,
107 M303,
109 M305,
111 M306,
113 M312,
115}
116
117pub struct KaiserMapper {
119 board: KaiserBoard,
120 prg_rom: Box<[u8]>,
121 chr: Box<[u8]>,
122 chr_is_ram: bool,
123 vram: Box<[u8]>,
124 wram: Box<[u8]>,
125 mirroring: Mirroring,
126 prg_regs: [u8; 4],
128 selected_reg: u8,
129 use_rom: bool,
130 chr_banks: [u8; 8],
131 win_6000: u8,
133 regs4: [u8; 4],
135 prg16: u8,
137 irq_counter: u16,
139 irq_reload: u16,
140 irq_enabled: bool,
141 irq_control: u8,
142 irq_pending: bool,
143}
144
145impl KaiserMapper {
146 const SAVE_LEN: usize = 4 + 1 + 1 + 8 + 1 + 4 + 1 + 2 + 2 + 1 + 1 + 1 + 1;
147
148 fn new(
149 board: KaiserBoard,
150 prg_rom: Box<[u8]>,
151 chr_rom: Box<[u8]>,
152 mirroring: Mirroring,
153 id: u16,
154 ) -> Result<Self, MapperError> {
155 check_prg(&prg_rom, id)?;
156 let chr_is_ram = chr_rom.is_empty();
157 let chr: Box<[u8]> = if chr_is_ram {
158 vec![0u8; CHR_BANK_8K].into_boxed_slice()
159 } else {
160 chr_rom
161 };
162 Ok(Self {
163 board,
164 prg_rom,
165 chr,
166 chr_is_ram,
167 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
168 wram: vec![0u8; PRG_BANK_8K].into_boxed_slice(),
169 mirroring,
170 prg_regs: [0; 4],
171 selected_reg: 0,
172 use_rom: false,
173 chr_banks: [0; 8],
174 win_6000: 8,
175 regs4: [0; 4],
176 prg16: 0,
177 irq_counter: 0,
178 irq_reload: 0,
179 irq_enabled: false,
180 irq_control: 0,
181 irq_pending: false,
182 })
183 }
184
185 fn prg_count_8k(&self) -> usize {
186 (self.prg_rom.len() / PRG_BANK_8K).max(1)
187 }
188
189 fn prg_count_16k(&self) -> usize {
190 (self.prg_rom.len() / PRG_BANK_16K).max(1)
191 }
192
193 fn prg_count_2k(&self) -> usize {
194 (self.prg_rom.len() / PRG_BANK_2K).max(1)
195 }
196
197 fn chr_count_1k(&self) -> usize {
198 (self.chr.len() / CHR_BANK_1K).max(1)
199 }
200}
201
202impl Mapper for KaiserMapper {
203 fn caps(&self) -> MapperCaps {
204 match self.board {
205 KaiserBoard::M56 | KaiserBoard::M142 | KaiserBoard::M303 => MapperCaps::CYCLE_IRQ,
206 _ => MapperCaps::NONE,
207 }
208 }
209
210 fn cpu_read(&mut self, addr: u16) -> u8 {
211 match self.board {
212 KaiserBoard::M56 | KaiserBoard::M142 => match addr {
213 0x6000..=0x7FFF => {
214 if self.use_rom || self.board == KaiserBoard::M142 {
219 let count = self.prg_count_8k();
220 let bank = (self.prg_regs[3] as usize) % count;
221 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
222 } else {
223 self.wram[addr as usize & 0x1FFF]
224 }
225 }
226 0x8000..=0xFFFF => {
227 let slot = (addr as usize - 0x8000) / PRG_BANK_8K;
228 let count = self.prg_count_8k();
229 let bank = if slot == 3 {
230 count - 1
231 } else {
232 self.prg_regs[slot] as usize % count
233 };
234 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
235 }
236 _ => 0,
237 },
238 KaiserBoard::M303 => match addr {
239 0x4030 => {
240 let p = self.irq_pending;
241 self.irq_pending = false;
242 u8::from(p)
243 }
244 0x8000..=0xBFFF => {
245 let count = self.prg_count_16k();
246 let bank = (self.prg16 as usize) % count;
247 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
248 }
249 0xC000..=0xFFFF => {
250 let count = self.prg_count_16k();
251 let bank = 2 % count;
252 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
253 }
254 _ => 0,
255 },
256 KaiserBoard::M305 => match addr {
257 0x6000..=0x7FFF => {
258 let win = (addr as usize - 0x6000) / PRG_BANK_2K;
259 let count = self.prg_count_2k();
260 let bank = (self.regs4[win] as usize) % count;
261 self.prg_rom[bank * PRG_BANK_2K + (addr as usize & 0x7FF)]
262 }
263 0x8000..=0xFFFF => {
264 let count = self.prg_count_2k();
266 let win = (addr as usize - 0x8000) / PRG_BANK_2K;
267 let bank = count.saturating_sub(16 - win) % count;
268 self.prg_rom[bank * PRG_BANK_2K + (addr as usize & 0x7FF)]
269 }
270 _ => 0,
271 },
272 KaiserBoard::M306 => match addr {
273 0x6000..=0x7FFF => {
274 let count = self.prg_count_8k();
275 let bank = (self.win_6000 as usize) % count;
276 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
277 }
278 0x8000..=0xFFFF => {
279 let count = self.prg_count_8k();
281 let slot = (addr as usize - 0x8000) / PRG_BANK_8K;
282 let bank = count.saturating_sub(4 - slot) % count;
283 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
284 }
285 _ => 0,
286 },
287 KaiserBoard::M312 => match addr {
288 0x8000..=0xBFFF => {
289 let count = self.prg_count_16k();
290 let bank = (self.prg16 as usize) % count;
291 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
292 }
293 0xC000..=0xFFFF => {
294 let count = self.prg_count_16k();
295 let bank = count - 1;
296 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
297 }
298 _ => 0,
299 },
300 }
301 }
302
303 fn cpu_read_unmapped(&self, addr: u16) -> bool {
304 let floats_6000 = matches!(self.board, KaiserBoard::M303 | KaiserBoard::M312)
308 && matches!(addr, 0x6000..=0x7FFF);
309 floats_6000
310 || match self.board {
311 KaiserBoard::M303 => addr != 0x4030 && (0x4020..=0x5FFF).contains(&addr),
312 _ => (0x4020..=0x5FFF).contains(&addr),
313 }
314 }
315
316 fn cpu_write(&mut self, addr: u16, value: u8) {
317 match self.board {
318 KaiserBoard::M56 | KaiserBoard::M142 => match addr & 0xF000 {
319 0x6000 | 0x7000 => {
326 if self.board == KaiserBoard::M56 && !self.use_rom {
327 self.wram[addr as usize & 0x1FFF] = value;
328 }
329 }
330 0x8000 => self.irq_reload = (self.irq_reload & 0xFFF0) | (value as u16 & 0x0F),
331 0x9000 => {
332 self.irq_reload = (self.irq_reload & 0xFF0F) | ((value as u16 & 0x0F) << 4);
333 }
334 0xA000 => {
335 self.irq_reload = (self.irq_reload & 0xF0FF) | ((value as u16 & 0x0F) << 8);
336 }
337 0xB000 => {
338 self.irq_reload = (self.irq_reload & 0x0FFF) | ((value as u16 & 0x0F) << 12);
339 }
340 0xC000 => {
341 self.irq_control = value;
342 if value & 0x02 != 0 {
343 self.irq_counter = self.irq_reload;
344 }
345 self.irq_enabled = value & 0x02 != 0;
346 self.irq_pending = false;
347 }
348 0xD000 => self.irq_pending = false,
349 0xE000 => self.selected_reg = (value & 0x07).wrapping_sub(1),
350 0xF000 => {
351 match self.selected_reg {
352 0..=3 => {
353 let i = self.selected_reg as usize;
354 self.prg_regs[i] = (self.prg_regs[i] & 0x10) | (value & 0x0F);
355 }
356 4 => self.use_rom = value & 0x04 != 0,
357 _ => {}
358 }
359 if self.board == KaiserBoard::M56 {
360 match addr & 0xFC00 {
361 0xF000 => {
362 let bank = (addr & 0x03) as usize;
363 self.prg_regs[bank] = (value & 0x10) | (self.prg_regs[bank] & 0x0F);
364 }
365 0xF800 => {
366 self.mirroring = if value & 0x01 != 0 {
367 Mirroring::Vertical
368 } else {
369 Mirroring::Horizontal
370 };
371 }
372 0xFC00 => self.chr_banks[(addr & 0x07) as usize] = value,
373 _ => {}
374 }
375 }
376 }
377 _ => {}
378 },
379 KaiserBoard::M303 => {
380 if addr & 0xFF00 == 0x4A00 {
381 self.prg16 = (((addr >> 2) & 0x03) | ((addr >> 4) & 0x04)) as u8;
382 } else if addr == 0x4020 {
383 self.irq_pending = false;
384 self.irq_counter = (self.irq_counter & 0xFF00) | value as u16;
385 } else if addr == 0x4021 {
386 self.irq_pending = false;
387 self.irq_counter = (self.irq_counter & 0x00FF) | ((value as u16) << 8);
388 self.irq_enabled = true;
389 } else if addr == 0x4025 {
390 self.mirroring = if (value >> 3) & 0x01 != 0 {
391 Mirroring::Horizontal
392 } else {
393 Mirroring::Vertical
394 };
395 }
396 }
397 KaiserBoard::M305 => {
398 if (0x8000..=0xFFFF).contains(&addr) {
399 self.regs4[((addr >> 11) & 0x03) as usize] = value;
400 }
401 }
402 KaiserBoard::M306 => {
403 if addr >= 0x8000 {
404 let mode = (addr & 0x30) == 0x30;
405 match addr & 0xD943 {
406 0xD943 => {
407 self.win_6000 = if mode {
408 0x0B
409 } else {
410 ((addr >> 2) & 0x0F) as u8
411 };
412 }
413 0xD903 => {
414 self.win_6000 = if mode {
415 0x08 | ((addr >> 2) & 0x03) as u8
416 } else {
417 0x0B
418 };
419 }
420 _ => {}
421 }
422 }
423 }
424 KaiserBoard::M312 => {
425 if addr < 0x8000 {
426 if (0x6000..=0x7FFF).contains(&addr) {
427 self.prg16 = value;
428 }
429 } else {
430 self.mirroring = if value & 0x01 != 0 {
431 Mirroring::Horizontal
432 } else {
433 Mirroring::Vertical
434 };
435 }
436 }
437 }
438 }
439
440 fn ppu_read(&mut self, addr: u16) -> u8 {
441 let addr = addr & 0x3FFF;
442 match addr {
443 0x0000..=0x1FFF => {
444 if self.chr_is_ram {
445 return self.chr[addr as usize & (self.chr.len() - 1)];
446 }
447 if self.board == KaiserBoard::M56 {
448 let slot = (addr as usize) / CHR_BANK_1K;
449 let count = self.chr_count_1k();
450 let bank = (self.chr_banks[slot] as usize) % count;
451 return self.chr[bank * CHR_BANK_1K + (addr as usize & 0x3FF)];
452 }
453 self.chr[addr as usize & (self.chr.len() - 1)]
454 }
455 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
456 _ => 0,
457 }
458 }
459
460 fn ppu_write(&mut self, addr: u16, value: u8) {
461 let addr = addr & 0x3FFF;
462 match addr {
463 0x0000..=0x1FFF if self.chr_is_ram => {
464 self.chr[addr as usize & (self.chr.len() - 1)] = value;
465 }
466 0x2000..=0x3EFF => {
467 let off = nametable_offset(addr, self.mirroring);
468 self.vram[off] = value;
469 }
470 _ => {}
471 }
472 }
473
474 fn notify_cpu_cycle(&mut self) {
475 match self.board {
476 KaiserBoard::M56 | KaiserBoard::M142 => {
477 if self.irq_control & 0x02 != 0 {
478 self.irq_counter = self.irq_counter.wrapping_add(1);
479 if self.irq_counter == 0xFFFF {
480 self.irq_counter = self.irq_reload;
481 self.irq_control &= !0x02;
482 self.irq_pending = true;
483 }
484 }
485 }
486 KaiserBoard::M303 if self.irq_enabled && self.irq_counter != 0 => {
487 self.irq_counter -= 1;
488 if self.irq_counter == 0 {
489 self.irq_enabled = false;
490 self.irq_pending = true;
491 }
492 }
493 _ => {}
494 }
495 }
496
497 fn irq_pending(&self) -> bool {
498 self.irq_pending
499 }
500
501 fn irq_acknowledge(&mut self) {
502 self.irq_pending = false;
503 }
504
505 fn current_mirroring(&self) -> Mirroring {
506 self.mirroring
507 }
508
509 fn save_state(&self) -> Vec<u8> {
510 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
511 let mut out =
512 Vec::with_capacity(1 + Self::SAVE_LEN + self.vram.len() + self.wram.len() + chr_ram);
513 out.push(SAVE_STATE_VERSION);
514 out.extend_from_slice(&self.prg_regs);
515 out.push(self.selected_reg);
516 out.push(u8::from(self.use_rom));
517 out.extend_from_slice(&self.chr_banks);
518 out.push(self.win_6000);
519 out.extend_from_slice(&self.regs4);
520 out.push(self.prg16);
521 out.extend_from_slice(&self.irq_counter.to_le_bytes());
522 out.extend_from_slice(&self.irq_reload.to_le_bytes());
523 out.push(u8::from(self.irq_enabled));
524 out.push(self.irq_control);
525 out.push(u8::from(self.irq_pending));
526 out.push(mirroring_to_byte(self.mirroring));
527 out.extend_from_slice(&self.vram);
528 out.extend_from_slice(&self.wram);
529 if self.chr_is_ram {
530 out.extend_from_slice(&self.chr);
531 }
532 out
533 }
534
535 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
536 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
537 let expected = 1 + Self::SAVE_LEN + self.vram.len() + self.wram.len() + chr_ram;
538 if data.len() != expected {
539 return Err(MapperError::WrongLength {
540 expected,
541 got: data.len(),
542 });
543 }
544 if data[0] != SAVE_STATE_VERSION {
545 return Err(MapperError::UnsupportedVersion(data[0]));
546 }
547 let mut c = 1;
548 self.prg_regs.copy_from_slice(&data[c..c + 4]);
549 c += 4;
550 self.selected_reg = data[c];
551 self.use_rom = data[c + 1] != 0;
552 c += 2;
553 self.chr_banks.copy_from_slice(&data[c..c + 8]);
554 c += 8;
555 self.win_6000 = data[c];
556 c += 1;
557 self.regs4.copy_from_slice(&data[c..c + 4]);
558 c += 4;
559 self.prg16 = data[c];
560 c += 1;
561 self.irq_counter = u16::from_le_bytes([data[c], data[c + 1]]);
562 self.irq_reload = u16::from_le_bytes([data[c + 2], data[c + 3]]);
563 c += 4;
564 self.irq_enabled = data[c] != 0;
565 self.irq_control = data[c + 1];
566 self.irq_pending = data[c + 2] != 0;
567 self.mirroring = byte_to_mirroring(data[c + 3], self.mirroring);
568 c += 4;
569 self.vram.copy_from_slice(&data[c..c + self.vram.len()]);
570 c += self.vram.len();
571 self.wram.copy_from_slice(&data[c..c + self.wram.len()]);
572 c += self.wram.len();
573 if self.chr_is_ram {
574 self.chr.copy_from_slice(&data[c..c + self.chr.len()]);
575 }
576 Ok(())
577 }
578}
579
580macro_rules! kaiser_ctor {
581 ($fn_name:ident, $board:expr, $id:expr, $doc:expr) => {
582 #[doc = $doc]
583 pub fn $fn_name(
587 prg_rom: Box<[u8]>,
588 chr_rom: Box<[u8]>,
589 mirroring: Mirroring,
590 ) -> Result<KaiserMapper, MapperError> {
591 KaiserMapper::new($board, prg_rom, chr_rom, mirroring, $id)
592 }
593 };
594}
595
596kaiser_ctor!(new_m56, KaiserBoard::M56, 56, "Mapper 56 (Kaiser KS202).");
597kaiser_ctor!(
598 new_m142,
599 KaiserBoard::M142,
600 142,
601 "Mapper 142 (Kaiser KS7032)."
602);
603kaiser_ctor!(
604 new_m303,
605 KaiserBoard::M303,
606 303,
607 "Mapper 303 (Kaiser KS7017)."
608);
609kaiser_ctor!(
610 new_m305,
611 KaiserBoard::M305,
612 305,
613 "Mapper 305 (Kaiser KS7031)."
614);
615kaiser_ctor!(
616 new_m306,
617 KaiserBoard::M306,
618 306,
619 "Mapper 306 (Kaiser KS7016)."
620);
621kaiser_ctor!(
622 new_m312,
623 KaiserBoard::M312,
624 312,
625 "Mapper 312 (Kaiser KS7013B)."
626);
627
628#[cfg(test)]
640#[allow(clippy::cast_possible_truncation)]
641mod tests {
642 use super::*;
643
644 fn synth_prg_8k(banks: usize) -> Box<[u8]> {
645 let mut v = vec![0xFFu8; banks * PRG_BANK_8K];
646 for b in 0..banks {
647 v[b * PRG_BANK_8K] = b as u8;
648 }
649 v.into_boxed_slice()
650 }
651
652 fn synth_prg_16k(banks: usize) -> Box<[u8]> {
653 let mut v = vec![0xFFu8; banks * PRG_BANK_16K];
654 for b in 0..banks {
655 v[b * PRG_BANK_16K] = b as u8;
656 }
657 v.into_boxed_slice()
658 }
659
660 fn synth_chr_1k(banks: usize) -> Box<[u8]> {
661 let mut v = vec![0u8; banks * CHR_BANK_1K];
662 for b in 0..banks {
663 v[b * CHR_BANK_1K] = b as u8;
664 }
665 v.into_boxed_slice()
666 }
667
668 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
669 let mut v = vec![0u8; banks * CHR_BANK_8K];
670 for b in 0..banks {
671 v[b * CHR_BANK_8K] = b as u8;
672 }
673 v.into_boxed_slice()
674 }
675
676 fn synth_prg_2k_tagged(banks: usize) -> Box<[u8]> {
677 let mut v = vec![0xFFu8; banks * PRG_BANK_2K];
678 for b in 0..banks {
679 v[b * PRG_BANK_2K] = b as u8;
680 }
681 v.into_boxed_slice()
682 }
683
684 #[test]
685 fn kaiser202_prg_regs_and_up_count_irq() {
686 let mut m = new_m142(synth_prg_8k(16), synth_chr_8k(1), Mirroring::Vertical).unwrap();
687 m.cpu_write(0xE000, 0x01); m.cpu_write(0xF000, 0x03); assert_eq!(m.cpu_read(0x8000), 3);
690
691 m.cpu_write(0x8000, 0x0E); m.cpu_write(0xC000, 0x02); let mut fired = false;
696 for _ in 0..0x20000 {
697 m.notify_cpu_cycle();
698 if m.irq_pending() {
699 fired = true;
700 break;
701 }
702 }
703 assert!(fired);
704 }
705
706 #[test]
711 fn ks7032_6000_window_is_prg_rom_from_bank_select_4() {
712 let mut m = new_m142(synth_prg_8k(16), synth_chr_8k(1), Mirroring::Vertical).unwrap();
713 assert_eq!(m.cpu_read(0x6000), 0);
715 m.cpu_write(0xE000, 0x04); m.cpu_write(0xF000, 0x05);
717 assert_eq!(m.cpu_read(0x6000), 5);
718 m.cpu_write(0x6000, 0xA5);
720 assert_eq!(m.cpu_read(0x6000), 5);
721 assert!(!m.cpu_read_unmapped(0x6000));
722 }
723
724 #[test]
725 fn kaiser202_save_state_round_trip() {
726 let mut m = new_m56(synth_prg_8k(16), synth_chr_1k(8), Mirroring::Vertical).unwrap();
727 m.cpu_write(0xE000, 0x01);
728 m.cpu_write(0xF000, 0x05);
729 m.cpu_write(0xFC00, 0x02); m.ppu_write(0x2002, 0x44);
731 let blob = m.save_state();
732 let mut m2 = new_m56(synth_prg_8k(16), synth_chr_1k(8), Mirroring::Vertical).unwrap();
733 m2.load_state(&blob).unwrap();
734 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
735 assert_eq!(m2.ppu_read(0x2002), 0x44);
736 }
737
738 #[test]
739 fn kaiser7017_prg_and_down_count_irq() {
740 let mut m = new_m303(synth_prg_16k(8), synth_chr_8k(1), Mirroring::Vertical).unwrap();
741 m.cpu_write(0x4A00 | (1 << 2), 0); assert_eq!(m.cpu_read(0x8000), 1);
744
745 m.cpu_write(0x4020, 0x03); m.cpu_write(0x4021, 0x00); for _ in 0..3 {
748 m.notify_cpu_cycle();
749 }
750 assert!(m.irq_pending());
751 assert_eq!(m.cpu_read(0x4030), 0x01); assert!(!m.irq_pending());
753 }
754
755 #[test]
756 fn kaiser7031_windowed_prg() {
757 let mut m = new_m305(synth_prg_2k_tagged(8), synth_chr_8k(1), Mirroring::Vertical).unwrap();
759 m.cpu_write(0x8000, 5); assert_eq!(m.cpu_read(0x6000), 5); }
763
764 #[test]
765 fn kaiser7031_save_state_round_trip() {
766 let mut m = new_m305(synth_prg_8k(8), Box::new([]), Mirroring::Vertical).unwrap();
767 m.cpu_write(0x8000, 3);
768 m.cpu_write(0x8800, 4);
769 m.ppu_write(0x0005, 0x21);
770 let blob = m.save_state();
771 let mut m2 = new_m305(synth_prg_8k(8), Box::new([]), Mirroring::Vertical).unwrap();
772 m2.load_state(&blob).unwrap();
773 assert_eq!(m2.cpu_read(0x6000), m.cpu_read(0x6000));
774 assert_eq!(m2.ppu_read(0x0005), 0x21);
775 }
776
777 #[test]
778 fn kaiser7016_window_decode() {
779 let mut m = new_m306(synth_prg_8k(16), synth_chr_8k(1), Mirroring::Vertical).unwrap();
780 let addr = 0xD943; m.cpu_write(addr, 0);
783 let v = m.cpu_read(0x6000);
784 assert!((v as usize) < 16);
785 }
786
787 #[test]
788 fn kaiser7013b_prg_and_mirror() {
789 let mut m = new_m312(synth_prg_16k(8), synth_chr_8k(1), Mirroring::Vertical).unwrap();
790 m.cpu_write(0x6000, 3); assert_eq!(m.cpu_read(0x8000), 3);
792 assert_eq!(m.cpu_read(0xC000), 7); m.cpu_write(0x8000, 0x01); assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
795 }
796}