1#![allow(
38 clippy::cast_possible_truncation,
39 clippy::cast_lossless,
40 clippy::missing_const_for_fn,
41 clippy::struct_excessive_bools,
42 clippy::match_same_arms,
43 clippy::doc_markdown
44)]
45
46use crate::cartridge::Mirroring;
47use crate::mapper::{Mapper, MapperCaps, MapperError};
48use alloc::{boxed::Box, vec::Vec};
49use alloc::{format, vec};
50
51const PRG_BANK_8K: usize = 0x2000;
52const CHR_BANK_1K: usize = 0x0400;
53const NAMETABLE_SIZE: usize = 0x0400;
54const NAMETABLE_SIZE_U16: u16 = 0x0400;
55
56const SAVE_STATE_VERSION: u8 = 1;
57
58pub struct IremH3001 {
60 prg_rom: Box<[u8]>,
61 chr_rom: Box<[u8]>,
62 vram: Box<[u8]>,
63 chr_is_ram: bool,
64
65 prg_reg0: u8,
66 prg_reg1: u8,
67 prg_layout: bool, chr_regs: [u8; 8], mirroring: Mirroring,
71
72 irq_reload: u16,
73 irq_counter: u16,
74 irq_enabled: bool,
75 irq_pending: bool,
76}
77
78impl IremH3001 {
79 pub fn new(
89 prg_rom: Box<[u8]>,
90 chr_rom: Box<[u8]>,
91 mirroring: Mirroring,
92 ) -> Result<Self, MapperError> {
93 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
94 return Err(MapperError::Invalid(format!(
95 "H3001 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
96 prg_rom.len()
97 )));
98 }
99 let chr_is_ram = chr_rom.is_empty();
100 let chr_data: Box<[u8]> = if chr_is_ram {
101 vec![0u8; 8 * CHR_BANK_1K].into_boxed_slice()
102 } else if chr_rom.len().is_multiple_of(CHR_BANK_1K) {
103 chr_rom
104 } else {
105 return Err(MapperError::Invalid(format!(
106 "H3001 CHR-ROM size {} is not a multiple of 1 KiB",
107 chr_rom.len()
108 )));
109 };
110 Ok(Self {
111 prg_rom,
112 chr_rom: chr_data,
113 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
114 chr_is_ram,
115 prg_reg0: 0x00,
117 prg_reg1: 0x01,
118 prg_layout: false,
119 chr_regs: [0; 8],
120 mirroring,
121 irq_reload: 0,
122 irq_counter: 0,
123 irq_enabled: false,
124 irq_pending: false,
125 })
126 }
127
128 fn prg_offset(&self, addr: u16) -> usize {
129 let total = (self.prg_rom.len() / PRG_BANK_8K).max(1);
130 let fixed_3e = 0x3E % total;
133 let fixed_3f = 0x3F % total;
134 let bank = match (addr & 0xE000, self.prg_layout) {
135 (0x8000, false) => self.prg_reg0 as usize,
136 (0x8000, true) => fixed_3e,
137 (0xA000, _) => self.prg_reg1 as usize,
138 (0xC000, false) => fixed_3e,
139 (0xC000, true) => self.prg_reg0 as usize,
140 (0xE000, _) => fixed_3f,
141 _ => 0,
142 };
143 (bank % total) * PRG_BANK_8K + (addr as usize & 0x1FFF)
144 }
145
146 fn chr_offset(&self, addr: u16) -> usize {
147 let addr = (addr & 0x1FFF) as usize;
148 let total_1k = (self.chr_rom.len() / CHR_BANK_1K).max(1);
149 let slot = addr / CHR_BANK_1K;
150 let bank = (self.chr_regs[slot] as usize) % total_1k;
151 bank * CHR_BANK_1K + (addr & (CHR_BANK_1K - 1))
152 }
153
154 fn nametable_offset(&self, addr: u16) -> usize {
155 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
156 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
157 let physical = self.mirroring.physical_bank(table);
158 physical * NAMETABLE_SIZE + local
159 }
160}
161
162impl Mapper for IremH3001 {
163 fn caps(&self) -> MapperCaps {
165 MapperCaps::CYCLE_IRQ
166 }
167
168 fn cpu_read(&mut self, addr: u16) -> u8 {
169 match addr {
170 0x8000..=0xFFFF => {
171 let off = self.prg_offset(addr);
172 self.prg_rom[off % self.prg_rom.len()]
173 }
174 _ => 0,
175 }
176 }
177
178 fn cpu_write(&mut self, addr: u16, value: u8) {
179 match addr {
180 0x8000..=0x8FFF => self.prg_reg0 = value,
181 0x9000..=0x9FFF => match addr & 0x0007 {
182 0 => self.prg_layout = (value & 0x80) != 0,
183 1 => {
184 self.mirroring = match (value >> 6) & 0x03 {
185 0 => Mirroring::Vertical,
186 2 => Mirroring::Horizontal,
187 _ => Mirroring::SingleScreenA,
189 };
190 }
191 3 => {
192 self.irq_enabled = (value & 0x80) != 0;
193 self.irq_pending = false;
194 }
195 4 => {
196 self.irq_counter = self.irq_reload;
197 self.irq_pending = false;
198 }
199 5 => self.irq_reload = (self.irq_reload & 0x00FF) | ((value as u16) << 8),
200 6 => self.irq_reload = (self.irq_reload & 0xFF00) | (value as u16),
201 _ => {}
202 },
203 0xA000..=0xAFFF => self.prg_reg1 = value,
204 0xB000..=0xBFFF => {
205 let slot = (addr & 0x0007) as usize;
206 self.chr_regs[slot] = value;
207 }
208 _ => {}
209 }
210 }
211
212 fn ppu_read(&mut self, addr: u16) -> u8 {
213 let addr = addr & 0x3FFF;
214 match addr {
215 0x0000..=0x1FFF => {
216 let off = self.chr_offset(addr);
217 self.chr_rom[off % self.chr_rom.len()]
218 }
219 0x2000..=0x3EFF => self.vram[self.nametable_offset(addr) % self.vram.len()],
220 _ => 0,
221 }
222 }
223
224 fn ppu_write(&mut self, addr: u16, value: u8) {
225 let addr = addr & 0x3FFF;
226 match addr {
227 0x0000..=0x1FFF => {
228 if self.chr_is_ram {
229 let off = self.chr_offset(addr);
230 let len = self.chr_rom.len();
231 self.chr_rom[off % len] = value;
232 }
233 }
234 0x2000..=0x3EFF => {
235 let off = self.nametable_offset(addr) % self.vram.len();
236 self.vram[off] = value;
237 }
238 _ => {}
239 }
240 }
241
242 fn notify_cpu_cycle(&mut self) {
243 if !self.irq_enabled || self.irq_counter == 0 {
244 return;
245 }
246 self.irq_counter -= 1;
247 if self.irq_counter == 0 {
248 self.irq_pending = true;
249 }
250 }
251
252 fn irq_pending(&self) -> bool {
253 self.irq_pending
254 }
255
256 fn current_mirroring(&self) -> Mirroring {
257 self.mirroring
258 }
259
260 fn debug_info(&self) -> crate::mapper::MapperDebugInfo {
261 let mut info = crate::mapper::MapperDebugInfo {
262 mapper_id: 65,
263 name: "Irem H3001 (65)".into(),
264 mirroring: crate::mapper::mirroring_name(self.mirroring),
265 ..Default::default()
266 };
267 info.prg_banks
268 .push(("layout".into(), format!("{}", u8::from(self.prg_layout))));
269 info.prg_banks
270 .push(("reg0".into(), format!("{:#04x}", self.prg_reg0)));
271 info.prg_banks
272 .push(("reg1".into(), format!("{:#04x}", self.prg_reg1)));
273 for (i, b) in self.chr_regs.iter().enumerate() {
274 info.chr_banks.push((format!("C{i}"), format!("{b:#04x}")));
275 }
276 info.irq_state
277 .push(("reload".into(), format!("{:#06x}", self.irq_reload)));
278 info.irq_state
279 .push(("counter".into(), format!("{:#06x}", self.irq_counter)));
280 info.irq_state
281 .push(("enabled".into(), format!("{}", self.irq_enabled)));
282 info.irq_state
283 .push(("pending".into(), format!("{}", self.irq_pending)));
284 info
285 }
286
287 fn save_state(&self) -> Vec<u8> {
288 let mut out = Vec::with_capacity(
289 24 + self.vram.len()
290 + if self.chr_is_ram {
291 self.chr_rom.len()
292 } else {
293 0
294 },
295 );
296 out.push(SAVE_STATE_VERSION);
297 out.push(self.prg_reg0);
298 out.push(self.prg_reg1);
299 out.push(u8::from(self.prg_layout));
300 out.extend_from_slice(&self.chr_regs);
301 out.push(self.mirroring as u8);
302 out.extend_from_slice(&self.irq_reload.to_le_bytes());
303 out.extend_from_slice(&self.irq_counter.to_le_bytes());
304 out.push(u8::from(self.irq_enabled));
305 out.push(u8::from(self.irq_pending));
306 out.extend_from_slice(&self.vram);
307 if self.chr_is_ram {
308 out.extend_from_slice(&self.chr_rom);
309 }
310 out
311 }
312
313 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
314 let chr_part = if self.chr_is_ram {
315 self.chr_rom.len()
316 } else {
317 0
318 };
319 let scalar_len = 1 + 1 + 1 + 1 + 8 + 1 + 2 + 2 + 1 + 1;
321 let expected = scalar_len + self.vram.len() + chr_part;
322 if data.len() != expected {
323 return Err(MapperError::WrongLength {
324 expected,
325 got: data.len(),
326 });
327 }
328 if data[0] != SAVE_STATE_VERSION {
329 return Err(MapperError::UnsupportedVersion(data[0]));
330 }
331 let mut c = 1usize;
332 self.prg_reg0 = data[c];
333 c += 1;
334 self.prg_reg1 = data[c];
335 c += 1;
336 self.prg_layout = data[c] != 0;
337 c += 1;
338 self.chr_regs.copy_from_slice(&data[c..c + 8]);
339 c += 8;
340 self.mirroring = match data[c] {
341 0 => Mirroring::Horizontal,
342 1 => Mirroring::Vertical,
343 2 => Mirroring::SingleScreenA,
344 3 => Mirroring::SingleScreenB,
345 4 => Mirroring::FourScreen,
346 5 => Mirroring::MapperControlled,
347 other => return Err(MapperError::Invalid(format!("mirroring {other}"))),
348 };
349 c += 1;
350 self.irq_reload = u16::from_le_bytes([data[c], data[c + 1]]);
351 c += 2;
352 self.irq_counter = u16::from_le_bytes([data[c], data[c + 1]]);
353 c += 2;
354 self.irq_enabled = data[c] != 0;
355 c += 1;
356 self.irq_pending = data[c] != 0;
357 c += 1;
358 self.vram.copy_from_slice(&data[c..c + self.vram.len()]);
359 c += self.vram.len();
360 if self.chr_is_ram {
361 self.chr_rom
362 .copy_from_slice(&data[c..c + self.chr_rom.len()]);
363 }
364 Ok(())
365 }
366}
367
368#[cfg(test)]
369#[allow(clippy::cast_possible_truncation)]
370mod tests {
371 use super::*;
372
373 fn synth_prg(banks_8k: usize) -> Box<[u8]> {
374 let mut v = vec![0u8; banks_8k * PRG_BANK_8K];
375 for b in 0..banks_8k {
376 v[b * PRG_BANK_8K] = b as u8;
377 }
378 v.into_boxed_slice()
379 }
380
381 fn synth_chr(banks_1k: usize) -> Box<[u8]> {
382 let mut v = vec![0u8; banks_1k * CHR_BANK_1K];
383 for b in 0..banks_1k {
384 v[b * CHR_BANK_1K] = b as u8;
385 }
386 v.into_boxed_slice()
387 }
388
389 fn fresh() -> IremH3001 {
390 IremH3001::new(synth_prg(64), synth_chr(64), Mirroring::Vertical).unwrap()
392 }
393
394 #[test]
395 fn powerup_register_values() {
396 let mut m = fresh();
397 assert_eq!(m.cpu_read(0x8000), 0);
399 assert_eq!(m.cpu_read(0xA000), 1);
400 }
401
402 #[test]
403 fn prg_layout_0_fixes_c000_to_3e_and_e000_to_3f() {
404 let mut m = fresh();
405 m.cpu_write(0x8000, 5); assert_eq!(m.cpu_read(0x8000), 5);
407 assert_eq!(m.cpu_read(0xC000), 0x3E);
408 assert_eq!(m.cpu_read(0xE000), 0x3F);
409 }
410
411 #[test]
412 fn prg_layout_1_swaps_to_c000() {
413 let mut m = fresh();
414 m.cpu_write(0x8000, 5); m.cpu_write(0x9000, 0x80); assert_eq!(m.cpu_read(0x8000), 0x3E); assert_eq!(m.cpu_read(0xC000), 5); assert_eq!(m.cpu_read(0xE000), 0x3F);
419 }
420
421 #[test]
422 fn chr_eight_1k_banks() {
423 let mut m = fresh();
424 m.cpu_write(0xB000, 10);
425 m.cpu_write(0xB004, 20);
426 assert_eq!(m.ppu_read(0x0000), 10);
427 assert_eq!(m.ppu_read(0x1000), 20);
428 }
429
430 #[test]
431 fn mirroring_select() {
432 let mut m = fresh();
433 m.cpu_write(0x9001, 0x00);
434 assert_eq!(m.current_mirroring(), Mirroring::Vertical);
435 m.cpu_write(0x9001, 0x80); assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
437 m.cpu_write(0x9001, 0x40); assert_eq!(m.current_mirroring(), Mirroring::SingleScreenA);
439 }
440
441 #[test]
442 fn irq_counts_down_and_asserts() {
443 let mut m = fresh();
444 m.cpu_write(0x9005, 0x00); m.cpu_write(0x9006, 0x03); m.cpu_write(0x9004, 0x00); m.cpu_write(0x9003, 0x80); m.notify_cpu_cycle(); m.notify_cpu_cycle(); assert!(!m.irq_pending());
451 m.notify_cpu_cycle(); assert!(m.irq_pending());
453 }
454
455 #[test]
456 fn irq_stops_at_zero_no_wrap() {
457 let mut m = fresh();
458 m.cpu_write(0x9005, 0x00);
459 m.cpu_write(0x9006, 0x01);
460 m.cpu_write(0x9004, 0x00);
461 m.cpu_write(0x9003, 0x80);
462 m.notify_cpu_cycle(); assert!(m.irq_pending());
464 assert_eq!(m.irq_counter, 0);
465 for _ in 0..1000 {
467 m.notify_cpu_cycle();
468 }
469 assert_eq!(m.irq_counter, 0);
470 }
471
472 #[test]
473 fn write_9003_or_9004_acks() {
474 let mut m = fresh();
475 m.irq_pending = true;
476 m.cpu_write(0x9003, 0x00); assert!(!m.irq_pending());
478 m.irq_pending = true;
479 m.cpu_write(0x9004, 0x00); assert!(!m.irq_pending());
481 }
482
483 #[test]
484 fn save_state_round_trip() {
485 let mut m = fresh();
486 m.cpu_write(0x8000, 5);
487 m.cpu_write(0xB002, 7);
488 m.cpu_write(0x9005, 0x12);
489 m.cpu_write(0x9006, 0x34);
490 m.cpu_write(0x9004, 0x00);
491 m.cpu_write(0x9003, 0x80);
492 m.ppu_write(0x2000, 0x55);
493 let blob = m.save_state();
494 let mut m2 = fresh();
495 m2.load_state(&blob).unwrap();
496 assert_eq!(m.cpu_read(0x8000), m2.cpu_read(0x8000));
497 assert_eq!(m.ppu_read(0x0800), m2.ppu_read(0x0800));
498 assert_eq!(m.irq_counter, m2.irq_counter);
499 assert_eq!(m.ppu_read(0x2000), m2.ppu_read(0x2000));
500 }
501}