1#![allow(
20 clippy::bool_to_int_with_if,
21 clippy::cast_lossless,
22 clippy::cast_possible_truncation,
23 clippy::doc_markdown,
24 clippy::match_same_arms,
25 clippy::missing_const_for_fn,
26 clippy::similar_names,
27 clippy::struct_excessive_bools,
28 clippy::too_many_lines,
29 clippy::unreadable_literal
30)]
31
32use crate::cartridge::Mirroring;
33use crate::mapper::{Mapper, MapperCaps, MapperError};
34use alloc::{boxed::Box, vec::Vec};
35use alloc::{format, vec};
36
37const PRG_BANK_8K: usize = 0x2000;
38const NAMETABLE_SIZE: usize = 0x0400;
39const NAMETABLE_SIZE_U16: u16 = 0x0400;
40
41const SAVE_STATE_VERSION: u8 = 1;
42
43const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
48 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
49 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
50 let physical = mirroring.physical_bank(table);
51 physical * NAMETABLE_SIZE + local
52}
53
54const CHR_BANK_1K: usize = 0x0400;
55
56pub struct Namcot3425M95 {
58 prg_rom: Box<[u8]>,
59 chr_rom: Box<[u8]>,
60 vram: Box<[u8]>,
61 reg_index: u8,
62 prg_banks: [u8; 2],
63 chr_regs: [u8; 6],
65 one_screen_b: bool,
66}
67
68impl Namcot3425M95 {
69 pub fn new(
76 prg_rom: Box<[u8]>,
77 chr_rom: Box<[u8]>,
78 _mirroring: Mirroring,
79 ) -> Result<Self, MapperError> {
80 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
81 return Err(MapperError::Invalid(format!(
82 "mapper 95 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
83 prg_rom.len()
84 )));
85 }
86 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_1K) {
87 return Err(MapperError::Invalid(format!(
88 "mapper 95 CHR-ROM size {} is not a non-zero multiple of 1 KiB",
89 chr_rom.len()
90 )));
91 }
92 Ok(Self {
93 prg_rom,
94 chr_rom,
95 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
96 reg_index: 0,
97 prg_banks: [0, 1],
98 chr_regs: [0; 6],
99 one_screen_b: false,
100 })
101 }
102
103 fn read_prg(&self, bank: usize, addr: u16) -> u8 {
104 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
105 let bank = bank % count;
106 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
107 }
108
109 fn read_chr(&self, addr: u16) -> u8 {
110 let count1k = (self.chr_rom.len() / CHR_BANK_1K).max(1);
111 let bank1k = match addr {
113 0x0000..=0x07FF => (self.chr_regs[0] as usize & !1) + ((addr as usize >> 10) & 1),
114 0x0800..=0x0FFF => (self.chr_regs[1] as usize & !1) + ((addr as usize >> 10) & 1),
115 0x1000..=0x13FF => self.chr_regs[2] as usize,
116 0x1400..=0x17FF => self.chr_regs[3] as usize,
117 0x1800..=0x1BFF => self.chr_regs[4] as usize,
118 _ => self.chr_regs[5] as usize,
119 };
120 let bank = bank1k % count1k;
121 self.chr_rom[bank * CHR_BANK_1K + (addr as usize & 0x03FF)]
122 }
123}
124
125impl Mapper for Namcot3425M95 {
126 fn caps(&self) -> MapperCaps {
127 MapperCaps::NONE
128 }
129
130 fn cpu_read(&mut self, addr: u16) -> u8 {
131 let last = (self.prg_rom.len() / PRG_BANK_8K).max(1) - 1;
132 match addr {
133 0x8000..=0x9FFF => self.read_prg(self.prg_banks[0] as usize, addr),
134 0xA000..=0xBFFF => self.read_prg(self.prg_banks[1] as usize, addr),
135 0xC000..=0xDFFF => self.read_prg(last - 1, addr),
136 0xE000..=0xFFFF => self.read_prg(last, addr),
137 _ => 0,
138 }
139 }
140
141 fn cpu_write(&mut self, addr: u16, value: u8) {
142 match addr {
143 0x8000..=0x9FFF if addr & 1 == 0 => self.reg_index = value & 0x07,
144 0x8000..=0x9FFF => match self.reg_index {
145 0 => {
146 self.chr_regs[0] = value & 0x3F;
147 self.one_screen_b = (value & 0x20) != 0;
149 }
150 1 => self.chr_regs[1] = value & 0x3F,
151 2..=5 => self.chr_regs[self.reg_index as usize] = value & 0x3F,
152 6 => self.prg_banks[0] = value,
153 _ => self.prg_banks[1] = value,
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.read_chr(addr),
163 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
164 _ => 0,
165 }
166 }
167
168 fn ppu_write(&mut self, addr: u16, value: u8) {
169 let addr = addr & 0x3FFF;
170 if (0x2000..=0x3EFF).contains(&addr) {
171 let off = nametable_offset(addr, self.current_mirroring());
172 self.vram[off] = value;
173 }
174 }
175
176 fn current_mirroring(&self) -> Mirroring {
177 if self.one_screen_b {
178 Mirroring::SingleScreenB
179 } else {
180 Mirroring::SingleScreenA
181 }
182 }
183
184 fn save_state(&self) -> Vec<u8> {
185 let mut out = Vec::with_capacity(11 + self.vram.len());
186 out.push(SAVE_STATE_VERSION);
187 out.push(self.reg_index);
188 out.extend_from_slice(&self.prg_banks);
189 out.extend_from_slice(&self.chr_regs);
190 out.push(u8::from(self.one_screen_b));
191 out.extend_from_slice(&self.vram);
192 out
193 }
194
195 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
196 let expected = 11 + self.vram.len();
197 if data.len() != expected {
198 return Err(MapperError::WrongLength {
199 expected,
200 got: data.len(),
201 });
202 }
203 if data[0] != SAVE_STATE_VERSION {
204 return Err(MapperError::UnsupportedVersion(data[0]));
205 }
206 self.reg_index = data[1];
207 self.prg_banks.copy_from_slice(&data[2..4]);
208 self.chr_regs.copy_from_slice(&data[4..10]);
209 self.one_screen_b = data[10] != 0;
210 self.vram.copy_from_slice(&data[11..11 + self.vram.len()]);
211 Ok(())
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 fn synth_chr_1k(banks: usize) -> Box<[u8]> {
220 let mut v = vec![0u8; banks * CHR_BANK_1K];
221 for b in 0..banks {
222 v[b * CHR_BANK_1K] = b as u8;
223 }
224 v.into_boxed_slice()
225 }
226
227 fn synth_prg_8k(banks: usize) -> Box<[u8]> {
228 let mut v = vec![0xFFu8; banks * PRG_BANK_8K];
229 for b in 0..banks {
230 v[b * PRG_BANK_8K] = b as u8;
231 }
232 v.into_boxed_slice()
233 }
234
235 #[test]
236 fn m95_prg_select_and_one_screen() {
237 let mut m =
238 Namcot3425M95::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
239 m.cpu_write(0x8000, 6);
241 m.cpu_write(0x8001, 3);
242 assert_eq!(m.cpu_read(0x8000), 3);
243 m.cpu_write(0x8000, 0);
245 m.cpu_write(0x8001, 0x20);
246 assert_eq!(m.current_mirroring(), Mirroring::SingleScreenB);
247 assert_eq!(m.cpu_read(0xC000), 6);
249 assert_eq!(m.cpu_read(0xE000), 7);
250 }
251
252 #[test]
253 fn m95_save_state_round_trip() {
254 let mut m =
255 Namcot3425M95::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
256 m.cpu_write(0x8000, 7);
257 m.cpu_write(0x8001, 4);
258 m.cpu_write(0x8000, 0);
259 m.cpu_write(0x8001, 0x20);
260 let blob = m.save_state();
261 let mut m2 =
262 Namcot3425M95::new(synth_prg_8k(8), synth_chr_1k(16), Mirroring::Vertical).unwrap();
263 m2.load_state(&blob).unwrap();
264 assert_eq!(m2.cpu_read(0xA000), m.cpu_read(0xA000));
265 assert_eq!(m2.current_mirroring(), Mirroring::SingleScreenB);
266 }
267}