use super::*; pub(crate) struct Mmc3 { prg_rom: Vec, chr_data: Vec, chr_is_ram: bool, prg_ram: Vec, prg_ram_enabled: bool, prg_ram_write_protect: bool, mirroring: Mirroring, bank_regs: [u8; 8], bank_select: u8, irq_latch: u8, irq_counter: u8, irq_reload: bool, irq_enabled: bool, irq_pending: bool, } impl Mmc3 { pub(crate) fn new(rom: InesRom) -> Self { Self { prg_rom: rom.prg_rom, chr_data: rom.chr_data, chr_is_ram: rom.chr_is_ram, prg_ram: vec![0; 0x2000], prg_ram_enabled: true, prg_ram_write_protect: false, mirroring: rom.header.mirroring, bank_regs: [0; 8], bank_select: 0, irq_latch: 0, irq_counter: 0, irq_reload: false, irq_enabled: false, irq_pending: false, } } fn prg_bank_count_8k(&self) -> usize { (self.prg_rom.len() / 0x2000).max(1) } fn prg_mode(&self) -> bool { (self.bank_select & 0x40) != 0 } fn chr_invert(&self) -> bool { (self.bank_select & 0x80) != 0 } fn prg_bank_for_slot(&self, slot: usize) -> usize { let last = self.prg_bank_count_8k() - 1; let second_last = last.saturating_sub(1); match (self.prg_mode(), slot) { (false, 0) => self.bank_regs[6] as usize, (false, 1) => self.bank_regs[7] as usize, (false, 2) => second_last, (false, 3) => last, (true, 0) => second_last, (true, 1) => self.bank_regs[7] as usize, (true, 2) => self.bank_regs[6] as usize, (true, 3) => last, _ => 0, } } pub(super) fn chr_bank_for_1k_page(&self, page: usize) -> usize { let regs = &self.bank_regs; let mut layout = [0usize; 8]; layout[0] = (regs[0] as usize) & !1; layout[1] = layout[0] + 1; layout[2] = (regs[1] as usize) & !1; layout[3] = layout[2] + 1; layout[4] = regs[2] as usize; layout[5] = regs[3] as usize; layout[6] = regs[4] as usize; layout[7] = regs[5] as usize; if self.chr_invert() { layout.rotate_left(4); } layout[page] } fn clock_irq_scanline(&mut self) { if self.irq_reload || self.irq_counter == 0 { self.irq_counter = self.irq_latch; self.irq_reload = false; } else { self.irq_counter = self.irq_counter.wrapping_sub(1); } if self.irq_enabled && self.irq_counter == 0 { self.irq_pending = true; } } } impl Mapper for Mmc3 { fn cpu_read(&self, addr: u16) -> u8 { if addr < 0x8000 { return 0; } let slot = ((addr - 0x8000) / 0x2000) as usize; let bank = self.prg_bank_for_slot(slot); read_bank( &self.prg_rom, 0x2000, bank, ((addr as usize) - 0x8000) & 0x1FFF, ) } fn cpu_write(&mut self, addr: u16, value: u8) { match addr { 0x8000..=0x9FFF if (addr & 1) == 0 => self.bank_select = value, 0x8000..=0x9FFF => { let reg = (self.bank_select & 0x07) as usize; self.bank_regs[reg] = value; } 0xA000..=0xBFFF if (addr & 1) == 0 => { self.mirroring = if (value & 1) == 0 { Mirroring::Vertical } else { Mirroring::Horizontal }; } 0xA000..=0xBFFF => { self.prg_ram_enabled = (value & 0x80) != 0; self.prg_ram_write_protect = (value & 0x40) != 0; } 0xC000..=0xDFFF if (addr & 1) == 0 => self.irq_latch = value, 0xC000..=0xDFFF => { self.irq_counter = 0; self.irq_reload = true; } 0xE000..=0xFFFF if (addr & 1) == 0 => { self.irq_enabled = false; self.irq_pending = false; } 0xE000..=0xFFFF => self.irq_enabled = true, _ => {} } } fn cpu_read_low(&self, addr: u16) -> Option { if (0x6000..=0x7FFF).contains(&addr) { if self.prg_ram_enabled { Some(self.prg_ram[(addr as usize) - 0x6000]) } else { Some(0) } } else { None } } fn cpu_write_low(&mut self, addr: u16, value: u8) -> bool { if (0x6000..=0x7FFF).contains(&addr) { if self.prg_ram_enabled && !self.prg_ram_write_protect { self.prg_ram[(addr as usize) - 0x6000] = value; } true } else { false } } fn ppu_read(&self, addr: u16) -> u8 { if addr > 0x1FFF { return 0; } let page = (addr / 0x0400) as usize; let bank = self.chr_bank_for_1k_page(page); read_bank(&self.chr_data, 0x0400, bank, (addr as usize) & 0x03FF) } fn ppu_write(&mut self, addr: u16, value: u8) { if !self.chr_is_ram || addr > 0x1FFF { return; } let page = (addr / 0x0400) as usize; let bank = self.chr_bank_for_1k_page(page); let total_banks = (self.chr_data.len() / 0x0400).max(1); let bank_idx = safe_mod(bank, total_banks); let idx = bank_idx * 0x0400 + ((addr as usize) & 0x03FF); if let Some(cell) = self.chr_data.get_mut(idx) { *cell = value; } } fn mirroring(&self) -> Mirroring { self.mirroring } fn clock_scanline(&mut self) { self.clock_irq_scanline(); } fn needs_ppu_a12_clock(&self) -> bool { 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.extend_from_slice(&self.bank_regs); out.push(self.bank_select); out.push(encode_mirroring(self.mirroring)); out.push(self.irq_latch); out.push(self.irq_counter); out.push(u8::from(self.irq_reload)); out.push(u8::from(self.irq_enabled)); out.push(u8::from(self.irq_pending)); out.push(u8::from(self.prg_ram_enabled)); out.push(u8::from(self.prg_ram_write_protect)); 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() < 17 { return Err("mapper state is truncated".to_string()); } self.bank_regs.copy_from_slice(&data[0..8]); self.bank_select = data[8]; self.mirroring = decode_mirroring(data[9]); self.irq_latch = data[10]; self.irq_counter = data[11]; self.irq_reload = data[12] != 0; self.irq_enabled = data[13] != 0; self.irq_pending = data[14] != 0; let mut cursor = 15usize; self.prg_ram_enabled = data[cursor] != 0; cursor += 1; self.prg_ram_write_protect = data[cursor] != 0; cursor += 1; 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..]) } }