rustynes_mappers/
m013_cprom.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_lossless,
13 clippy::missing_const_for_fn,
14 clippy::needless_pass_by_ref_mut,
15 clippy::manual_range_patterns,
16 clippy::match_same_arms,
17 clippy::too_many_arguments
18)]
19
20use crate::cartridge::Mirroring;
21use crate::mapper::{Mapper, MapperCaps, MapperError};
22use alloc::{boxed::Box, vec::Vec};
23use alloc::{format, vec};
24
25const CHR_BANK_4K: usize = 0x1000;
26const NAMETABLE_SIZE: usize = 0x0400;
27const NAMETABLE_SIZE_U16: u16 = 0x0400;
28
29fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
30 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
31 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
32 let physical = mirroring.physical_bank(table);
33 physical * NAMETABLE_SIZE + local
34}
35
36pub struct Cprom {
38 prg_rom: Box<[u8]>,
39 chr_ram: Box<[u8]>, vram: Box<[u8]>,
41 chr_bank: u8,
42 mirroring: Mirroring,
43}
44
45impl Cprom {
46 pub fn new(prg_rom: Box<[u8]>, mirroring: Mirroring) -> Result<Self, MapperError> {
52 if prg_rom.len() != 32 * 1024 {
53 return Err(MapperError::Invalid(format!(
54 "CPROM expects 32 KiB PRG, got {} bytes",
55 prg_rom.len()
56 )));
57 }
58 Ok(Self {
59 prg_rom,
60 chr_ram: vec![0u8; 16 * 1024].into_boxed_slice(),
61 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
62 chr_bank: 0,
63 mirroring,
64 })
65 }
66}
67
68impl Mapper for Cprom {
69 fn has_hardwired_mirroring(&self) -> bool {
72 true
73 }
74
75 fn caps(&self) -> MapperCaps {
78 MapperCaps::NONE
79 }
80
81 fn cpu_read(&mut self, addr: u16) -> u8 {
82 if addr < 0x8000 {
83 return 0;
84 }
85 self.prg_rom[(addr as usize - 0x8000) % self.prg_rom.len()]
86 }
87
88 fn cpu_write(&mut self, addr: u16, value: u8) {
89 if addr >= 0x8000 {
90 self.chr_bank = value & 0x03;
91 }
92 }
93
94 fn ppu_read(&mut self, addr: u16) -> u8 {
95 let addr = addr & 0x3FFF;
96 match addr {
97 0x0000..=0x0FFF => self.chr_ram[addr as usize],
98 0x1000..=0x1FFF => {
99 let bank = (self.chr_bank as usize) & 0x03;
100 let off = bank * CHR_BANK_4K + (addr as usize - 0x1000);
101 self.chr_ram[off % self.chr_ram.len()]
102 }
103 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring) % self.vram.len()],
104 _ => 0,
105 }
106 }
107
108 fn ppu_write(&mut self, addr: u16, value: u8) {
109 let addr = addr & 0x3FFF;
110 match addr {
111 0x0000..=0x0FFF => self.chr_ram[addr as usize] = value,
112 0x1000..=0x1FFF => {
113 let bank = (self.chr_bank as usize) & 0x03;
114 let off = (bank * CHR_BANK_4K + (addr as usize - 0x1000)) % self.chr_ram.len();
115 self.chr_ram[off] = value;
116 }
117 0x2000..=0x3EFF => {
118 let off = nametable_offset(addr, self.mirroring) % self.vram.len();
119 self.vram[off] = value;
120 }
121 _ => {}
122 }
123 }
124
125 fn current_mirroring(&self) -> Mirroring {
126 self.mirroring
127 }
128
129 fn save_state(&self) -> Vec<u8> {
130 let mut out = Vec::with_capacity(2 + self.chr_ram.len() + self.vram.len());
131 out.push(1u8);
132 out.push(self.chr_bank);
133 out.extend_from_slice(&self.chr_ram);
134 out.extend_from_slice(&self.vram);
135 out
136 }
137
138 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
139 let expected = 2 + self.chr_ram.len() + self.vram.len();
140 if data.len() != expected {
141 return Err(MapperError::WrongLength {
142 expected,
143 got: data.len(),
144 });
145 }
146 if data[0] != 1 {
147 return Err(MapperError::UnsupportedVersion(data[0]));
148 }
149 self.chr_bank = data[1];
150 self.chr_ram
151 .copy_from_slice(&data[2..2 + self.chr_ram.len()]);
152 let off = 2 + self.chr_ram.len();
153 self.vram.copy_from_slice(&data[off..off + self.vram.len()]);
154 Ok(())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn cprom_chr_bank_select() {
164 let mut m =
165 Cprom::new(vec![0u8; 32 * 1024].into_boxed_slice(), Mirroring::Vertical).unwrap();
166 m.ppu_write(0x1000, 0xAA); m.cpu_write(0x8000, 1);
168 m.ppu_write(0x1000, 0xBB); m.cpu_write(0x8000, 0);
170 assert_eq!(m.ppu_read(0x1000), 0xAA);
171 m.cpu_write(0x8000, 1);
172 assert_eq!(m.ppu_read(0x1000), 0xBB);
173 }
174}