rustynes_mappers/
m077_irem_napoleon.rs1#![allow(
20 clippy::bool_to_int_with_if,
21 clippy::cast_lossless,
22 clippy::cast_possible_truncation,
23 clippy::doc_markdown,
24 clippy::match_same_arms,
25 clippy::missing_const_for_fn,
26 clippy::similar_names,
27 clippy::struct_excessive_bools,
28 clippy::too_many_lines,
29 clippy::unreadable_literal
30)]
31
32use crate::cartridge::Mirroring;
33use crate::mapper::{Mapper, MapperCaps, MapperError};
34use alloc::{boxed::Box, vec::Vec};
35use alloc::{format, vec};
36
37const PRG_BANK_32K: usize = 0x8000;
38const CHR_BANK_2K: usize = 0x0800;
39const NAMETABLE_SIZE: usize = 0x0400;
40const NAMETABLE_SIZE_U16: u16 = 0x0400;
41
42const SAVE_STATE_VERSION: u8 = 1;
43
44pub struct Irem77 {
50 prg_rom: Box<[u8]>,
51 chr_rom: Box<[u8]>,
52 chr_ram: Box<[u8]>,
54 nt_ram: Box<[u8]>,
56 prg_bank: u8,
57 chr_bank: u8,
58}
59
60impl Irem77 {
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 77 PRG-ROM size {} is not a non-zero multiple of 32 KiB",
71 prg_rom.len()
72 )));
73 }
74 if chr_rom.is_empty() || !chr_rom.len().is_multiple_of(CHR_BANK_2K) {
75 return Err(MapperError::Invalid(format!(
76 "mapper 77 CHR-ROM size {} is not a non-zero multiple of 2 KiB",
77 chr_rom.len()
78 )));
79 }
80 Ok(Self {
81 prg_rom,
82 chr_rom,
83 chr_ram: vec![0u8; 0x1800].into_boxed_slice(), nt_ram: vec![0u8; 4 * NAMETABLE_SIZE].into_boxed_slice(),
85 prg_bank: 0,
86 chr_bank: 0,
87 })
88 }
89
90 fn read_prg(&self, addr: u16) -> u8 {
91 let count = (self.prg_rom.len() / PRG_BANK_32K).max(1);
92 let bank = (self.prg_bank as usize) % count;
93 self.prg_rom[bank * PRG_BANK_32K + (addr as usize - 0x8000)]
94 }
95
96 const fn nt_offset(addr: u16) -> usize {
98 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as usize;
99 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
100 table * NAMETABLE_SIZE + local
101 }
102}
103
104impl Mapper for Irem77 {
105 fn caps(&self) -> MapperCaps {
106 MapperCaps::NONE
107 }
108
109 fn cpu_read(&mut self, addr: u16) -> u8 {
110 if (0x8000..=0xFFFF).contains(&addr) {
111 self.read_prg(addr)
112 } else {
113 0
114 }
115 }
116
117 fn cpu_write(&mut self, addr: u16, value: u8) {
118 if (0x8000..=0xFFFF).contains(&addr) {
119 let effective = value & self.read_prg(addr);
121 self.prg_bank = effective & 0x0F;
122 self.chr_bank = (effective >> 4) & 0x0F;
123 }
124 }
125
126 fn ppu_read(&mut self, addr: u16) -> u8 {
127 let addr = addr & 0x3FFF;
128 match addr {
129 0x0000..=0x07FF => {
130 let count = (self.chr_rom.len() / CHR_BANK_2K).max(1);
132 let bank = (self.chr_bank as usize) % count;
133 self.chr_rom[bank * CHR_BANK_2K + addr as usize]
134 }
135 0x0800..=0x1FFF => self.chr_ram[addr as usize - 0x0800],
136 0x2000..=0x3EFF => self.nt_ram[Self::nt_offset(addr)],
137 _ => 0,
138 }
139 }
140
141 fn ppu_write(&mut self, addr: u16, value: u8) {
142 let addr = addr & 0x3FFF;
143 match addr {
145 0x0800..=0x1FFF => self.chr_ram[addr as usize - 0x0800] = value,
146 0x2000..=0x3EFF => self.nt_ram[Self::nt_offset(addr)] = value,
147 _ => {}
148 }
149 }
150
151 fn nametable_fetch(&mut self, addr: u16) -> Option<u8> {
152 Some(self.nt_ram[Self::nt_offset(addr)])
154 }
155
156 fn nametable_write(&mut self, addr: u16, value: u8) -> bool {
157 self.nt_ram[Self::nt_offset(addr)] = value;
158 true
159 }
160
161 fn current_mirroring(&self) -> Mirroring {
162 Mirroring::FourScreen
163 }
164
165 fn save_state(&self) -> Vec<u8> {
166 let mut out = Vec::with_capacity(3 + self.chr_ram.len() + self.nt_ram.len());
167 out.push(SAVE_STATE_VERSION);
168 out.push(self.prg_bank);
169 out.push(self.chr_bank);
170 out.extend_from_slice(&self.chr_ram);
171 out.extend_from_slice(&self.nt_ram);
172 out
173 }
174
175 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
176 let expected = 3 + self.chr_ram.len() + self.nt_ram.len();
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 let mut cursor = 3;
189 self.chr_ram
190 .copy_from_slice(&data[cursor..cursor + self.chr_ram.len()]);
191 cursor += self.chr_ram.len();
192 self.nt_ram
193 .copy_from_slice(&data[cursor..cursor + self.nt_ram.len()]);
194 Ok(())
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 fn synth_prg_32k(banks: usize) -> Box<[u8]> {
203 let mut v = vec![0xFFu8; banks * PRG_BANK_32K];
204 for b in 0..banks {
205 v[b * PRG_BANK_32K] = b as u8;
206 }
207 v.into_boxed_slice()
208 }
209
210 fn synth_chr_2k(banks: usize) -> Box<[u8]> {
211 let mut v = vec![0u8; banks * CHR_BANK_2K];
212 for b in 0..banks {
213 v[b * CHR_BANK_2K] = b as u8;
214 }
215 v.into_boxed_slice()
216 }
217
218 #[test]
219 fn m77_prg_and_2k_chr_with_bus_conflict() {
220 let mut m = Irem77::new(synth_prg_32k(4), synth_chr_2k(16)).unwrap();
221 m.cpu_write(0x8001, 0b0011_0010);
224 assert_eq!(m.cpu_read(0x8000), 2);
225 assert_eq!(m.ppu_read(0x0000), 3);
226 assert_eq!(m.current_mirroring(), Mirroring::FourScreen);
227 }
228
229 #[test]
230 fn m77_chr_ram_and_four_screen_nt() {
231 let mut m = Irem77::new(synth_prg_32k(2), synth_chr_2k(8)).unwrap();
232 m.ppu_write(0x0800, 0xAB);
234 assert_eq!(m.ppu_read(0x0800), 0xAB);
235 assert!(m.nametable_write(0x2000, 0x11));
237 assert!(m.nametable_write(0x2400, 0x22));
238 assert!(m.nametable_write(0x2800, 0x33));
239 assert!(m.nametable_write(0x2C00, 0x44));
240 assert_eq!(m.nametable_fetch(0x2000), Some(0x11));
241 assert_eq!(m.nametable_fetch(0x2400), Some(0x22));
242 assert_eq!(m.nametable_fetch(0x2800), Some(0x33));
243 assert_eq!(m.nametable_fetch(0x2C00), Some(0x44));
244 }
245}