rustynes_mappers/
m079_ave_nina03_06.rs1#![allow(
23 clippy::bool_to_int_with_if,
24 clippy::cast_lossless,
25 clippy::cast_possible_truncation,
26 clippy::doc_markdown,
27 clippy::match_same_arms,
28 clippy::missing_const_for_fn,
29 clippy::similar_names,
30 clippy::struct_excessive_bools,
31 clippy::too_many_lines,
32 clippy::unreadable_literal
33)]
34
35use crate::cartridge::Mirroring;
36use crate::mapper::{Mapper, MapperCaps, MapperError};
37use alloc::{boxed::Box, vec::Vec};
38use alloc::{format, vec};
39
40const PRG_BANK_32K: usize = 0x8000;
41const CHR_BANK_8K: usize = 0x2000;
42const NAMETABLE_SIZE: usize = 0x0400;
43const NAMETABLE_SIZE_U16: u16 = 0x0400;
44
45const SAVE_STATE_VERSION: u8 = 1;
46
47const 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 Nina0379 {
56 prg_rom: Box<[u8]>,
57 chr: Box<[u8]>,
58 vram: Box<[u8]>,
59 chr_is_ram: bool,
60 prg_bank: u8,
61 chr_bank: u8,
62 mirroring: Mirroring,
63}
64
65impl Nina0379 {
66 pub fn new(
73 prg_rom: Box<[u8]>,
74 chr_rom: Box<[u8]>,
75 mirroring: Mirroring,
76 ) -> Result<Self, MapperError> {
77 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_32K) {
78 return Err(MapperError::Invalid(format!(
79 "mapper 79 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
80 prg_rom.len()
81 )));
82 }
83 let chr_is_ram = chr_rom.is_empty();
84 let chr: Box<[u8]> = if chr_is_ram {
85 vec![0u8; CHR_BANK_8K].into_boxed_slice()
86 } else if chr_rom.len().is_multiple_of(CHR_BANK_8K) {
87 chr_rom
88 } else {
89 return Err(MapperError::Invalid(format!(
90 "mapper 79 CHR-ROM size {} is not a multiple of 8 KiB",
91 chr_rom.len()
92 )));
93 };
94 Ok(Self {
95 prg_rom,
96 chr,
97 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
98 chr_is_ram,
99 prg_bank: 0,
100 chr_bank: 0,
101 mirroring,
102 })
103 }
104
105 fn chr_offset(&self, addr: u16) -> usize {
106 let count = (self.chr.len() / CHR_BANK_8K).max(1);
107 let bank = (self.chr_bank as usize) % count;
108 bank * CHR_BANK_8K + addr as usize
109 }
110}
111
112impl Mapper for Nina0379 {
113 fn has_hardwired_mirroring(&self) -> bool {
116 true
117 }
118
119 fn caps(&self) -> MapperCaps {
120 MapperCaps::NONE
121 }
122
123 fn cpu_read(&mut self, addr: u16) -> u8 {
127 if (0x8000..=0xFFFF).contains(&addr) {
128 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
129 let bank = (self.prg_bank as usize) % count;
130 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
131 } else {
132 0
133 }
134 }
135
136 fn cpu_write(&mut self, addr: u16, value: u8) {
137 if (0x4100..=0x5FFF).contains(&addr) && (addr & 0x0100) != 0 {
139 self.chr_bank = value & 0x07;
140 self.prg_bank = (value >> 3) & 0x01;
141 }
142 }
143
144 fn ppu_read(&mut self, addr: u16) -> u8 {
145 let addr = addr & 0x3FFF;
146 match addr {
147 0x0000..=0x1FFF => self.chr[self.chr_offset(addr)],
148 0x2000..=0x3EFF => self.vram[nametable_offset(addr, self.mirroring)],
149 _ => 0,
150 }
151 }
152
153 fn ppu_write(&mut self, addr: u16, value: u8) {
154 let addr = addr & 0x3FFF;
155 match addr {
156 0x0000..=0x1FFF => {
157 if self.chr_is_ram {
158 let off = self.chr_offset(addr);
159 self.chr[off] = value;
160 }
161 }
162 0x2000..=0x3EFF => {
163 let off = nametable_offset(addr, self.mirroring);
164 self.vram[off] = value;
165 }
166 _ => {}
167 }
168 }
169
170 fn current_mirroring(&self) -> Mirroring {
171 self.mirroring
172 }
173
174 fn save_state(&self) -> Vec<u8> {
175 let chr_extra = if self.chr_is_ram { self.chr.len() } else { 0 };
176 let mut out = Vec::with_capacity(3 + self.vram.len() + chr_extra);
177 out.push(SAVE_STATE_VERSION);
178 out.push(self.prg_bank);
179 out.push(self.chr_bank);
180 out.extend_from_slice(&self.vram);
181 if self.chr_is_ram {
182 out.extend_from_slice(&self.chr);
183 }
184 out
185 }
186
187 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
188 let chr_extra = if self.chr_is_ram { self.chr.len() } else { 0 };
189 let expected = 3 + self.vram.len() + chr_extra;
190 if data.len() != expected {
191 return Err(MapperError::WrongLength {
192 expected,
193 got: data.len(),
194 });
195 }
196 if data[0] != SAVE_STATE_VERSION {
197 return Err(MapperError::UnsupportedVersion(data[0]));
198 }
199 self.prg_bank = data[1];
200 self.chr_bank = data[2];
201 let mut cursor = 3;
202 self.vram
203 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
204 cursor += self.vram.len();
205 if self.chr_is_ram {
206 self.chr
207 .copy_from_slice(&data[cursor..cursor + self.chr.len()]);
208 }
209 Ok(())
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 fn synth_prg_32k(banks: usize) -> Box<[u8]> {
218 let mut v = vec![0xFFu8; banks * PRG_BANK_32K];
219 for b in 0..banks {
220 v[b * PRG_BANK_32K] = b as u8;
221 }
222 v.into_boxed_slice()
223 }
224
225 fn synth_chr_8k(banks: usize) -> Box<[u8]> {
226 let mut v = vec![0u8; banks * CHR_BANK_8K];
227 for b in 0..banks {
228 v[b * CHR_BANK_8K] = b as u8;
229 }
230 v.into_boxed_slice()
231 }
232
233 #[test]
234 fn m79_register_decodes_on_a8() {
235 let mut m =
236 Nina0379::new(synth_prg_32k(2), synth_chr_8k(8), Mirroring::Horizontal).unwrap();
237 m.cpu_write(0x4100, 0b0000_1101);
239 assert_eq!(m.cpu_read(0x8000), 1);
240 assert_eq!(m.ppu_read(0x0000), 5);
241 }
242
243 #[test]
244 fn m79_no_decode_without_a8() {
245 let mut m =
246 Nina0379::new(synth_prg_32k(2), synth_chr_8k(8), Mirroring::Horizontal).unwrap();
247 m.cpu_write(0x4200, 0b0000_1111);
250 assert_eq!(m.cpu_read(0x8000), 0);
251 assert_eq!(m.ppu_read(0x0000), 0);
252 }
253}