use super::*; pub(crate) struct InesMapper105 { prg_rom: Vec, chr_data: Vec, prg_ram: Vec, shift_reg: u8, shift_count: u8, control: u8, reg_a: u8, reg_b: u8, wram_disabled: bool, prg_unlocked: bool, saw_i_low: bool, irq_counter: u32, // 30-bit counter irq_pending: bool, } impl InesMapper105 { pub(crate) const IRQ_THRESHOLD: u32 = 0x2800_0000; // Official Nintendo World Championships DIP setting. pub(crate) fn new(rom: InesRom) -> Self { Self { prg_rom: rom.prg_rom, chr_data: if rom.chr_data.is_empty() { vec![0; 0x2000] } else { rom.chr_data }, prg_ram: vec![0; 0x2000], shift_reg: 0, shift_count: 0, control: 0x0C, reg_a: 0x10, // I bit high after reset keeps timer halted. reg_b: 0, wram_disabled: false, prg_unlocked: false, saw_i_low: false, irq_counter: 0, irq_pending: false, } } fn prg_mode(&self) -> u8 { (self.control >> 2) & 0x03 } fn timer_halted(&self) -> bool { (self.reg_a & 0x10) != 0 } fn outer_chip_selected(&self) -> bool { (self.reg_a & 0x08) != 0 } fn outer_32k_bank(&self) -> usize { ((self.reg_a >> 1) & 0x03) as usize } fn prg_bank_value(&self) -> usize { (self.reg_b & 0x0F) as usize } fn prg_bank_count_16k(&self) -> usize { (self.prg_rom.len() / 0x4000).max(1) } fn first_chip_banks_16k(&self) -> usize { (self.prg_bank_count_16k() / 2).max(1) } fn second_chip_base_16k(&self) -> usize { self.first_chip_banks_16k() } fn second_chip_banks_16k(&self) -> usize { self.prg_bank_count_16k() .saturating_sub(self.second_chip_base_16k()) .max(1) } fn on_reg_a_write(&mut self, value: u8) { let previous_i = (self.reg_a & 0x10) != 0; let next_i = (value & 0x10) != 0; self.reg_a = value & 0x1F; if !next_i { self.saw_i_low = true; } if previous_i && !next_i { self.irq_pending = false; } if !previous_i && next_i && self.saw_i_low { self.prg_unlocked = true; self.irq_pending = false; } if next_i { self.irq_counter = 0; self.irq_pending = false; } } fn write_serial(&mut self, addr: u16, value: u8) { if (value & 0x80) != 0 { self.shift_reg = 0; self.shift_count = 0; self.control |= 0x0C; return; } self.shift_reg |= (value & 1) << self.shift_count; self.shift_count = self.shift_count.wrapping_add(1); if self.shift_count < 5 { return; } let latched = self.shift_reg & 0x1F; match (addr >> 13) & 0x03 { 0 => self.control = latched, 1 => self.on_reg_a_write(latched), 2 => {} _ => { self.reg_b = latched; self.wram_disabled = (latched & 0x10) != 0; } } self.shift_reg = 0; self.shift_count = 0; } } impl Mapper for InesMapper105 { fn cpu_read(&self, addr: u16) -> u8 { if addr < 0x8000 { return 0; } if !self.prg_unlocked { // Reset/power-on maps a fixed 32KiB window from the first PRG chip. return read_bank( &self.prg_rom, 0x8000, 0, (addr as usize).saturating_sub(0x8000), ); } if !self.outer_chip_selected() { let bank32 = self.outer_32k_bank(); return read_bank( &self.prg_rom, 0x8000, bank32, (addr as usize).saturating_sub(0x8000), ); } let second_base = self.second_chip_base_16k(); let second_count = self.second_chip_banks_16k(); let prg_bank = self.prg_bank_value(); let bank16 = match self.prg_mode() { 0 | 1 => { let bank32 = prg_bank >> 1; if addr < 0xC000 { second_base + safe_mod(bank32 * 2, second_count) } else { second_base + safe_mod(bank32 * 2 + 1, second_count) } } 2 => { if addr < 0xC000 { second_base } else { second_base + safe_mod(prg_bank, second_count) } } _ => { if addr < 0xC000 { second_base + safe_mod(prg_bank, second_count) } else { second_base + second_count.saturating_sub(1) } } }; read_bank(&self.prg_rom, 0x4000, bank16, (addr as usize) & 0x3FFF) } fn cpu_write(&mut self, addr: u16, value: u8) { if addr >= 0x8000 { self.write_serial(addr, value); } } fn cpu_read_low(&mut self, addr: u16) -> Option { if (0x6000..=0x7FFF).contains(&addr) && !self.wram_disabled { let idx = (addr as usize) & 0x1FFF; return self.prg_ram.get(idx).copied(); } None } fn cpu_write_low(&mut self, addr: u16, value: u8) -> bool { if (0x6000..=0x7FFF).contains(&addr) && !self.wram_disabled { let idx = (addr as usize) & 0x1FFF; if let Some(cell) = self.prg_ram.get_mut(idx) { *cell = value; } return true; } false } fn ppu_read(&self, addr: u16) -> u8 { if addr > 0x1FFF { return 0; } self.chr_data.get(addr as usize).copied().unwrap_or(0) } fn ppu_write(&mut self, addr: u16, value: u8) { if addr > 0x1FFF { return; } if let Some(cell) = self.chr_data.get_mut(addr as usize) { *cell = value; } } fn mirroring(&self) -> Mirroring { match self.control & 0x03 { 0 => Mirroring::OneScreenLow, 1 => Mirroring::OneScreenHigh, 2 => Mirroring::Vertical, _ => Mirroring::Horizontal, } } fn clock_cpu(&mut self, cycles: u8) { if self.timer_halted() || self.irq_pending { return; } let previous = self.irq_counter; self.irq_counter = (self.irq_counter.wrapping_add(cycles as u32)) & 0x3FFF_FFFF; if previous < Self::IRQ_THRESHOLD && self.irq_counter >= Self::IRQ_THRESHOLD { self.irq_pending = true; } } fn poll_irq(&mut self) -> bool { let out = self.irq_pending; self.irq_pending = false; out } fn save_state(&self, out: &mut Vec) { out.push(self.shift_reg); out.push(self.shift_count); out.push(self.control); out.push(self.reg_a); out.push(self.reg_b); out.push(u8::from(self.wram_disabled)); out.push(u8::from(self.prg_unlocked)); out.push(u8::from(self.saw_i_low)); out.extend_from_slice(&self.irq_counter.to_le_bytes()); out.push(u8::from(self.irq_pending)); write_state_bytes(out, &self.prg_ram); write_chr_state(out, &self.chr_data); } fn load_state(&mut self, data: &[u8]) -> Result<(), String> { if data.len() < 13 { return Err("mapper state is truncated".to_string()); } self.shift_reg = data[0]; self.shift_count = data[1]; self.control = data[2]; self.reg_a = data[3]; self.reg_b = data[4]; self.wram_disabled = data[5] != 0; self.prg_unlocked = data[6] != 0; self.saw_i_low = data[7] != 0; self.irq_counter = u32::from_le_bytes([data[8], data[9], data[10], data[11]]) & 0x3FFF_FFFF; self.irq_pending = data[12] != 0; let mut cursor = 13usize; let prg_ram = read_state_bytes(data, &mut cursor)?; if prg_ram.len() != self.prg_ram.len() { return Err("mapper state does not match loaded ROM".to_string()); } self.prg_ram.copy_from_slice(prg_ram); load_chr_state(&mut self.chr_data, &data[cursor..]) } }