1#![allow(
22 clippy::bool_to_int_with_if,
23 clippy::cast_lossless,
24 clippy::cast_possible_truncation,
25 clippy::doc_markdown,
26 clippy::match_same_arms,
27 clippy::missing_const_for_fn,
28 clippy::similar_names,
29 clippy::struct_excessive_bools,
30 clippy::too_many_lines,
31 clippy::unreadable_literal
32)]
33
34use crate::cartridge::Mirroring;
35use crate::mapper::{Mapper, MapperCaps, MapperError};
36use alloc::{boxed::Box, vec::Vec};
37use alloc::{format, vec};
38
39const PRG_BANK_32K: usize = 0x8000;
40const CHR_BANK_8K: usize = 0x2000;
41const NAMETABLE_SIZE: usize = 0x0400;
42const NAMETABLE_SIZE_U16: u16 = 0x0400;
43
44const SAVE_STATE_VERSION: u8 = 1;
45
46const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
47 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
48 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
49 let physical = mirroring.physical_bank(table);
50 physical * NAMETABLE_SIZE + local
51}
52
53pub struct Decathlon244 {
55 prg_rom: Box<[u8]>,
56 chr_rom: Box<[u8]>,
57 vram: Box<[u8]>,
58 prg_bank: u8,
59 chr_bank: u8,
60 mirroring: Mirroring,
61}
62
63impl Decathlon244 {
64 pub fn new(
71 prg_rom: Box<[u8]>,
72 chr_rom: Box<[u8]>,
73 mirroring: Mirroring,
74 ) -> Result<Self, MapperError> {
75 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
76 return Err(MapperError::Invalid(format!(
77 "mapper 244 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
78 prg_rom.len()
79 )));
80 }
81 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
82 return Err(MapperError::Invalid(format!(
83 "mapper 244 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
84 chr_rom.len()
85 )));
86 }
87 Ok(Self {
88 prg_rom,
89 chr_rom,
90 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
91 prg_bank: 0,
92 chr_bank: 0,
93 mirroring,
94 })
95 }
96}
97
98impl Mapper for Decathlon244 {
99 fn caps(&self) -> MapperCaps {
100 MapperCaps::NONE
101 }
102
103 fn cpu_read(&mut self, addr: u16) -> u8 {
104 if (0x8000..=0xFFFF).contains(&addr) {
105 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
106 let bank = (self.prg_bank as usize) % count;
107 self.prg_rom[bank * PRG_BANK_32K + (addr as usize & 0x7FFF)]
108 } else {
109 0
110 }
111 }
112
113 fn cpu_write(&mut self, addr: u16, value: u8) {
114 const LUT_PRG: [[u8; 4]; 4] = [[0, 1, 2, 3], [3, 2, 1, 0], [0, 2, 1, 3], [3, 1, 2, 0]];
122 const LUT_CHR: [[u8; 8]; 8] = [
123 [0, 1, 2, 3, 4, 5, 6, 7],
124 [0, 2, 1, 3, 4, 6, 5, 7],
125 [0, 1, 4, 5, 2, 3, 6, 7],
126 [0, 4, 1, 5, 2, 6, 3, 7],
127 [0, 4, 2, 6, 1, 5, 3, 7],
128 [0, 2, 4, 6, 1, 3, 5, 7],
129 [7, 6, 5, 4, 3, 2, 1, 0],
130 [7, 6, 5, 4, 3, 2, 1, 0],
131 ];
132 if (0x8000..=0xFFFF).contains(&addr) {
133 if value & 0x08 != 0 {
134 self.chr_bank = LUT_CHR[((value >> 4) & 0x07) as usize][(value & 0x07) as usize];
135 } else {
136 self.prg_bank = LUT_PRG[((value >> 4) & 0x03) as usize][(value & 0x03) as usize];
137 }
138 }
139 }
140
141 fn ppu_read(&mut self, addr: u16) -> u8 {
142 let addr = addr & 0x3FFF;
143 match addr {
144 0x0000..=0x1FFF => {
145 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
146 let bank = (self.chr_bank as usize) % count;
147 self.chr_rom[bank * CHR_BANK_8K + (addr as usize & 0x1FFF)]
148 }
149 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
150 _ => 0,
151 }
152 }
153
154 fn ppu_write(&mut self, addr: u16, value: u8) {
155 let addr = addr & 0x3FFF;
156 if (0x2000..=0x3EFF).contains(&addr) {
157 let off = nametable_offset(addr, self.mirroring);
158 self.vram[off] = value;
159 }
160 }
161
162 fn current_mirroring(&self) -> Mirroring {
163 self.mirroring
164 }
165
166 fn save_state(&self) -> Vec<u8> {
167 let mut out = Vec::with_capacity(3 + self.vram.len());
168 out.push(SAVE_STATE_VERSION);
169 out.push(self.prg_bank);
170 out.push(self.chr_bank);
171 out.extend_from_slice(&self.vram);
172 out
173 }
174
175 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
176 let expected = 3 + self.vram.len();
177 if data.len() != expected {
178 return Err(MapperError::WrongLength {
179 expected,
180 got: data.len(),
181 });
182 }
183 if data[0] != SAVE_STATE_VERSION {
184 return Err(MapperError::UnsupportedVersion(data[0]));
185 }
186 self.prg_bank = data[1];
187 self.chr_bank = data[2];
188 self.vram.copy_from_slice(&data[3..3 + self.vram.len()]);
189 Ok(())
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 fn synth_prg_32k(banks: usize) -> Box<[u8]> {
198 let mut v = vec![0xFFu8; banks * PRG_BANK_32K];
199 for b in 0..banks {
200 v[b * PRG_BANK_32K] = b as u8;
201 }
202 v.into_boxed_slice()
203 }
204
205 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
206 let mut v = vec![0u8; banks * CHR_BANK_8K];
207 for b in 0..banks {
208 v[b * CHR_BANK_8K] = b as u8;
209 }
210 v.into_boxed_slice()
211 }
212
213 #[test]
214 fn m244_value_decoded_banks() {
215 let mut m =
216 Decathlon244::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
217 m.cpu_write(0x8000, 0x11);
219 assert_eq!(m.cpu_read(0x8000), 2);
220 m.cpu_write(0x8000, 0x30);
222 assert_eq!(m.cpu_read(0x8000), 3);
223 m.cpu_write(0x8000, 0x09);
225 assert_eq!(m.ppu_read(0x0000), 1);
226 m.cpu_write(0x8000, 0x6E);
228 assert_eq!(m.ppu_read(0x0000), 1);
229 }
230
231 #[test]
232 fn m244_save_state_round_trip() {
233 let mut m =
234 Decathlon244::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
235 m.cpu_write(0x8000, 0x11); let blob = m.save_state();
237 let mut m2 =
238 Decathlon244::new(synth_prg_32k(4), synth_chr_8k(8), Mirroring::Vertical).unwrap();
239 m2.load_state(&blob).unwrap();
240 assert_eq!(m2.cpu_read(0x8000), m.cpu_read(0x8000));
241 assert_eq!(m2.ppu_read(0x0000), m.ppu_read(0x0000));
242 }
243}