rustynes_mappers/
m113_ave_nina006.rs1#![allow(
18 clippy::bool_to_int_with_if,
19 clippy::cast_lossless,
20 clippy::cast_possible_truncation,
21 clippy::doc_markdown,
22 clippy::match_same_arms,
23 clippy::missing_const_for_fn,
24 clippy::similar_names,
25 clippy::struct_excessive_bools,
26 clippy::too_many_lines,
27 clippy::unreadable_literal
28)]
29
30use crate::cartridge::Mirroring;
31use crate::mapper::{Mapper, MapperCaps, MapperError};
32use alloc::{boxed::Box, vec::Vec};
33use alloc::{format, vec};
34
35const PRG_BANK_32K: usize = 0x8000;
36const CHR_BANK_8K: usize = 0x2000;
37const NAMETABLE_SIZE: usize = 0x0400;
38const NAMETABLE_SIZE_U16: u16 = 0x0400;
39
40const SAVE_STATE_VERSION: u8 = 1;
41
42const fn nametable_offset(addr: u16, mirroring: Mirroring) -> usize {
43 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
44 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
45 let physical = mirroring.physical_bank(table);
46 physical * NAMETABLE_SIZE + local
47}
48
49pub struct Nina006M113 {
51 prg_rom: Box<[u8]>,
52 chr: Box<[u8]>,
53 vram: Box<[u8]>,
54 chr_is_ram: bool,
55 prg_bank: u8,
56 chr_bank: u8,
57 vertical_mirroring: bool,
58}
59
60impl Nina006M113 {
61 pub fn new(prg_rom: Box<[u8]>, chr_rom: Box<[u8]>) -> Result<Self, MapperError> {
68 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
69 return Err(MapperError::Invalid(format!(
70 "mapper 113 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
71 prg_rom.len()
72 )));
73 }
74 let chr_is_ram = chr_rom.is_empty();
75 let chr: Box<[u8]> = if chr_is_ram {
76 vec![0u8; CHR_BANK_8K].into_boxed_slice()
77 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
78 chr_rom
79 } else {
80 return Err(MapperError::Invalid(format!(
81 "mapper 113 CHR-ROM size {} is not a multiple of 8 KiB",
82 chr_rom.len()
83 )));
84 };
85 Ok(Self {
86 prg_rom,
87 chr,
88 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
89 chr_is_ram,
90 prg_bank: 0,
91 chr_bank: 0,
92 vertical_mirroring: false,
93 })
94 }
95
96 fn chr_offset(&self, addr: u16) -> usize {
97 let count = (self.chr.len() / CHR_BANK_8K).max(1);
98 let bank = (self.chr_bank as usize) % count;
99 bank * CHR_BANK_8K + addr as usize
100 }
101}
102
103impl Mapper for Nina006M113 {
104 fn caps(&self) -> MapperCaps {
105 MapperCaps::NONE
106 }
107
108 fn cpu_read(&mut self, addr: u16) -> u8 {
109 if (0x8000..=0xFFFF).contains(&addr) {
110 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
111 let bank = (self.prg_bank as usize) % count;
112 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
113 } else {
114 0
115 }
116 }
117
118 fn cpu_write(&mut self, addr: u16, value: u8) {
119 if (0x4100..=0x5FFF).contains(&addr) && (addr & 0x0100) != 0 {
120 self.prg_bank = (value >> 3) & 0x07;
121 self.chr_bank = (value & 0x07) | ((value >> 3) & 0x08);
122 self.vertical_mirroring = (value & 0x80) != 0;
123 }
124 }
125
126 fn ppu_read(&mut self, addr: u16) -> u8 {
127 let addr = addr & 0x3FFF;
128 match addr {
129 0x0000..=0x1FFF => self.chr[self.chr_offset(addr)],
130 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.current_mirroring())],
131 _ => 0,
132 }
133 }
134
135 fn ppu_write(&mut self, addr: u16, value: u8) {
136 let addr = addr & 0x3FFF;
137 match addr {
138 0x0000..=0x1FFF => {
139 if self.chr_is_ram {
140 let off = self.chr_offset(addr);
141 self.chr[off] = value;
142 }
143 }
144 0x2000..=0x3EFF => {
145 let off = nametable_offset(addr, self.current_mirroring());
146 self.vram[off] = value;
147 }
148 _ => {}
149 }
150 }
151
152 fn current_mirroring(&self) -> Mirroring {
153 if self.vertical_mirroring {
154 Mirroring::Vertical
155 } else {
156 Mirroring::Horizontal
157 }
158 }
159
160 fn save_state(&self) -> Vec<u8> {
161 let chr_extra = if self.chr_is_ram { self.chr.len() } else { 0 };
162 let mut out = Vec::with_capacity(4 + self.vram.len() + chr_extra);
163 out.push(SAVE_STATE_VERSION);
164 out.push(self.prg_bank);
165 out.push(self.chr_bank);
166 out.push(u8::from(self.vertical_mirroring));
167 out.extend_from_slice(&self.vram);
168 if self.chr_is_ram {
169 out.extend_from_slice(&self.chr);
170 }
171 out
172 }
173
174 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
175 let chr_extra = if self.chr_is_ram { self.chr.len() } else { 0 };
176 let expected = 4 + self.vram.len() + chr_extra;
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.vertical_mirroring = data[3] != 0;
189 let mut cursor = 4;
190 self.vram
191 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
192 cursor += self.vram.len();
193 if self.chr_is_ram {
194 self.chr
195 .copy_from_slice(&data[cursor..cursor + self.chr.len()]);
196 }
197 Ok(())
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 fn synth_prg_32k(banks: usize) -> Box<[u8]> {
206 let mut v = vec![0xFFu8; banks * PRG_BANK_32K];
207 for b in 0..banks {
208 v[b * PRG_BANK_32K] = b as u8;
209 }
210 v.into_boxed_slice()
211 }
212
213 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
214 let mut v = vec![0u8; banks * CHR_BANK_8K];
215 for b in 0..banks {
216 v[b * CHR_BANK_8K] = b as u8;
217 }
218 v.into_boxed_slice()
219 }
220
221 #[test]
222 fn m113_decodes_prg_chr_and_mirroring() {
223 let mut m = Nina006M113::new(synth_prg_32k(4), synth_chr_8k(16)).unwrap();
224 m.cpu_write(0x4100, 0b1100_0010);
229 assert_eq!(m.cpu_read(0x8000), 0);
230 assert_eq!(m.ppu_read(0x0000), 10);
231 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
232 m.cpu_write(0x4100, 0b0000_0001);
234 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
235 }
236}