use crate::{
cart::Cart,
common::{Clock, Regional, Reset, ResetKind, Sram},
mapper::{self, Map, Mapper},
mem::{BankAccess, Banks, Memory},
ppu::{CIRam, Mirroring},
};
use serde::{Deserialize, Serialize};
#[derive(Debug)]
#[must_use]
enum PageBit {
Low,
High,
}
impl PageBit {
const fn page(&self, page: usize, val: u8) -> usize {
let val = (val as usize) & 0x0F;
match self {
PageBit::Low => (page & 0xF0) | val,
PageBit::High => (val << 4) | (page & 0x0F),
}
}
}
impl From<u16> for PageBit {
fn from(addr: u16) -> Self {
if addr & 0x01 == 0x01 {
Self::High
} else {
Self::Low
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[must_use]
pub struct Regs {
pub irq_enabled: bool,
pub irq_pending: bool,
pub irq_reload: [u8; 4],
pub irq_counter_size: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[must_use]
pub struct JalecoSs88006 {
pub chr_rom: Memory<Box<[u8]>>,
pub prg_rom: Memory<Box<[u8]>>,
pub prg_ram: Memory<Box<[u8]>>,
pub regs: Regs,
pub irq_counter: u16,
pub mirroring: Mirroring,
pub chr_banks: Banks,
pub prg_ram_banks: Banks,
pub prg_rom_banks: Banks,
}
impl JalecoSs88006 {
const PRG_WINDOW: usize = 8 * 1024;
const PRG_RAM_SIZE: usize = 8 * 1024;
const CHR_WINDOW: usize = 1024;
const IRQ_MASKS: [u16; 4] = [0xFFFF, 0x0FFF, 0x00FF, 0x000F];
pub fn load(
cart: &Cart,
chr_rom: Memory<Box<[u8]>>,
prg_rom: Memory<Box<[u8]>>,
) -> Result<Mapper, mapper::Error> {
let prg_ram = cart.prg_ram_or_default(Self::PRG_RAM_SIZE);
let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_WINDOW)?;
let prg_ram_banks = Banks::new(0x6000, 0x7FFF, prg_ram.len(), Self::PRG_WINDOW)?;
let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;
let mut jalecoss88006 = Self {
chr_rom,
prg_rom,
prg_ram,
regs: Regs::default(),
irq_counter: 0,
mirroring: cart.mirroring(),
chr_banks,
prg_ram_banks,
prg_rom_banks,
};
jalecoss88006
.prg_rom_banks
.set(3, jalecoss88006.prg_rom_banks.last());
Ok(jalecoss88006.into())
}
fn update_prg_bank(&mut self, bank: usize, val: u8, bits: PageBit) {
self.prg_rom_banks
.set(bank, bits.page(self.prg_rom_banks.page(bank), val));
}
fn update_chr_bank(&mut self, bank: usize, val: u8, bits: PageBit) {
self.chr_banks
.set(bank, bits.page(self.chr_banks.page(bank), val));
}
}
impl Map for JalecoSs88006 {
#[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 if self.prg_ram_banks.readable(addr) => {
self.prg_ram[self.prg_ram_banks.translate(addr)]
}
0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],
_ => 0,
}
}
fn prg_write(&mut self, addr: u16, val: u8) {
match addr {
0x6000..=0x7FFF => {
if self.prg_ram_banks.writable(addr) {
self.prg_ram[self.prg_ram_banks.translate(addr)] = val;
}
}
_ => match addr & 0xF003 {
0x8000 | 0x8001 => self.update_prg_bank(0, val, PageBit::from(addr)),
0x8002 | 0x8003 => self.update_prg_bank(1, val, PageBit::from(addr)),
0x9000 | 0x9001 => self.update_prg_bank(2, val, PageBit::from(addr)),
0x9002 => {
let prg_ram_access = if val & 0x01 == 0x01 {
if val & 0x02 == 0x02 {
BankAccess::ReadWrite
} else {
BankAccess::Read
}
} else {
BankAccess::None
};
self.prg_ram_banks.set_access(0, prg_ram_access);
}
0xA000 | 0xA001 => self.update_chr_bank(0, val, PageBit::from(addr)),
0xA002 | 0xA003 => self.update_chr_bank(1, val, PageBit::from(addr)),
0xB000 | 0xB001 => self.update_chr_bank(2, val, PageBit::from(addr)),
0xB002 | 0xB003 => self.update_chr_bank(3, val, PageBit::from(addr)),
0xC000 | 0xC001 => self.update_chr_bank(4, val, PageBit::from(addr)),
0xC002 | 0xC003 => self.update_chr_bank(5, val, PageBit::from(addr)),
0xD000 | 0xD001 => self.update_chr_bank(6, val, PageBit::from(addr)),
0xD002 | 0xD003 => self.update_chr_bank(7, val, PageBit::from(addr)),
0xE000..=0xE003 => self.regs.irq_reload[(addr & 0x03) as usize] = val,
0xF000 => {
self.regs.irq_pending = false;
self.irq_counter = u16::from(self.regs.irq_reload[0])
| (u16::from(self.regs.irq_reload[1]) << 4)
| (u16::from(self.regs.irq_reload[2]) << 8)
| (u16::from(self.regs.irq_reload[3]) << 12);
}
0xF001 => {
self.regs.irq_enabled = val & 0x01 == 0x01;
self.regs.irq_pending = false;
if val & 0x08 == 0x08 {
self.regs.irq_counter_size = 3;
} else if val & 0x04 == 0x04 {
self.regs.irq_counter_size = 2;
} else if val & 0x02 == 0x02 {
self.regs.irq_counter_size = 1;
} else {
self.regs.irq_counter_size = 0;
}
}
0xF002 => {
self.mirroring = match val & 0x03 {
0b00 => Mirroring::Horizontal,
0b01 => Mirroring::Vertical,
0b10 => Mirroring::SingleScreenA,
_ => Mirroring::SingleScreenB,
};
}
0xF003 => {
}
_ => (),
},
}
}
fn irq_pending(&self) -> bool {
self.regs.irq_pending
}
#[inline(always)]
fn mirroring(&self) -> Mirroring {
self.mirroring
}
}
impl Reset for JalecoSs88006 {
fn reset(&mut self, kind: ResetKind) {
self.regs = Regs::default();
if kind == ResetKind::Hard {
self.prg_rom_banks.set(3, self.prg_rom_banks.last());
}
}
}
impl Clock for JalecoSs88006 {
fn clock(&mut self) {
if self.regs.irq_enabled {
let irq_mask = Self::IRQ_MASKS[self.regs.irq_counter_size as usize];
let counter = self.irq_counter & irq_mask;
if counter == 0 {
self.regs.irq_pending = true;
}
self.irq_counter =
(self.irq_counter & !irq_mask) | (counter.wrapping_sub(1) & irq_mask);
}
}
}
impl Regional for JalecoSs88006 {}
impl Sram for JalecoSs88006 {}