rustynes_mappers/
m232_camerica_bf9096.rs1use crate::cartridge::Mirroring;
20use crate::mapper::{Mapper, MapperCaps, MapperError};
21use alloc::{boxed::Box, vec::Vec};
22use alloc::{format, vec};
23
24const PRG_BANK_16K: usize = 0x4000;
25const CHR_BANK_8K: usize = 0x2000;
26const NAMETABLE_SIZE: usize = 0x0400;
27const NAMETABLE_SIZE_U16: u16 = 0x0400;
28
29const SAVE_STATE_VERSION: u8 = 1;
30
31const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
32 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
33 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
34 let physical = mirroring.physical_bank(table);
35 physical * NAMETABLE_SIZE + local
36}
37
38pub struct Camerica232 {
40 prg_rom: Box<[u8]>,
41 chr: Box<[u8]>,
42 vram: Box<[u8]>,
43 outer_block: u8,
44 inner_page: u8,
45 mirroring: Mirroring,
46}
47
48impl Camerica232 {
49 pub fn new(
56 prg_rom: Box<[u8]>,
57 chr_rom: Box<[u8]>,
58 mirroring: Mirroring,
59 ) -> Result<Self, MapperError> {
60 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
61 return Err(MapperError::Invalid(format!(
62 "mapper 232 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
63 prg_rom.len()
64 )));
65 }
66 let chr: Box<[u8]> = if chr_rom.is_empty() {
67 vec![0u8; CHR_BANK_8K].into_boxed_slice()
68 } else if chr_rom.len() == CHR_BANK_8K {
69 chr_rom
71 } else {
72 return Err(MapperError::Invalid(format!(
73 "mapper 232 expects 8 KiB CHR (RAM or ROM); got {} bytes",
74 chr_rom.len()
75 )));
76 };
77 Ok(Self {
78 prg_rom,
79 chr,
80 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
81 outer_block: 0,
82 inner_page: 0,
83 mirroring,
84 })
85 }
86
87 fn map_16k(&self, page_in_block: u8) -> usize {
88 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
89 let bank = ((self.outer_block << 2) | (page_in_block & 0x03)) as usize;
90 bank % count
91 }
92}
93
94impl Mapper for Camerica232 {
95 fn caps(&self) -> MapperCaps {
96 MapperCaps::NONE
97 }
98
99 fn cpu_read(&mut self, addr: u16) -> u8 {
100 match addr {
101 0x8000..=0xBFFF => {
102 let bank = self.map_16k(self.inner_page);
103 self.prg_rom[bank * PRG_BANK_16K + (addr as usize - 0x8000)]
104 }
105 0xC000..=0xFFFF => {
106 let bank = self.map_16k(3);
107 self.prg_rom[bank * PRG_BANK_16K + (addr as usize - 0xC000)]
108 }
109 _ => 0,
110 }
111 }
112
113 fn cpu_write(&mut self, addr: u16, value: u8) {
114 match addr {
115 0x8000..=0xBFFF => self.outer_block = (value >> 3) & 0x03,
116 0xC000..=0xFFFF => self.inner_page = value & 0x03,
117 _ => {}
118 }
119 }
120
121 fn ppu_read(&mut self, addr: u16) -> u8 {
122 let addr = addr & 0x3FFF;
123 match addr {
124 0x0000..=0x1FFF => self.chr[addr as usize],
125 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
126 _ => 0,
127 }
128 }
129
130 fn ppu_write(&mut self, addr: u16, value: u8) {
131 let addr = addr & 0x3FFF;
132 match addr {
133 0x0000..=0x1FFF => {
134 self.chr[addr as usize] = value;
137 }
138 0x2000..=0x3EFF => {
139 let off = nametable_offset(addr, self.mirroring);
140 self.vram[off] = value;
141 }
142 _ => {}
143 }
144 }
145
146 fn current_mirroring(&self) -> Mirroring {
147 self.mirroring
148 }
149
150 fn save_state(&self) -> Vec<u8> {
151 let mut out = Vec::with_capacity(3 + self.vram.len() + self.chr.len());
152 out.push(SAVE_STATE_VERSION);
153 out.push(self.outer_block);
154 out.push(self.inner_page);
155 out.extend_from_slice(&self.vram);
156 out.extend_from_slice(&self.chr);
157 out
158 }
159
160 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
161 let expected = 3 + self.vram.len() + self.chr.len();
162 if data.len() != expected {
163 return Err(MapperError::WrongLength {
164 expected,
165 got: data.len(),
166 });
167 }
168 if data[0] != SAVE_STATE_VERSION {
169 return Err(MapperError::UnsupportedVersion(data[0]));
170 }
171 self.outer_block = data[1];
172 self.inner_page = data[2];
173 let mut cursor = 3;
174 self.vram
175 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
176 cursor += self.vram.len();
177 self.chr
178 .copy_from_slice(&data[cursor..cursor + self.chr.len()]);
179 Ok(())
180 }
181}
182
183#[cfg(test)]
184#[allow(clippy::cast_possible_truncation)]
185mod tests {
186 use super::*;
187
188 fn synth_prg_16k(banks: usize) -> Box<[u8]> {
189 let mut v = vec![0xFFu8; banks * PRG_BANK_16K];
190 for b in 0..banks {
191 v[b * PRG_BANK_16K] = b as u8;
192 }
193 v.into_boxed_slice()
194 }
195
196 #[test]
197 fn m232_two_level_banking() {
198 let mut m = Camerica232::new(synth_prg_16k(8), Box::new([]), Mirroring::Vertical).unwrap();
200 m.cpu_write(0x8000, 0b0000_1000);
202 m.cpu_write(0xC000, 0b0000_0010);
204 assert_eq!(m.cpu_read(0x8000), 6);
206 assert_eq!(m.cpu_read(0xC000), 7);
208 }
209}