1#![allow(
28 clippy::cast_possible_truncation,
29 clippy::cast_lossless,
30 clippy::missing_const_for_fn,
31 clippy::doc_markdown
32)]
33
34use crate::cartridge::Mirroring;
35use crate::m004_mmc3::Mmc3;
36use crate::mapper::{Mapper, MapperCaps, MapperDebugInfo, MapperError};
37use alloc::format;
38use alloc::{boxed::Box, vec::Vec};
39
40const NAMETABLE_SIZE_U16: u16 = 0x0400;
41
42const SAVE_STATE_VERSION: u8 = 1;
43
44pub struct TxSrom {
46 inner: Mmc3,
47 chr_regs: [u8; 6],
51 bank_select: u8,
52 chr_mode: bool,
53}
54
55impl TxSrom {
56 pub fn new(
64 prg_rom: Box<[u8]>,
65 chr_rom: Box<[u8]>,
66 initial_mirroring: Mirroring,
67 prg_ram_bytes: usize,
68 ) -> Result<Self, MapperError> {
69 let inner = Mmc3::new(
70 prg_rom,
71 chr_rom,
72 initial_mirroring,
73 prg_ram_bytes,
74 crate::m004_mmc3::Mmc3Revision::Sharp,
75 )?;
76 Ok(Self {
77 inner,
78 chr_regs: [0; 6],
79 bank_select: 0,
80 chr_mode: false,
81 })
82 }
83
84 fn nt_bank(&self, slot: u8) -> usize {
87 let reg = if self.chr_mode {
89 match slot {
91 0 => self.chr_regs[2],
92 1 => self.chr_regs[3],
93 2 => self.chr_regs[4],
94 _ => self.chr_regs[5],
95 }
96 } else {
97 if slot < 2 {
100 self.chr_regs[0]
101 } else {
102 self.chr_regs[1]
103 }
104 };
105 usize::from((reg & 0x80) != 0)
106 }
107}
108
109impl Mapper for TxSrom {
110 fn sram(&self) -> &[u8] {
113 self.inner.sram()
114 }
115 fn sram_mut(&mut self) -> &mut [u8] {
116 self.inner.sram_mut()
117 }
118
119 fn caps(&self) -> MapperCaps {
121 MapperCaps::CYCLE_IRQ
122 }
123
124 fn cpu_read(&mut self, addr: u16) -> u8 {
125 self.inner.cpu_read(addr)
126 }
127
128 fn cpu_write(&mut self, addr: u16, value: u8) {
129 if let 0x8000..=0x9FFF = addr {
131 if addr & 1 == 0 {
132 self.bank_select = value & 0x07;
133 self.chr_mode = (value & 0x80) != 0;
134 } else {
135 let idx = (self.bank_select & 0x07) as usize;
136 if idx < 6 {
137 self.chr_regs[idx] = value;
138 }
139 }
140 }
141 self.inner.cpu_write(addr, value);
142 }
143
144 fn cpu_read_unmapped(&self, addr: u16) -> bool {
145 self.inner.cpu_read_unmapped(addr)
146 }
147
148 fn ppu_read(&mut self, addr: u16) -> u8 {
149 self.inner.ppu_read(addr)
150 }
151
152 fn ppu_write(&mut self, addr: u16, value: u8) {
153 self.inner.ppu_write(addr, value);
154 }
155
156 fn nametable_address(&self, addr: u16) -> u16 {
157 let table = (((addr.wrapping_sub(0x2000)) / NAMETABLE_SIZE_U16) & 0x03) as u8;
160 let local = addr & (NAMETABLE_SIZE_U16 - 1);
161 let bank = self.nt_bank(table) as u16;
162 bank * NAMETABLE_SIZE_U16 + local
163 }
164
165 fn current_mirroring(&self) -> Mirroring {
166 Mirroring::MapperControlled
169 }
170
171 fn notify_a12(&mut self, level: bool) {
172 self.inner.notify_a12(level);
173 }
174
175 fn notify_a12_at_sub_dot(&mut self, level: bool, sub_dot: u8) {
176 self.inner.notify_a12_at_sub_dot(level, sub_dot);
177 }
178
179 fn notify_cpu_cycle(&mut self) {
180 self.inner.notify_cpu_cycle();
181 }
182
183 fn irq_pending(&self) -> bool {
184 self.inner.irq_pending()
185 }
186
187 fn irq_acknowledge(&mut self) {
188 self.inner.irq_acknowledge();
189 }
190
191 fn debug_info(&self) -> MapperDebugInfo {
192 let mut info = self.inner.debug_info();
193 info.mapper_id = 118;
194 info.name = "TxSROM / TLSROM (118)".into();
195 info.mirroring = "MapperControlled";
196 for slot in 0u8..4 {
197 info.extra
198 .push((format!("NT{slot}"), format!("{}", self.nt_bank(slot))));
199 }
200 info
201 }
202
203 fn save_state(&self) -> Vec<u8> {
204 let inner = self.inner.save_state();
207 let mut out = Vec::with_capacity(1 + 6 + 1 + 1 + inner.len());
208 out.push(SAVE_STATE_VERSION);
209 out.extend_from_slice(&self.chr_regs);
210 out.push(self.bank_select);
211 out.push(u8::from(self.chr_mode));
212 out.extend_from_slice(&inner);
213 out
214 }
215
216 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
217 const HEADER: usize = 1 + 6 + 1 + 1;
218 if data.len() < HEADER {
219 return Err(MapperError::WrongLength {
220 expected: HEADER,
221 got: data.len(),
222 });
223 }
224 if data[0] != SAVE_STATE_VERSION {
225 return Err(MapperError::UnsupportedVersion(data[0]));
226 }
227 self.chr_regs.copy_from_slice(&data[1..7]);
228 self.bank_select = data[7];
229 self.chr_mode = data[8] != 0;
230 self.inner.load_state(&data[HEADER..])
231 }
232}
233
234#[cfg(test)]
235#[allow(clippy::cast_possible_truncation)]
236mod tests {
237 use super::*;
238 use alloc::vec;
239
240 fn synth_prg(banks_8k: usize) -> Box<[u8]> {
241 let mut v = vec![0u8; banks_8k * 0x2000];
242 for b in 0..banks_8k {
243 v[b * 0x2000] = b as u8;
244 }
245 v.into_boxed_slice()
246 }
247
248 fn synth_chr(banks_1k: usize) -> Box<[u8]> {
249 let mut v = vec![0u8; banks_1k * 0x0400];
250 for b in 0..banks_1k {
251 v[b * 0x0400] = b as u8;
252 }
253 v.into_boxed_slice()
254 }
255
256 fn fresh() -> TxSrom {
257 TxSrom::new(synth_prg(8), synth_chr(64), Mirroring::Vertical, 0).unwrap()
258 }
259
260 fn select_write(m: &mut TxSrom, reg: u8, value: u8) {
261 m.cpu_write(0x8000, reg);
262 m.cpu_write(0x8001, value);
263 }
264
265 #[test]
266 fn delegates_prg_banking_to_mmc3() {
267 let mut m = fresh();
268 assert_eq!(m.cpu_read(0xE000), 7);
270 select_write(&mut m, 6, 3); assert_eq!(m.cpu_read(0x8000), 3);
272 }
273
274 #[test]
275 fn reports_mapper_controlled_mirroring() {
276 let m = fresh();
277 assert_eq!(m.current_mirroring(), Mirroring::MapperControlled);
278 }
279
280 #[test]
281 fn mode0_nametable_bits_from_r0_r1() {
282 let mut m = fresh();
283 select_write(&mut m, 0, 0x80); select_write(&mut m, 1, 0x00); assert_eq!(m.nametable_address(0x2000) >> 10, 1);
288 assert_eq!(m.nametable_address(0x2400) >> 10, 1);
289 assert_eq!(m.nametable_address(0x2800) >> 10, 0);
291 assert_eq!(m.nametable_address(0x2C00) >> 10, 0);
292 }
293
294 #[test]
295 fn mode1_nametable_bits_from_r2_r5() {
296 let mut m = fresh();
297 m.cpu_write(0x8000, 0x80 | 2); m.cpu_write(0x8001, 0x80); m.cpu_write(0x8000, 0x80 | 3); m.cpu_write(0x8001, 0x00); m.cpu_write(0x8000, 0x80 | 4);
304 m.cpu_write(0x8001, 0x80); m.cpu_write(0x8000, 0x80 | 5);
306 m.cpu_write(0x8001, 0x00); assert_eq!(m.nametable_address(0x2000) >> 10, 1);
308 assert_eq!(m.nametable_address(0x2400) >> 10, 0);
309 assert_eq!(m.nametable_address(0x2800) >> 10, 1);
310 assert_eq!(m.nametable_address(0x2C00) >> 10, 0);
311 }
312
313 #[test]
314 fn irq_delegates_to_mmc3() {
315 let mut m = fresh();
316 m.cpu_write(0xC000, 3);
317 m.cpu_write(0xC001, 0);
318 m.cpu_write(0xE001, 0);
319 for _ in 0..5 {
320 m.notify_a12(false);
321 for _ in 0..4 {
322 m.notify_cpu_cycle();
323 }
324 m.notify_a12(true);
325 }
326 assert!(m.irq_pending());
327 }
328
329 #[test]
330 fn save_state_round_trip() {
331 let mut m = fresh();
332 select_write(&mut m, 0, 0x80);
333 select_write(&mut m, 6, 3);
334 m.cpu_write(0xC000, 0x10);
335 let blob = m.save_state();
336 let mut m2 = fresh();
337 m2.load_state(&blob).unwrap();
338 assert_eq!(m.cpu_read(0x8000), m2.cpu_read(0x8000));
339 assert_eq!(m.nametable_address(0x2000), m2.nametable_address(0x2000));
340 }
341}