1#![allow(clippy::cast_possible_truncation, clippy::missing_const_for_fn)]
38
39use crate::cartridge::Mirroring;
40use crate::mapper::{Mapper, MapperCaps, MapperDebugInfo, MapperError};
41use alloc::{boxed::Box, format, vec, vec::Vec};
42
43const PRG_BANK_8K: usize = 0x2000;
44const CHR_BANK_1K: usize = 0x0400;
45const WRAM_SUB2: usize = 0x8000;
46const SAVE_STATE_VERSION: u8 = 1;
47
48pub struct Cony83 {
50 prg_rom: Box<[u8]>,
51 chr: Box<[u8]>,
52 chr_is_ram: bool,
53 vram: Box<[u8]>,
54 wram: Box<[u8]>,
55 submapper: u8,
56 mode: u8,
57 reg4: u8,
59 prg_regs: [u8; 4],
60 chr_regs: [u8; 8],
61 scratch: [u8; 4],
62 dip: u8,
63 irq_counter: u16,
64 irq_enabled: bool,
65 irq_pending: bool,
66}
67
68impl Cony83 {
69 pub fn new(
77 prg_rom: Box<[u8]>,
78 chr_rom: Box<[u8]>,
79 submapper: Option<u8>,
80 ) -> Result<Self, MapperError> {
81 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
82 return Err(MapperError::Invalid(format!(
83 "mapper 83 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
84 prg_rom.len()
85 )));
86 }
87 if !chr_rom.len().is_multiple_of(CHR_BANK_1K) {
88 return Err(MapperError::Invalid(format!(
89 "mapper 83 CHR-ROM size {} is not a multiple of 1 KiB",
90 chr_rom.len()
91 )));
92 }
93 let submapper = match submapper {
94 Some(s @ 0..=2) => s,
95 Some(_) => 0,
96 None => match chr_rom.len() {
97 0x8_0000 => 1,
98 0x10_0000 => 2,
99 _ => 0,
100 },
101 };
102 let chr_is_ram = chr_rom.is_empty();
103 let chr = if chr_is_ram {
104 vec![0u8; 8 * CHR_BANK_1K].into_boxed_slice()
105 } else {
106 chr_rom
107 };
108 Ok(Self {
109 prg_rom,
110 chr,
111 chr_is_ram,
112 vram: vec![0u8; 0x800].into_boxed_slice(),
113 wram: vec![0u8; if submapper == 2 { WRAM_SUB2 } else { 0 }].into_boxed_slice(),
114 submapper,
115 mode: 0,
116 reg4: 0,
117 prg_regs: [0; 4],
118 chr_regs: [0; 8],
119 scratch: [0; 4],
120 dip: 0,
121 irq_counter: 0,
122 irq_enabled: false,
123 irq_pending: false,
124 })
125 }
126
127 pub fn set_dip(&mut self, dip: u8) {
129 self.dip = dip & 0x03;
130 }
131
132 fn prg_bank(&self, addr: u16) -> usize {
134 let total = self.prg_rom.len() / PRG_BANK_8K;
135 let (base, window) = if self.submapper == 2 {
136 (usize::from((self.reg4 >> 4) & 0x03) * 32, 32.min(total))
137 } else {
138 (0, total)
139 };
140 let last = window - 1;
141 let slot = usize::from((addr >> 13) & 0x03);
142 let r4 = usize::from(self.reg4 & 0x0F);
143 let inner = match (self.mode >> 3) & 0x03 {
144 0 => {
145 if slot < 2 {
146 r4 * 2 + slot
147 } else {
148 (last + window + slot - 3) % window
153 }
154 }
155 1 => (r4 >> 1) * 4 + slot,
156 _ => {
157 if slot < 3 {
158 usize::from(self.prg_regs[slot])
159 } else {
160 last
161 }
162 }
163 };
164 let inner = if self.submapper == 2 {
165 inner % window
166 } else {
167 inner
168 };
169 (base + inner) % total
170 }
171
172 fn chr_offset(&self, addr: u16) -> usize {
173 let within = usize::from(addr & 0x03FF);
174 let slot = usize::from((addr >> 10) & 0x07);
175 let bank = match self.submapper {
176 1 => {
177 let reg = [0, 0, 1, 1, 6, 6, 7, 7][slot];
179 usize::from(self.chr_regs[reg]) * 2 + (slot & 1)
180 }
181 2 => usize::from(self.chr_regs[slot]) | (usize::from((self.reg4 >> 4) & 0x03) << 8),
182 _ => usize::from(self.chr_regs[slot]),
183 };
184 (bank % (self.chr.len() / CHR_BANK_1K)) * CHR_BANK_1K + within
185 }
186
187 fn mirroring(&self) -> Mirroring {
188 match self.mode & 0x03 {
189 0 => Mirroring::Vertical,
190 1 => Mirroring::Horizontal,
191 2 => Mirroring::SingleScreenA,
192 _ => Mirroring::SingleScreenB,
193 }
194 }
195
196 fn nt_offset(&self, addr: u16) -> usize {
197 let table = ((addr - 0x2000) / 0x400) & 0x03;
198 self.mirroring().physical_bank(table as u8) * 0x400 + usize::from(addr & 0x3FF)
199 }
200
201 fn low_rom_enabled(&self) -> bool {
203 self.submapper != 2 && self.mode & 0x20 != 0
204 }
205
206 fn wram_offset(&self, addr: u16) -> usize {
207 (usize::from(self.reg4 >> 6) * 0x2000 + usize::from(addr & 0x1FFF)) % self.wram.len()
208 }
209}
210
211impl Mapper for Cony83 {
212 fn sram(&self) -> &[u8] {
213 &self.wram
214 }
215
216 fn sram_mut(&mut self) -> &mut [u8] {
217 &mut self.wram
218 }
219
220 fn caps(&self) -> MapperCaps {
221 MapperCaps::CYCLE_IRQ
222 }
223
224 fn cpu_read_unmapped(&self, addr: u16) -> bool {
225 match addr {
226 0x4020..=0x4FFF => true,
227 0x6000..=0x7FFF => !self.low_rom_enabled() && self.wram.is_empty(),
228 _ => false,
230 }
231 }
232
233 fn cpu_read_driven_mask(&self, addr: u16) -> u8 {
234 if addr & 0xFF00 == 0x5000 { 0x03 } else { 0xFF }
235 }
236
237 fn cpu_read(&mut self, addr: u16) -> u8 {
238 match addr {
239 0x5000..=0x50FF => self.dip,
240 0x5100..=0x5FFF => self.scratch[usize::from(addr & 0x03)],
241 0x6000..=0x7FFF if self.low_rom_enabled() => {
242 let bank = usize::from(self.prg_regs[3]) % (self.prg_rom.len() / PRG_BANK_8K);
243 self.prg_rom[bank * PRG_BANK_8K + usize::from(addr & 0x1FFF)]
244 }
245 0x6000..=0x7FFF if !self.wram.is_empty() => self.wram[self.wram_offset(addr)],
246 0x8000..=0xFFFF => {
247 self.prg_rom[self.prg_bank(addr) * PRG_BANK_8K + usize::from(addr & 0x1FFF)]
248 }
249 _ => 0,
250 }
251 }
252
253 fn cpu_write(&mut self, addr: u16, value: u8) {
254 match addr {
255 0x5100..=0x5FFF => self.scratch[usize::from(addr & 0x03)] = value,
256 0x6000..=0x7FFF if !self.wram.is_empty() => {
257 let off = self.wram_offset(addr);
258 self.wram[off] = value;
259 }
260 0x8000..=0xFFFF => match addr & 0x8300 {
261 0x8000 => self.reg4 = value,
262 0x8100 => self.mode = value,
263 0x8200 => {
264 if addr & 0x01 == 0 {
265 self.irq_counter = (self.irq_counter & 0xFF00) | u16::from(value);
266 self.irq_pending = false;
267 } else {
268 self.irq_counter = (self.irq_counter & 0x00FF) | (u16::from(value) << 8);
269 self.irq_enabled = self.mode & 0x80 != 0;
270 }
271 }
272 _ => match addr & 0x831F {
273 r @ 0x8300..=0x8303 => self.prg_regs[usize::from(r & 0x03)] = value,
274 r @ 0x8310..=0x8317 => self.chr_regs[usize::from(r & 0x07)] = value,
275 _ => {}
276 },
277 },
278 _ => {}
279 }
280 }
281
282 fn notify_cpu_cycle(&mut self) {
283 if !self.irq_enabled || self.irq_counter == 0 {
284 return;
285 }
286 self.irq_counter = if self.mode & 0x40 != 0 {
287 self.irq_counter.wrapping_sub(1)
288 } else {
289 self.irq_counter.wrapping_add(1)
290 };
291 if self.irq_counter == 0 {
292 self.irq_pending = true;
293 self.irq_enabled = false;
294 }
295 }
296
297 fn irq_pending(&self) -> bool {
298 self.irq_pending
299 }
300
301 fn chr_phys(&self, addr: u16) -> Option<u32> {
302 if self.chr_is_ram {
303 None
304 } else {
305 u32::try_from(self.chr_offset(addr & 0x1FFF)).ok()
306 }
307 }
308
309 fn ppu_read(&mut self, addr: u16) -> u8 {
310 let addr = addr & 0x3FFF;
311 match addr {
312 0x0000..=0x1FFF => self.chr[self.chr_offset(addr)],
313 0x2000..=0x3EFF => self.vram[self.nt_offset(addr)],
314 _ => 0,
315 }
316 }
317
318 fn ppu_write(&mut self, addr: u16, value: u8) {
319 let addr = addr & 0x3FFF;
320 match addr {
321 0x0000..=0x1FFF if self.chr_is_ram => {
322 let off = self.chr_offset(addr);
323 self.chr[off] = value;
324 }
325 0x2000..=0x3EFF => {
326 let off = self.nt_offset(addr);
327 self.vram[off] = value;
328 }
329 _ => {}
330 }
331 }
332
333 fn nametable_address(&self, addr: u16) -> u16 {
334 u16::try_from(self.nt_offset(addr)).unwrap_or(0)
335 }
336
337 fn current_mirroring(&self) -> Mirroring {
338 self.mirroring()
339 }
340
341 fn debug_info(&self) -> MapperDebugInfo {
342 let mut info = MapperDebugInfo {
343 mapper_id: 83,
344 name: format!("Cony/Yoko (submapper {})", self.submapper),
345 mirroring: crate::mapper::mirroring_name(self.mirroring()),
346 ..Default::default()
347 };
348 info.prg_banks
349 .push(("mode".into(), format!("{}", (self.mode >> 3) & 3)));
350 info.prg_banks
351 .push(("R4".into(), format!("{:#04x}", self.reg4)));
352 for (i, r) in self.prg_regs.iter().enumerate() {
353 info.prg_banks.push((format!("R{i}"), format!("{r:#04x}")));
354 }
355 for (i, r) in self.chr_regs.iter().enumerate() {
356 info.chr_banks.push((format!("C{i}"), format!("{r:#04x}")));
357 }
358 info.irq_state
359 .push(("counter".into(), format!("{:#06x}", self.irq_counter)));
360 info.irq_state
361 .push(("enabled".into(), format!("{}", self.irq_enabled)));
362 info
363 }
364
365 fn save_state(&self) -> Vec<u8> {
366 let mut out = Vec::with_capacity(32 + self.vram.len() + self.wram.len());
367 out.push(SAVE_STATE_VERSION);
368 out.push(self.submapper);
369 out.push(self.mode);
370 out.push(self.reg4);
371 out.extend_from_slice(&self.prg_regs);
372 out.extend_from_slice(&self.chr_regs);
373 out.extend_from_slice(&self.scratch);
374 out.push(self.dip);
375 out.extend_from_slice(&self.irq_counter.to_le_bytes());
376 out.push(u8::from(self.irq_enabled));
377 out.push(u8::from(self.irq_pending));
378 out.extend_from_slice(&self.vram);
379 out.extend_from_slice(&self.wram);
380 if self.chr_is_ram {
381 out.extend_from_slice(&self.chr);
382 }
383 out
384 }
385
386 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
387 const HEAD: usize = 25;
388 let chr = if self.chr_is_ram { self.chr.len() } else { 0 };
389 let expected = HEAD + self.vram.len() + self.wram.len() + chr;
390 if data.len() != expected {
391 return Err(MapperError::WrongLength {
392 expected,
393 got: data.len(),
394 });
395 }
396 if data[0] != SAVE_STATE_VERSION {
397 return Err(MapperError::UnsupportedVersion(data[0]));
398 }
399 if data[1] != self.submapper {
400 return Err(MapperError::Invalid(format!(
401 "state is for mapper 83 submapper {}, this board is {}",
402 data[1], self.submapper
403 )));
404 }
405 self.mode = data[2];
406 self.reg4 = data[3];
407 self.prg_regs.copy_from_slice(&data[4..8]);
408 self.chr_regs.copy_from_slice(&data[8..16]);
409 self.scratch.copy_from_slice(&data[16..20]);
410 self.dip = data[20] & 0x03;
411 self.irq_counter = u16::from_le_bytes([data[21], data[22]]);
412 self.irq_enabled = data[23] != 0;
413 self.irq_pending = data[24] != 0;
414 let mut cur = HEAD;
415 self.vram.copy_from_slice(&data[cur..cur + self.vram.len()]);
416 cur += self.vram.len();
417 let n = self.wram.len();
418 self.wram.copy_from_slice(&data[cur..cur + n]);
419 cur += n;
420 if self.chr_is_ram {
421 self.chr.copy_from_slice(&data[cur..]);
422 }
423 Ok(())
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 fn board(prg_8k: usize, chr_1k: usize, sub: Option<u8>) -> Cony83 {
432 let mut prg = vec![0u8; prg_8k * PRG_BANK_8K];
433 for b in 0..prg_8k {
434 prg[b * PRG_BANK_8K] = b as u8;
435 }
436 let mut chr = vec![0u8; chr_1k * CHR_BANK_1K];
437 for b in 0..chr_1k {
438 chr[b * CHR_BANK_1K] = b as u8;
439 chr[b * CHR_BANK_1K + 1] = (b >> 8) as u8;
440 }
441 Cony83::new(prg.into_boxed_slice(), chr.into_boxed_slice(), sub).unwrap()
442 }
443
444 fn chr_at(m: &mut Cony83, addr: u16) -> usize {
445 usize::from(m.ppu_read(addr)) | usize::from(m.ppu_read(addr + 1)) << 8
446 }
447
448 #[test]
449 fn submapper_follows_chr_size_on_ines() {
450 assert_eq!(board(32, 256, None).submapper, 0);
451 assert_eq!(board(32, 512, None).submapper, 1);
452 assert_eq!(board(128, 1024, None).submapper, 2);
453 assert_eq!(board(32, 256, Some(2)).submapper, 2, "NES 2.0 wins");
454 }
455
456 #[test]
457 fn prg_modes_follow_the_page() {
458 let mut m = board(32, 256, None);
459 m.cpu_write(0x8000, 0x05);
460 m.cpu_write(0x8100, 0x00);
461 assert_eq!(m.cpu_read(0x8000), 10, "mode 0: 16 KiB bank 5");
462 assert_eq!(m.cpu_read(0xA000), 11);
463 assert_eq!(m.cpu_read(0xC000), 30, "the last 16 KiB");
464 assert_eq!(m.cpu_read(0xE000), 31);
465 m.cpu_write(0x8100, 0x08);
466 assert_eq!(m.cpu_read(0x8000), 8, "mode 1: 32 KiB bank 5>>1 = 2");
467 assert_eq!(m.cpu_read(0xE000), 11);
468 m.cpu_write(0x8100, 0x10);
469 m.cpu_write(0x8300, 3);
470 m.cpu_write(0x8301, 4);
471 m.cpu_write(0x8302, 5);
472 assert_eq!(m.cpu_read(0x8000), 3, "mode 2: registers 0-2");
473 assert_eq!(m.cpu_read(0xA000), 4);
474 assert_eq!(m.cpu_read(0xC000), 5);
475 assert_eq!(m.cpu_read(0xE000), 31);
476 m.cpu_write(0x8100, 0x18);
477 assert_eq!(m.cpu_read(0xA000), 4, "mode 3 = mode 2");
478 }
479
480 #[test]
481 fn prg_rom_at_6000_on_submappers_0_and_1() {
482 let mut m = board(32, 256, None);
483 m.cpu_write(0x8303, 7);
484 assert!(m.cpu_read_unmapped(0x6000), "bit 5 clear: open bus");
485 m.cpu_write(0x8100, 0x20);
486 assert!(!m.cpu_read_unmapped(0x6000));
487 assert_eq!(m.cpu_read(0x6000), 7);
488 }
489
490 #[test]
491 fn register_masks_mirror_the_decodes() {
492 let mut m = board(32, 256, None);
493 m.cpu_write(0x8100, 0x20);
494 m.cpu_write(0xB3E3, 6); assert_eq!(m.cpu_read(0x6000), 6);
496 m.cpu_write(0x8318, 0x44); m.cpu_write(0xBF14, 0x21); assert_eq!(chr_at(&mut m, 0x1000), 0x21);
499 }
500
501 #[test]
502 fn submapper1_uses_2k_chr_registers() {
503 let mut m = board(32, 512, None);
504 m.cpu_write(0x8310, 3);
505 m.cpu_write(0x8311, 4);
506 m.cpu_write(0x8316, 5);
507 m.cpu_write(0x8317, 6);
508 m.cpu_write(0x8312, 0x7F); assert_eq!(chr_at(&mut m, 0x0000), 6);
510 assert_eq!(chr_at(&mut m, 0x0400), 7);
511 assert_eq!(chr_at(&mut m, 0x0800), 8);
512 assert_eq!(chr_at(&mut m, 0x1000), 10);
513 assert_eq!(chr_at(&mut m, 0x1C00), 13);
514 }
515
516 #[test]
517 fn submapper2_outer_bank_and_wram() {
518 let mut m = board(128, 1024, None);
519 m.cpu_write(0x8100, 0x10);
520 m.cpu_write(0x8300, 3);
521 m.cpu_write(0x8314, 5);
522 m.cpu_write(0x8000, 0x20); assert_eq!(m.cpu_read(0x8000), 64 + 3);
524 assert_eq!(
525 m.cpu_read(0xE000),
526 64 + 31,
527 "last bank of the outer 256 KiB"
528 );
529 assert_eq!(chr_at(&mut m, 0x1000), 0x205);
530 m.cpu_write(0x6000, 0xAA);
532 m.cpu_write(0x8000, 0x60);
533 m.cpu_write(0x6000, 0xBB);
534 m.cpu_write(0x8000, 0x20);
535 assert_eq!(m.cpu_read(0x6000), 0xAA);
536 assert_eq!(m.sram().len(), WRAM_SUB2);
537 }
538
539 #[test]
540 fn irq_counts_up_or_down_and_disables_at_zero() {
541 let mut m = board(32, 256, None);
542 m.cpu_write(0x8100, 0x80); m.cpu_write(0x8200, 0xFE);
544 m.cpu_write(0x8201, 0xFF);
545 m.notify_cpu_cycle();
546 assert!(!m.irq_pending());
547 m.notify_cpu_cycle();
548 assert!(m.irq_pending(), "$FFFE + 2 = 0");
549 m.cpu_write(0x8200, 0x02);
550 assert!(!m.irq_pending(), "$8200 acknowledges");
551 m.cpu_write(0x8100, 0xC0); m.cpu_write(0x8201, 0x00);
553 m.notify_cpu_cycle();
554 m.notify_cpu_cycle();
555 assert!(m.irq_pending(), "2 - 2 = 0");
556 m.cpu_write(0x8200, 0x05);
557 m.notify_cpu_cycle();
558 assert_eq!(m.irq_counter, 5, "disabled after firing");
559 m.cpu_write(0x8100, 0x40);
561 m.cpu_write(0x8201, 0x00);
562 m.cpu_write(0x8100, 0xC0);
563 m.notify_cpu_cycle();
564 assert_eq!(m.irq_counter, 5);
565 }
566
567 #[test]
568 fn dip_and_scratch_ram() {
569 let mut m = board(32, 256, None);
570 m.set_dip(2);
571 assert_eq!(m.cpu_read(0x5000) & 0x03, 2);
572 assert_eq!(m.cpu_read_driven_mask(0x5000), 0x03);
573 m.cpu_write(0x5102, 0x5A);
574 assert_eq!(m.cpu_read(0x5F06), 0x5A, "scratch byte 2, mask $DF03");
575 }
576
577 #[test]
583 fn mode_0_fixed_half_on_a_small_image() {
584 let mut m = board(2, 256, None);
585 m.cpu_write(0x8100, 0x00);
586 assert_eq!(m.cpu_read(0xC000), 0, "second-to-last of two banks");
587 assert_eq!(m.cpu_read(0xE000), 1, "the last bank");
588 let mut m = board(1, 256, None);
589 m.cpu_write(0x8100, 0x00);
590 assert_eq!(m.cpu_read(0xC000), 0);
591 assert_eq!(m.cpu_read(0xE000), 0);
592 }
593
594 #[test]
595 fn state_round_trips() {
596 for chr in [256, 512, 1024] {
597 let mut a = board(128, chr, None);
598 a.cpu_write(0x8100, 0x93);
599 a.cpu_write(0x8000, 0x65);
600 a.cpu_write(0x8313, 0x22);
601 a.cpu_write(0x5101, 7);
602 a.cpu_write(0x6001, 3);
603 let blob = a.save_state();
604 let mut b = board(128, chr, None);
605 b.load_state(&blob).unwrap();
606 assert_eq!(b.save_state(), blob);
607 }
608 }
609}