use crate::{
cart::Cart,
common::{Clock, Regional, Reset, ResetKind, Sram},
fs,
mapper::{self, Map, Mapper, Mirroring},
mem::{Banks, Memory},
ppu::CIRam,
};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[must_use]
pub struct Fxrom {
pub chr_rom: Memory<Box<[u8]>>,
pub prg_rom: Memory<Box<[u8]>>,
pub prg_ram: Memory<Box<[u8]>>,
pub mirroring: Mirroring,
pub chr_banks: Banks,
pub prg_rom_banks: Banks,
pub latch: [usize; 2],
pub latch_banks: [u8; 4],
}
impl Fxrom {
const PRG_WINDOW: usize = 16 * 1024;
const CHR_ROM_WINDOW: usize = 4 * 1024;
const PRG_RAM_SIZE: usize = 8 * 1024;
const MIRRORING_MASK: u8 = 0x01;
pub fn load(
cart: &Cart,
chr_rom: Memory<Box<[u8]>>,
prg_rom: Memory<Box<[u8]>>,
) -> Result<Mapper, mapper::Error> {
let prg_ram = Memory::with_ram_state(Self::PRG_RAM_SIZE, cart.ram_state);
let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_ROM_WINDOW)?;
let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;
let mut fxrom = Self {
chr_rom,
prg_rom,
prg_ram,
mirroring: cart.mirroring(),
chr_banks,
prg_rom_banks,
latch: [0x00; 2],
latch_banks: [0x00; 4],
};
fxrom.prg_rom_banks.set(1, fxrom.prg_rom_banks.last());
Ok(fxrom.into())
}
pub fn update_banks(&mut self) {
let bank0 = self.latch_banks[self.latch[0]] as usize;
let bank1 = self.latch_banks[self.latch[1] + 2] as usize;
self.chr_banks.set(0, bank0);
self.chr_banks.set(1, bank1);
}
}
impl Map for Fxrom {
#[inline(always)]
fn chr_read(&mut self, addr: u16, ciram: &CIRam) -> u8 {
let val = self.chr_peek(addr, ciram);
match addr {
0x0FD8..=0x0FDF | 0x0FE8..=0xFEF | 0x1FD8..=0x1FDF | 0x1FE8..=0x1FEF => {
let addr = addr as usize;
self.latch[addr >> 12] = ((addr >> 4) & 0xFF) - 0xFD;
self.update_banks();
}
_ => (),
}
val
}
#[inline(always)]
fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {
match addr {
0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],
0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),
_ => 0,
}
}
#[inline(always)]
fn prg_peek(&self, addr: u16) -> u8 {
match addr {
0x6000..=0x7FFF => self.prg_ram[usize::from(addr & 0x1FFF)],
0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],
_ => 0,
}
}
#[inline(always)]
fn prg_write(&mut self, addr: u16, val: u8) {
match addr {
0x6000..=0x7FFF => self.prg_ram[usize::from(addr & 0x1FFF)] = val,
0xA000..=0xAFFF => {
self.prg_rom_banks.set(0, (val & 0x0F).into());
}
0xB000..=0xEFFF => {
self.latch_banks[((addr - 0xB000) >> 12) as usize] = val & 0x1F;
self.update_banks();
}
0xF000..=0xFFFF => {
self.mirroring = match val & Self::MIRRORING_MASK {
0b00 => Mirroring::Vertical,
_ => Mirroring::Horizontal,
};
}
_ => (),
}
}
#[inline(always)]
fn mirroring(&self) -> Mirroring {
self.mirroring
}
}
impl Reset for Fxrom {
fn reset(&mut self, _kind: ResetKind) {
self.latch = [0x00; 2];
self.latch_banks = [0x00; 4];
self.update_banks();
}
}
impl Sram for Fxrom {
fn save(&self, path: impl AsRef<Path>) -> fs::Result<()> {
fs::save(path.as_ref(), &self.prg_ram)
}
fn load(&mut self, path: impl AsRef<Path>) -> fs::Result<()> {
fs::load(path.as_ref()).map(|data: Memory<Box<[u8]>>| self.prg_ram = data)
}
}
impl Clock for Fxrom {}
impl Regional for Fxrom {}