use super::*; pub(crate) struct InesMapper253 { base: Vrc2_23, chr_ram_2k: [u8; 0x800], // PPU pages 4/5 ($1000-$17FF): on-cart CHR-RAM overlay } impl InesMapper253 { pub(crate) fn new(rom: InesRom) -> Self { let mut base = Vrc2_23::new_with_submapper(rom, 2); // VRC4e-style decode base.mapper_id = 23; Self { base, chr_ram_2k: [0; 0x800], } } } impl Mapper for InesMapper253 { fn cpu_read(&self, addr: u16) -> u8 { self.base.cpu_read(addr) } fn cpu_write(&mut self, addr: u16, value: u8) { self.base.cpu_write(addr, value); } fn cpu_read_low(&self, addr: u16) -> Option { self.base.cpu_read_low(addr) } fn cpu_write_low(&mut self, addr: u16, value: u8) -> bool { self.base.cpu_write_low(addr, value) } fn ppu_read(&self, addr: u16) -> u8 { if addr > 0x1FFF { return 0; } if (0x1000..0x1800).contains(&addr) { return self.chr_ram_2k[(addr as usize) - 0x1000]; } self.base.ppu_read(addr) } fn ppu_write(&mut self, addr: u16, value: u8) { if addr > 0x1FFF { return; } if (0x1000..0x1800).contains(&addr) { self.chr_ram_2k[(addr as usize) - 0x1000] = value; return; } self.base.ppu_write(addr, value); } fn mirroring(&self) -> Mirroring { self.base.mirroring() } fn clock_cpu(&mut self, cycles: u8) { self.base.clock_cpu(cycles); } fn poll_irq(&mut self) -> bool { self.base.poll_irq() } fn save_state(&self, out: &mut Vec) { let mut base_state = Vec::new(); self.base.save_state(&mut base_state); write_state_bytes(out, &base_state); out.extend_from_slice(&self.chr_ram_2k); } fn load_state(&mut self, data: &[u8]) -> Result<(), String> { let mut cursor = 0usize; let base_state = read_state_bytes(data, &mut cursor)?; if data.len().saturating_sub(cursor) != self.chr_ram_2k.len() { return Err("mapper state does not match loaded ROM".to_string()); } self.base.load_state(base_state)?; self.chr_ram_2k.copy_from_slice(&data[cursor..]); Ok(()) } } impl Fme7 { 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, mirroring: rom.header.mirroring, command: 0, chr_banks: [0; 8], prg_banks: [0, 1, 0xFE], low_bank: 0, low_is_ram: false, low_ram_enabled: false, low_ram: vec![0; 0x8000], irq_counter: 0, irq_enabled: false, irq_counter_enabled: false, irq_pending: false, } } fn prg_bank_count_8k(&self) -> usize { (self.prg_rom.len() / 0x2000).max(1) } fn low_ram_index(&self, addr: u16) -> usize { let bank = (self.low_bank & 0x03) as usize; bank * 0x2000 + ((addr as usize) & 0x1FFF) } } impl Mapper for Fme7 { fn cpu_read(&self, addr: u16) -> u8 { if addr < 0x8000 { return 0; } let bank = match ((addr - 0x8000) / 0x2000) as usize { 0 => self.prg_banks[0] as usize, 1 => self.prg_banks[1] as usize, 2 => self.prg_banks[2] as usize, _ => self.prg_bank_count_8k().saturating_sub(1), }; read_bank( &self.prg_rom, 0x2000, bank, ((addr as usize) - 0x8000) & 0x1FFF, ) } fn cpu_write(&mut self, addr: u16, value: u8) { if (0x8000..=0x9FFF).contains(&addr) { self.command = value & 0x0F; return; } if !(0xA000..=0xBFFF).contains(&addr) { return; } match self.command { 0x0..=0x7 => self.chr_banks[self.command as usize] = value, 0x8 => { self.low_bank = value & 0x3F; self.low_is_ram = (value & 0x40) != 0; self.low_ram_enabled = (value & 0x80) != 0; } 0x9 => self.prg_banks[0] = value & 0x3F, 0xA => self.prg_banks[1] = value & 0x3F, 0xB => self.prg_banks[2] = value & 0x3F, 0xC => { self.mirroring = match value & 0x03 { 0 => Mirroring::Vertical, 1 => Mirroring::Horizontal, 2 => Mirroring::OneScreenLow, _ => Mirroring::OneScreenHigh, }; } 0xD => { self.irq_enabled = (value & 0x01) != 0; self.irq_counter_enabled = (value & 0x80) != 0; if !self.irq_enabled { self.irq_pending = false; } } 0xE => { self.irq_counter = (self.irq_counter & 0xFF00) | value as u16; self.irq_pending = false; } 0xF => { self.irq_counter = (self.irq_counter & 0x00FF) | ((value as u16) << 8); self.irq_pending = false; } _ => {} } } fn cpu_read_low(&self, addr: u16) -> Option { if !(0x6000..=0x7FFF).contains(&addr) { return None; } if self.low_is_ram && self.low_ram_enabled { return Some(self.low_ram[self.low_ram_index(addr)]); } if self.low_is_ram { return Some(0); } Some(read_bank( &self.prg_rom, 0x2000, self.low_bank as usize, (addr as usize) & 0x1FFF, )) } fn cpu_write_low(&mut self, addr: u16, value: u8) -> bool { if !(0x6000..=0x7FFF).contains(&addr) { return false; } if self.low_is_ram && self.low_ram_enabled { let idx = self.low_ram_index(addr); self.low_ram[idx] = value; } true } fn ppu_read(&self, addr: u16) -> u8 { if addr > 0x1FFF { return 0; } let page = (addr / 0x0400) as usize; let bank = self.chr_banks[page] as usize; 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_banks[page] as usize; 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_cpu(&mut self, cycles: u8) { if !self.irq_counter_enabled { return; } for _ in 0..cycles { if self.irq_counter == 0 { self.irq_counter = 0xFFFF; if self.irq_enabled { self.irq_pending = true; } } else { self.irq_counter = self.irq_counter.wrapping_sub(1); } } } 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.command); out.extend_from_slice(&self.chr_banks); out.extend_from_slice(&self.prg_banks); out.push(self.low_bank); out.push(u8::from(self.low_is_ram)); out.push(u8::from(self.low_ram_enabled)); out.extend_from_slice(&self.irq_counter.to_le_bytes()); out.push(u8::from(self.irq_enabled)); out.push(u8::from(self.irq_counter_enabled)); out.push(u8::from(self.irq_pending)); out.push(encode_mirroring(self.mirroring)); write_state_bytes(out, &self.low_ram); write_chr_state(out, &self.chr_data); } fn load_state(&mut self, data: &[u8]) -> Result<(), String> { if data.len() < 21 { return Err("mapper state is truncated".to_string()); } let mut cursor = 0usize; self.command = data[cursor]; cursor += 1; self.chr_banks.copy_from_slice(&data[cursor..cursor + 8]); cursor += 8; self.prg_banks.copy_from_slice(&data[cursor..cursor + 3]); cursor += 3; self.low_bank = data[cursor]; cursor += 1; self.low_is_ram = data[cursor] != 0; cursor += 1; self.low_ram_enabled = data[cursor] != 0; cursor += 1; self.irq_counter = u16::from_le_bytes([data[cursor], data[cursor + 1]]); cursor += 2; self.irq_enabled = data[cursor] != 0; cursor += 1; self.irq_counter_enabled = data[cursor] != 0; cursor += 1; self.irq_pending = data[cursor] != 0; cursor += 1; self.mirroring = decode_mirroring(data[cursor]); cursor += 1; let low_ram_payload = read_state_bytes(data, &mut cursor)?; if low_ram_payload.len() != self.low_ram.len() { return Err("mapper state does not match loaded ROM".to_string()); } self.low_ram.copy_from_slice(low_ram_payload); load_chr_state(&mut self.chr_data, &data[cursor..])?; Ok(()) } }