1#![allow(clippy::cast_possible_truncation, clippy::doc_markdown)]
35
36use crate::cartridge::Mirroring;
37use crate::mapper::{Mapper, MapperCaps, MapperError};
38use alloc::{boxed::Box, vec::Vec};
39use alloc::{format, vec};
40
41const PRG_BANK_8K: usize = 0x2000;
42const CHR_BANK_1K: usize = 0x0400;
43const NAMETABLE_SIZE: usize = 0x0400;
44const NAMETABLE_SIZE_U16: u16 = 0x0400;
45
46const SAVE_STATE_VERSION: u8 = 1;
47
48pub struct IremG101 {
50 prg_rom: Box<[u8]>,
51 chr: Box<[u8]>,
52 vram: Box<[u8]>,
53 chr_is_ram: bool,
54 prg_bank: [u8; 2],
56 chr_1k: [u8; 8],
58 prg_swap_mode: bool,
60 mirroring: Mirroring,
61 force_one_screen: bool,
64}
65
66impl IremG101 {
67 pub fn new(
78 prg_rom: Box<[u8]>,
79 chr_rom: Box<[u8]>,
80 mirroring: Mirroring,
81 force_one_screen: bool,
82 ) -> Result<Self, MapperError> {
83 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
84 return Err(MapperError::Invalid(format!(
85 "Irem-G101 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
86 prg_rom.len()
87 )));
88 }
89 let chr_is_ram = chr_rom.is_empty();
90 let chr: Box<[u8]> = if chr_is_ram {
91 vec![0u8; 8 * CHR_BANK_1K].into_boxed_slice()
92 } else if chr_rom.len().is_multiple_of(CHR_BANK_1K) {
93 chr_rom
94 } else {
95 return Err(MapperError::Invalid(format!(
96 "Irem-G101 expects a 1 KiB multiple of CHR; got {} bytes",
97 chr_rom.len()
98 )));
99 };
100 let mirroring = if force_one_screen {
101 Mirroring::SingleScreenA
102 } else {
103 mirroring
104 };
105 Ok(Self {
106 prg_rom,
107 chr,
108 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
109 chr_is_ram,
110 prg_bank: [0, 0],
111 chr_1k: [0, 0, 0, 0, 0, 0, 0, 0],
112 prg_swap_mode: false,
113 mirroring,
114 force_one_screen,
115 })
116 }
117
118 const fn nametable_offset(&self, addr: u16) -> usize {
119 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
120 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
121 let physical = self.mirroring.physical_bank(table);
122 physical * NAMETABLE_SIZE + local
123 }
124
125 fn read_prg(&self, addr: u16) -> u8 {
126 let bank_count = (self.prg_rom.len() / PRG_BANK_8K).max(1);
127 let fixed_second_last = bank_count.saturating_sub(2);
134 let slot = (addr >> 13) & 0x03; let bank = match slot {
136 0 => {
137 if self.prg_swap_mode {
138 fixed_second_last } else {
140 self.prg_bank[0] as usize }
142 }
143 1 => self.prg_bank[1] as usize, 2 => {
145 if self.prg_swap_mode {
146 self.prg_bank[0] as usize } else {
148 fixed_second_last }
150 }
151 _ => bank_count - 1, } % bank_count;
153 let off = (addr as usize) & (PRG_BANK_8K - 1);
154 self.prg_rom[bank * PRG_BANK_8K + off]
155 }
156
157 fn chr_offset(&self, addr: u16) -> usize {
158 let len = self.chr.len().max(1);
159 let idx = ((addr >> 10) & 0x07) as usize; let base = (self.chr_1k[idx] as usize) * CHR_BANK_1K;
161 (base + (addr as usize & (CHR_BANK_1K - 1))) % len
162 }
163}
164
165impl Mapper for IremG101 {
166 fn caps(&self) -> MapperCaps {
169 MapperCaps::NONE
170 }
171
172 fn cpu_read(&mut self, addr: u16) -> u8 {
173 if (0x8000..=0xFFFF).contains(&addr) {
174 self.read_prg(addr)
175 } else {
176 0
177 }
178 }
179
180 fn cpu_write(&mut self, addr: u16, value: u8) {
181 if !(0x8000..=0xFFFF).contains(&addr) {
182 return;
183 }
184 match addr & 0xF000 {
185 0x8000 => self.prg_bank[0] = value & 0x3F,
186 0x9000 => {
187 if !self.force_one_screen {
188 self.mirroring = if (value & 0x01) != 0 {
189 Mirroring::Horizontal
190 } else {
191 Mirroring::Vertical
192 };
193 }
194 self.prg_swap_mode = (value & 0x02) != 0;
195 }
196 0xA000 => self.prg_bank[1] = value & 0x3F,
197 0xB000 => {
198 let idx = (addr & 0x07) as usize;
199 self.chr_1k[idx] = value;
200 }
201 _ => {}
202 }
203 }
204
205 fn ppu_read(&mut self, addr: u16) -> u8 {
206 let addr = addr & 0x3FFF;
207 match addr {
208 0x0000..=0x1FFF => self.chr[self.chr_offset(addr)],
209 0x2000..=0x3EFF => self.vram[self.nametable_offset(addr)],
210 _ => 0,
211 }
212 }
213
214 fn ppu_write(&mut self, addr: u16, value: u8) {
215 let addr = addr & 0x3FFF;
216 match addr {
217 0x0000..=0x1FFF => {
218 if self.chr_is_ram {
219 let off = self.chr_offset(addr);
220 self.chr[off] = value;
221 }
222 }
223 0x2000..=0x3EFF => {
224 let off = self.nametable_offset(addr);
225 self.vram[off] = value;
226 }
227 _ => {}
228 }
229 }
230
231 fn current_mirroring(&self) -> Mirroring {
232 self.mirroring
233 }
234
235 fn debug_info(&self) -> crate::mapper::MapperDebugInfo {
236 let mut info = crate::mapper::MapperDebugInfo {
237 mapper_id: 32,
238 name: "Irem G-101 (32)".into(),
239 mirroring: crate::mapper::mirroring_name(self.mirroring),
240 ..Default::default()
241 };
242 info.prg_banks
243 .push(("swap".into(), format!("{}", u8::from(self.prg_swap_mode))));
244 for (i, b) in self.prg_bank.iter().enumerate() {
245 info.prg_banks
246 .push((format!("PRG{i}"), format!("{b:#04x}")));
247 }
248 for (i, b) in self.chr_1k.iter().enumerate() {
249 info.chr_banks
250 .push((format!("CHR{i}"), format!("{b:#04x}")));
251 }
252 info
253 }
254
255 fn save_state(&self) -> Vec<u8> {
256 let mut out = Vec::with_capacity(
257 13 + self.vram.len() + if self.chr_is_ram { self.chr.len() } else { 0 },
258 );
259 out.push(SAVE_STATE_VERSION);
260 out.extend_from_slice(&self.prg_bank);
261 out.extend_from_slice(&self.chr_1k);
262 out.push(u8::from(self.prg_swap_mode));
263 out.push(match self.mirroring {
264 Mirroring::Horizontal => 1,
265 Mirroring::SingleScreenA => 2,
266 _ => 0,
267 });
268 out.extend_from_slice(&self.vram);
269 if self.chr_is_ram {
270 out.extend_from_slice(&self.chr);
271 }
272 out
273 }
274
275 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
276 let need_chr = if self.chr_is_ram { self.chr.len() } else { 0 };
277 let expected = 13 + self.vram.len() + need_chr;
278 if data.len() != expected {
279 return Err(MapperError::WrongLength {
280 expected,
281 got: data.len(),
282 });
283 }
284 if data[0] != SAVE_STATE_VERSION {
285 return Err(MapperError::UnsupportedVersion(data[0]));
286 }
287 self.prg_bank.copy_from_slice(&data[1..3]);
288 self.chr_1k.copy_from_slice(&data[3..11]);
289 self.prg_swap_mode = data[11] != 0;
290 self.mirroring = if self.force_one_screen {
292 Mirroring::SingleScreenA
293 } else {
294 match data[12] {
295 1 => Mirroring::Horizontal,
296 _ => Mirroring::Vertical,
297 }
298 };
299 let mut cursor = 13;
300 self.vram
301 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
302 cursor += self.vram.len();
303 if self.chr_is_ram {
304 self.chr
305 .copy_from_slice(&data[cursor..cursor + self.chr.len()]);
306 }
307 Ok(())
308 }
309}
310
311#[cfg(test)]
312#[allow(clippy::cast_possible_truncation)]
313mod tests {
314 use super::*;
315
316 fn synth_prg(banks_8k: usize) -> Box<[u8]> {
317 let mut v = vec![0u8; banks_8k * PRG_BANK_8K];
318 for b in 0..banks_8k {
319 v[b * PRG_BANK_8K] = b as u8;
320 }
321 v.into_boxed_slice()
322 }
323
324 fn synth_chr_1k(banks: usize) -> Box<[u8]> {
325 let mut v = vec![0u8; banks * CHR_BANK_1K];
326 for b in 0..banks {
327 v[b * CHR_BANK_1K] = b as u8;
328 }
329 v.into_boxed_slice()
330 }
331
332 #[test]
333 fn prg_mode_0_default_and_fixed_tail() {
334 let mut m =
335 IremG101::new(synth_prg(8), synth_chr_1k(8), Mirroring::Vertical, false).unwrap();
336 assert_eq!(m.cpu_read(0x8000), 0);
338 assert_eq!(m.cpu_read(0xA000), 0);
339 assert_eq!(m.cpu_read(0xC000), 6);
340 assert_eq!(m.cpu_read(0xE000), 7);
341 m.cpu_write(0x8000, 0x03);
342 m.cpu_write(0xA000, 0x05);
343 assert_eq!(m.cpu_read(0x8000), 3);
344 assert_eq!(m.cpu_read(0xA000), 5);
345 assert_eq!(m.cpu_read(0xC000), 6);
346 assert_eq!(m.cpu_read(0xE000), 7);
347 }
348
349 #[test]
350 fn prg_swap_mode_1_fixes_8000_swaps_c000() {
351 let mut m =
352 IremG101::new(synth_prg(8), synth_chr_1k(8), Mirroring::Vertical, false).unwrap();
353 m.cpu_write(0x8000, 0x03); m.cpu_write(0x9000, 0x02); assert_eq!(m.cpu_read(0x8000), 6);
357 assert_eq!(m.cpu_read(0xC000), 3);
358 assert_eq!(m.cpu_read(0xE000), 7);
359 }
360
361 #[test]
362 fn mirroring_bit_and_chr_banks() {
363 let mut m =
364 IremG101::new(synth_prg(4), synth_chr_1k(16), Mirroring::Vertical, false).unwrap();
365 m.cpu_write(0x9000, 0x01); assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
367 m.cpu_write(0x9000, 0x00); assert_eq!(m.current_mirroring(), Mirroring::Vertical);
369 m.cpu_write(0xB000, 0x09);
371 assert_eq!(m.ppu_read(0x0000), 9);
372 m.cpu_write(0xB007, 0x0B);
374 assert_eq!(m.ppu_read(0x1C00), 11);
375 }
376
377 #[test]
378 fn force_one_screen_ignores_mirroring_bit() {
379 let mut m =
380 IremG101::new(synth_prg(4), synth_chr_1k(8), Mirroring::Vertical, true).unwrap();
381 assert_eq!(m.current_mirroring(), Mirroring::SingleScreenA);
382 m.cpu_write(0x9000, 0x01); assert_eq!(m.current_mirroring(), Mirroring::SingleScreenA);
384 assert!(!m.prg_swap_mode);
386 m.cpu_write(0x9000, 0x02);
387 assert!(m.prg_swap_mode);
388 }
389
390 #[test]
391 fn save_state_round_trip() {
392 let mut m =
393 IremG101::new(synth_prg(8), synth_chr_1k(16), Mirroring::Vertical, false).unwrap();
394 m.cpu_write(0x8000, 0x05);
395 m.cpu_write(0x9000, 0x03); m.cpu_write(0xA000, 0x07);
397 m.cpu_write(0xB003, 0x0A);
398 let blob = m.save_state();
399 let mut m2 =
400 IremG101::new(synth_prg(8), synth_chr_1k(16), Mirroring::Vertical, false).unwrap();
401 m2.load_state(&blob).unwrap();
402 assert_eq!(m.cpu_read(0x8000), m2.cpu_read(0x8000));
403 assert_eq!(m.cpu_read(0xC000), m2.cpu_read(0xC000));
404 assert_eq!(m.ppu_read(0x0C00), m2.ppu_read(0x0C00));
405 assert_eq!(m.current_mirroring(), m2.current_mirroring());
406 }
407}