rustynes_mappers/
m036_txc_policeman.rs1#![allow(
19 clippy::bool_to_int_with_if,
20 clippy::cast_lossless,
21 clippy::cast_possible_truncation,
22 clippy::doc_markdown,
23 clippy::match_same_arms,
24 clippy::missing_const_for_fn,
25 clippy::similar_names,
26 clippy::struct_excessive_bools,
27 clippy::too_many_lines,
28 clippy::unreadable_literal
29)]
30
31use crate::cartridge::Mirroring;
32use crate::mapper::{Mapper, MapperCaps, MapperError};
33use alloc::{boxed::Box, vec::Vec};
34use alloc::{format, vec};
35
36const PRG_BANK_32K: usize = 0x8000;
37const CHR_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
54pub struct Txc36 {
56 prg_rom: Box<[u8]>,
57 chr_rom: Box<[u8]>,
58 vram: Box<[u8]>,
59 prg_bank: u8,
60 chr_bank: u8,
61 mirroring: Mirroring,
62}
63
64impl Txc36 {
65 pub fn new(
72 prg_rom: Box<[u8]>,
73 chr_rom: Box<[u8]>,
74 mirroring: Mirroring,
75 ) -> Result<Self, MapperError> {
76 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
77 return Err(MapperError::Invalid(format!(
78 "mapper 36 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
79 prg_rom.len()
80 )));
81 }
82 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_8K) {
83 return Err(MapperError::Invalid(format!(
84 "mapper 36 CHR-ROM size {} is not a non-zero multiple of 8 KiB",
85 chr_rom.len()
86 )));
87 }
88 Ok(Self {
89 prg_rom,
90 chr_rom,
91 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
92 prg_bank: 0,
93 chr_bank: 0,
94 mirroring,
95 })
96 }
97}
98
99impl Mapper for Txc36 {
100 fn caps(&self) -> MapperCaps {
101 MapperCaps::NONE
102 }
103
104 fn cpu_read(&mut self, addr: u16) -> u8 {
108 if (0x8000..=0xFFFF).contains(&addr) {
109 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
110 let bank = (self.prg_bank as usize) % count;
111 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
112 } else {
113 0
114 }
115 }
116
117 fn cpu_write(&mut self, addr: u16, value: u8) {
118 if (0x4100..=0x5FFF).contains(&addr) && (addr & 0x0100) != 0 {
119 self.prg_bank = (value >> 4) & 0x0F;
120 self.chr_bank = value & 0x0F;
121 }
122 }
123
124 fn ppu_read(&mut self, addr: u16) -> u8 {
125 let addr = addr & 0x3FFF;
126 match addr {
127 0x0000..=0x1FFF => {
128 let count = (self.chr_rom.len() / CHR_BANK_8K).max(1);
129 let bank = (self.chr_bank as usize) % count;
130 self.chr_rom[bank * CHR_BANK_8K + addr as usize]
131 }
132 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
133 _ => 0,
134 }
135 }
136
137 fn ppu_write(&mut self, addr: u16, value: u8) {
138 let addr = addr & 0x3FFF;
139 if let 0x2000..=0x3EFF = addr {
140 let off = nametable_offset(addr, self.mirroring);
141 self.vram[off] = value;
142 }
143 }
144
145 fn current_mirroring(&self) -> Mirroring {
146 self.mirroring
147 }
148
149 fn save_state(&self) -> Vec<u8> {
150 let mut out = Vec::with_capacity(3 + self.vram.len());
151 out.push(SAVE_STATE_VERSION);
152 out.push(self.prg_bank);
153 out.push(self.chr_bank);
154 out.extend_from_slice(&self.vram);
155 out
156 }
157
158 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
159 let expected = 3 + self.vram.len();
160 if data.len() != expected {
161 return Err(MapperError::WrongLength {
162 expected,
163 got: data.len(),
164 });
165 }
166 if data[0] != SAVE_STATE_VERSION {
167 return Err(MapperError::UnsupportedVersion(data[0]));
168 }
169 self.prg_bank = data[1];
170 self.chr_bank = data[2];
171 self.vram.copy_from_slice(&data[3..3 + self.vram.len()]);
172 Ok(())
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 fn synth_prg_32k(banks: usize) -> Box<[u8]> {
181 let mut v = vec![0xFFu8; banks * PRG_BANK_32K];
182 for b in 0..banks {
183 v[b * PRG_BANK_32K] = b as u8;
184 }
185 v.into_boxed_slice()
186 }
187
188 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
189 let mut v = vec![0u8; banks * CHR_BANK_8K];
190 for b in 0..banks {
191 v[b * CHR_BANK_8K] = b as u8;
192 }
193 v.into_boxed_slice()
194 }
195
196 #[test]
197 fn m36_register_decodes_on_a8() {
198 let mut m = Txc36::new(synth_prg_32k(4), synth_chr_8k(16), Mirroring::Vertical).unwrap();
199 m.cpu_write(0x4100, 0b0011_1010);
201 assert_eq!(m.cpu_read(0x8000), 3);
202 assert_eq!(m.ppu_read(0x0000), 10);
203 m.cpu_write(0x4200, 0b0000_0001);
205 assert_eq!(m.cpu_read(0x8000), 3);
206 }
207}