rustynes_mappers/
m071_camerica_bf9093.rs1#![allow(
12 clippy::cast_possible_truncation,
13 clippy::cast_lossless,
14 clippy::missing_const_for_fn,
15 clippy::needless_pass_by_ref_mut,
16 clippy::manual_range_patterns,
17 clippy::match_same_arms,
18 clippy::too_many_arguments
19)]
20
21use crate::cartridge::Mirroring;
22use crate::mapper::{Mapper, MapperCaps, MapperError};
23use alloc::{boxed::Box, vec::Vec};
24use alloc::{format, vec};
25
26const PRG_BANK_16K: usize = 0x4000;
27const CHR_BANK_8K: usize = 0x2000;
28const NAMETABLE_SIZE: usize = 0x0400;
29const NAMETABLE_SIZE_U16: u16 = 0x0400;
30
31fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
32 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
33 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
34 let physical = mirroring.physical_bank(table);
35 physical * NAMETABLE_SIZE + local
36}
37
38pub struct Camerica {
40 prg_rom: Box<[u8]>,
41 chr_ram: Box<[u8]>,
42 vram: Box<[u8]>,
43 prg_bank: u8,
44 mirroring: Mirroring,
45 has_single_screen: bool,
46}
47
48impl Camerica {
49 pub fn new(
55 prg_rom: Box<[u8]>,
56 mirroring: Mirroring,
57 has_single_screen: bool,
58 ) -> Result<Self, MapperError> {
59 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
60 return Err(MapperError::Invalid(format!(
61 "Camerica PRG-ROM size {} is not a non-zero multiple of 16 KiB",
62 prg_rom.len()
63 )));
64 }
65 Ok(Self {
66 prg_rom,
67 chr_ram: vec![0u8; CHR_BANK_8K].into_boxed_slice(),
68 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
69 prg_bank: 0,
70 mirroring,
71 has_single_screen,
72 })
73 }
74}
75
76impl Mapper for Camerica {
77 fn caps(&self) -> MapperCaps {
80 MapperCaps::NONE
81 }
82
83 fn cpu_read(&mut self, addr: u16) -> u8 {
84 let total_16k = (self.prg_rom.len() / PRG_BANK_16K).max(1);
85 let last = total_16k - 1;
86 match addr {
87 0x8000..=0xBFFF => {
88 let bank = (self.prg_bank as usize) % total_16k;
89 self.prg_rom[(bank * PRG_BANK_16K + (addr as usize - 0x8000)) % self.prg_rom.len()]
90 }
91 0xC000..=0xFFFF => {
92 self.prg_rom[(last * PRG_BANK_16K + (addr as usize - 0xC000)) % self.prg_rom.len()]
93 }
94 _ => 0,
95 }
96 }
97
98 fn cpu_write(&mut self, addr: u16, value: u8) {
99 match addr {
100 0x9000..=0x9FFF if self.has_single_screen => {
101 self.mirroring = if value & 0x10 == 0 {
102 Mirroring::SingleScreenA
103 } else {
104 Mirroring::SingleScreenB
105 };
106 }
107 0xC000..=0xFFFF | 0x8000..=0xBFFF => self.prg_bank = value & 0x0F,
108 _ => {}
109 }
110 }
111
112 fn ppu_read(&mut self, addr: u16) -> u8 {
113 let addr = addr & 0x3FFF;
114 match addr {
115 0x0000..=0x1FFF => self.chr_ram[addr as usize % self.chr_ram.len()],
116 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring) % self.vram.len()],
117 _ => 0,
118 }
119 }
120
121 fn ppu_write(&mut self, addr: u16, value: u8) {
122 let addr = addr & 0x3FFF;
123 match addr {
124 0x0000..=0x1FFF => {
125 let len = self.chr_ram.len();
126 self.chr_ram[addr as usize % len] = value;
127 }
128 0x2000..=0x3EFF => {
129 let off = nametable_offset(addr, self.mirroring) % self.vram.len();
130 self.vram[off] = value;
131 }
132 _ => {}
133 }
134 }
135
136 fn current_mirroring(&self) -> Mirroring {
137 self.mirroring
138 }
139
140 fn save_state(&self) -> Vec<u8> {
141 let mut out = Vec::with_capacity(4 + self.vram.len() + self.chr_ram.len());
142 out.push(1u8);
143 out.push(self.prg_bank);
144 out.push(self.mirroring as u8);
145 out.push(u8::from(self.has_single_screen));
146 out.extend_from_slice(&self.chr_ram);
147 out.extend_from_slice(&self.vram);
148 out
149 }
150
151 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
152 let expected = 4 + self.chr_ram.len() + self.vram.len();
153 if data.len() != expected {
154 return Err(MapperError::WrongLength {
155 expected,
156 got: data.len(),
157 });
158 }
159 if data[0] != 1 {
160 return Err(MapperError::UnsupportedVersion(data[0]));
161 }
162 self.prg_bank = data[1];
163 self.mirroring = match data[2] {
164 0 => Mirroring::Horizontal,
165 1 => Mirroring::Vertical,
166 2 => Mirroring::SingleScreenA,
167 3 => Mirroring::SingleScreenB,
168 4 => Mirroring::FourScreen,
169 5 => Mirroring::MapperControlled,
170 other => return Err(MapperError::Invalid(format!("mirroring {other}"))),
171 };
172 self.has_single_screen = data[3] != 0;
173 let mut cur = 4usize;
174 self.chr_ram
175 .copy_from_slice(&data[cur..cur + self.chr_ram.len()]);
176 cur += self.chr_ram.len();
177 self.vram.copy_from_slice(&data[cur..cur + self.vram.len()]);
178 Ok(())
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 const PRG_BANK_8K: usize = 0x2000;
187
188 fn synth(banks_8k: usize) -> Box<[u8]> {
189 let mut v = vec![0u8; banks_8k * PRG_BANK_8K];
190 for b in 0..banks_8k {
191 v[b * PRG_BANK_8K] = b as u8;
192 }
193 v.into_boxed_slice()
194 }
195
196 #[test]
197 fn camerica_bank_swap() {
198 let mut m = Camerica::new(synth(8 * 2), Mirroring::Vertical, false).unwrap();
199 assert_eq!(m.cpu_read(0x8000), 0);
201 m.cpu_write(0xC000, 5);
202 assert_ne!(m.cpu_read(0x8000), 0);
205 }
206}