use crate::memory::mapper::*;
use crate::memory::mappers::mmc3::irq_state::IrqState;
pub const PRG_LAYOUT_8000_SWITCHABLE: PrgLayout = PrgLayout::new(&[
PrgWindow::new(0x6000, 0x7FFF, 8 * KIBIBYTE, Bank::WORK_RAM.status_register(S0)),
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::fixed_rom(BankIndex::SECOND_LAST)),
PrgWindow::new(0xE000, 0xFFFF, 8 * KIBIBYTE, Bank::fixed_rom(BankIndex::LAST)),
]);
pub const PRG_LAYOUT_C000_SWITCHABLE: PrgLayout = PrgLayout::new(&[
PrgWindow::new(0x6000, 0x7FFF, 8 * KIBIBYTE, Bank::WORK_RAM.status_register(S0)),
PrgWindow::new(0x8000, 0x9FFF, 8 * KIBIBYTE, Bank::fixed_rom(BankIndex::SECOND_LAST)),
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)),
]);
pub const CHR_BIG_WINDOWS_FIRST: 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)),
]);
pub const CHR_SMALL_WINDOWS_FIRST: 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_LAYOUTS: [ChrLayout; 2] = [CHR_BIG_WINDOWS_FIRST, CHR_SMALL_WINDOWS_FIRST];
const PRG_LAYOUTS: [PrgLayout; 2] = [PRG_LAYOUT_8000_SWITCHABLE, PRG_LAYOUT_C000_SWITCHABLE];
pub const INITIAL_LAYOUT: InitialLayout = InitialLayout::builder()
.prg_max_bank_count(64)
.prg_bank_size(8 * KIBIBYTE)
.prg_windows(PRG_LAYOUT_8000_SWITCHABLE)
.chr_max_bank_count(256)
.chr_bank_size(1 * KIBIBYTE)
.chr_windows(CHR_BIG_WINDOWS_FIRST)
.name_table_mirroring_source(NameTableMirroringSource::Cartridge)
.build();
pub const BANK_INDEX_REGISTER_IDS: [BankRegisterId; 8] = [C0, C1, C2, C3, C4, C5, P0, P1];
pub struct Mapper004Mmc3 {
selected_register_id: BankRegisterId,
irq_state: Box<dyn IrqState>,
}
impl Mapper for Mapper004Mmc3 {
fn initial_layout(&self) -> InitialLayout {
INITIAL_LAYOUT
}
fn write_to_cartridge_space(&mut self, params: &mut MapperParams, address: CpuAddress, value: u8) {
let is_even_address = address.to_raw() % 2 == 0;
match (address.to_raw(), is_even_address) {
(0x0000..=0x401F, _) => unreachable!(),
(0x4020..=0x5FFF, _) => { }
(0x6000..=0x7FFF, _) => params.write_prg(address, value),
(0x8000..=0x9FFF, true ) => bank_select(params, &mut self.selected_register_id, value),
(0x8000..=0x9FFF, false) => set_bank_index(params, &mut self.selected_register_id, value),
(0xA000..=0xBFFF, true ) => set_mirroring(params, value),
(0xA000..=0xBFFF, false) => prg_ram_protect(params, value),
(0xC000..=0xDFFF, true ) => self.irq_state.set_counter_reload_value(value),
(0xC000..=0xDFFF, false) => self.irq_state.reload_counter(),
(0xE000..=0xFFFF, true ) => self.irq_state.disable(),
(0xE000..=0xFFFF, false) => self.irq_state.enable(),
}
}
fn on_end_of_ppu_cycle(&mut self) {
self.irq_state.decrement_suppression_cycle_count();
}
fn process_current_ppu_address(&mut self, address: PpuAddress) {
self.irq_state.tick_counter(address);
}
fn irq_pending(&self) -> bool {
self.irq_state.pending()
}
}
impl Mapper004Mmc3 {
pub fn new(irq_state: Box<dyn IrqState>) -> Self {
Self {
selected_register_id: C0,
irq_state,
}
}
}
pub fn bank_select(
params: &mut MapperParams,
selected_register_id: &mut BankRegisterId,
value: u8,
) {
let fields = splitbits!(value, "cp...rrr");
params.set_chr_layout(CHR_LAYOUTS[fields.c as usize]);
params.set_prg_layout(PRG_LAYOUTS[fields.p as usize]);
*selected_register_id = BANK_INDEX_REGISTER_IDS[fields.r as usize];
}
pub fn set_bank_index(
params: &mut MapperParams,
selected_register_id: &mut BankRegisterId,
value: u8,
) {
let mut bank_index = value;
if matches!(*selected_register_id, C0 | C1) {
bank_index &= 0b1111_1110;
}
if matches!(*selected_register_id, P0 | P1) {
bank_index &= 0b0011_1111;
}
params.set_bank_register(*selected_register_id, bank_index);
}
pub fn set_mirroring(params: &mut MapperParams, value: u8) {
use NameTableMirroring::*;
match (params.name_table_mirroring(), value & 0b0000_0001) {
(Vertical, 1) => params.set_name_table_mirroring(Horizontal),
(Horizontal, 0) => params.set_name_table_mirroring(Vertical),
_ => { }
}
}
pub fn prg_ram_protect(params: &mut MapperParams, value: u8) {
let (enable_ram, read_only) = splitbits_named!(value, "wr......");
let status = if read_only {
RamStatus::ReadOnly
} else if enable_ram {
RamStatus::ReadWrite
} else {
RamStatus::Disabled
};
params.set_ram_status(S0, status);
}