1#![allow(
22 clippy::cast_possible_truncation,
23 clippy::cast_lossless,
24 clippy::match_same_arms,
25 clippy::doc_markdown,
26 clippy::similar_names,
27 clippy::too_many_lines,
28 clippy::missing_const_for_fn,
29 clippy::struct_excessive_bools,
30 clippy::bool_to_int_with_if
31)]
32
33use crate::cartridge::Mirroring;
34use crate::mapper::{Mapper, MapperCaps, MapperError};
35use alloc::{boxed::Box, format, vec, vec::Vec};
36
37const PRG_BANK_8K: usize = 0x2000;
38const CHR_BANK_8K: usize = 0x2000;
39const NAMETABLE_SIZE: usize = 0x0400;
40const NAMETABLE_SIZE_U16: u16 = 0x0400;
41
42const SAVE_STATE_VERSION: u8 = 1;
43
44const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
49 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
50 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
51 let physical = mirroring.physical_bank(table);
52 physical * NAMETABLE_SIZE + local
53}
54
55pub struct Mapper50 {
75 prg_rom: Box<[u8]>,
76 chr_ram: Box<[u8]>,
77 vram: Box<[u8]>,
78 switch_bank: u8,
79 irq_enabled: bool,
80 irq_counter: u16,
81 irq_pending: bool,
82 mirroring: Mirroring,
83}
84
85impl Mapper50 {
86 pub fn new(
91 prg_rom: Box<[u8]>,
92 _chr_rom: &[u8],
93 mirroring: Mirroring,
94 ) -> Result<Self, MapperError> {
95 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
96 return Err(MapperError::Invalid(format!(
97 "mapper 50 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
98 prg_rom.len()
99 )));
100 }
101 Ok(Self {
102 prg_rom,
103 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
104 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
105 switch_bank: 0,
106 irq_enabled: false,
107 irq_counter: 0,
108 irq_pending: false,
109 mirroring,
110 })
111 }
112
113 fn prg_8k(&self, bank: usize, addr: u16) -> u8 {
114 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
115 let bank = bank % count;
116 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
117 }
118}
119
120impl Mapper for Mapper50 {
121 fn caps(&self) -> MapperCaps {
122 MapperCaps::CYCLE_IRQ
123 }
124
125 fn cpu_read(&mut self, addr: u16) -> u8 {
126 match addr {
127 0x6000..=0x7FFF => self.prg_8k(15, addr),
128 0x8000..=0x9FFF => self.prg_8k(8, addr),
129 0xA000..=0xBFFF => self.prg_8k(9, addr),
130 0xC000..=0xDFFF => self.prg_8k(self.switch_bank as usize, addr),
131 0xE000..=0xFFFF => self.prg_8k(11, addr),
132 _ => 0,
133 }
134 }
135
136 fn cpu_read_unmapped(&self, addr: u16) -> bool {
137 (0x4020..=0x5FFF).contains(&addr)
138 }
139
140 fn cpu_write(&mut self, addr: u16, value: u8) {
141 match addr & 0x4120 {
142 0x4020 => {
143 self.switch_bank = (value & 0x08) | ((value & 0x01) << 2) | ((value & 0x06) >> 1);
144 }
145 0x4120 => {
146 self.irq_enabled = value & 0x01 != 0;
152 self.irq_pending = false;
153 self.irq_counter = 0;
154 }
155 _ => {}
156 }
157 }
158
159 fn ppu_read(&mut self, addr: u16) -> u8 {
160 let addr = addr & 0x3FFF;
161 match addr {
162 0x0000..=0x1FFF => self.chr_ram[addr as usize],
163 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
164 _ => 0,
165 }
166 }
167
168 fn ppu_write(&mut self, addr: u16, value: u8) {
169 let addr = addr & 0x3FFF;
170 match addr {
171 0x0000..=0x1FFF => self.chr_ram[addr as usize] = value,
172 0x2000..=0x3EFF => {
173 let off = nametable_offset(addr, self.mirroring);
174 self.vram[off] = value;
175 }
176 _ => {}
177 }
178 }
179
180 fn notify_cpu_cycle(&mut self) {
181 if !self.irq_enabled {
182 return;
183 }
184 self.irq_counter += 1;
185 if self.irq_counter == 0x1000 {
186 self.irq_pending = true;
187 self.irq_enabled = false;
188 }
189 }
190
191 fn irq_pending(&self) -> bool {
192 self.irq_pending
193 }
194
195 fn irq_acknowledge(&mut self) {
196 self.irq_pending = false;
197 }
198
199 fn current_mirroring(&self) -> Mirroring {
200 self.mirroring
201 }
202
203 fn save_state(&self) -> Vec<u8> {
204 let mut out = Vec::with_capacity(6 + self.vram.len() + self.chr_ram.len());
205 out.push(SAVE_STATE_VERSION);
206 out.push(self.switch_bank);
207 out.push(u8::from(self.irq_enabled));
208 out.push((self.irq_counter & 0xFF) as u8);
209 out.push((self.irq_counter >> 8) as u8);
210 out.push(u8::from(self.irq_pending));
211 out.extend_from_slice(&self.vram);
212 out.extend_from_slice(&self.chr_ram);
213 out
214 }
215
216 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
217 let expected = 6 + self.vram.len() + self.chr_ram.len();
218 if data.len() != expected {
219 return Err(MapperError::WrongLength {
220 expected,
221 got: data.len(),
222 });
223 }
224 if data[0] != SAVE_STATE_VERSION {
225 return Err(MapperError::UnsupportedVersion(data[0]));
226 }
227 self.switch_bank = data[1];
228 self.irq_enabled = data[2] != 0;
229 self.irq_counter = u16::from(data[3]) | (u16::from(data[4]) << 8);
230 self.irq_pending = data[5] != 0;
231 let mut cursor = 6;
232 self.vram
233 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
234 cursor += self.vram.len();
235 self.chr_ram
236 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
237 Ok(())
238 }
239}
240
241#[cfg(test)]
247mod tests {
248 use super::*;
249
250 fn synth_prg_8k(banks: usize) -> Box<[u8]> {
251 let mut v = vec![0xFFu8; banks * PRG_BANK_8K];
252 for b in 0..banks {
253 v[b * PRG_BANK_8K] = b as u8;
254 }
255 v.into_boxed_slice()
256 }
257
258 #[test]
259 fn m50_fixed_layout_and_scrambled_switch() {
260 let mut m = Mapper50::new(synth_prg_8k(16), &[], Mirroring::Vertical).unwrap();
261 assert_eq!(m.cpu_read(0x6000), 15);
262 assert_eq!(m.cpu_read(0x8000), 8);
263 assert_eq!(m.cpu_read(0xA000), 9);
264 assert_eq!(m.cpu_read(0xE000), 11);
265 m.cpu_write(0x4020, 0x05);
267 assert_eq!(m.cpu_read(0xC000), 6);
268 }
269
270 #[test]
271 fn m50_irq_fires_once_then_disables() {
272 let mut m = Mapper50::new(synth_prg_8k(16), &[], Mirroring::Vertical).unwrap();
273 m.cpu_write(0x4120, 0x01); for _ in 0..0x1000 {
275 m.notify_cpu_cycle();
276 }
277 assert!(m.irq_pending());
278 m.cpu_write(0x4120, 0x00); assert!(!m.irq_pending());
280 }
281
282 #[test]
283 fn m50_save_state_round_trip() {
284 let mut m = Mapper50::new(synth_prg_8k(16), &[], Mirroring::Vertical).unwrap();
285 m.cpu_write(0x4020, 0x05);
286 m.cpu_write(0x4120, 0x01);
287 m.notify_cpu_cycle();
288 m.ppu_write(0x0007, 0x33);
289 let blob = m.save_state();
290 let mut m2 = Mapper50::new(synth_prg_8k(16), &[], Mirroring::Vertical).unwrap();
291 m2.load_state(&blob).unwrap();
292 assert_eq!(m2.cpu_read(0xC000), 6);
293 assert_eq!(m2.ppu_read(0x0007), 0x33);
294 }
295}