1#![allow(
18 clippy::cast_possible_truncation,
19 clippy::cast_lossless,
20 clippy::missing_const_for_fn,
21 clippy::needless_pass_by_ref_mut,
22 clippy::manual_range_patterns,
23 clippy::match_same_arms,
24 clippy::struct_excessive_bools,
25 clippy::doc_markdown,
26 clippy::range_plus_one,
27 clippy::single_match_else,
28 clippy::bool_to_int_with_if,
29 clippy::unnested_or_patterns,
30 clippy::single_match,
31 clippy::doc_lazy_continuation,
32 clippy::too_long_first_doc_paragraph
33)]
34
35use crate::cartridge::Mirroring;
36use crate::mapper::{Mapper, MapperCaps, MapperError};
37use alloc::{boxed::Box, vec::Vec};
38use alloc::{format, vec};
39
40const PRG_BANK_8K: usize = 0x2000;
41const CHR_BANK_1K: usize = 0x0400;
42const CHR_BANK_8K: usize = 0x2000;
43const NAMETABLE_SIZE: usize = 0x0400;
44const NAMETABLE_SIZE_U16: u16 = 0x0400;
45
46const VRC2_SECTION_VERSION: u8 = 2;
59
60fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
61 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
62 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
63 let physical = mirroring.physical_bank(table);
64 physical * NAMETABLE_SIZE + local
65}
66
67fn vrc_a_bits(mapper_id: u16, _submapper: u8, addr: u16) -> (bool, bool) {
103 let bit = |n: u16| (addr >> n) & 1 != 0;
104 match mapper_id {
105 21 => (bit(1) | bit(6), bit(2) | bit(7)),
106 22 => (bit(1), bit(0)), 25 => (bit(1) | bit(3), bit(0) | bit(2)), _ => (bit(0) | bit(2), bit(1) | bit(3)),
110 }
111}
112
113pub struct Vrc2 {
115 prg_rom: Box<[u8]>,
116 chr_rom: Box<[u8]>,
117 vram: Box<[u8]>,
118 chr_is_ram: bool,
119 prg_lo: u8,
120 prg_mid: u8,
121 chr: [u8; 8],
122 mirroring: Mirroring,
123 mapper_id: u16,
124 submapper: u8,
125 prg_ram: Box<[u8]>,
128}
129
130impl Vrc2 {
131 pub fn new(
137 prg_rom: Box<[u8]>,
138 chr_rom: Box<[u8]>,
139 mapper_id: u16,
140 submapper: u8,
141 mirroring: Mirroring,
142 ) -> Result<Self, MapperError> {
143 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
144 return Err(MapperError::Invalid(format!(
145 "VRC2 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
146 prg_rom.len()
147 )));
148 }
149 let chr_is_ram = chr_rom.is_empty();
150 let chr: Box<[u8]> = if chr_is_ram {
151 vec![0u8; CHR_BANK_8K].into_boxed_slice()
152 } else if chr_rom.len().is_multiple_of(CHR_BANK_1K) {
153 chr_rom
154 } else {
155 return Err(MapperError::Invalid(format!(
156 "VRC2 CHR-ROM size {} is not a multiple of 1 KiB",
157 chr_rom.len()
158 )));
159 };
160 Ok(Self {
161 prg_rom,
162 chr_rom: chr,
163 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
164 chr_is_ram,
165 prg_lo: 0,
166 prg_mid: 1,
167 chr: [0; 8],
168 mirroring,
169 mapper_id,
170 submapper,
171 prg_ram: vec![0u8; 8 * 1024].into_boxed_slice(),
173 })
174 }
175
176 fn prg_offset(&self, addr: u16) -> usize {
177 let total_8k = (self.prg_rom.len() / PRG_BANK_8K).max(1);
178 let last1 = total_8k - 1;
179 let last2 = total_8k.saturating_sub(2);
180 let bank = match addr & 0xE000 {
181 0x8000 => (self.prg_lo as usize) % total_8k,
182 0xA000 => (self.prg_mid as usize) % total_8k,
183 0xC000 => last2,
184 0xE000 => last1,
185 _ => 0,
186 };
187 bank * PRG_BANK_8K + (addr as usize & 0x1FFF)
188 }
189
190 fn chr_offset(&self, addr: u16) -> usize {
191 let addr = (addr & 0x1FFF) as usize;
192 let total_1k = (self.chr_rom.len() / CHR_BANK_1K).max(1);
193 let slot = addr / CHR_BANK_1K;
194 let raw = if self.mapper_id == 22 && !self.chr_is_ram {
202 self.chr[slot] as usize >> 1
203 } else {
204 self.chr[slot] as usize
205 };
206 let bank = raw % total_1k;
207 bank * CHR_BANK_1K + (addr & (CHR_BANK_1K - 1))
208 }
209
210 fn write_chr_reg(&mut self, slot: usize, low: bool, value: u8) {
211 let cur = self.chr[slot];
212 let v = if low {
213 (cur & 0xF0) | (value & 0x0F)
214 } else {
215 (cur & 0x0F) | ((value & 0x1F) << 4)
216 };
217 self.chr[slot] = v;
218 }
219}
220
221impl Mapper for Vrc2 {
222 fn sram(&self) -> &[u8] {
223 &self.prg_ram
224 }
225 fn sram_mut(&mut self) -> &mut [u8] {
226 &mut self.prg_ram
227 }
228 fn caps(&self) -> MapperCaps {
231 MapperCaps::NONE
232 }
233
234 fn cpu_read(&mut self, addr: u16) -> u8 {
235 match addr {
236 0x6000..=0x7FFF => self.prg_ram[(addr - 0x6000) as usize % self.prg_ram.len()],
243 0x8000..=0xFFFF => {
244 let off = self.prg_offset(addr);
245 self.prg_rom[off % self.prg_rom.len()]
246 }
247 _ => 0,
248 }
249 }
250
251 fn cpu_write(&mut self, addr: u16, value: u8) {
252 if (0x6000..=0x7FFF).contains(&addr) {
256 let len = self.prg_ram.len();
257 self.prg_ram[(addr - 0x6000) as usize % len] = value;
258 return;
259 }
260 let (a0, a1) = vrc_a_bits(self.mapper_id, self.submapper, addr);
261 match addr & 0xF000 {
262 0x8000 => self.prg_lo = value & 0x1F,
263 0x9000 => {
264 self.mirroring = match value & 0x03 {
265 0 => Mirroring::Vertical,
266 1 => Mirroring::Horizontal,
267 2 => Mirroring::SingleScreenA,
268 _ => Mirroring::SingleScreenB,
269 };
270 }
271 0xA000 => self.prg_mid = value & 0x1F,
272 0xB000 => {
273 let slot = if a1 { 1 } else { 0 };
274 self.write_chr_reg(slot, !a0, value);
275 }
276 0xC000 => {
277 let slot = if a1 { 3 } else { 2 };
278 self.write_chr_reg(slot, !a0, value);
279 }
280 0xD000 => {
281 let slot = if a1 { 5 } else { 4 };
282 self.write_chr_reg(slot, !a0, value);
283 }
284 0xE000 => {
285 let slot = if a1 { 7 } else { 6 };
286 self.write_chr_reg(slot, !a0, value);
287 }
288 _ => {}
289 }
290 }
291
292 fn ppu_read(&mut self, addr: u16) -> u8 {
293 let addr = addr & 0x3FFF;
294 match addr {
295 0x0000..=0x1FFF => {
296 let off = self.chr_offset(addr);
297 self.chr_rom[off % self.chr_rom.len()]
298 }
299 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring) % self.vram.len()],
300 _ => 0,
301 }
302 }
303
304 fn ppu_write(&mut self, addr: u16, value: u8) {
305 let addr = addr & 0x3FFF;
306 match addr {
307 0x0000..=0x1FFF => {
308 if self.chr_is_ram {
309 let len = self.chr_rom.len();
310 self.chr_rom[addr as usize % len] = value;
311 }
312 }
313 0x2000..=0x3EFF => {
314 let off = nametable_offset(addr, self.mirroring) % self.vram.len();
315 self.vram[off] = value;
316 }
317 _ => {}
318 }
319 }
320
321 fn current_mirroring(&self) -> Mirroring {
322 self.mirroring
323 }
324
325 fn save_state(&self) -> Vec<u8> {
326 let mut out = Vec::with_capacity(20 + self.vram.len() + self.ram_block_len());
327 out.push(VRC2_SECTION_VERSION);
328 out.push(self.prg_lo);
329 out.push(self.prg_mid);
330 out.extend_from_slice(&self.chr);
331 out.push(self.mirroring as u8);
332 out.extend_from_slice(&self.vram);
333 out.extend_from_slice(&self.prg_ram);
335 if self.chr_is_ram {
336 out.extend_from_slice(&self.chr_rom);
337 }
338 out
339 }
340
341 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
342 let version = data.first().copied().unwrap_or(0);
343 if version != VRC2_SECTION_VERSION {
347 return Err(MapperError::UnsupportedVersion(version));
348 }
349 let ram_len = self.ram_block_len();
350 let core_len = 12 + self.vram.len();
352 let expected = core_len + ram_len;
353 if data.len() != expected {
354 return Err(MapperError::WrongLength {
355 expected,
356 got: data.len(),
357 });
358 }
359 self.prg_lo = data[1];
360 self.prg_mid = data[2];
361 self.chr.copy_from_slice(&data[3..11]);
362 self.mirroring = match data[11] {
363 0 => Mirroring::Horizontal,
364 1 => Mirroring::Vertical,
365 2 => Mirroring::SingleScreenA,
366 3 => Mirroring::SingleScreenB,
367 4 => Mirroring::FourScreen,
368 5 => Mirroring::MapperControlled,
369 other => return Err(MapperError::Invalid(format!("mirroring {other}"))),
370 };
371 self.vram.copy_from_slice(&data[12..core_len]);
372 let (prg, chr) = data[core_len..].split_at(self.prg_ram.len());
373 self.prg_ram.copy_from_slice(prg);
374 if self.chr_is_ram {
375 self.chr_rom.copy_from_slice(chr);
376 }
377 Ok(())
378 }
379}
380
381impl Vrc2 {
382 fn ram_block_len(&self) -> usize {
386 self.prg_ram.len()
387 + if self.chr_is_ram {
388 self.chr_rom.len()
389 } else {
390 0
391 }
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 fn synth(banks_8k: usize) -> Box<[u8]> {
400 let mut v = vec![0u8; banks_8k * PRG_BANK_8K];
401 for b in 0..banks_8k {
402 v[b * PRG_BANK_8K] = b as u8;
403 }
404 v.into_boxed_slice()
405 }
406
407 fn synth_chr(banks_1k: usize) -> Box<[u8]> {
408 let mut v = vec![0u8; banks_1k * CHR_BANK_1K];
409 for b in 0..banks_1k {
410 v[b * CHR_BANK_1K] = b as u8;
411 }
412 v.into_boxed_slice()
413 }
414
415 #[test]
416 fn vrc24_a_bits_per_board_pin_rewiring() {
417 assert_eq!(vrc_a_bits(21, 0, 0x8000 | (1 << 1)), (true, false));
426 assert_eq!(vrc_a_bits(21, 0, 0x8000 | (1 << 6)), (true, false));
427 assert_eq!(vrc_a_bits(21, 0, 0x8000 | (1 << 2)), (false, true));
428 assert_eq!(vrc_a_bits(21, 0, 0x8000 | (1 << 7)), (false, true));
429 assert_eq!(vrc_a_bits(22, 0, 0x8000 | (1 << 1)), (true, false));
431 assert_eq!(vrc_a_bits(22, 0, 0x8000 | (1 << 0)), (false, true));
432 assert_eq!(vrc_a_bits(23, 0, 0x8000 | (1 << 0)), (true, false));
434 assert_eq!(vrc_a_bits(23, 0, 0x8000 | (1 << 2)), (true, false));
435 assert_eq!(vrc_a_bits(23, 0, 0x8000 | (1 << 1)), (false, true));
436 assert_eq!(vrc_a_bits(23, 0, 0x8000 | (1 << 3)), (false, true));
437 assert_eq!(vrc_a_bits(25, 0, 0x8000 | (1 << 1)), (true, false));
439 assert_eq!(vrc_a_bits(25, 0, 0x8000 | (1 << 3)), (true, false));
440 assert_eq!(vrc_a_bits(25, 0, 0x8000 | (1 << 0)), (false, true));
441 assert_eq!(vrc_a_bits(25, 0, 0x8000 | (1 << 2)), (false, true));
442 }
443
444 #[test]
445 fn vrc2_prg_bank_registers_and_fixed_banks() {
446 let mut m = Vrc2::new(synth(8), synth_chr(8), 22, 0, Mirroring::Vertical).unwrap();
448 m.cpu_write(0x8000, 3);
451 m.cpu_write(0xA000, 5);
452 assert_eq!(m.cpu_read(0x8000), 3, "prg_lo -> bank 3");
453 assert_eq!(m.cpu_read(0xA000), 5, "prg_mid -> bank 5");
454 assert_eq!(m.cpu_read(0xC000), 6, "fixed -> last-2 (bank 6 of 8)");
455 assert_eq!(m.cpu_read(0xE000), 7, "fixed -> last-1 (bank 7 of 8)");
456 m.cpu_write(0x8000, 0xE0 | 2);
458 assert_eq!(m.cpu_read(0x8000), 2, "high bits above 5-bit field ignored");
459 }
460
461 #[test]
462 fn vrc2_mirroring_control_register() {
463 let mut m = Vrc2::new(synth(8), synth_chr(8), 22, 0, Mirroring::Vertical).unwrap();
464 m.cpu_write(0x9000, 0);
465 assert_eq!(m.mirroring, Mirroring::Vertical);
466 m.cpu_write(0x9000, 1);
467 assert_eq!(m.mirroring, Mirroring::Horizontal);
468 m.cpu_write(0x9000, 2);
469 assert_eq!(m.mirroring, Mirroring::SingleScreenA);
470 m.cpu_write(0x9000, 3);
471 assert_eq!(m.mirroring, Mirroring::SingleScreenB);
472 }
473
474 #[test]
475 fn vrc2_chr_bank_low_high_nibble_split() {
476 let mut m = Vrc2::new(synth(8), synth_chr(0x20), 23, 3, Mirroring::Vertical).unwrap();
482 m.cpu_write(0xB000, 0x2);
484 m.cpu_write(0xB001, 0x1);
486 assert_eq!(m.ppu_read(0x0000), 0x12, "CHR slot 0 -> bank 0x12");
487 }
488
489 #[test]
493 fn vrc2_save_state_carries_prg_ram_and_chr_ram() {
494 let mut m = Vrc2::new(synth(8), Box::new([]), 22, 0, Mirroring::Vertical).unwrap();
495 m.cpu_write(0x6000, 0x5A);
496 m.cpu_write(0x7FFF, 0xA5);
497 m.ppu_write(0x0000, 0x11);
498 m.ppu_write(0x03FF, 0x22);
501 let blob = m.save_state();
502 let mut m2 = Vrc2::new(synth(8), Box::new([]), 22, 0, Mirroring::Vertical).unwrap();
503 m2.load_state(&blob).expect("round-trip");
504 assert_eq!(m2.cpu_read(0x6000), 0x5A);
505 assert_eq!(m2.cpu_read(0x7FFF), 0xA5);
506 assert_eq!(m2.ppu_read(0x0000), 0x11);
507 assert_eq!(m2.ppu_read(0x03FF), 0x22);
508 }
509
510 #[test]
513 fn vrc2_v1_blob_is_refused() {
514 let mut m = Vrc2::new(synth(8), synth_chr(8), 22, 0, Mirroring::Vertical).unwrap();
515 m.cpu_write(0x8000, 3);
516 let core_len = 12 + m.vram.len();
517 let mut v1 = m.save_state()[..core_len].to_vec();
518 v1[0] = 1;
519 let mut m2 = Vrc2::new(synth(8), synth_chr(8), 22, 0, Mirroring::Vertical).unwrap();
520 assert!(matches!(
521 m2.load_state(&v1),
522 Err(MapperError::UnsupportedVersion(1))
523 ));
524 }
525
526 #[test]
528 fn vrc2_truncated_ram_tail_is_rejected() {
529 let m = Vrc2::new(synth(8), synth_chr(8), 22, 0, Mirroring::Vertical).unwrap();
530 let blob = m.save_state();
531 let mut m2 = Vrc2::new(synth(8), synth_chr(8), 22, 0, Mirroring::Vertical).unwrap();
532 let err = m2
533 .load_state(&blob[..blob.len() - 1])
534 .expect_err("a truncated v2 blob must be rejected");
535 assert!(matches!(err, MapperError::WrongLength { .. }), "{err:?}");
536 }
537}