rustynes_mappers/
m246_fong_shen_bang246.rs1use crate::cartridge::Mirroring;
19use crate::mapper::{Mapper, MapperCaps, MapperError};
20use alloc::{boxed::Box, vec::Vec};
21use alloc::{format, vec};
22
23const PRG_BANK_8K: usize = 0x2000;
24const CHR_BANK_2K: usize = 0x0800;
25const NAMETABLE_SIZE: usize = 0x0400;
26const NAMETABLE_SIZE_U16: u16 = 0x0400;
27
28const SAVE_STATE_VERSION: u8 = 1;
29
30const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
35 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
36 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
37 let physical = mirroring.physical_bank(table);
38 physical * NAMETABLE_SIZE + local
39}
40
41pub struct FongShenBang246 {
43 prg_rom: Box<[u8]>,
44 chr_rom: Box<[u8]>,
45 vram: Box<[u8]>,
46 prg_ram: Box<[u8]>,
48 prg_banks: [u8; 4],
49 chr_banks: [u8; 4],
50 mirroring: Mirroring,
51}
52
53impl FongShenBang246 {
54 pub fn new(
61 prg_rom: Box<[u8]>,
62 chr_rom: Box<[u8]>,
63 mirroring: Mirroring,
64 ) -> Result<Self, MapperError> {
65 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
66 return Err(MapperError::Invalid(format!(
67 "mapper 246 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
68 prg_rom.len()
69 )));
70 }
71 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_2K) {
72 return Err(MapperError::Invalid(format!(
73 "mapper 246 CHR-ROM size {} is not a non-zero multiple of 2 KiB",
74 chr_rom.len()
75 )));
76 }
77 let prg_banks = [0, 0, 0, 0xFF];
81 Ok(Self {
82 prg_rom,
83 chr_rom,
84 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
85 prg_ram: vec![0u8; 0x0800].into_boxed_slice(),
86 prg_banks,
87 chr_banks: [0, 0, 0, 0],
88 mirroring,
89 })
90 }
91
92 fn prg_byte(&self, slot: usize, addr: u16) -> u8 {
93 let count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
94 let mut bank = self.prg_banks[slot] as usize;
95 if slot == 3 {
99 let low = addr & 0x001F;
100 let in_window = (0xFFE4..=0xFFFF).contains(&addr)
101 && matches!(low, 0x04..=0x07 | 0x0C..=0x0F | 0x14..=0x17 | 0x1C..=0x1F);
102 if in_window {
103 bank |= 0x10;
104 }
105 }
106 let bank = bank % count;
107 self.prg_rom[bank * PRG_BANK_8K + (addr as usize & 0x1FFF)]
108 }
109}
110
111impl Mapper for FongShenBang246 {
112 fn sram(&self) -> &[u8] {
113 &self.prg_ram
114 }
115 fn sram_mut(&mut self) -> &mut [u8] {
116 &mut self.prg_ram
117 }
118 fn caps(&self) -> MapperCaps {
119 MapperCaps::NONE
120 }
121
122 fn cpu_read_unmapped(&self, addr: u16) -> bool {
128 (0x4020..=0x67FF).contains(&addr) || (0x7000..=0x7FFF).contains(&addr)
129 }
130
131 fn cpu_read(&mut self, addr: u16) -> u8 {
132 match addr {
133 0x6800..=0x6FFF => self.prg_ram[(addr - 0x6800) as usize],
134 0x8000..=0x9FFF => self.prg_byte(0, addr),
135 0xA000..=0xBFFF => self.prg_byte(1, addr),
136 0xC000..=0xDFFF => self.prg_byte(2, addr),
137 0xE000..=0xFFFF => self.prg_byte(3, addr),
138 _ => 0,
139 }
140 }
141
142 fn cpu_write(&mut self, addr: u16, value: u8) {
143 match addr {
144 0x6000..=0x6003 => self.prg_banks[(addr & 0x03) as usize] = value,
145 0x6004..=0x6007 => self.chr_banks[(addr & 0x03) as usize] = value,
146 0x6800..=0x6FFF => self.prg_ram[(addr - 0x6800) as usize] = value,
147 _ => {}
148 }
149 }
150
151 fn ppu_read(&mut self, addr: u16) -> u8 {
152 let addr = addr & 0x3FFF;
153 match addr {
154 0x0000..=0x1FFF => {
155 let slot = (addr >> 11) as usize & 0x03;
156 let count = (self.chr_rom.len() / CHR_BANK_2K).max(1);
157 let bank = (self.chr_banks[slot] as usize) % count;
158 self.chr_rom[bank * CHR_BANK_2K + (addr as usize & 0x07FF)]
159 }
160 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
161 _ => 0,
162 }
163 }
164
165 fn ppu_write(&mut self, addr: u16, value: u8) {
166 let addr = addr & 0x3FFF;
167 if let 0x2000..=0x3EFF = addr {
168 let off = nametable_offset(addr, self.mirroring);
169 self.vram[off] = value;
170 }
171 }
172
173 fn current_mirroring(&self) -> Mirroring {
174 self.mirroring
175 }
176
177 fn save_state(&self) -> Vec<u8> {
178 let mut out = Vec::with_capacity(1 + 4 + 4 + self.prg_ram.len() + self.vram.len());
179 out.push(SAVE_STATE_VERSION);
180 out.extend_from_slice(&self.prg_banks);
181 out.extend_from_slice(&self.chr_banks);
182 out.extend_from_slice(&self.prg_ram);
183 out.extend_from_slice(&self.vram);
184 out
185 }
186
187 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
188 let expected = 1 + 4 + 4 + self.prg_ram.len() + self.vram.len();
189 if data.len() != expected {
190 return Err(MapperError::WrongLength {
191 expected,
192 got: data.len(),
193 });
194 }
195 if data[0] != SAVE_STATE_VERSION {
196 return Err(MapperError::UnsupportedVersion(data[0]));
197 }
198 self.prg_banks.copy_from_slice(&data[1..5]);
199 self.chr_banks.copy_from_slice(&data[5..9]);
200 let mut cursor = 9;
201 self.prg_ram
202 .copy_from_slice(&data[cursor..cursor + self.prg_ram.len()]);
203 cursor += self.prg_ram.len();
204 self.vram
205 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
206 Ok(())
207 }
208}
209
210#[cfg(test)]
211#[allow(clippy::cast_possible_truncation)]
212mod tests {
213 use super::*;
214
215 fn synth_prg_8k(banks: usize) -> Box<[u8]> {
216 let mut v = vec![0xFFu8; banks * PRG_BANK_8K];
217 for b in 0..banks {
218 v[b * PRG_BANK_8K] = b as u8;
219 }
220 v.into_boxed_slice()
221 }
222
223 fn synth_chr_2k(banks: usize) -> Box<[u8]> {
224 let mut v = vec![0u8; banks * CHR_BANK_2K];
225 for b in 0..banks {
226 v[b * CHR_BANK_2K] = b as u8;
227 }
228 v.into_boxed_slice()
229 }
230
231 #[test]
232 fn m246_register_banking_and_prg_ram() {
233 let mut m =
234 FongShenBang246::new(synth_prg_8k(8), synth_chr_2k(8), Mirroring::Vertical).unwrap();
235 m.cpu_write(0x6000, 3);
237 assert_eq!(m.cpu_read(0x8000), 3);
238 m.cpu_write(0x6004, 5);
240 assert_eq!(m.ppu_read(0x0000), 5);
241 m.cpu_write(0x6800, 0xC4);
243 assert_eq!(m.cpu_read(0x6800), 0xC4);
244 }
245
246 #[test]
247 fn m246_save_state_round_trip() {
248 let mut m =
249 FongShenBang246::new(synth_prg_8k(8), synth_chr_2k(8), Mirroring::Vertical).unwrap();
250 m.cpu_write(0x6001, 4); m.cpu_write(0x6007, 6); m.cpu_write(0x6900, 0x9D); let blob = m.save_state();
254 let mut m2 =
255 FongShenBang246::new(synth_prg_8k(8), synth_chr_2k(8), Mirroring::Vertical).unwrap();
256 m2.load_state(&blob).unwrap();
257 assert_eq!(m2.cpu_read(0xA000), 4);
258 assert_eq!(m2.ppu_read(0x1800), 6);
259 assert_eq!(m2.cpu_read(0x6900), 0x9D);
260 }
261}