use super::*; pub(crate) struct InesMapper155 { prg_rom: Vec, chr_data: Vec, chr_is_ram: bool, prg_ram: Vec, shift_reg: u8, shift_count: u8, control: u8, chr_bank0: u8, chr_bank1: u8, prg_bank: u8, mirroring_default: Mirroring, } impl InesMapper155 { 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], shift_reg: 0, shift_count: 0, control: 0x0C, chr_bank0: 0, chr_bank1: 0, prg_bank: 0, mirroring_default: rom.header.mirroring, } } fn prg_mode(&self) -> u8 { (self.control >> 2) & 0x03 } fn chr_mode(&self) -> u8 { (self.control >> 4) & 1 } fn prg_bank_count_16k(&self) -> usize { (self.prg_rom.len() / 0x4000).max(1) } fn outer_a17_override_enabled(&self) -> bool { (self.prg_bank & 0x10) != 0 } fn outer_bank_half(&self) -> usize { ((self.prg_bank >> 3) & 1) as usize } fn map_bank_16k_with_outer(&self, inner_bank: usize) -> usize { let total = self.prg_bank_count_16k(); if !self.outer_a17_override_enabled() || total <= 8 { return safe_mod(inner_bank, total); } let half = (total / 2).max(1); let base = safe_mod(self.outer_bank_half(), 2) * half; base + safe_mod(inner_bank, half) } 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 reg = (addr >> 13) & 0x03; match reg { 0 => self.control = self.shift_reg & 0x1F, 1 => self.chr_bank0 = self.shift_reg & 0x1F, 2 => self.chr_bank1 = self.shift_reg & 0x1F, _ => self.prg_bank = self.shift_reg & 0x1F, // MMC1A uses bit 4 for A17 behavior } self.shift_reg = 0; self.shift_count = 0; } } impl Mapper for InesMapper155 { fn cpu_read(&self, addr: u16) -> u8 { if addr < 0x8000 { return 0; } let prg_mode = self.prg_mode(); if prg_mode <= 1 { let bank32 = (self.prg_bank as usize) >> 1; let bank16 = bank32 * 2 + usize::from(addr >= 0xC000); read_bank( &self.prg_rom, 0x4000, self.map_bank_16k_with_outer(bank16), (addr as usize) & 0x3FFF, ) } else if prg_mode == 2 { if addr < 0xC000 { read_bank( &self.prg_rom, 0x4000, self.map_bank_16k_with_outer(0), (addr as usize) - 0x8000, ) } else { read_bank( &self.prg_rom, 0x4000, self.map_bank_16k_with_outer((self.prg_bank & 0x0F) as usize), (addr as usize) - 0xC000, ) } } else if addr < 0xC000 { read_bank( &self.prg_rom, 0x4000, self.map_bank_16k_with_outer((self.prg_bank & 0x0F) as usize), (addr as usize) - 0x8000, ) } else { let fixed_last = if self.outer_a17_override_enabled() && self.prg_bank_count_16k() > 8 { self.prg_bank_count_16k() / 2 - 1 } else { self.prg_bank_count_16k().saturating_sub(1) }; read_bank( &self.prg_rom, 0x4000, self.map_bank_16k_with_outer(fixed_last), (addr as usize) - 0xC000, ) } } fn cpu_write(&mut self, addr: u16, value: u8) { if addr >= 0x8000 { self.write_serial(addr, value); } } fn cpu_read_low(&self, addr: u16) -> Option { if (0x6000..=0x7FFF).contains(&addr) { return self.prg_ram.get((addr as usize) & 0x1FFF).copied(); } None } fn cpu_write_low(&mut self, addr: u16, value: u8) -> bool { if (0x6000..=0x7FFF).contains(&addr) { if let Some(cell) = self.prg_ram.get_mut((addr as usize) & 0x1FFF) { *cell = value; } return true; } false } fn ppu_read(&self, addr: u16) -> u8 { if addr > 0x1FFF { return 0; } if self.chr_mode() == 0 { let bank = (self.chr_bank0 as usize) >> 1; read_bank(&self.chr_data, 0x2000, bank, addr as usize) } else if addr < 0x1000 { read_bank( &self.chr_data, 0x1000, self.chr_bank0 as usize, addr as usize, ) } else { read_bank( &self.chr_data, 0x1000, self.chr_bank1 as usize, (addr as usize) - 0x1000, ) } } fn ppu_write(&mut self, addr: u16, value: u8) { if !self.chr_is_ram || addr > 0x1FFF { return; } let idx = if self.chr_mode() == 0 { let total_banks = (self.chr_data.len() / 0x2000).max(1); let bank = safe_mod((self.chr_bank0 as usize) >> 1, total_banks); bank * 0x2000 + ((addr as usize) & 0x1FFF) } else { let total_banks = (self.chr_data.len() / 0x1000).max(1); let bank = if addr < 0x1000 { self.chr_bank0 as usize } else { self.chr_bank1 as usize }; safe_mod(bank, total_banks) * 0x1000 + ((addr as usize) & 0x0FFF) }; if let Some(cell) = self.chr_data.get_mut(idx) { *cell = value; } } fn mirroring(&self) -> Mirroring { match self.control & 0x03 { 0 => Mirroring::OneScreenLow, 1 => Mirroring::OneScreenHigh, 2 => Mirroring::Vertical, 3 => Mirroring::Horizontal, _ => self.mirroring_default, } } fn save_state(&self, out: &mut Vec) { out.push(self.shift_reg); out.push(self.shift_count); out.push(self.control); out.push(self.chr_bank0); out.push(self.chr_bank1); out.push(self.prg_bank); 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() < 6 { return Err("mapper state is truncated".to_string()); } self.shift_reg = data[0]; self.shift_count = data[1]; self.control = data[2]; self.chr_bank0 = data[3]; self.chr_bank1 = data[4]; self.prg_bank = data[5]; let mut cursor = 6usize; 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..]) } }