1use crate::a12_filter::A12RiseFilter;
24use crate::cartridge::Mirroring;
25use crate::mapper::{Mapper, MapperCaps, MapperError};
26use alloc::{boxed::Box, vec::Vec};
27use alloc::{format, vec};
28
29const PRG_BANK_8K: usize = 0x2000;
30const CHR_BANK_1K: usize = 0x0400;
31const NAMETABLE_SIZE: usize = 0x0400;
32const NAMETABLE_SIZE_U16: u16 = 0x0400;
33
34const SAVE_STATE_VERSION: u8 = 2;
38
39const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
44 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
45 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
46 let physical = mirroring.physical_bank(table);
47 physical * NAMETABLE_SIZE + local
48}
49
50#[allow(clippy::struct_excessive_bools)]
54pub struct Nitra250 {
55 prg_rom: Box<[u8]>,
56 chr_rom: Box<[u8]>,
57 vram: Box<[u8]>,
58 reg_index: u8,
59 bank_regs: [u8; 8],
60 prg_mode: bool,
61 chr_mode: bool,
62 horizontal_mirroring: bool,
63 irq_latch: u8,
64 irq_counter: u8,
65 irq_reload: bool,
66 irq_enabled: bool,
67 irq_pending: bool,
68 a12: A12RiseFilter,
70}
71
72impl Nitra250 {
73 pub fn new(
80 prg_rom: Box<[u8]>,
81 chr_rom: Box<[u8]>,
82 mirroring: Mirroring,
83 ) -> Result<Self, MapperError> {
84 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
85 return Err(MapperError::Invalid(format!(
86 "mapper 250 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
87 prg_rom.len()
88 )));
89 }
90 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_1K) {
91 return Err(MapperError::Invalid(format!(
92 "mapper 250 CHR-ROM size {} is not a non-zero multiple of 1 KiB",
93 chr_rom.len()
94 )));
95 }
96 Ok(Self {
97 prg_rom,
98 chr_rom,
99 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
100 reg_index: 0,
101 bank_regs: [0; 8],
102 prg_mode: false,
103 chr_mode: false,
104 horizontal_mirroring: mirroring == Mirroring::Horizontal,
105 irq_latch: 0,
106 irq_counter: 0,
107 irq_reload: false,
108 irq_enabled: false,
109 irq_pending: false,
110 a12: A12RiseFilter::new(),
111 })
112 }
113
114 fn read_prg(&self, bank: usize, addr: u16) -> u8 {
115 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
116 let bank = bank % count;
117 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
118 }
119
120 fn prg_bank_for(&self, addr: u16) -> usize {
121 let last = (self.prg_rom.len() / PRG_BANK_8K).max(1) - 1;
122 let second_last = last.saturating_sub(1);
128 let r6 = self.bank_regs[6] as usize;
129 let r7 = self.bank_regs[7] as usize;
130 match (self.prg_mode, addr) {
131 (false, 0x8000..=0x9FFF) | (true, 0xC000..=0xDFFF) => r6,
132 (false, 0xC000..=0xDFFF) | (true, 0x8000..=0x9FFF) => second_last,
133 (_, 0xA000..=0xBFFF) => r7,
134 _ => last,
135 }
136 }
137
138 fn read_chr(&self, addr: u16) -> u8 {
139 let count1k = (self.chr_rom.len() / CHR_BANK_1K).max(1);
140 let region = (addr >> 10) & 0x07;
142 let region = if self.chr_mode { region ^ 0x04 } else { region };
143 let bank1k = match region {
144 0 => self.bank_regs[0] as usize & !1,
145 1 => (self.bank_regs[0] as usize & !1) + 1,
146 2 => self.bank_regs[1] as usize & !1,
147 3 => (self.bank_regs[1] as usize & !1) + 1,
148 4 => self.bank_regs[2] as usize,
149 5 => self.bank_regs[3] as usize,
150 6 => self.bank_regs[4] as usize,
151 _ => self.bank_regs[5] as usize,
152 };
153 let bank = bank1k % count1k;
154 self.chr_rom[bank * CHR_BANK_1K + (addr as usize & 0x03FF)]
155 }
156}
157
158impl Mapper for Nitra250 {
159 fn caps(&self) -> MapperCaps {
160 MapperCaps::CYCLE_IRQ
161 }
162
163 fn cpu_read(&mut self, addr: u16) -> u8 {
164 if (0x8000..=0xFFFF).contains(&addr) {
165 let bank = self.prg_bank_for(addr);
166 self.read_prg(bank, addr)
167 } else {
168 0
169 }
170 }
171
172 fn cpu_write(&mut self, addr: u16, _value: u8) {
173 let value = (addr & 0x00FF) as u8;
179 let odd = (addr & 0x0400) != 0;
180 match addr & 0xE000 {
181 0x8000 => {
182 if odd {
183 self.bank_regs[self.reg_index as usize] = value;
184 } else {
185 self.reg_index = value & 0x07;
186 self.prg_mode = (value & 0x40) != 0;
187 self.chr_mode = (value & 0x80) != 0;
188 }
189 }
190 0xA000 => {
191 if !odd {
192 self.horizontal_mirroring = (value & 0x01) != 0;
193 }
194 }
195 0xC000 => {
196 if odd {
197 self.irq_reload = true;
198 } else {
199 self.irq_latch = value;
200 }
201 }
202 0xE000 => {
203 if odd {
204 self.irq_enabled = true;
205 } else {
206 self.irq_enabled = false;
207 self.irq_pending = false;
208 }
209 }
210 _ => {}
211 }
212 }
213
214 fn ppu_read(&mut self, addr: u16) -> u8 {
215 let addr = addr & 0x3FFF;
216 match addr {
217 0x0000..=0x1FFF => self.read_chr(addr),
218 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
219 _ => 0,
220 }
221 }
222
223 fn ppu_write(&mut self, addr: u16, value: u8) {
224 let addr = addr & 0x3FFF;
225 if (0x2000..=0x3EFF).contains(&addr) {
226 let off = nametable_offset(addr, self.current_mirroring());
227 self.vram[off] = value;
228 }
229 }
230
231 fn notify_cpu_cycle(&mut self) {
232 self.a12.tick();
233 }
234
235 fn notify_a12(&mut self, level: bool) {
244 if !self.a12.edge(level) {
245 return;
246 }
247 if self.irq_counter == 0 || self.irq_reload {
248 self.irq_counter = self.irq_latch;
249 self.irq_reload = false;
250 } else {
251 self.irq_counter -= 1;
252 }
253 if self.irq_counter == 0 && self.irq_enabled {
254 self.irq_pending = true;
255 }
256 }
257
258 fn irq_pending(&self) -> bool {
259 self.irq_pending
260 }
261
262 fn irq_acknowledge(&mut self) {
263 self.irq_pending = false;
264 }
265
266 fn current_mirroring(&self) -> Mirroring {
267 if self.horizontal_mirroring {
268 Mirroring::Horizontal
269 } else {
270 Mirroring::Vertical
271 }
272 }
273
274 fn save_state(&self) -> Vec<u8> {
275 let mut out = Vec::with_capacity(19 + self.vram.len());
276 out.push(SAVE_STATE_VERSION);
277 out.push(self.reg_index);
278 out.extend_from_slice(&self.bank_regs);
279 out.push(u8::from(self.prg_mode));
280 out.push(u8::from(self.chr_mode));
281 out.push(u8::from(self.horizontal_mirroring));
282 out.push(self.irq_latch);
283 out.push(self.irq_counter);
284 out.push(u8::from(self.irq_reload));
285 out.push(u8::from(self.irq_enabled));
286 out.push(u8::from(self.irq_pending));
287 out.push(self.a12.to_byte());
288 out.extend_from_slice(&self.vram);
289 out
290 }
291
292 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
293 match data.first() {
297 None => {
298 return Err(MapperError::WrongLength {
299 expected: 1,
300 got: 0,
301 });
302 }
303 Some(&v) if v != SAVE_STATE_VERSION => {
304 return Err(MapperError::UnsupportedVersion(v));
305 }
306 Some(_) => {}
307 }
308 let expected = 19 + self.vram.len();
309 if data.len() != expected {
310 return Err(MapperError::WrongLength {
311 expected,
312 got: data.len(),
313 });
314 }
315 self.reg_index = data[1] & 0x07;
316 self.bank_regs.copy_from_slice(&data[2..10]);
317 self.prg_mode = data[10] != 0;
318 self.chr_mode = data[11] != 0;
319 self.horizontal_mirroring = data[12] != 0;
320 self.irq_latch = data[13];
321 self.irq_counter = data[14];
322 self.irq_reload = data[15] != 0;
323 self.irq_enabled = data[16] != 0;
324 self.irq_pending = data[17] != 0;
325 self.a12 = A12RiseFilter::from_byte(data[18]);
326 self.vram.copy_from_slice(&data[19..19 + self.vram.len()]);
327 Ok(())
328 }
329}
330
331#[cfg(test)]
332#[allow(clippy::cast_possible_truncation)]
333mod tests {
334 use super::*;
335
336 fn synth_prg_8k(banks: usize) -> Box<[u8]> {
337 let mut v = vec![0xFFu8; banks * PRG_BANK_8K];
338 for b in 0..banks {
339 v[b * PRG_BANK_8K] = b as u8;
340 }
341 v.into_boxed_slice()
342 }
343
344 fn synth_chr_1k(banks: usize) -> Box<[u8]> {
345 let mut v = vec![0u8; banks * CHR_BANK_1K];
346 for b in 0..banks {
347 v[b * CHR_BANK_1K] = b as u8;
348 }
349 v.into_boxed_slice()
350 }
351
352 #[test]
353 fn m250_address_encoded_mmc3_banking() {
354 let mut m = Nitra250::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
355 m.cpu_write(0x8000 | 0x06, 0);
358 m.cpu_write(0x8000 | 0x400 | 0x03, 0);
360 assert_eq!(m.cpu_read(0x8000), 3);
361 m.cpu_write(0xA000 | 0x01, 0);
363 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
364 }
365
366 #[test]
375 fn m250_irq_is_the_mmc3_scanline_counter() {
376 let mut m = Nitra250::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
377 m.cpu_write(0xC000 | 0x07, 0);
378 m.cpu_write(0xC000 | 0x400, 0);
379 m.cpu_write(0xE000 | 0x400, 0);
380 let mut irqs = 0;
381 for _ in 0..16 {
382 for _ in 0..85 {
383 m.notify_cpu_cycle();
384 }
385 for _ in 0..8 {
386 m.notify_a12(true);
387 m.notify_cpu_cycle();
388 m.notify_a12(false);
389 m.notify_cpu_cycle();
390 }
391 if m.irq_pending() {
392 irqs += 1;
393 m.irq_acknowledge();
394 }
395 }
396 assert_eq!(irqs, 2, "once per eight scanlines, as an MMC3");
397 }
398
399 #[test]
400 fn m250_save_state_round_trip() {
401 let mut m = Nitra250::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
402 m.cpu_write(0x8000 | 0x06, 0);
403 m.cpu_write(0x8000 | 0x400 | 0x02, 0);
404 m.cpu_write(0xC000 | 0x05, 0);
405 m.cpu_write(0xC000 | 0x400, 0);
406 m.cpu_write(0xE000 | 0x400, 0);
407 m.notify_a12(true);
408 m.notify_cpu_cycle();
409 let blob = m.save_state();
410 let mut m2 = Nitra250::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
411 m2.load_state(&blob).unwrap();
412 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
413 }
414
415 #[test]
419 fn m250_v1_state_is_refused_by_version() {
420 let mut m = Nitra250::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
421 let mut v1 = m.save_state();
422 v1[0] = 1;
423 v1.remove(18);
424 assert!(matches!(
425 m.load_state(&v1),
426 Err(MapperError::UnsupportedVersion(1))
427 ));
428 assert!(matches!(
429 m.load_state(&[]),
430 Err(MapperError::WrongLength { .. })
431 ));
432 }
433}