use crate::memory::mapper::*;
const PRG_LAYOUT_PRIMARY: PrgLayout = PrgLayout::new(&[
PrgWindow::new(0x6000, 0x7FFF, 8 * KIBIBYTE, Bank::EMPTY),
PrgWindow::new(0x8000, 0x9FFF, 8 * KIBIBYTE, Bank::switchable_rom(P0)),
PrgWindow::new(0xA000, 0xBFFF, 8 * KIBIBYTE, Bank::switchable_rom(P1)),
PrgWindow::new(0xC000, 0xDFFF, 8 * KIBIBYTE, Bank::switchable_rom(P2)),
PrgWindow::new(0xE000, 0xFFFF, 8 * KIBIBYTE, Bank::fixed_rom(BankIndex::LAST)),
]);
const PRG_LAYOUT_SECONDARY: PrgLayout = PrgLayout::new(&[
PrgWindow::new(0x6000, 0x7FFF, 8 * KIBIBYTE, Bank::EMPTY),
PrgWindow::new(0x8000, 0x9FFF, 8 * KIBIBYTE, Bank::switchable_rom(P2)),
PrgWindow::new(0xA000, 0xBFFF, 8 * KIBIBYTE, Bank::switchable_rom(P1)),
PrgWindow::new(0xC000, 0xDFFF, 8 * KIBIBYTE, Bank::switchable_rom(P0)),
PrgWindow::new(0xE000, 0xFFFF, 8 * KIBIBYTE, Bank::fixed_rom(BankIndex::LAST)),
]);
const CHR_BIG_WINDOWS_PRIMARY: ChrLayout = ChrLayout::new(&[
ChrWindow::new(0x0000, 0x07FF, 2 * KIBIBYTE, Bank::switchable_rom(C0)),
ChrWindow::new(0x0800, 0x0FFF, 2 * KIBIBYTE, Bank::switchable_rom(C1)),
ChrWindow::new(0x1000, 0x13FF, 1 * KIBIBYTE, Bank::switchable_rom(C2)),
ChrWindow::new(0x1400, 0x17FF, 1 * KIBIBYTE, Bank::switchable_rom(C3)),
ChrWindow::new(0x1800, 0x1BFF, 1 * KIBIBYTE, Bank::switchable_rom(C4)),
ChrWindow::new(0x1C00, 0x1FFF, 1 * KIBIBYTE, Bank::switchable_rom(C5)),
]);
const CHR_BIG_WINDOWS_SECONDARY: ChrLayout = ChrLayout::new(&[
ChrWindow::new(0x0000, 0x03FF, 1 * KIBIBYTE, Bank::switchable_rom(C2)),
ChrWindow::new(0x0400, 0x07FF, 1 * KIBIBYTE, Bank::switchable_rom(C3)),
ChrWindow::new(0x0800, 0x0BFF, 1 * KIBIBYTE, Bank::switchable_rom(C4)),
ChrWindow::new(0x0C00, 0x0FFF, 1 * KIBIBYTE, Bank::switchable_rom(C5)),
ChrWindow::new(0x1000, 0x17FF, 2 * KIBIBYTE, Bank::switchable_rom(C0)),
ChrWindow::new(0x1800, 0x1FFF, 2 * KIBIBYTE, Bank::switchable_rom(C1)),
]);
const CHR_SMALL_WINDOWS_PRIMARY: ChrLayout = ChrLayout::new(&[
ChrWindow::new(0x0000, 0x03FF, 1 * KIBIBYTE, Bank::switchable_rom(C0)),
ChrWindow::new(0x0400, 0x07FF, 1 * KIBIBYTE, Bank::switchable_rom(C6)),
ChrWindow::new(0x0800, 0x0BFF, 1 * KIBIBYTE, Bank::switchable_rom(C1)),
ChrWindow::new(0x0C00, 0x0FFF, 1 * KIBIBYTE, Bank::switchable_rom(C7)),
ChrWindow::new(0x1000, 0x13FF, 1 * KIBIBYTE, Bank::switchable_rom(C2)),
ChrWindow::new(0x1400, 0x17FF, 1 * KIBIBYTE, Bank::switchable_rom(C3)),
ChrWindow::new(0x1800, 0x1BFF, 1 * KIBIBYTE, Bank::switchable_rom(C4)),
ChrWindow::new(0x1C00, 0x1FFF, 1 * KIBIBYTE, Bank::switchable_rom(C5)),
]);
const CHR_SMALL_WINDOWS_SECONDARY: ChrLayout = ChrLayout::new(&[
ChrWindow::new(0x0000, 0x03FF, 1 * KIBIBYTE, Bank::switchable_rom(C2)),
ChrWindow::new(0x0400, 0x07FF, 1 * KIBIBYTE, Bank::switchable_rom(C3)),
ChrWindow::new(0x0800, 0x0BFF, 1 * KIBIBYTE, Bank::switchable_rom(C4)),
ChrWindow::new(0x0C00, 0x0FFF, 1 * KIBIBYTE, Bank::switchable_rom(C5)),
ChrWindow::new(0x1000, 0x13FF, 1 * KIBIBYTE, Bank::switchable_rom(C0)),
ChrWindow::new(0x1400, 0x17FF, 1 * KIBIBYTE, Bank::switchable_rom(C6)),
ChrWindow::new(0x1800, 0x1BFF, 1 * KIBIBYTE, Bank::switchable_rom(C1)),
ChrWindow::new(0x1C00, 0x1FFF, 1 * KIBIBYTE, Bank::switchable_rom(C7)),
]);
const CPU_CYCLE_MODE_IRQ_PENDING_DELAY: u8 = 1;
const SCANLINE_MODE_IRQ_PENDING_DELAY: u8 = 4;
const BANK_INDEX_REGISTER_IDS: [Option<BankRegisterId>; 16] =
[Some(C0), Some(C1), Some(C2), Some(C3), Some(C4), Some(C5), Some(P0), Some(P1),
Some(C6), Some(C7), None, None, None, None, None, Some(P2),
];
const PRG_LAYOUTS: [PrgLayout; 2] = [
PRG_LAYOUT_PRIMARY,
PRG_LAYOUT_SECONDARY,
];
const CHR_LAYOUTS: [[ChrLayout; 2]; 2] = [
[ CHR_BIG_WINDOWS_PRIMARY, CHR_SMALL_WINDOWS_PRIMARY ],
[ CHR_BIG_WINDOWS_SECONDARY, CHR_SMALL_WINDOWS_SECONDARY ],
];
const MIRRORINGS: [NameTableMirroring; 2] = [
NameTableMirroring::Vertical,
NameTableMirroring::Horizontal,
];
pub struct Mapper064 {
selected_register_id: BankRegisterId,
irq_enabled: bool,
irq_pending: bool,
irq_pending_delay_cycles: u8,
irq_counter: u8,
force_reload_irq_counter: bool,
irq_counter_reload_value: u8,
irq_counter_reload_mode: IrqCounterReloadMode,
irq_counter_suppression_cycles: u8,
pattern_table_side: PatternTableSide,
}
impl Mapper for Mapper064 {
fn initial_layout(&self) -> InitialLayout {
InitialLayout::builder()
.prg_max_bank_count(64)
.prg_bank_size(8 * KIBIBYTE)
.prg_windows(PRG_LAYOUT_PRIMARY)
.chr_max_bank_count(256)
.chr_bank_size(1 * KIBIBYTE)
.chr_windows(CHR_BIG_WINDOWS_PRIMARY)
.name_table_mirroring_source(NameTableMirroringSource::Cartridge)
.build()
}
fn write_to_cartridge_space(&mut self, params: &mut MapperParams, cpu_address: CpuAddress, value: u8) {
let is_even_address = cpu_address.to_raw() % 2 == 0;
match cpu_address.to_raw() {
0x0000..=0x401F => unreachable!(),
0x4020..=0x7FFF => { }
0x8000..=0x9FFF if is_even_address => self.bank_select(params, value),
0x8000..=0x9FFF => self.set_bank_index(params, value),
0xA000..=0xBFFF if is_even_address => Mapper064::set_name_table_mirroring(params, value),
0xA000..=0xBFFF => {}
0xC000..=0xDFFF if is_even_address => self.set_irq_counter_reload_value(value),
0xC000..=0xDFFF => self.set_irq_reload_mode(value),
0xE000..=0xFFFF if is_even_address => self.acknowledge_irq(),
0xE000..=0xFFFF => self.enabled_irq()
}
}
fn on_end_of_cpu_cycle(&mut self, cycle: i64) {
if self.irq_pending_delay_cycles > 0 {
self.irq_pending_delay_cycles -= 1;
if self.irq_pending_delay_cycles == 0 {
self.irq_pending = true;
}
}
if self.irq_counter_reload_mode == IrqCounterReloadMode::CpuCycle && cycle % 4 == 0 {
self.tick_irq_counter(CPU_CYCLE_MODE_IRQ_PENDING_DELAY);
}
}
fn on_end_of_ppu_cycle(&mut self) {
if self.irq_counter_suppression_cycles > 0 {
self.irq_counter_suppression_cycles -= 1;
}
}
fn process_current_ppu_address(&mut self, address: PpuAddress) {
if self.irq_counter_reload_mode != IrqCounterReloadMode::Scanline {
return;
}
if !address.is_in_pattern_table() {
return;
}
let next_side = address.pattern_table_side();
let should_tick_irq_counter =
self.pattern_table_side == PatternTableSide::Left
&& next_side == PatternTableSide::Right
&& self.irq_counter_suppression_cycles == 0;
if next_side == PatternTableSide::Right {
self.irq_counter_suppression_cycles = 16;
}
if should_tick_irq_counter {
self.tick_irq_counter(SCANLINE_MODE_IRQ_PENDING_DELAY);
}
self.pattern_table_side = next_side;
}
fn irq_pending(&self) -> bool {
self.irq_pending
}
}
impl Mapper064 {
pub fn new() -> Self {
Self {
selected_register_id: C0,
irq_enabled: false,
irq_pending: false,
irq_pending_delay_cycles: 0,
irq_counter: 0,
force_reload_irq_counter: false,
irq_counter_reload_value: 0,
irq_counter_reload_mode: IrqCounterReloadMode::Scanline,
irq_counter_suppression_cycles: 0,
pattern_table_side: PatternTableSide::Left,
}
}
fn bank_select(&mut self, params: &mut MapperParams, value: u8) {
let fields = splitbits!(value, "ipc.bbbb");
params.set_prg_layout(PRG_LAYOUTS[fields.p as usize]);
params.set_chr_layout(CHR_LAYOUTS[fields.i as usize][fields.c as usize]);
if let Some(reg_id) = BANK_INDEX_REGISTER_IDS[fields.b as usize] {
self.selected_register_id = reg_id;
}
}
fn set_bank_index(&self, params: &mut MapperParams, value: u8) {
params.set_bank_register(self.selected_register_id, value);
}
fn set_name_table_mirroring(params: &mut MapperParams, value: u8) {
params.set_name_table_mirroring(MIRRORINGS[usize::from(value & 1)]);
}
fn set_irq_counter_reload_value(&mut self, value: u8) {
self.irq_counter_reload_value = value;
}
fn set_irq_reload_mode(&mut self, value: u8) {
self.force_reload_irq_counter = true;
self.irq_counter_reload_mode = if value & 1 == 0 {
IrqCounterReloadMode::Scanline
} else {
IrqCounterReloadMode::CpuCycle
};
}
fn acknowledge_irq(&mut self) {
self.irq_enabled = false;
self.irq_pending = false;
}
fn enabled_irq(&mut self) {
self.irq_enabled = true;
}
fn tick_irq_counter(&mut self, irq_pending_delay_cycles: u8) {
if self.force_reload_irq_counter {
self.force_reload_irq_counter = false;
self.irq_counter = self.irq_counter_reload_value;
if self.irq_counter > 0 {
self.irq_counter |= 1;
}
} else if self.irq_counter == 0 {
self.irq_counter = self.irq_counter_reload_value;
} else {
self.irq_counter -= 1;
}
if self.irq_enabled && self.irq_counter == 0 {
self.irq_pending_delay_cycles = irq_pending_delay_cycles;
}
}
}
#[derive(PartialEq, Eq)]
enum IrqCounterReloadMode {
Scanline,
CpuCycle,
}