use std::fmt::{Debug, Display};
use crate::core::{
cartridge::mapper::{bank_addr, num_banks},
CartridgeMemory, Mapper, NametableArrangement,
};
use log::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct TxRom {
prg_banks: [u32; 2],
chr_banks: [u32; 6],
prg_mode: u32,
chr_mode: u32,
irq_enable: bool,
irq_reload: bool,
irq_counter: u32,
irq_latch: u32,
last_ppu_addr: u32,
generate_irq: bool,
bank_select: u32,
nametable: NametableArrangement,
}
impl Default for TxRom {
fn default() -> Self {
TxRom {
prg_banks: [0; 2],
chr_banks: [0; 6],
prg_mode: 0,
chr_mode: 0,
irq_enable: false,
irq_reload: false,
irq_counter: 0,
irq_latch: 0,
generate_irq: false,
last_ppu_addr: 0,
bank_select: 0,
nametable: NametableArrangement::Horizontal,
}
}
}
#[typetag::serde]
impl Mapper for TxRom {
fn mapper_num(&self) -> u32 {
4
}
fn read_cpu(&self, cpu_addr: usize, mem: &CartridgeMemory) -> u8 {
if cpu_addr < 0x6000 {
warn!("Trying to read PRG RAM where there is none: {:X}", cpu_addr);
0
} else if cpu_addr < 0x8000 {
mem.read_prg_ram(cpu_addr - 0x6000)
} else {
let prg_addr = if cpu_addr < 0xA000 {
if self.prg_mode == 0 {
bank_addr(0x2000, self.prg_banks[0] as usize, cpu_addr)
} else {
bank_addr(
0x2000,
num_banks(0x2000, mem.prg_rom.as_slice()) - 2,
cpu_addr,
)
}
} else if cpu_addr < 0xC000 {
bank_addr(0x2000, self.prg_banks[1] as usize, cpu_addr)
} else if cpu_addr < 0xE000 {
if self.prg_mode == 1 {
bank_addr(0x2000, self.prg_banks[0] as usize, cpu_addr)
} else {
bank_addr(
0x2000,
num_banks(0x2000, mem.prg_rom.as_slice()) - 2,
cpu_addr,
)
}
} else {
bank_addr(0x2000, num_banks(0x2000, &mem.prg_rom) - 1, cpu_addr)
};
mem.read_prg_rom(prg_addr)
}
}
fn write_cpu(&mut self, cpu_addr: usize, mem: &mut CartridgeMemory, value: u8) {
if (0x6000..0x8000).contains(&cpu_addr) {
mem.write_prg_ram(cpu_addr - 0x6000, value);
} else if cpu_addr < 0xA000 {
if cpu_addr % 2 == 0 {
self.bank_select = (value & 0x07) as u32;
self.prg_mode = ((value & 0x40) >> 6) as u32;
self.chr_mode = ((value & 0x80) >> 7) as u32;
} else {
if self.bank_select < 6 {
self.chr_banks[self.bank_select as usize] = value as u32;
} else if self.bank_select < 8 {
self.prg_banks[self.bank_select as usize - 6] = value as u32
} else {
error!("Invalid bank select value: {:X}", self.bank_select);
}
}
} else if cpu_addr < 0xC000 {
if cpu_addr % 2 == 0 {
self.nametable = if value & 0x01 == 0 {
NametableArrangement::Horizontal
} else {
NametableArrangement::Vertical
}
} else {
}
} else if cpu_addr < 0xE000 {
if cpu_addr % 2 == 0 {
self.irq_latch = value as u32;
} else {
self.irq_reload = true;
}
} else if cpu_addr % 2 == 0 {
self.irq_enable = false;
self.generate_irq = false;
} else {
self.irq_enable = true;
}
}
fn read_ppu(&mut self, ppu_addr: usize, mem: &CartridgeMemory) -> u8 {
self.set_addr_value(ppu_addr as u32);
self.read_ppu_debug(ppu_addr, mem)
}
fn read_ppu_debug(&self, ppu_addr: usize, mem: &CartridgeMemory) -> u8 {
let (bank_size, bank_num) = if self.chr_mode == 0 {
if ppu_addr < 0x1000 {
(0x800, self.chr_banks[ppu_addr / 0x800] / 2)
} else {
(0x400, self.chr_banks[(ppu_addr - 0x1000) / 0x400 + 2])
}
} else if ppu_addr < 0x1000 {
(0x400, self.chr_banks[ppu_addr / 0x400 + 2])
} else {
(0x800, self.chr_banks[(ppu_addr - 0x1000) / 0x800] / 2)
};
mem.read_chr(bank_addr(bank_size, bank_num as usize, ppu_addr))
}
fn write_ppu(&mut self, _ppu_addr: usize, _mem: &mut CartridgeMemory, _value: u8) {}
fn set_addr_value(&mut self, ppu_addr: u32) {
if self.last_ppu_addr == 0 && ppu_addr & 0x1000 != 0 {
if self.irq_counter == 0 || self.irq_reload {
self.irq_counter = self.irq_latch;
self.irq_reload = false;
} else {
self.irq_counter -= 1;
}
if self.irq_counter == 0 && self.irq_enable {
self.generate_irq = true;
}
}
self.last_ppu_addr = ppu_addr & 0x1000;
}
fn nametable_arrangement(&self, _: &CartridgeMemory) -> NametableArrangement {
self.nametable
}
fn irq(&mut self) -> bool {
if self.generate_irq {
self.generate_irq = false;
true
} else {
false
}
}
}
impl Debug for TxRom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"TxROM irq_enable = {:} irq_reload = {:}",
self.irq_enable, self.irq_reload
)
}
}
impl Display for TxRom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TxROM")
}
}