1#![allow(
28 clippy::cast_possible_truncation,
29 clippy::cast_lossless,
30 clippy::missing_const_for_fn,
31 clippy::struct_excessive_bools,
32 clippy::doc_markdown
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_16K: usize = 0x4000;
41const CHR_RAM_8K: usize = 0x2000;
42const NAMETABLE_SIZE: usize = 0x0400;
43const NAMETABLE_SIZE_U16: u16 = 0x0400;
44
45const SAVE_STATE_VERSION: u8 = 1;
46
47pub struct Vrc3 {
49 prg_rom: Box<[u8]>,
50 chr_ram: Box<[u8]>,
51 vram: Box<[u8]>,
52 prg_ram: Box<[u8]>,
53 prg_bank: u8,
54 mirroring: Mirroring,
55
56 irq_latch: u16,
58 irq_counter: u16,
59 irq_enabled: bool,
60 irq_enable_after_ack: bool,
61 irq_mode_8bit: bool,
62 irq_pending: bool,
63}
64
65impl Vrc3 {
66 pub fn new(prg_rom: Box<[u8]>, mirroring: Mirroring) -> Result<Self, MapperError> {
75 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
76 return Err(MapperError::Invalid(format!(
77 "VRC3 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
78 prg_rom.len()
79 )));
80 }
81 Ok(Self {
82 prg_rom,
83 chr_ram: vec![0u8; CHR_RAM_8K].into_boxed_slice(),
84 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
85 prg_ram: vec![0u8; 8 * 1024].into_boxed_slice(),
86 prg_bank: 0,
87 mirroring,
88 irq_latch: 0,
89 irq_counter: 0,
90 irq_enabled: false,
91 irq_enable_after_ack: false,
92 irq_mode_8bit: false,
93 irq_pending: false,
94 })
95 }
96
97 const fn nametable_offset(&self, addr: u16) -> usize {
98 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
99 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
100 let physical = self.mirroring.physical_bank(table);
101 physical * NAMETABLE_SIZE + local
102 }
103}
104
105impl Mapper for Vrc3 {
106 fn sram(&self) -> &[u8] {
107 &self.prg_ram
108 }
109 fn sram_mut(&mut self) -> &mut [u8] {
110 &mut self.prg_ram
111 }
112 fn caps(&self) -> MapperCaps {
114 MapperCaps::CYCLE_IRQ
115 }
116
117 fn cpu_read(&mut self, addr: u16) -> u8 {
118 match addr {
119 0x6000..=0x7FFF => self.prg_ram[(addr - 0x6000) as usize % self.prg_ram.len()],
120 0x8000..=0xBFFF => {
121 let total = (self.prg_rom.len() / PRG_BANK_16K).max(1);
122 let bank = (self.prg_bank as usize) % total;
123 self.prg_rom[bank * PRG_BANK_16K + (addr - 0x8000) as usize]
124 }
125 0xC000..=0xFFFF => {
126 let total = (self.prg_rom.len() / PRG_BANK_16K).max(1);
127 let last = total - 1;
128 self.prg_rom[last * PRG_BANK_16K + (addr - 0xC000) as usize]
129 }
130 _ => 0,
131 }
132 }
133
134 fn cpu_write(&mut self, addr: u16, value: u8) {
135 match addr {
136 0x6000..=0x7FFF => {
137 let len = self.prg_ram.len();
138 self.prg_ram[(addr - 0x6000) as usize % len] = value;
139 }
140 0x8000..=0x8FFF => {
142 self.irq_latch = (self.irq_latch & 0xFFF0) | (value as u16 & 0x0F);
143 }
144 0x9000..=0x9FFF => {
145 self.irq_latch = (self.irq_latch & 0xFF0F) | ((value as u16 & 0x0F) << 4);
146 }
147 0xA000..=0xAFFF => {
148 self.irq_latch = (self.irq_latch & 0xF0FF) | ((value as u16 & 0x0F) << 8);
149 }
150 0xB000..=0xBFFF => {
151 self.irq_latch = (self.irq_latch & 0x0FFF) | ((value as u16 & 0x0F) << 12);
152 }
153 0xC000..=0xCFFF => {
155 self.irq_enable_after_ack = (value & 0x01) != 0;
156 self.irq_enabled = (value & 0x02) != 0;
157 self.irq_mode_8bit = (value & 0x04) != 0;
158 self.irq_pending = false;
159 if self.irq_enabled {
160 self.irq_counter = self.irq_latch;
162 }
163 }
164 0xD000..=0xDFFF => {
166 self.irq_pending = false;
167 self.irq_enabled = self.irq_enable_after_ack;
168 }
169 0xF000..=0xFFFF => {
171 self.prg_bank = value & 0x07;
172 }
173 _ => {}
174 }
175 }
176
177 fn ppu_read(&mut self, addr: u16) -> u8 {
178 let addr = addr & 0x3FFF;
179 match addr {
180 0x0000..=0x1FFF => self.chr_ram[addr as usize],
181 0x2000..=0x3EFF => self.vram[self.nametable_offset(addr)],
182 _ => 0,
183 }
184 }
185
186 fn ppu_write(&mut self, addr: u16, value: u8) {
187 let addr = addr & 0x3FFF;
188 match addr {
189 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
190 0x2000..=0x3EFF => {
191 let off = self.nametable_offset(addr);
192 self.vram[off] = value;
193 }
194 _ => {}
195 }
196 }
197
198 fn notify_cpu_cycle(&mut self) {
199 if !self.irq_enabled {
200 return;
201 }
202 if self.irq_mode_8bit {
203 let lo = (self.irq_counter & 0x00FF) as u8;
205 if lo == 0xFF {
206 self.irq_counter = (self.irq_counter & 0xFF00) | (self.irq_latch & 0x00FF);
207 self.irq_pending = true;
208 } else {
209 self.irq_counter = (self.irq_counter & 0xFF00) | ((lo as u16) + 1);
210 }
211 } else if self.irq_counter == 0xFFFF {
212 self.irq_counter = self.irq_latch;
213 self.irq_pending = true;
214 } else {
215 self.irq_counter = self.irq_counter.wrapping_add(1);
216 }
217 }
218
219 fn irq_pending(&self) -> bool {
220 self.irq_pending
221 }
222
223 fn current_mirroring(&self) -> Mirroring {
224 self.mirroring
225 }
226
227 fn debug_info(&self) -> crate::mapper::MapperDebugInfo {
228 let mut info = crate::mapper::MapperDebugInfo {
229 mapper_id: 73,
230 name: "Konami VRC3 (73)".into(),
231 mirroring: crate::mapper::mirroring_name(self.mirroring),
232 ..Default::default()
233 };
234 info.prg_banks
235 .push(("PRG".into(), format!("{:#04x}", self.prg_bank)));
236 info.irq_state
237 .push(("latch".into(), format!("{:#06x}", self.irq_latch)));
238 info.irq_state
239 .push(("counter".into(), format!("{:#06x}", self.irq_counter)));
240 info.irq_state
241 .push(("enabled".into(), format!("{}", self.irq_enabled)));
242 info.irq_state
243 .push(("8bit".into(), format!("{}", self.irq_mode_8bit)));
244 info.irq_state
245 .push(("pending".into(), format!("{}", self.irq_pending)));
246 info
247 }
248
249 fn save_state(&self) -> Vec<u8> {
250 let mut out =
251 Vec::with_capacity(12 + self.vram.len() + self.chr_ram.len() + self.prg_ram.len());
252 out.push(SAVE_STATE_VERSION);
253 out.push(self.prg_bank);
254 out.push(self.mirroring as u8);
255 out.extend_from_slice(&self.irq_latch.to_le_bytes());
256 out.extend_from_slice(&self.irq_counter.to_le_bytes());
257 out.push(u8::from(self.irq_enabled));
258 out.push(u8::from(self.irq_enable_after_ack));
259 out.push(u8::from(self.irq_mode_8bit));
260 out.push(u8::from(self.irq_pending));
261 out.push(0); out.extend_from_slice(&self.vram);
263 out.extend_from_slice(&self.chr_ram);
264 out.extend_from_slice(&self.prg_ram);
265 out
266 }
267
268 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
269 let expected = 12 + self.vram.len() + self.chr_ram.len() + self.prg_ram.len();
270 if data.len() != expected {
271 return Err(MapperError::WrongLength {
272 expected,
273 got: data.len(),
274 });
275 }
276 if data[0] != SAVE_STATE_VERSION {
277 return Err(MapperError::UnsupportedVersion(data[0]));
278 }
279 self.prg_bank = data[1];
280 self.mirroring = match data[2] {
281 0 => Mirroring::Horizontal,
282 1 => Mirroring::Vertical,
283 2 => Mirroring::SingleScreenA,
284 3 => Mirroring::SingleScreenB,
285 4 => Mirroring::FourScreen,
286 other => return Err(MapperError::Invalid(format!("mirroring {other}"))),
287 };
288 self.irq_latch = u16::from_le_bytes([data[3], data[4]]);
289 self.irq_counter = u16::from_le_bytes([data[5], data[6]]);
290 self.irq_enabled = data[7] != 0;
291 self.irq_enable_after_ack = data[8] != 0;
292 self.irq_mode_8bit = data[9] != 0;
293 self.irq_pending = data[10] != 0;
294 let mut cursor = 12;
296 self.vram
297 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
298 cursor += self.vram.len();
299 self.chr_ram
300 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
301 cursor += self.chr_ram.len();
302 self.prg_ram
303 .copy_from_slice(&data[cursor..cursor + self.prg_ram.len()]);
304 Ok(())
305 }
306}
307
308#[cfg(test)]
309#[allow(clippy::cast_possible_truncation)]
310mod tests {
311 use super::*;
312
313 fn synth_prg(banks_16k: usize) -> Box<[u8]> {
314 let mut v = vec![0u8; banks_16k * PRG_BANK_16K];
315 for b in 0..banks_16k {
316 v[b * PRG_BANK_16K] = b as u8;
317 }
318 v.into_boxed_slice()
319 }
320
321 #[test]
322 fn prg_bank_select_and_fixed_last() {
323 let mut m = Vrc3::new(synth_prg(8), Mirroring::Vertical).unwrap();
324 assert_eq!(m.cpu_read(0x8000), 0);
325 assert_eq!(m.cpu_read(0xC000), 7);
326 m.cpu_write(0xF000, 5);
327 assert_eq!(m.cpu_read(0x8000), 5);
328 assert_eq!(m.cpu_read(0xC000), 7);
329 }
330
331 #[test]
332 fn irq_latch_nibble_assembly() {
333 let mut m = Vrc3::new(synth_prg(8), Mirroring::Vertical).unwrap();
334 m.cpu_write(0x8000, 0x0A); m.cpu_write(0x9000, 0x0B); m.cpu_write(0xA000, 0x0C); m.cpu_write(0xB000, 0x0D); assert_eq!(m.irq_latch, 0xDCBA);
339 }
340
341 #[test]
342 fn irq_16bit_fires_after_reload_count() {
343 let mut m = Vrc3::new(synth_prg(8), Mirroring::Vertical).unwrap();
344 m.cpu_write(0x8000, 0x0E);
346 m.cpu_write(0x9000, 0x0F);
347 m.cpu_write(0xA000, 0x0F);
348 m.cpu_write(0xB000, 0x0F);
349 assert_eq!(m.irq_latch, 0xFFFE);
350 m.cpu_write(0xC000, 0x02);
352 assert_eq!(m.irq_counter, 0xFFFE);
353 m.notify_cpu_cycle(); assert!(!m.irq_pending());
355 m.notify_cpu_cycle(); assert!(m.irq_pending());
357 assert_eq!(m.irq_counter, 0xFFFE);
358 }
359
360 #[test]
361 fn irq_8bit_mode_only_low_byte() {
362 let mut m = Vrc3::new(synth_prg(8), Mirroring::Vertical).unwrap();
363 m.cpu_write(0x8000, 0x0E); m.cpu_write(0x9000, 0x0F); m.cpu_write(0xC000, 0x06);
367 assert_eq!(m.irq_counter & 0xFF, 0xFE);
368 m.notify_cpu_cycle(); assert!(!m.irq_pending());
370 m.notify_cpu_cycle(); assert!(m.irq_pending());
372 }
373
374 #[test]
375 fn irq_acknowledge_moves_a_into_e() {
376 let mut m = Vrc3::new(synth_prg(8), Mirroring::Vertical).unwrap();
377 m.cpu_write(0xC000, 0x03);
379 m.irq_pending = true;
381 m.cpu_write(0xD000, 0x00);
382 assert!(!m.irq_pending());
383 assert!(m.irq_enabled);
384 }
385
386 #[test]
387 fn disabled_counter_does_not_increment() {
388 let mut m = Vrc3::new(synth_prg(8), Mirroring::Vertical).unwrap();
389 for _ in 0..10_000 {
390 m.notify_cpu_cycle();
391 }
392 assert!(!m.irq_pending());
393 assert_eq!(m.irq_counter, 0);
394 }
395
396 #[test]
397 fn save_state_round_trip() {
398 let mut m = Vrc3::new(synth_prg(8), Mirroring::Vertical).unwrap();
399 m.cpu_write(0xF000, 3);
400 m.cpu_write(0x8000, 0x05);
401 m.cpu_write(0xC000, 0x02);
402 m.ppu_write(0x0040, 0x99);
403 let blob = m.save_state();
404 let mut m2 = Vrc3::new(synth_prg(8), Mirroring::Vertical).unwrap();
405 m2.load_state(&blob).unwrap();
406 assert_eq!(m.cpu_read(0x8000), m2.cpu_read(0x8000));
407 assert_eq!(m.ppu_read(0x0040), m2.ppu_read(0x0040));
408 assert_eq!(m.irq_counter, m2.irq_counter);
409 }
410}