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