1#![allow(
25 clippy::cast_possible_truncation,
26 clippy::cast_lossless,
27 clippy::match_same_arms,
28 clippy::doc_markdown,
29 clippy::similar_names,
30 clippy::too_many_lines,
31 clippy::missing_const_for_fn,
32 clippy::struct_excessive_bools,
33 clippy::bool_to_int_with_if
34)]
35
36use crate::cartridge::Mirroring;
37use crate::mapper::{Mapper, MapperCaps, MapperError};
38use alloc::{boxed::Box, format, vec, vec::Vec};
39
40const PRG_BANK_8K: usize = 0x2000;
41const CHR_BANK_8K: usize = 0x2000;
42const NAMETABLE_SIZE: usize = 0x0400;
43const NAMETABLE_SIZE_U16: u16 = 0x0400;
44
45const SAVE_STATE_VERSION: u8 = 1;
46
47const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
52 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
53 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
54 let physical = mirroring.physical_bank(table);
55 physical * NAMETABLE_SIZE + local
56}
57
58const fn mirroring_to_byte(m: Mirroring) -> u8 {
59 match m {
60 Mirroring::Horizontal => 0,
61 Mirroring::Vertical => 1,
62 Mirroring::SingleScreenA => 2,
63 Mirroring::SingleScreenB => 3,
64 Mirroring::FourScreen => 4,
65 Mirroring::MapperControlled => 5,
66 }
67}
68
69const fn byte_to_mirroring(b: u8, fallback: Mirroring) -> Mirroring {
70 match b {
71 0 => Mirroring::Horizontal,
72 1 => Mirroring::Vertical,
73 2 => Mirroring::SingleScreenA,
74 3 => Mirroring::SingleScreenB,
75 4 => Mirroring::FourScreen,
76 5 => Mirroring::MapperControlled,
77 _ => fallback,
78 }
79}
80
81pub struct Mapper42 {
101 prg_rom: Box<[u8]>,
102 chr: Box<[u8]>,
103 chr_is_ram: bool,
104 vram: Box<[u8]>,
105 prg_ram_bank: u8,
106 chr_bank: u8,
107 mirroring: Mirroring,
108 irq_counter: u16,
109 irq_enabled: bool,
110 irq_pending: bool,
111}
112
113impl Mapper42 {
114 pub fn new(
119 prg_rom: Box<[u8]>,
120 chr_rom: Box<[u8]>,
121 mirroring: Mirroring,
122 ) -> Result<Self, MapperError> {
123 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
124 return Err(MapperError::Invalid(format!(
125 "mapper 42 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
126 prg_rom.len()
127 )));
128 }
129 let chr_is_ram = chr_rom.is_empty();
130 let chr: Box<[u8]> = if chr_is_ram {
131 vec![0u8; CHR_BANK_8K].into_boxed_slice()
132 } else {
133 chr_rom
134 };
135 Ok(Self {
136 prg_rom,
137 chr,
138 chr_is_ram,
139 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
140 prg_ram_bank: 0,
141 chr_bank: 0,
142 mirroring,
143 irq_counter: 0,
144 irq_enabled: false,
145 irq_pending: false,
146 })
147 }
148
149 fn prg_8k(&self, bank: usize, addr: u16) -> u8 {
150 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
151 let bank = bank % count;
152 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
153 }
154}
155
156impl Mapper for Mapper42 {
157 fn caps(&self) -> MapperCaps {
158 MapperCaps::CYCLE_IRQ
159 }
160
161 fn cpu_read(&mut self, addr: u16) -> u8 {
162 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
163 match addr {
164 0x6000..=0x7FFF => self.prg_8k(self.prg_ram_bank as usize, addr),
165 0x8000..=0x9FFF => self.prg_8k(count.saturating_sub(4), addr),
166 0xA000..=0xBFFF => self.prg_8k(count.saturating_sub(3), addr),
167 0xC000..=0xDFFF => self.prg_8k(count.saturating_sub(2), addr),
168 0xE000..=0xFFFF => self.prg_8k(count.saturating_sub(1), addr),
169 _ => 0,
170 }
171 }
172
173 fn cpu_read_unmapped(&self, addr: u16) -> bool {
174 (0x4020..=0x5FFF).contains(&addr)
175 }
176
177 fn cpu_write(&mut self, addr: u16, value: u8) {
178 match addr & 0xE003 {
179 0x8000 => self.chr_bank = value & 0x0F,
180 0xE000 => self.prg_ram_bank = value & 0x0F,
181 0xE001 => {
182 self.mirroring = if value & 0x08 != 0 {
183 Mirroring::Horizontal
184 } else {
185 Mirroring::Vertical
186 };
187 }
188 0xE002 => {
189 self.irq_enabled = value & 0x02 != 0;
190 if !self.irq_enabled {
191 self.irq_pending = false;
192 self.irq_counter = 0;
193 }
194 }
195 _ => {}
196 }
197 }
198
199 fn ppu_read(&mut self, addr: u16) -> u8 {
200 let addr = addr & 0x3FFF;
201 match addr {
202 0x0000..=0x1FFF => {
203 if self.chr_is_ram {
204 return self.chr[addr as usize & (self.chr.len() - 1)];
205 }
206 let count = (self.chr.len() / CHR_BANK_8K).max(1);
207 let bank = (self.chr_bank as usize) % count;
208 self.chr[bank * CHR_BANK_8K + (addr as usize & 0x1FFF)]
209 }
210 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
211 _ => 0,
212 }
213 }
214
215 fn ppu_write(&mut self, addr: u16, value: u8) {
216 let addr = addr & 0x3FFF;
217 match addr {
218 0x0000..=0x1FFF if self.chr_is_ram => {
219 let off = addr as usize & (self.chr.len() - 1);
220 self.chr[off] = value;
221 }
222 0x2000..=0x3EFF => {
223 let off = nametable_offset(addr, self.mirroring);
224 self.vram[off] = value;
225 }
226 _ => {}
227 }
228 }
229
230 fn notify_cpu_cycle(&mut self) {
231 if !self.irq_enabled {
232 return;
233 }
234 self.irq_counter += 1;
235 if self.irq_counter >= 0x8000 {
236 self.irq_counter -= 0x8000;
237 }
238 self.irq_pending = self.irq_counter >= 0x6000;
239 }
240
241 fn irq_pending(&self) -> bool {
242 self.irq_pending
243 }
244
245 fn irq_acknowledge(&mut self) {
246 self.irq_pending = false;
247 }
248
249 fn current_mirroring(&self) -> Mirroring {
250 self.mirroring
251 }
252
253 fn save_state(&self) -> Vec<u8> {
254 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
255 let mut out = Vec::with_capacity(8 + self.vram.len() + chr_ram);
256 out.push(SAVE_STATE_VERSION);
257 out.push(self.prg_ram_bank);
258 out.push(self.chr_bank);
259 out.push(mirroring_to_byte(self.mirroring));
260 out.push((self.irq_counter & 0xFF) as u8);
261 out.push((self.irq_counter >> 8) as u8);
262 out.push(u8::from(self.irq_enabled));
263 out.push(u8::from(self.irq_pending));
264 out.extend_from_slice(&self.vram);
265 if self.chr_is_ram {
266 out.extend_from_slice(&self.chr);
267 }
268 out
269 }
270
271 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
272 let chr_ram = if self.chr_is_ram { self.chr.len() } else { 0 };
273 let expected = 8 + self.vram.len() + chr_ram;
274 if data.len() != expected {
275 return Err(MapperError::WrongLength {
276 expected,
277 got: data.len(),
278 });
279 }
280 if data[0] != SAVE_STATE_VERSION {
281 return Err(MapperError::UnsupportedVersion(data[0]));
282 }
283 self.prg_ram_bank = data[1];
284 self.chr_bank = data[2];
285 self.mirroring = byte_to_mirroring(data[3], self.mirroring);
286 self.irq_counter = u16::from(data[4]) | (u16::from(data[5]) << 8);
287 self.irq_enabled = data[6] != 0;
288 self.irq_pending = data[7] != 0;
289 let mut cursor = 8;
290 self.vram
291 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
292 cursor += self.vram.len();
293 if self.chr_is_ram {
294 self.chr
295 .copy_from_slice(&data[cursor..cursor + self.chr.len()]);
296 }
297 Ok(())
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 fn synth_prg_8k(banks: usize) -> Box<[u8]> {
306 let mut v = vec![0xFFu8; banks * PRG_BANK_8K];
307 for b in 0..banks {
308 v[b * PRG_BANK_8K] = b as u8;
309 }
310 v.into_boxed_slice()
311 }
312
313 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
314 let mut v = vec![0u8; banks * CHR_BANK_8K];
315 for b in 0..banks {
316 v[b * CHR_BANK_8K] = b as u8;
317 }
318 v.into_boxed_slice()
319 }
320
321 #[test]
322 fn m42_fixed_tail_and_switchable_window() {
323 let mut m = Mapper42::new(synth_prg_8k(8), synth_chr_8k(2), Mirroring::Vertical).unwrap();
324 assert_eq!(m.cpu_read(0x8000), 4);
325 assert_eq!(m.cpu_read(0xA000), 5);
326 assert_eq!(m.cpu_read(0xC000), 6);
327 assert_eq!(m.cpu_read(0xE000), 7);
328 m.cpu_write(0xE000, 3);
329 assert_eq!(m.cpu_read(0x6000), 3);
330 m.cpu_write(0x8000, 1);
331 assert_eq!(m.ppu_read(0x0000), 1);
332 }
333
334 #[test]
335 fn m42_irq_window() {
336 let mut m = Mapper42::new(synth_prg_8k(8), synth_chr_8k(1), Mirroring::Vertical).unwrap();
337 m.cpu_write(0xE002, 0x02); let mut fired = false;
339 for _ in 0..0x8000 {
340 m.notify_cpu_cycle();
341 if m.irq_pending() {
342 fired = true;
343 break;
344 }
345 }
346 assert!(fired);
347 m.cpu_write(0xE002, 0x00); assert!(!m.irq_pending());
349 }
350
351 #[test]
352 fn m42_save_state_round_trip() {
353 let mut m = Mapper42::new(synth_prg_8k(8), synth_chr_8k(2), Mirroring::Vertical).unwrap();
354 m.cpu_write(0xE000, 2);
355 m.cpu_write(0x8000, 1);
356 m.cpu_write(0xE002, 0x02);
357 m.notify_cpu_cycle();
358 let blob = m.save_state();
359 let mut m2 = Mapper42::new(synth_prg_8k(8), synth_chr_8k(2), Mirroring::Vertical).unwrap();
360 m2.load_state(&blob).unwrap();
361 assert_eq!(m2.cpu_read(0x6000), 2);
362 assert_eq!(m2.ppu_read(0x0000), 1);
363 }
364}