use crate::cartridge::{Cartridge, ChrRomContentStatus};
use crate::rom_loader::ines::HeaderData;
use crate::LoadRomResult;
use crate::RomError;
use devices6502::size_const::*;
use devices6502::*;
use std::io;
const PRG_ROM_PAGE_SIZE: usize = SIZE_16K;
const CHR_MEM_PAGE_SIZE: usize = SIZE_4K;
const MAX_PRG_ROM_PAGES: usize = 16;
const MAX_CHR_ROM_PAGES: usize = 32;
const CART_CPU_MAP_BEGIN_ADDR: u16 = 0x4020;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum PrgRomSetup {
FirstFixed { second_page_index: u8 },
SecondFixed { first_page_index: u8 },
NoneFixed { large_page_index: u8 },
}
#[derive(Copy, Clone, Default)]
enum MirroringSetup {
#[default]
LowerBank,
UpperBank,
Vertical,
Horizontal,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum ChrRomSetup {
LargePages { page_index: u8 },
SmallPages { page_index_0: u8, page_index_1: u8 },
}
struct Cart {
prg_ram: Ram<SIZE_8K>,
prg_rom: Vec<Rom<PRG_ROM_PAGE_SIZE>>,
chr_mem: Vec<Ram<CHR_MEM_PAGE_SIZE>>,
name_tables_ram: Ram<SIZE_2K>,
control_register: u8,
prg_bank: u8,
chr_bank_0: u8,
chr_bank_1: u8,
shift_register: u8,
use_prg_ram: bool,
use_chr_ram: bool,
prg_rom_setup: PrgRomSetup,
mirroring_setup: MirroringSetup,
chr_rom_setup: ChrRomSetup,
}
pub fn load(header: &HeaderData, reader: &mut impl io::Read) -> LoadRomResult {
if header.chr_rom_size as usize > (MAX_CHR_ROM_PAGES * CHR_MEM_PAGE_SIZE) {
return Err(RomError::RomFormat(
"Wrong chr rom size using mapper 001. Up until 128K allowed".into(),
));
}
if (header.chr_rom_size as usize % CHR_MEM_PAGE_SIZE) != 0 {
return Err(RomError::RomFormat(
"Wrong chr rom size using mapper 001. It must be multiple of 4K".into(),
));
}
if header.chr_rom_size as usize > (MAX_PRG_ROM_PAGES * PRG_ROM_PAGE_SIZE) {
return Err(RomError::RomFormat(
"Wrong prg rom size using mapper 001. Up until 256K allowed".into(),
));
}
if (header.prg_rom_size as usize % PRG_ROM_PAGE_SIZE) != 0 {
return Err(RomError::RomFormat(
"Wrong chr rom size using mapper 001. It must be multiple of 16K".into(),
));
}
let mut prg_rom_data = Vec::with_capacity(header.prg_rom_size as usize);
prg_rom_data.resize(header.prg_rom_size as usize, 0u8);
reader.read_exact(prg_rom_data.as_mut_slice())?;
let mut chr_rom_data = Vec::new();
chr_rom_data.resize(header.chr_rom_size as usize, 0u8);
reader.read_exact(&mut chr_rom_data)?;
let chr_rom_data = if header.chr_rom_size == 0 {
None
} else {
Some(chr_rom_data.as_slice())
};
let mut cart = Box::new(Cart::new(prg_rom_data.as_slice(), chr_rom_data));
cart.use_chr_ram = header.chr_rom_size == 0;
Ok(cart)
}
impl Default for PrgRomSetup {
fn default() -> Self {
PrgRomSetup::NoneFixed {
large_page_index: 0,
}
}
}
impl Default for ChrRomSetup {
fn default() -> Self {
ChrRomSetup::LargePages { page_index: 0 }
}
}
impl Default for Cart {
fn default() -> Self {
Self {
prg_ram: Default::default(),
prg_rom: Default::default(),
chr_mem: Default::default(),
name_tables_ram: Default::default(),
control_register: 0,
prg_bank: 0,
chr_bank_0: 0,
chr_bank_1: 0,
shift_register: 0b_0010_000,
use_prg_ram: false,
use_chr_ram: false,
prg_rom_setup: Default::default(),
mirroring_setup: Default::default(),
chr_rom_setup: Default::default(),
}
}
}
impl Cartridge for Cart {
fn read_cpu_mapped(&self, addr: u16) -> u8 {
let addr = addr + CART_CPU_MAP_BEGIN_ADDR;
match addr {
0x0..=0x5FFF => 0,
0x6000..=0x7FFF => {
if self.use_prg_ram {
let addr = addr - 0x6000;
self.prg_ram.read(addr)
} else {
0
}
}
0x8000..=0xBFFF => {
let offset = addr - 0x8000;
match self.prg_rom_setup {
PrgRomSetup::FirstFixed { .. } => self.read_prg_rom_page(offset, 0),
PrgRomSetup::SecondFixed { first_page_index } => {
if self.is_32k_prg_rom() == false {
self.read_prg_rom_page(offset, first_page_index)
} else {
self.read_prg_rom_page(offset, 0)
}
}
PrgRomSetup::NoneFixed { large_page_index } => {
self.read_prg_rom_large_page(offset, large_page_index)
}
}
}
0xC000..=0xFFFF => {
let offset = addr - 0xC000;
match self.prg_rom_setup {
PrgRomSetup::SecondFixed { .. } => {
let last_page_index = self.last_prg_rom_page();
self.read_prg_rom_page(offset, last_page_index)
}
PrgRomSetup::FirstFixed { second_page_index } => {
if self.is_32k_prg_rom() == false {
self.read_prg_rom_page(offset, second_page_index)
} else {
self.read_prg_rom_page(offset, 1)
}
}
PrgRomSetup::NoneFixed { large_page_index } => {
self.read_prg_rom_large_page(offset + SIZE_16K as u16, large_page_index)
}
}
}
}
}
fn write_cpu_mapped(&mut self, data: u8, addr: u16) -> ChrRomContentStatus {
let addr = addr + CART_CPU_MAP_BEGIN_ADDR;
match addr {
0x4020..=0x5FFF => (),
0x6000..=0x7FFF => {
let addr = addr - 0x6000;
self.prg_ram.write(data, addr)
}
0x8000..=0xFFFF => {
#[cfg(feature = "mapper_debug_log")]
println!("Writing {data:#X} to addr {addr:#x}");
if (data & 0b_1000_0000) != 0 {
#[cfg(feature = "mapper_debug_log")]
println!("\tReset\n");
self.reset();
} else {
let new_bit = (data & 1) << 4;
let lost_bit = self.shift_register & 1;
self.shift_register >>= 1;
self.shift_register |= new_bit;
#[cfg(feature = "mapper_debug_log")]
println!("\t\tshift reg: {:08b}", self.shift_register);
if lost_bit != 0 {
#[cfg(feature = "mapper_debug_log")]
match addr {
0x8000..=0x9FFF => {
self.load_control_reg();
println!("\tcontrol reg: {:#X}\n", self.control_register);
}
0xA000..=0xBFFF => {
self.load_chr_bank_0_reg();
println!("\tchr_bank_0 reg: {:#X}\n", self.chr_bank_0);
}
0xC000..=0xDFFF => {
self.load_chr_bank_1_reg();
println!("\tchr_bank_1 reg: {:#X}\n", self.chr_bank_1);
}
0xE000..=0xFFFF => {
self.load_prg_bank_reg();
println!("\tprg_bank reg: {:#X}\n", self.prg_bank);
}
_ => unreachable!(),
}
#[cfg(not(feature = "mapper_debug_log"))]
match addr {
0x8000..=0x9FFF => self.load_control_reg(),
0xA000..=0xBFFF => self.load_chr_bank_0_reg(),
0xC000..=0xDFFF => self.load_chr_bank_1_reg(),
0xE000..=0xFFFF => self.load_prg_bank_reg(),
_ => unreachable!(),
}
self.shift_register = 0b_0001_0000;
}
}
}
_ => unreachable!(),
}
ChrRomContentStatus::Unchanged
}
fn read_ppu_mapped(&mut self, addr: u16) -> u8 {
match addr {
0..=0x0FFF => match self.chr_rom_setup {
ChrRomSetup::LargePages { page_index } => {
self.read_chr_mem_large_page(addr, page_index)
}
ChrRomSetup::SmallPages { page_index_0, .. } => {
self.read_chr_mem_page(addr, page_index_0)
}
},
0x1000..=0x1FFF => match self.chr_rom_setup {
ChrRomSetup::LargePages { page_index } => {
self.read_chr_mem_large_page(addr, page_index)
}
ChrRomSetup::SmallPages { page_index_1, .. } => {
self.read_chr_mem_page(addr - 0x1000, page_index_1)
}
},
0x2000..=0x3EFF => {
let offset = self.calculate_mirroring_offset(addr);
self.name_tables_ram.read(offset)
}
0x3F00..=0xFFFF => unreachable!(),
}
}
fn write_ppu_mapped(&mut self, data: u8, addr: u16) -> ChrRomContentStatus {
match addr {
0..=0x0FFF => {
if self.use_chr_ram {
match self.chr_rom_setup {
ChrRomSetup::LargePages { page_index } => {
self.write_chr_mem_large_page(data, addr, page_index)
}
ChrRomSetup::SmallPages { page_index_0, .. } => {
self.write_chr_mem_page(data, addr, page_index_0)
}
}
}
}
0x1000..=0x1FFF => {
if self.use_chr_ram {
match self.chr_rom_setup {
ChrRomSetup::LargePages { page_index } => {
self.write_chr_mem_large_page(data, addr, page_index)
}
ChrRomSetup::SmallPages { page_index_1, .. } => {
self.write_chr_mem_page(data, addr - 0x1000, page_index_1)
}
}
}
}
0x2000..=0x3EFF => {
let offset = self.calculate_mirroring_offset(addr);
self.name_tables_ram.write(data, offset);
}
0x3F00..=0xFFFF => unreachable!(),
}
ChrRomContentStatus::Unchanged
}
}
impl Cart {
fn new(prg_rom: &[u8], chr_rom: Option<&[u8]>) -> Self {
let prg_pages_count = prg_rom.len() / PRG_ROM_PAGE_SIZE;
let chr_pages_count = chr_rom.map_or(2, |chr_rom| chr_rom.len() / CHR_MEM_PAGE_SIZE);
let mut cart = Self::default();
cart.prg_rom.reserve(prg_pages_count);
cart.chr_mem.reserve(chr_pages_count);
for page_index in 0..prg_pages_count {
let start = page_index * PRG_ROM_PAGE_SIZE;
let end = start + PRG_ROM_PAGE_SIZE;
cart.prg_rom.push(Device::with_data(&prg_rom[start..end]));
}
cart.prg_bank = cart.last_prg_rom_page() & 0b_1111_1110;
cart.prg_rom_setup = PrgRomSetup::NoneFixed {
large_page_index: cart.prg_bank,
};
if let Some(chr_rom) = chr_rom {
for page_index in 0..chr_pages_count {
let start = page_index * CHR_MEM_PAGE_SIZE;
let end = start + CHR_MEM_PAGE_SIZE;
cart.chr_mem.push(Device::with_data(&chr_rom[start..end]));
cart.use_chr_ram = false;
}
} else {
cart.use_chr_ram = true;
cart.chr_mem.resize(2, Default::default());
}
cart
}
fn last_prg_rom_page(&self) -> u8 {
self.prg_rom.len() as u8 - 1
}
fn read_prg_rom_page(&self, offset: u16, page_index: u8) -> u8 {
let page_index = page_index as usize % self.prg_rom.len();
self.prg_rom[page_index].read(offset)
}
fn calculate_mirroring_offset(&self, addr: u16) -> u16 {
let offset = (addr - 0x2000) % 0x1000;
match self.mirroring_setup {
MirroringSetup::LowerBank => offset % 0x0400,
MirroringSetup::UpperBank => (offset % 0x0400) + 0x0400,
MirroringSetup::Vertical => offset % 0x800,
MirroringSetup::Horizontal => {
let map = offset / 0x800;
let offset = offset % 0x400;
offset + map * 0x400
}
}
}
fn read_prg_rom_large_page(&self, offset: u16, large_page_index: u8) -> u8 {
let mut offset = offset;
let mut page_index = large_page_index;
if offset >= SIZE_16K as u16 {
offset -= SIZE_16K as u16;
page_index += 1;
}
self.read_prg_rom_page(offset, page_index)
}
fn read_chr_mem_page(&self, offset: u16, page_index: u8) -> u8 {
let page_index = page_index as usize % self.chr_mem.len();
self.chr_mem[page_index].read(offset)
}
fn write_chr_mem_page(&mut self, data: u8, offset: u16, page_index: u8) {
let page_index = page_index as usize % self.chr_mem.len();
self.chr_mem[page_index].write(data, offset);
}
fn calculate_chr_mem_small_page(offset: u16, large_page_index: u8) -> (u16, u8) {
let mut offset = offset;
let mut page_index = large_page_index;
if offset >= SIZE_4K as u16 {
offset -= SIZE_4K as u16;
page_index += 1;
}
(offset, page_index)
}
fn read_chr_mem_large_page(&self, offset: u16, large_page_index: u8) -> u8 {
let (offset, page_index) = Self::calculate_chr_mem_small_page(offset, large_page_index);
self.read_chr_mem_page(offset, page_index)
}
fn write_chr_mem_large_page(&mut self, data: u8, offset: u16, large_page_index: u8) {
let (offset, page_index) = Self::calculate_chr_mem_small_page(offset, large_page_index);
self.write_chr_mem_page(data, offset, page_index);
}
fn load_control_reg(&mut self) {
self.control_register = self.shift_register;
let mirroring = self.control_register & 0b_11;
match mirroring {
0 => self.mirroring_setup = MirroringSetup::LowerBank,
1 => self.mirroring_setup = MirroringSetup::UpperBank,
2 => self.mirroring_setup = MirroringSetup::Vertical,
3 => self.mirroring_setup = MirroringSetup::Horizontal,
_ => unreachable!(),
}
self.update_prg_rom_setup();
self.update_chr_mem_setup();
}
fn load_chr_bank_0_reg(&mut self) {
self.chr_bank_0 = self.shift_register;
self.update_chr_mem_setup();
}
fn load_chr_bank_1_reg(&mut self) {
self.chr_bank_1 = self.shift_register;
self.update_chr_mem_setup();
}
fn load_prg_bank_reg(&mut self) {
self.prg_bank = self.shift_register & 0b_1111;
self.use_prg_ram = (self.shift_register & 0b_0001_0000) == 0;
self.update_prg_rom_setup();
}
fn update_prg_rom_setup(&mut self) {
let prg_rom_mode = (self.control_register >> 2) & 0b_11;
let prg_rom_page_index = (self.prg_bank & 0b_1111) % self.prg_rom.len() as u8;
match prg_rom_mode {
0..=1 => {
self.prg_rom_setup = PrgRomSetup::NoneFixed {
large_page_index: prg_rom_page_index & 0b_1111_1110,
}
}
2 => {
self.prg_rom_setup = PrgRomSetup::FirstFixed {
second_page_index: prg_rom_page_index,
}
}
3 => {
self.prg_rom_setup = PrgRomSetup::SecondFixed {
first_page_index: prg_rom_page_index,
}
}
_ => unreachable!(),
}
}
fn update_chr_mem_setup(&mut self) {
let chr_mem_mode = (self.control_register >> 4) & 1;
let chr_mem_0_page_index = self.chr_bank_0 % self.chr_mem.len() as u8;
let chr_mem_1_page_index = self.chr_bank_1 % self.chr_mem.len() as u8;
match chr_mem_mode {
0 => {
self.chr_rom_setup = ChrRomSetup::LargePages {
page_index: chr_mem_0_page_index & 0b_1111_1110,
}
}
1 => {
self.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: chr_mem_0_page_index,
page_index_1: chr_mem_1_page_index,
}
}
_ => unreachable!(),
}
}
fn reset(&mut self) {
self.shift_register = 0b_0001_0000;
self.control_register |= 0b_1100;
self.update_prg_rom_setup();
}
fn is_32k_prg_rom(&self) -> bool {
self.prg_rom.len() == 2
}
}
#[cfg(test)]
mod test {
use super::*;
fn new_cart_with_default_rom_data(
pgr_rom_page_count: usize,
chr_rom_page_count: usize,
) -> Cart {
let pgr_rom_data = vec![0; pgr_rom_page_count * PRG_ROM_PAGE_SIZE];
let chr_rom_data = vec![0; chr_rom_page_count * CHR_MEM_PAGE_SIZE];
Cart::new(pgr_rom_data.as_slice(), Some(chr_rom_data.as_slice()))
}
fn reset_shift_reg(cart: &mut Cart) {
cart.write_cpu_mapped(0x80, 0x8000);
}
fn load_reg(cart: &mut Cart, value: u8, addr: u16) {
let mut value = value;
println!("value: {:b}\tshift reg: {:b}", value, cart.shift_register);
cart.write_cpu_mapped(value, addr);
value >>= 1;
println!("value: {:b}\tshift reg: {:b}", value, cart.shift_register);
cart.write_cpu_mapped(value, addr);
value >>= 1;
println!("value: {:b}\tshift reg: {:b}", value, cart.shift_register);
cart.write_cpu_mapped(value, addr);
value >>= 1;
println!("value: {:b}\tshift reg: {:b}", value, cart.shift_register);
cart.write_cpu_mapped(value, addr);
value >>= 1;
println!("value: {:b}\tshift reg: {:b}", value, cart.shift_register);
cart.write_cpu_mapped(value, addr);
value >>= 1;
println!("value: {:b}\tshift reg: {:b}", value, cart.shift_register);
println!("");
}
#[test]
fn mirror_single_screen() {
let mut cart = new_cart_with_default_rom_data(4, 2);
cart.mirroring_setup = MirroringSetup::LowerBank;
cart.write_ppu_mapped(1, 0x2000);
cart.write_ppu_mapped(2, 0x2401);
cart.write_ppu_mapped(3, 0x2802);
cart.write_ppu_mapped(4, 0x2C03);
cart.mirroring_setup = MirroringSetup::UpperBank;
cart.write_ppu_mapped(0x10, 0x2000);
cart.write_ppu_mapped(0x20, 0x2401);
cart.write_ppu_mapped(0x30, 0x2802);
cart.write_ppu_mapped(0x40, 0x2C03);
cart.mirroring_setup = MirroringSetup::LowerBank;
assert_eq!(cart.read_ppu_mapped(0x2000), 1);
assert_eq!(cart.read_ppu_mapped(0x2001), 2);
assert_eq!(cart.read_ppu_mapped(0x2002), 3);
assert_eq!(cart.read_ppu_mapped(0x2003), 4);
cart.mirroring_setup = MirroringSetup::UpperBank;
assert_eq!(cart.read_ppu_mapped(0x2000), 0x10);
assert_eq!(cart.read_ppu_mapped(0x2001), 0x20);
assert_eq!(cart.read_ppu_mapped(0x2002), 0x30);
assert_eq!(cart.read_ppu_mapped(0x2003), 0x40);
}
#[test]
fn mirror_horizontal() {
let mut cart = new_cart_with_default_rom_data(4, 2);
cart.mirroring_setup = MirroringSetup::Horizontal;
cart.write_ppu_mapped(1, 0x2000);
cart.write_ppu_mapped(2, 0x2401);
cart.write_ppu_mapped(3, 0x2802);
cart.write_ppu_mapped(4, 0x2C03);
assert_eq!(cart.read_ppu_mapped(0x2000), 1);
assert_eq!(cart.read_ppu_mapped(0x2001), 2);
assert_eq!(cart.read_ppu_mapped(0x2802), 3);
assert_eq!(cart.read_ppu_mapped(0x2803), 4);
}
#[test]
fn mirror_horizontal_uses_two_distinct_1kb_tiles() {
let mut cart = new_cart_with_default_rom_data(4, 2);
cart.mirroring_setup = MirroringSetup::Horizontal;
cart.write_ppu_mapped(0xAA, 0x2000);
cart.write_ppu_mapped(0xBB, 0x2800);
assert_eq!(
cart.read_ppu_mapped(0x2000),
0xAA,
"NT0 byte 0 (lower half of NT0's 1KB tile)"
);
assert_eq!(
cart.read_ppu_mapped(0x2400),
0xAA,
"NT1 must mirror NT0 ($2400 aliases $2000)"
);
assert_eq!(
cart.read_ppu_mapped(0x2800),
0xBB,
"NT2 byte 0 (lower half of NT1's 1KB tile) must be distinct from NT0"
);
assert_eq!(
cart.read_ppu_mapped(0x2C00),
0xBB,
"NT3 must mirror NT2 ($2C00 aliases $2800)"
);
cart.write_ppu_mapped(0xCC, 0x2000);
assert_eq!(cart.read_ppu_mapped(0x2000), 0xCC, "rewritten NT0 byte 0");
assert_eq!(
cart.read_ppu_mapped(0x2800),
0xBB,
"NT1 byte 0 must not be affected by a write to NT0"
);
}
#[test]
fn mirror_vertical() {
let mut cart = new_cart_with_default_rom_data(4, 2);
cart.mirroring_setup = MirroringSetup::Vertical;
cart.write_ppu_mapped(1, 0x2000);
cart.write_ppu_mapped(2, 0x2401);
cart.write_ppu_mapped(3, 0x2802);
cart.write_ppu_mapped(4, 0x2C03);
assert_eq!(cart.read_ppu_mapped(0x2000), 1);
assert_eq!(cart.read_ppu_mapped(0x2401), 2);
assert_eq!(cart.read_ppu_mapped(0x2002), 3);
assert_eq!(cart.read_ppu_mapped(0x2403), 4);
}
#[test]
fn chr_rom_small_pages() {
let mut cart = new_cart_with_default_rom_data(2, 4);
cart.chr_mem[0].init_data(&[1; SIZE_4K]);
cart.chr_mem[1].init_data(&[2; SIZE_4K]);
cart.chr_mem[2].init_data(&[3; SIZE_4K]);
cart.chr_mem[3].init_data(&[4; SIZE_4K]);
cart.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: 0,
page_index_1: 0,
};
assert_eq!(cart.read_ppu_mapped(0x0100), 1);
assert_eq!(cart.read_ppu_mapped(0x1100), 1);
cart.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: 1,
page_index_1: 2,
};
assert_eq!(cart.read_ppu_mapped(0x0100), 2);
assert_eq!(cart.read_ppu_mapped(0x1100), 3);
cart.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: 2,
page_index_1: 1,
};
assert_eq!(cart.read_ppu_mapped(0x0100), 3);
assert_eq!(cart.read_ppu_mapped(0x1100), 2);
cart.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: 3,
page_index_1: 3,
};
assert_eq!(cart.read_ppu_mapped(0x0100), 4);
assert_eq!(cart.read_ppu_mapped(0x1100), 4);
}
#[test]
fn chr_rom_large_pages() {
let mut cart = new_cart_with_default_rom_data(6, 4);
cart.chr_mem[0].init_data(&[1; SIZE_4K]);
cart.chr_mem[1].init_data(&[2; SIZE_4K]);
cart.chr_mem[2].init_data(&[3; SIZE_4K]);
cart.chr_mem[3].init_data(&[4; SIZE_4K]);
cart.chr_rom_setup = ChrRomSetup::LargePages { page_index: 0 };
assert_eq!(cart.read_ppu_mapped(0x0100), 1);
assert_eq!(cart.read_ppu_mapped(0x1100), 2);
cart.chr_rom_setup = ChrRomSetup::LargePages { page_index: 2 };
assert_eq!(cart.read_ppu_mapped(0x0100), 3);
assert_eq!(cart.read_ppu_mapped(0x1100), 4);
}
#[test]
fn prg_rom_large_pages() {
let mut cart = new_cart_with_default_rom_data(4, 2);
cart.prg_rom[0].init_data(&[1; SIZE_16K]);
cart.prg_rom[1].init_data(&[2; SIZE_16K]);
cart.prg_rom[2].init_data(&[3; SIZE_16K]);
cart.prg_rom[3].init_data(&[4; SIZE_16K]);
cart.prg_rom_setup = PrgRomSetup::NoneFixed {
large_page_index: 0,
};
assert_eq!(cart.read_cpu_mapped(0x4000), 1);
assert_eq!(cart.read_cpu_mapped(0x8000), 2);
cart.prg_rom_setup = PrgRomSetup::NoneFixed {
large_page_index: 2,
};
assert_eq!(cart.read_cpu_mapped(0x4000), 3);
assert_eq!(cart.read_cpu_mapped(0x8000), 4);
}
#[test]
fn prg_rom_small_pages() {
let mut cart = new_cart_with_default_rom_data(4, 2);
cart.prg_rom[0].init_data(&[1; SIZE_16K]);
cart.prg_rom[1].init_data(&[2; SIZE_16K]);
cart.prg_rom[2].init_data(&[3; SIZE_16K]);
cart.prg_rom[3].init_data(&[4; SIZE_16K]);
cart.prg_rom_setup = PrgRomSetup::FirstFixed {
second_page_index: 0,
};
assert_eq!(cart.read_cpu_mapped(0x4000), 1);
assert_eq!(cart.read_cpu_mapped(0x8000), 1);
cart.prg_rom_setup = PrgRomSetup::FirstFixed {
second_page_index: 2,
};
assert_eq!(cart.read_cpu_mapped(0x4000), 1);
assert_eq!(cart.read_cpu_mapped(0x8000), 3);
cart.prg_rom_setup = PrgRomSetup::SecondFixed {
first_page_index: 3,
};
assert_eq!(cart.read_cpu_mapped(0x4000), 4);
assert_eq!(cart.read_cpu_mapped(0x8000), 4);
cart.prg_rom_setup = PrgRomSetup::SecondFixed {
first_page_index: 1,
};
assert_eq!(cart.read_cpu_mapped(0x4000), 2);
assert_eq!(cart.read_cpu_mapped(0x8000), 4);
}
#[test]
fn chr_ram_write() {
let mut cart = new_cart_with_default_rom_data(6, 2);
cart.use_chr_ram = true;
cart.write_ppu_mapped(0x69, 0x100);
assert_eq!(cart.read_ppu_mapped(0x100), 0x69);
cart.use_chr_ram = false;
cart.write_ppu_mapped(0x96, 0x100);
assert_eq!(cart.read_ppu_mapped(0x100), 0x69);
}
#[test]
fn write_chr_ram_small_pages() {
let mut cart = new_cart_with_default_rom_data(6, 4);
cart.use_chr_ram = true;
cart.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: 0,
page_index_1: 1,
};
cart.write_ppu_mapped(1, 0x0100);
cart.write_ppu_mapped(2, 0x1100);
cart.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: 2,
page_index_1: 3,
};
cart.write_ppu_mapped(3, 0x0100);
cart.write_ppu_mapped(4, 0x1100);
cart.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: 0,
page_index_1: 0,
};
assert_eq!(cart.read_ppu_mapped(0x0100), 1);
assert_eq!(cart.read_ppu_mapped(0x1100), 1);
cart.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: 1,
page_index_1: 2,
};
assert_eq!(cart.read_ppu_mapped(0x0100), 2);
assert_eq!(cart.read_ppu_mapped(0x1100), 3);
cart.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: 2,
page_index_1: 1,
};
assert_eq!(cart.read_ppu_mapped(0x0100), 3);
assert_eq!(cart.read_ppu_mapped(0x1100), 2);
cart.chr_rom_setup = ChrRomSetup::SmallPages {
page_index_0: 3,
page_index_1: 3,
};
assert_eq!(cart.read_ppu_mapped(0x0100), 4);
assert_eq!(cart.read_ppu_mapped(0x1100), 4);
}
#[test]
fn load_chr_bank_reg() {
let mut cart = new_cart_with_default_rom_data(6, 4);
let chr_bank_0 = 1;
let chr_bank_1 = 3;
reset_shift_reg(&mut cart);
load_reg(&mut cart, chr_bank_0, 0xA000 - CART_CPU_MAP_BEGIN_ADDR);
load_reg(&mut cart, chr_bank_1, 0xC000 - CART_CPU_MAP_BEGIN_ADDR);
assert_eq!(cart.chr_bank_0, 1);
assert_eq!(cart.chr_bank_1, 3);
}
#[test]
fn chr_rom_bank_mode_reg() {
let mut cart = new_cart_with_default_rom_data(6, 4);
let chr_bank_0 = 3;
let chr_bank_1 = 3;
reset_shift_reg(&mut cart);
load_reg(&mut cart, chr_bank_0, 0xA000 - CART_CPU_MAP_BEGIN_ADDR);
load_reg(&mut cart, chr_bank_1, 0xC000 - CART_CPU_MAP_BEGIN_ADDR);
load_reg(&mut cart, 0, 0x8000 - CART_CPU_MAP_BEGIN_ADDR);
assert_eq!(
cart.chr_rom_setup,
ChrRomSetup::LargePages { page_index: 2 }
);
load_reg(&mut cart, 0b_0001_0000, 0x8000 - CART_CPU_MAP_BEGIN_ADDR);
assert_eq!(
cart.chr_rom_setup,
ChrRomSetup::SmallPages {
page_index_0: 3,
page_index_1: 3
}
);
}
#[test]
fn load_prg_bank_reg() {
let mut cart = new_cart_with_default_rom_data(4, 6);
reset_shift_reg(&mut cart);
load_reg(&mut cart, 3, 0xE000 - CART_CPU_MAP_BEGIN_ADDR);
load_reg(&mut cart, 0, 0x8000 - CART_CPU_MAP_BEGIN_ADDR);
assert_eq!(
cart.prg_rom_setup,
PrgRomSetup::NoneFixed {
large_page_index: 2
}
);
reset_shift_reg(&mut cart);
load_reg(&mut cart, 0b_0100, 0x8000 - CART_CPU_MAP_BEGIN_ADDR);
assert_eq!(
cart.prg_rom_setup,
PrgRomSetup::NoneFixed {
large_page_index: 2
}
);
load_reg(&mut cart, 0b_1000, 0x8000 - CART_CPU_MAP_BEGIN_ADDR);
assert_eq!(
cart.prg_rom_setup,
PrgRomSetup::FirstFixed {
second_page_index: 3
}
);
load_reg(&mut cart, 0b_1100, 0x8000 - CART_CPU_MAP_BEGIN_ADDR);
assert_eq!(
cart.prg_rom_setup,
PrgRomSetup::SecondFixed {
first_page_index: 3
}
);
}
#[test]
fn ram_enable() {
let mut cart = new_cart_with_default_rom_data(6, 4);
reset_shift_reg(&mut cart);
load_reg(&mut cart, 0b_1_0000, 0xE000 - CART_CPU_MAP_BEGIN_ADDR);
assert_eq!(cart.use_prg_ram, false);
reset_shift_reg(&mut cart);
load_reg(&mut cart, 0, 0xE000 - CART_CPU_MAP_BEGIN_ADDR);
assert_eq!(cart.use_prg_ram, true);
}
}