rustynes_mappers/
m156_daou156.rs1use crate::cartridge::Mirroring;
20use crate::mapper::{Mapper, MapperCaps, MapperError};
21use alloc::{boxed::Box, vec::Vec};
22use alloc::{format, vec};
23
24const PRG_BANK_16K: usize = 0x4000;
25const CHR_BANK_1K: usize = 0x0400;
26const NAMETABLE_SIZE: usize = 0x0400;
27const NAMETABLE_SIZE_U16: u16 = 0x0400;
28
29const SAVE_STATE_VERSION: u8 = 2;
32const WRAM_SIZE: usize = 0x2000;
33
34const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
39 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
40 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
41 let physical = mirroring.physical_bank(table);
42 physical * NAMETABLE_SIZE + local
43}
44
45pub struct Daou156 {
47 prg_rom: Box<[u8]>,
48 chr_rom: Box<[u8]>,
49 vram: Box<[u8]>,
50 prg_bank: u8,
51 chr_lo: [u8; 8],
53 chr_hi: [u8; 8],
54 mirroring: Mirroring,
55 wram: Box<[u8]>,
58 battery: bool,
60}
61
62impl Daou156 {
63 pub fn new(
70 prg_rom: Box<[u8]>,
71 chr_rom: Box<[u8]>,
72 _mirroring: Mirroring,
73 ) -> Result<Self, MapperError> {
74 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
75 return Err(MapperError::Invalid(format!(
76 "mapper 156 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
77 prg_rom.len()
78 )));
79 }
80 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_1K) {
81 return Err(MapperError::Invalid(format!(
82 "mapper 156 CHR-ROM size {} is not a non-zero multiple of 1 KiB",
83 chr_rom.len()
84 )));
85 }
86 Ok(Self {
87 prg_rom,
88 chr_rom,
89 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
90 prg_bank: 0,
91 chr_lo: [0; 8],
92 chr_hi: [0; 8],
93 mirroring: Mirroring::SingleScreenA,
96 wram: vec![0u8; WRAM_SIZE].into_boxed_slice(),
97 battery: false,
98 })
99 }
100
101 #[must_use]
103 pub const fn with_battery(mut self, battery: bool) -> Self {
104 self.battery = battery;
105 self
106 }
107
108 fn read_chr(&self, addr: u16) -> u8 {
109 let count1k = (self.chr_rom.len() / CHR_BANK_1K).max(1);
110 let slot = (addr as usize >> 10) & 0x07;
111 let bank = ((self.chr_lo[slot] as usize) | ((self.chr_hi[slot] as usize) << 8)) % count1k;
112 self.chr_rom[bank * CHR_BANK_1K + (addr as usize & 0x03FF)]
113 }
114}
115
116impl Mapper for Daou156 {
117 fn caps(&self) -> MapperCaps {
118 MapperCaps::NONE
119 }
120
121 fn sram(&self) -> &[u8] {
122 if self.battery { &self.wram } else { &[] }
123 }
124 fn sram_mut(&mut self) -> &mut [u8] {
125 if self.battery {
126 &mut self.wram
127 } else {
128 &mut []
129 }
130 }
131
132 fn cpu_read_unmapped(&self, addr: u16) -> bool {
134 (0x4020..=0x5FFF).contains(&addr)
135 }
136
137 fn cpu_read(&mut self, addr: u16) -> u8 {
138 let count = (self.prg_rom.len() / PRG_BANK_16K).max(1);
139 match addr {
140 0x6000..=0x7FFF => self.wram[usize::from(addr - 0x6000)],
141 0x8000..=0xBFFF => {
142 let bank = (self.prg_bank as usize) % count;
143 self.prg_rom[bank * PRG_BANK_16K + (addr as usize & 0x3FFF)]
144 }
145 0xC000..=0xFFFF => {
146 let last = count - 1;
147 self.prg_rom[last * PRG_BANK_16K + (addr as usize & 0x3FFF)]
148 }
149 _ => 0,
150 }
151 }
152
153 fn cpu_write(&mut self, addr: u16, value: u8) {
154 match addr {
155 0x6000..=0x7FFF => self.wram[usize::from(addr - 0x6000)] = value,
156 0xC000..=0xC00F => {
162 let slot = ((addr & 0x03) + if addr >= 0xC008 { 4 } else { 0 }) as usize;
163 if addr & 0x04 != 0 {
164 self.chr_hi[slot] = value;
165 } else {
166 self.chr_lo[slot] = value;
167 }
168 }
169 0xC010 => self.prg_bank = value,
170 0xC014 => {
174 self.mirroring = if value & 0x01 != 0 {
175 Mirroring::Horizontal
176 } else {
177 Mirroring::Vertical
178 };
179 }
180 _ => {}
181 }
182 }
183
184 fn ppu_read(&mut self, addr: u16) -> u8 {
185 let addr = addr & 0x3FFF;
186 match addr {
187 0x0000..=0x1FFF => self.read_chr(addr),
188 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
189 _ => 0,
190 }
191 }
192
193 fn ppu_write(&mut self, addr: u16, value: u8) {
194 let addr = addr & 0x3FFF;
195 if (0x2000..=0x3EFF).contains(&addr) {
196 let off = nametable_offset(addr, self.current_mirroring());
197 self.vram[off] = value;
198 }
199 }
200
201 fn current_mirroring(&self) -> Mirroring {
202 self.mirroring
203 }
204
205 fn save_state(&self) -> Vec<u8> {
206 let mut out = Vec::with_capacity(19 + self.vram.len());
207 out.push(SAVE_STATE_VERSION);
208 out.push(self.prg_bank);
209 out.extend_from_slice(&self.chr_lo);
210 out.extend_from_slice(&self.chr_hi);
211 out.push(match self.mirroring {
212 Mirroring::Horizontal => 0,
213 Mirroring::Vertical => 1,
214 Mirroring::SingleScreenB => 2,
215 _ => 3, });
217 out.extend_from_slice(&self.vram);
218 out.extend_from_slice(&self.wram);
219 out
220 }
221
222 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
223 let version = *data.first().ok_or(MapperError::WrongLength {
224 expected: 1,
225 got: 0,
226 })?;
227 if version != SAVE_STATE_VERSION {
228 return Err(MapperError::UnsupportedVersion(version));
229 }
230 let wram_len = self.wram.len();
231 let expected = 19 + self.vram.len() + wram_len;
232 if data.len() != expected {
233 return Err(MapperError::WrongLength {
234 expected,
235 got: data.len(),
236 });
237 }
238 self.prg_bank = data[1];
239 self.chr_lo.copy_from_slice(&data[2..10]);
240 self.chr_hi.copy_from_slice(&data[10..18]);
241 self.mirroring = match data[18] {
242 0 => Mirroring::Horizontal,
243 1 => Mirroring::Vertical,
244 2 => Mirroring::SingleScreenB,
245 _ => Mirroring::SingleScreenA,
246 };
247 self.vram.copy_from_slice(&data[19..19 + self.vram.len()]);
248 let cur = 19 + self.vram.len();
249 self.wram.copy_from_slice(&data[cur..cur + wram_len]);
250 Ok(())
251 }
252}
253
254#[cfg(test)]
255#[allow(clippy::cast_possible_truncation)]
256mod tests {
257 use super::*;
258
259 fn synth_prg_16k(banks: usize) -> Box<[u8]> {
260 let mut v = vec![0xFFu8; banks * PRG_BANK_16K];
261 for b in 0..banks {
262 v[b * PRG_BANK_16K] = b as u8;
263 }
264 v.into_boxed_slice()
265 }
266
267 fn synth_chr_1k(banks: usize) -> Box<[u8]> {
268 let mut v = vec![0u8; banks * CHR_BANK_1K];
269 for b in 0..banks {
270 v[b * CHR_BANK_1K] = b as u8;
271 }
272 v.into_boxed_slice()
273 }
274
275 #[test]
276 fn m156_chr_compose_prg_and_mirroring() {
277 let mut m = Daou156::new(synth_prg_16k(8), synth_chr_1k(32), Mirroring::Vertical).unwrap();
278 assert_eq!(m.current_mirroring(), Mirroring::SingleScreenA);
280 m.cpu_write(0xC010, 3);
282 assert_eq!(m.cpu_read(0x8000), 3);
283 assert_eq!(m.cpu_read(0xC000), 7); m.cpu_write(0xC000, 5);
286 assert_eq!(m.ppu_read(0x0000), 5);
287 m.cpu_write(0xC004, 1);
290 assert_eq!(m.ppu_read(0x0000), (0x105usize % 32) as u8);
291 m.cpu_write(0xC014, 1);
293 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
294 m.cpu_write(0xC014, 0);
295 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
296 }
297
298 #[test]
299 fn m156_save_state_round_trip() {
300 let mut m = Daou156::new(synth_prg_16k(8), synth_chr_1k(32), Mirroring::Vertical).unwrap();
301 m.cpu_write(0xC010, 2);
302 m.cpu_write(0xC001, 4);
303 m.cpu_write(0xC014, 1);
304 let blob = m.save_state();
305 let mut m2 = Daou156::new(synth_prg_16k(8), synth_chr_1k(32), Mirroring::Vertical).unwrap();
306 m2.load_state(&blob).unwrap();
307 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
308 assert_eq!(m2.ppu_read(0x0400), m.ppu_read(0x0400));
309 assert_eq!(m2.current_mirroring(), Mirroring::Horizontal);
310 }
311}