use super::apu_device::ApuDevice;
use super::controller_device::{
ControllerDevice, VS_INPUT_COIN_SLOT1, VS_INPUT_COIN_SLOT2, VS_INPUT_SERVICE,
};
use super::mapper_device::MapperDevice;
use super::oam_dma_device::OamDmaDevice;
use super::ppu_device::PpuDevice;
use super::ram_device::RamDevice;
use crate::nes::apu::SharedApu;
use crate::nes::cartridge::Cartridge;
use crate::nes::console::{ExpansionPort, HardwareMode};
use crate::nes::input::{
ArkanoidController, ArkanoidState, Button, Controller, ControllerType, JoypadState, NesJoypad,
PowerPad, PowerPadButton, PowerPadState, SnesAdapter, SnesAdapterState, SnesButton, Zapper,
ZapperState,
};
use crate::nes::ppu::{self, SharedPpu};
use crate::platform::app_context::SharedAppContext;
use crate::platform::debugging::log_info;
use serde::{Deserialize, Serialize};
use std::cell::{Cell, RefCell};
use std::io;
use std::ops::RangeInclusive;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ControllerStateWrapper {
Joypad(JoypadState),
SnesAdapter(SnesAdapterState),
Arkanoid(ArkanoidState),
Zapper(ZapperState),
PowerPad(PowerPadState),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BusState {
pub open_bus: u8,
pub oam_dma_page: Option<u8>,
pub port1_controller: ControllerStateWrapper,
pub port2_controller: ControllerStateWrapper,
#[serde(default)]
pub expansion_arkanoid: Option<ArkanoidState>,
#[serde(default)]
pub expansion_zapper: Option<ZapperState>,
#[serde(default)]
pub expansion_power_pad: Option<PowerPadState>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MapperState {
pub mapper_number: u16,
pub prg_ram: Vec<u8>,
pub chr_ram: Vec<u8>,
pub registers: Vec<u8>,
}
use std::path::PathBuf;
use std::rc::Rc;
#[derive(Debug, Clone, Default)]
pub struct ControllerModes {
pub four_score_enabled: bool,
pub famicom_four_players_enabled: bool,
pub famicom_mode: bool,
pub arkanoid_famicom_enabled: bool,
pub zapper_famicom_enabled: bool,
pub power_pad_famicom_enabled: bool,
pub vs_system_enabled: bool,
pub vs_dip_switches: u8,
pub vs_hardware_type: Option<crate::nes::cartridge::VsHardwareType>,
}
pub trait BusDevice {
fn read(&mut self, addr: u16, open_bus: u8, is_dummy_read: bool) -> Option<u8>;
fn write(&mut self, addr: u16, value: u8, is_dummy_write: bool) -> bool;
fn address_range(&self) -> RangeInclusive<u16>;
fn sync_controller_modes(&mut self, _modes: &ControllerModes) {}
}
pub type SharedBus = Rc<RefCell<Bus>>;
pub struct Bus {
cpu_ram: Rc<RefCell<Vec<u8>>>,
cartridge: Rc<RefCell<Option<Rc<RefCell<Cartridge>>>>>,
ppu: SharedPpu,
apu: SharedApu,
app_context: SharedAppContext,
oam_dma_page: Rc<RefCell<Option<u8>>>, dma_triggered: Rc<RefCell<bool>>,
controllers: [Rc<RefCell<Box<dyn Controller>>>; 2], four_score_extra_button_states: Rc<RefCell<[u8; 2]>>, expansion_arkanoid: Rc<RefCell<ArkanoidController>>, expansion_zapper: Rc<RefCell<Zapper>>, expansion_power_pad: Rc<RefCell<PowerPad>>, vs_arcade_input: Rc<Cell<u8>>, vs_hardware_type: Option<crate::nes::cartridge::VsHardwareType>, open_bus: u8, devices: Vec<Box<dyn BusDevice>>,
}
impl Bus {
fn build_controller(
ppu: Rc<RefCell<ppu::Ppu>>,
app_context: Rc<RefCell<crate::platform::app_context::AppContext>>,
controller_type: ControllerType,
) -> Box<dyn Controller> {
match controller_type {
ControllerType::Joypad => Box::new(NesJoypad::new()),
ControllerType::SnesAdapter => Box::new(SnesAdapter::new()),
ControllerType::SnesController => Box::new(SnesAdapter::new_controller()),
ControllerType::SnesMouse => Box::new(SnesAdapter::new_mouse()),
ControllerType::Arkanoid => Box::new(ArkanoidController::new()),
ControllerType::Zapper => Box::new(Zapper::new(ppu, app_context)),
ControllerType::PowerPad => Box::new(PowerPad::new()),
}
}
pub fn new(
ppu: Rc<RefCell<ppu::Ppu>>,
apu: SharedApu,
app_context: Rc<RefCell<crate::platform::app_context::AppContext>>,
) -> Self {
let controllers = [
Rc::new(RefCell::new(Self::build_controller(
ppu.clone(),
app_context.clone(),
ControllerType::Joypad,
))),
Rc::new(RefCell::new(Self::build_controller(
ppu.clone(),
app_context.clone(),
ControllerType::Joypad,
))),
];
let mut cpu_ram = vec![0; 0x10000];
let ram_init_mode = app_context.borrow().config().frontend.ram_init_mode;
crate::nes::console::initialize_ram(&mut cpu_ram[0..0x800], ram_init_mode);
let expansion_arkanoid = Rc::new(RefCell::new(ArkanoidController::new()));
let expansion_zapper = Rc::new(RefCell::new(Zapper::new(ppu.clone(), app_context.clone())));
let expansion_power_pad = Rc::new(RefCell::new(PowerPad::new()));
let vs_arcade_input = Rc::new(Cell::new(0u8));
let mut controller = Self {
cpu_ram: Rc::new(RefCell::new(cpu_ram)),
cartridge: Rc::new(RefCell::new(None)),
ppu,
apu,
app_context,
oam_dma_page: Rc::new(RefCell::new(None)),
dma_triggered: Rc::new(RefCell::new(false)),
controllers,
four_score_extra_button_states: Rc::new(RefCell::new([0, 0])),
expansion_arkanoid,
expansion_zapper,
expansion_power_pad,
vs_arcade_input,
vs_hardware_type: None,
open_bus: 0xFF, devices: Vec::new(),
};
controller.register_device(Box::new(RamDevice::new(controller.cpu_ram.clone())));
controller.register_device(Box::new(PpuDevice::new(
controller.ppu.clone(),
controller.cartridge.clone(),
)));
let four_score_enabled = controller
.app_context
.borrow()
.config()
.nes
.four_score_enabled;
let famicom_four_players_enabled = controller.is_famicom_four_players_configured();
let mut controller_device = ControllerDevice::new_with_four_score_state(
controller.controllers[0].clone(),
controller.controllers[1].clone(),
four_score_enabled,
famicom_four_players_enabled,
controller.four_score_extra_button_states.clone(),
);
controller_device.set_four_score_enabled(four_score_enabled);
controller_device.set_famicom_four_players_enabled(famicom_four_players_enabled);
controller_device
.set_arkanoid_famicom_expansion(Some(controller.expansion_arkanoid.clone()));
controller_device.set_zapper_famicom_expansion(Some(controller.expansion_zapper.clone()));
controller_device
.set_power_pad_famicom_expansion(Some(controller.expansion_power_pad.clone()));
controller_device.set_vs_arcade_input(controller.vs_arcade_input.clone());
controller.register_device(Box::new(controller_device));
controller.register_device(Box::new(ApuDevice::new(controller.apu.clone())));
controller.register_device(Box::new(OamDmaDevice::new(
controller.oam_dma_page.clone(),
controller.dma_triggered.clone(),
)));
controller.register_device(Box::new(MapperDevice::new(
controller.cartridge.clone(),
controller.ppu.clone(),
)));
controller.sync_controller_modes_from_config();
controller
}
pub fn sync_controller_modes_from_config(&mut self) {
let app_context = self.app_context.borrow();
let config = app_context.config();
let modes = ControllerModes {
four_score_enabled: config.nes.four_score_enabled,
famicom_four_players_enabled: Self::is_famicom_four_players(config),
famicom_mode: config.nes.hardware_mode == HardwareMode::Famicom,
arkanoid_famicom_enabled: Self::is_arkanoid_famicom(config),
zapper_famicom_enabled: Self::is_zapper_famicom(config),
power_pad_famicom_enabled: Self::is_power_pad_famicom(config),
vs_system_enabled: Self::is_vs_system(config),
vs_dip_switches: config.nes.vs_dip_switches,
vs_hardware_type: self.vs_hardware_type,
};
drop(app_context);
for device in self.devices.iter_mut() {
device.sync_controller_modes(&modes);
}
}
fn is_famicom_four_players_configured(&self) -> bool {
Self::is_famicom_four_players(self.app_context.borrow().config())
}
fn is_famicom_four_players(config: &crate::nes::console::Config) -> bool {
config.nes.hardware_mode == HardwareMode::Famicom
&& config.nes.expansion_port == ExpansionPort::FamicomFourPlayers
}
fn is_arkanoid_famicom(config: &crate::nes::console::Config) -> bool {
config.nes.hardware_mode == HardwareMode::Famicom
&& config.nes.expansion_port == ExpansionPort::ArkanoidFamicom
}
fn is_zapper_famicom(config: &crate::nes::console::Config) -> bool {
config.nes.hardware_mode == HardwareMode::Famicom
&& config.nes.expansion_port == ExpansionPort::ZapperFamicom
}
fn is_power_pad_famicom(config: &crate::nes::console::Config) -> bool {
config.nes.hardware_mode == HardwareMode::Famicom
&& config.nes.expansion_port == ExpansionPort::PowerPadFamicom
}
fn is_vs_system(config: &crate::nes::console::Config) -> bool {
config.nes.expansion_port == ExpansionPort::VsSystem
}
fn has_player34_serial_enabled(&self) -> bool {
let config = self.app_context.borrow();
config.config().nes.four_score_enabled || Self::is_famicom_four_players(config.config())
}
pub fn register_device(&mut self, device: Box<dyn BusDevice>) {
self.devices.push(device);
}
pub fn map_cartridge(&mut self, cartridge: Cartridge) {
let trainer_data = cartridge.trainer().map(|t| t.to_vec());
let vs_ppu_type = cartridge.vs_ppu_type();
let vs_hardware_type = cartridge.vs_hardware_type();
let cartridge_rc = Rc::new(RefCell::new(cartridge));
if let Some(trainer_bytes) = trainer_data {
let mut cart = cartridge_rc.borrow_mut();
let mapper = cart.mapper_mut();
let base = mapper.capabilities().trainer_load_address;
for (i, byte) in trainer_bytes.iter().enumerate() {
mapper.write_prg(base + i as u16, *byte);
}
}
{
let mut ppu = self.ppu.borrow_mut();
ppu.set_cartridge(cartridge_rc.clone());
ppu.set_mirroring(cartridge_rc.borrow().mapper().get_mirroring());
ppu.set_vs_ppu_type(vs_ppu_type);
}
*self.cartridge.borrow_mut() = Some(cartridge_rc);
self.vs_hardware_type = vs_hardware_type;
self.sync_controller_modes_from_config();
}
pub fn reset(&mut self, soft_reset: bool, ram_init_mode: crate::nes::console::RamInitMode) {
if !soft_reset {
let mut cpu_ram = self.cpu_ram.borrow_mut();
crate::nes::console::initialize_ram(&mut cpu_ram[0..0x800], ram_init_mode);
}
self.reset_cartridge(soft_reset, ram_init_mode);
}
pub fn reset_cartridge(
&mut self,
soft_reset: bool,
ram_init_mode: crate::nes::console::RamInitMode,
) {
let Some(cartridge) = self.cartridge.borrow().as_ref().cloned() else {
return;
};
if !soft_reset {
cartridge.borrow_mut().initialize_ram(ram_init_mode);
}
cartridge.borrow_mut().reset();
let mirroring = cartridge.borrow().mapper().get_mirroring();
self.ppu.borrow_mut().set_mirroring(mirroring);
}
pub fn cartridge_has_trainer_jsr(&self) -> bool {
self.cartridge
.borrow()
.as_ref()
.map(|c| {
let cart = c.borrow();
cart.has_trainer() && cart.mapper().capabilities().trainer_jsr
})
.unwrap_or(false)
}
#[cfg(test)]
#[allow(dead_code)]
pub fn cpu_ram_ref(&self) -> Rc<RefCell<Vec<u8>>> {
self.cpu_ram.clone()
}
pub fn save_ram(&self) -> io::Result<()> {
let Some(cartridge) = self.cartridge.borrow().as_ref().cloned() else {
return Ok(());
};
cartridge.borrow().save_ram()
}
pub fn cartridge_state_path(&self) -> Option<PathBuf> {
self.cartridge
.borrow()
.as_ref()
.and_then(|cart| cart.borrow().state_path())
}
pub fn cartridge_debug_path(&self) -> Option<PathBuf> {
self.cartridge
.borrow()
.as_ref()
.and_then(|cart| cart.borrow().debug_path())
}
pub fn read_prg_rom_for_debugger(&self, addr: u16) -> u8 {
if !(0x8000..=0xFFFF).contains(&addr) {
return 0;
}
self.cartridge
.borrow()
.as_ref()
.map(|cart| cart.borrow().mapper().read_prg(addr))
.unwrap_or(0)
}
pub fn read_cpu_for_debugger(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x1FFF => self.cpu_ram.borrow()[(addr & 0x07FF) as usize],
0x6000..=0xFFFF => self
.cartridge
.borrow()
.as_ref()
.map(|cart| {
cart.borrow()
.mapper()
.read_prg_open_bus(addr, self.open_bus)
})
.unwrap_or(0),
_ => 0,
}
}
pub fn read(&mut self, addr: u16, is_dummy_read: bool) -> u8 {
if (0xFFFA..=0xFFFB).contains(&addr)
&& let Some(cartridge) = self.cartridge.borrow().as_ref().cloned()
{
cartridge.borrow_mut().mapper_mut().on_irq_vector_read(addr);
}
if let Some(value) = self.read_from_devices(addr, is_dummy_read) {
self.open_bus = value;
return value;
}
let value = {
log_info(format!(
"Warning: Read from unimplemented address {:04X}, returning 0",
addr
));
0
};
self.open_bus = value;
value
}
fn read_from_devices(&mut self, addr: u16, is_dummy_read: bool) -> Option<u8> {
for device in self.devices.iter_mut() {
if device.address_range().contains(&addr)
&& let Some(value) = device.read(addr, self.open_bus, is_dummy_read)
{
return Some(value);
}
}
None
}
pub fn mapper_irq_pending(&self) -> bool {
self.cartridge
.borrow()
.as_ref()
.map(|cart| cart.borrow().mapper().irq_pending())
.unwrap_or(false)
}
pub fn cartridge_mapper_capabilities(
&self,
) -> Option<crate::nes::cartridge::MapperCapabilities> {
self.cartridge
.borrow()
.as_ref()
.map(|cart| cart.borrow().mapper().capabilities())
}
pub fn mapper_expansion_audio_sample(&self) -> f32 {
self.cartridge
.borrow()
.as_ref()
.map(|cart| cart.borrow().mapper().expansion_audio_sample())
.unwrap_or(0.0)
}
pub fn mapper_cpu_cycle(&mut self) {
let Some(cartridge) = self.cartridge.borrow().as_ref().cloned() else {
return;
};
cartridge.borrow_mut().mapper_mut().cpu_cycle();
}
#[cfg(test)]
fn has_device_for_address(&self, addr: u16) -> bool {
self.devices
.iter()
.any(|device| device.address_range().contains(&addr))
}
#[cfg(any(test, debug_assertions))]
#[allow(dead_code)]
pub fn read_for_testing(&mut self, addr: u16) -> u8 {
let old_open_bus = self.open_bus;
let value = self.read(addr, false);
self.open_bus = old_open_bus;
value
}
#[cfg(test)]
pub fn write_for_testing(&mut self, addr: u16, value: u8) {
let old_open_bus = self.open_bus;
self.write(addr, value, false);
self.open_bus = old_open_bus;
}
#[cfg(test)]
pub fn mapper_ppu_address_changed_for_test(&mut self, addr: u16) {
let Some(cartridge) = self.cartridge.borrow().as_ref().cloned() else {
return;
};
cartridge
.borrow_mut()
.mapper_mut()
.ppu_address_changed(addr & 0x1FFF);
}
pub fn write(&mut self, addr: u16, value: u8, is_dummy_write: bool) -> bool {
self.open_bus = value;
let wrote = self.write_to_devices(addr, value, is_dummy_write);
if wrote {
if addr == 0x4014
&& !is_dummy_write
&& let Some(cartridge) = self.cartridge.borrow().as_ref().cloned()
{
cartridge.borrow_mut().mapper_mut().on_oam_dma();
}
if addr == 0x4016
&& !is_dummy_write
&& let Some(cartridge) = self.cartridge.borrow().as_ref().cloned()
{
cartridge
.borrow_mut()
.mapper_mut()
.on_controller_port_write(addr, value);
}
return self.dma_triggered.replace(false);
}
{
log_info(format!(
"Warning: Write to unimplemented address {:04X} ignored",
addr
));
}
false }
fn write_to_devices(&mut self, addr: u16, value: u8, is_dummy_write: bool) -> bool {
for device in self.devices.iter_mut() {
if device.address_range().contains(&addr) && device.write(addr, value, is_dummy_write) {
return true;
}
}
false
}
#[cfg(test)]
pub fn write_u16(&mut self, addr: u16, value: u16) {
let lo = (value & 0xFF) as u8;
let hi = (value >> 8) as u8;
self.write(addr, lo, false);
self.write(addr.wrapping_add(1), hi, false);
}
pub fn oam_dma_pending(&self) -> bool {
self.oam_dma_page.borrow().is_some()
}
pub fn take_oam_dma_page(&mut self) -> Option<u8> {
self.oam_dma_page.borrow_mut().take()
}
#[cfg(test)]
#[allow(dead_code)]
pub fn execute_oam_dma(&mut self, page: u8) {
let source_page = (page as u16) << 8;
for i in 0..256u16 {
let byte = self.read(source_page + i, false);
self.ppu.borrow_mut().write_oam_data(byte);
}
}
pub fn set_button(&mut self, port: u8, button: Button, pressed: bool) {
if (1..=2).contains(&port) {
let config = self.app_context.borrow();
let cfg = config.config();
let is_vs = Self::is_vs_system(cfg);
let vs_swapped = cfg.nes.vs_controllers_swapped;
drop(config);
let effective_port = if vs_swapped && !matches!(button, Button::Start | Button::Select)
{
3 - port } else {
port
};
let effective_button = if is_vs {
match button {
Button::Start => Button::Select,
Button::Select => Button::Start,
other => other,
}
} else {
button
};
self.controllers[(effective_port - 1) as usize]
.borrow_mut()
.set_button(effective_button, pressed);
return;
}
if !self.has_player34_serial_enabled() {
return;
}
if !(3..=4).contains(&port) {
return;
}
let mut states = self.four_score_extra_button_states.borrow_mut();
let player_index = (port - 3) as usize;
let bit = button as u8;
if pressed {
states[player_index] |= 1 << bit;
} else {
states[player_index] &= !(1 << bit);
}
}
pub fn set_vs_coin_insert(&self, slot: u8, pressed: bool) {
let bit = if slot == 0 {
VS_INPUT_COIN_SLOT1
} else {
VS_INPUT_COIN_SLOT2
};
let current = self.vs_arcade_input.get();
if pressed {
self.vs_arcade_input.set(current | bit);
} else {
self.vs_arcade_input.set(current & !bit);
}
}
pub fn set_vs_service_button(&self, pressed: bool) {
let current = self.vs_arcade_input.get();
if pressed {
self.vs_arcade_input.set(current | VS_INPUT_SERVICE);
} else {
self.vs_arcade_input.set(current & !VS_INPUT_SERVICE);
}
}
pub fn set_snes_button(&mut self, port: u8, button: SnesButton, pressed: bool) -> bool {
if !(1..=2).contains(&port) {
return false;
}
self.controllers[(port - 1) as usize]
.borrow_mut()
.set_snes_button(button, pressed)
}
pub fn set_power_pad_button(
&mut self,
port: u8,
button: PowerPadButton,
pressed: bool,
) -> bool {
if !(1..=2).contains(&port) {
return false;
}
self.controllers[(port - 1) as usize]
.borrow_mut()
.set_power_pad_button(button, pressed)
}
pub fn set_expansion_power_pad_button(
&mut self,
button: PowerPadButton,
pressed: bool,
) -> bool {
if !self.is_power_pad_famicom_configured() {
return false;
}
self.expansion_power_pad
.borrow_mut()
.set_button(button, pressed);
true
}
pub fn get_joypad_button_states(&self, port: u8) -> u8 {
if self.has_player34_serial_enabled() && (3..=4).contains(&port) {
return self.four_score_extra_button_states.borrow()[(port - 3) as usize];
}
if !(1..=2).contains(&port) {
return 0;
}
let controller = self.controllers[(port - 1) as usize].borrow();
let state = controller.capture_state();
match state {
crate::nes::input::ControllerState::Joypad(joypad_state) => joypad_state.button_states,
_ => 0, }
}
pub fn set_controller_type(&mut self, port: u8, controller_type: ControllerType) {
if !(1..=2).contains(&port) {
return;
}
let new_controller =
Self::build_controller(self.ppu.clone(), self.app_context.clone(), controller_type);
*self.controllers[(port - 1) as usize].borrow_mut() = new_controller;
}
pub fn set_mouse_x_position(&mut self, position: u8) {
for controller in &self.controllers {
controller.borrow_mut().set_mouse_x_position(position);
}
self.expansion_arkanoid.borrow_mut().set_position(position);
self.expansion_zapper
.borrow_mut()
.set_mouse_x_position(position);
}
pub fn set_mouse_y_position(&mut self, position: u8) {
for controller in &self.controllers {
controller.borrow_mut().set_mouse_y_position(position);
}
self.expansion_zapper
.borrow_mut()
.set_mouse_y_position(position);
}
pub fn add_mouse_delta(&mut self, dx: i16, dy: i16) {
for controller in &self.controllers {
controller.borrow_mut().add_mouse_delta(dx, dy);
}
}
pub fn set_mouse_left_button(&mut self, pressed: bool) {
for controller in &self.controllers {
controller.borrow_mut().set_mouse_left_button(pressed);
}
self.expansion_arkanoid.borrow_mut().set_trigger(pressed);
self.expansion_zapper
.borrow_mut()
.set_mouse_left_button(pressed);
}
pub fn set_mouse_right_button(&mut self, pressed: bool) {
for controller in &self.controllers {
controller.borrow_mut().set_mouse_right_button(pressed);
}
}
pub fn has_snes_mouse(&self) -> bool {
self.controllers
.iter()
.any(|controller| controller.borrow().is_snes_mouse())
}
pub fn controller_input_type(&self, port: u8) -> Option<crate::nes::input::ControllerInput> {
if self.has_player34_serial_enabled() && (3..=4).contains(&port) {
return Some(crate::nes::input::ControllerInput::Gamepad);
}
if !(1..=2).contains(&port) {
return None;
}
Some(self.controllers[(port - 1) as usize].borrow().input_type())
}
pub fn has_expansion_mouse_controller(&self) -> bool {
self.is_arkanoid_famicom_configured() || self.is_zapper_famicom_configured()
}
#[allow(dead_code)]
pub fn has_expansion_power_pad(&self) -> bool {
self.is_power_pad_famicom_configured()
}
pub fn has_expansion_zapper(&self) -> bool {
self.is_zapper_famicom_configured()
}
fn is_arkanoid_famicom_configured(&self) -> bool {
Self::is_arkanoid_famicom(self.app_context.borrow().config())
}
fn is_zapper_famicom_configured(&self) -> bool {
Self::is_zapper_famicom(self.app_context.borrow().config())
}
fn is_power_pad_famicom_configured(&self) -> bool {
Self::is_power_pad_famicom(self.app_context.borrow().config())
}
#[allow(dead_code)]
pub fn is_zapper_active(&self, port: u8) -> bool {
if self.is_zapper_famicom_configured() {
return true;
}
if !(1..=2).contains(&port) {
return false;
}
matches!(
self.controllers[(port - 1) as usize]
.borrow()
.capture_state(),
crate::nes::input::ControllerState::Zapper(_)
)
}
#[cfg(test)]
fn open_bus_value_for_test(&self) -> u8 {
self.open_bus
}
pub fn ram_snapshot(&self) -> Vec<u8> {
self.cpu_ram.borrow()[..0x800].to_vec()
}
pub fn restore_ram(&mut self, data: &[u8]) {
let mut ram = self.cpu_ram.borrow_mut();
let len = data.len().min(0x800);
ram[..len].copy_from_slice(&data[..len]);
}
pub fn capture_mapper_state(&self) -> MapperState {
if let Some(ref cartridge_opt) = *self.cartridge.borrow() {
let cartridge = cartridge_opt.borrow();
let mapper = cartridge.mapper();
MapperState {
mapper_number: mapper.mapper_number(),
prg_ram: mapper.prg_ram_snapshot(),
chr_ram: mapper.chr_ram_snapshot(),
registers: mapper.registers_snapshot(),
}
} else {
MapperState {
mapper_number: 0,
prg_ram: vec![],
chr_ram: vec![],
registers: vec![],
}
}
}
pub fn restore_mapper_state(&mut self, state: &MapperState) {
if let Some(ref cartridge_opt) = *self.cartridge.borrow() {
let mut cartridge = cartridge_opt.borrow_mut();
let mapper = cartridge.mapper_mut();
mapper.restore_prg_ram(&state.prg_ram);
mapper.restore_chr_ram(&state.chr_ram);
mapper.restore_registers(&state.registers);
let mirroring = mapper.get_mirroring();
self.ppu.borrow_mut().set_mirroring(mirroring);
}
}
pub fn capture_state(&self) -> BusState {
let port1_state = self.controllers[0].borrow().capture_state();
let port2_state = self.controllers[1].borrow().capture_state();
BusState {
open_bus: self.open_bus,
oam_dma_page: *self.oam_dma_page.borrow(),
port1_controller: match port1_state {
crate::nes::input::ControllerState::Joypad(s) => ControllerStateWrapper::Joypad(s),
crate::nes::input::ControllerState::SnesAdapter(s) => {
ControllerStateWrapper::SnesAdapter(s)
}
crate::nes::input::ControllerState::Paddle(s) => {
ControllerStateWrapper::Arkanoid(s)
}
crate::nes::input::ControllerState::Zapper(s) => ControllerStateWrapper::Zapper(s),
crate::nes::input::ControllerState::PowerPad(s) => {
ControllerStateWrapper::PowerPad(s)
}
},
port2_controller: match port2_state {
crate::nes::input::ControllerState::Joypad(s) => ControllerStateWrapper::Joypad(s),
crate::nes::input::ControllerState::SnesAdapter(s) => {
ControllerStateWrapper::SnesAdapter(s)
}
crate::nes::input::ControllerState::Paddle(s) => {
ControllerStateWrapper::Arkanoid(s)
}
crate::nes::input::ControllerState::Zapper(s) => ControllerStateWrapper::Zapper(s),
crate::nes::input::ControllerState::PowerPad(s) => {
ControllerStateWrapper::PowerPad(s)
}
},
expansion_arkanoid: if self.is_arkanoid_famicom_configured() {
Some(self.expansion_arkanoid.borrow().capture_state())
} else {
None
},
expansion_zapper: if self.is_zapper_famicom_configured() {
Some(self.expansion_zapper.borrow().capture_state())
} else {
None
},
expansion_power_pad: if self.is_power_pad_famicom_configured() {
Some(self.expansion_power_pad.borrow().capture_state())
} else {
None
},
}
}
pub fn restore_state(&mut self, state: &BusState) {
self.open_bus = state.open_bus;
*self.oam_dma_page.borrow_mut() = state.oam_dma_page;
self.dma_triggered.replace(false);
match &state.port1_controller {
ControllerStateWrapper::Joypad(s) => {
let mut controller = Self::build_controller(
self.ppu.clone(),
self.app_context.clone(),
ControllerType::Joypad,
);
controller.restore_state(&crate::nes::input::ControllerState::Joypad(s.clone()));
*self.controllers[0].borrow_mut() = controller;
}
ControllerStateWrapper::SnesAdapter(s) => {
let mut controller = Self::build_controller(
self.ppu.clone(),
self.app_context.clone(),
ControllerType::SnesAdapter,
);
controller
.restore_state(&crate::nes::input::ControllerState::SnesAdapter(s.clone()));
*self.controllers[0].borrow_mut() = controller;
}
ControllerStateWrapper::Arkanoid(s) => {
let mut controller = Self::build_controller(
self.ppu.clone(),
self.app_context.clone(),
ControllerType::Arkanoid,
);
controller.restore_state(&crate::nes::input::ControllerState::Paddle(s.clone()));
*self.controllers[0].borrow_mut() = controller;
}
ControllerStateWrapper::Zapper(s) => {
let mut controller = Self::build_controller(
self.ppu.clone(),
self.app_context.clone(),
ControllerType::Zapper,
);
controller.restore_state(&crate::nes::input::ControllerState::Zapper(s.clone()));
*self.controllers[0].borrow_mut() = controller;
}
ControllerStateWrapper::PowerPad(s) => {
let mut controller = Self::build_controller(
self.ppu.clone(),
self.app_context.clone(),
ControllerType::PowerPad,
);
controller.restore_state(&crate::nes::input::ControllerState::PowerPad(s.clone()));
*self.controllers[0].borrow_mut() = controller;
}
}
match &state.port2_controller {
ControllerStateWrapper::Joypad(s) => {
let mut controller = Self::build_controller(
self.ppu.clone(),
self.app_context.clone(),
ControllerType::Joypad,
);
controller.restore_state(&crate::nes::input::ControllerState::Joypad(s.clone()));
*self.controllers[1].borrow_mut() = controller;
}
ControllerStateWrapper::SnesAdapter(s) => {
let mut controller = Self::build_controller(
self.ppu.clone(),
self.app_context.clone(),
ControllerType::SnesAdapter,
);
controller
.restore_state(&crate::nes::input::ControllerState::SnesAdapter(s.clone()));
*self.controllers[1].borrow_mut() = controller;
}
ControllerStateWrapper::Arkanoid(s) => {
let mut controller = Self::build_controller(
self.ppu.clone(),
self.app_context.clone(),
ControllerType::Arkanoid,
);
controller.restore_state(&crate::nes::input::ControllerState::Paddle(s.clone()));
*self.controllers[1].borrow_mut() = controller;
}
ControllerStateWrapper::Zapper(s) => {
let mut controller = Self::build_controller(
self.ppu.clone(),
self.app_context.clone(),
ControllerType::Zapper,
);
controller.restore_state(&crate::nes::input::ControllerState::Zapper(s.clone()));
*self.controllers[1].borrow_mut() = controller;
}
ControllerStateWrapper::PowerPad(s) => {
let mut controller = Self::build_controller(
self.ppu.clone(),
self.app_context.clone(),
ControllerType::PowerPad,
);
controller.restore_state(&crate::nes::input::ControllerState::PowerPad(s.clone()));
*self.controllers[1].borrow_mut() = controller;
}
}
if let Some(ref arkanoid_state) = state.expansion_arkanoid {
self.expansion_arkanoid
.borrow_mut()
.restore_state(arkanoid_state);
}
if let Some(ref zapper_state) = state.expansion_zapper {
self.expansion_zapper
.borrow_mut()
.restore_state(zapper_state);
}
if let Some(ref power_pad_state) = state.expansion_power_pad {
self.expansion_power_pad
.borrow_mut()
.restore_state(power_pad_state);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nes::cartridge::NametableLayout;
use crate::nes::console::TimingMode;
use std::rc::Rc;
struct TestBusDevice {
range: std::ops::RangeInclusive<u16>,
read_value: u8,
last_write: Rc<RefCell<Option<(u16, u8)>>>,
}
fn create_test_base_mapper() -> crate::nes::cartridge::BaseMapper {
let ctx = crate::nes::cartridge::MapperContext::new_for_test(
0,
vec![0; 0x8000],
vec![0; 8192],
crate::nes::cartridge::NametableLayout::Horizontal,
);
crate::nes::cartridge::BaseMapper::new(
&ctx,
crate::nes::cartridge::MapperCapabilities::default(),
)
}
struct OamDmaCountingMapper {
base: crate::nes::cartridge::BaseMapper,
oam_dma_calls: Rc<RefCell<u32>>,
}
impl OamDmaCountingMapper {
fn new(oam_dma_calls: Rc<RefCell<u32>>) -> Self {
Self {
base: create_test_base_mapper(),
oam_dma_calls,
}
}
}
impl TestBusDevice {
fn new(
range: std::ops::RangeInclusive<u16>,
read_value: u8,
last_write: Rc<RefCell<Option<(u16, u8)>>>,
) -> Self {
Self {
range,
read_value,
last_write,
}
}
}
impl BusDevice for TestBusDevice {
fn read(&mut self, addr: u16, _open_bus: u8, _is_dummy_read: bool) -> Option<u8> {
if self.range.contains(&addr) {
return Some(self.read_value);
}
None
}
fn write(&mut self, addr: u16, value: u8, _is_dummy_write: bool) -> bool {
if self.range.contains(&addr) {
*self.last_write.borrow_mut() = Some((addr, value));
return true;
}
false
}
fn address_range(&self) -> std::ops::RangeInclusive<u16> {
self.range.clone()
}
}
impl crate::nes::cartridge::Mapper for OamDmaCountingMapper {
fn base(&self) -> &crate::nes::cartridge::BaseMapper {
&self.base
}
fn base_mut(&mut self) -> &mut crate::nes::cartridge::BaseMapper {
&mut self.base
}
fn read_prg(&self, _addr: u16) -> u8 {
0
}
fn write_prg(&mut self, _addr: u16, _value: u8) {}
fn read_chr(&mut self, _addr: u16) -> u8 {
0
}
fn write_chr(&mut self, _addr: u16, _value: u8) {}
fn ppu_address_changed(&mut self, _addr: u16) {}
fn on_oam_dma(&mut self) {
*self.oam_dma_calls.borrow_mut() += 1;
}
fn get_mirroring(&self) -> crate::nes::cartridge::NametableLayout {
crate::nes::cartridge::NametableLayout::Horizontal
}
}
fn create_mmc1_rom() -> Vec<u8> {
let prg_rom_banks = 1u8;
let chr_rom_banks = 0u8; let flags6 = 0x10; let flags7 = 0x00;
let mut rom = vec![
b'N',
b'E',
b'S',
0x1A,
prg_rom_banks,
chr_rom_banks,
flags6,
flags7,
0,
0,
0,
0,
0,
0,
0,
0,
];
let prg_size = prg_rom_banks as usize * 0x4000;
rom.extend(std::iter::repeat_n(0, prg_size));
rom
}
fn write_mmc1_register(bus: &mut Bus, addr: u16, value: u8) {
for i in 0..5 {
bus.mapper_cpu_cycle();
bus.mapper_cpu_cycle();
let bit = (value >> i) & 0x01;
bus.write(addr, bit, false);
}
}
fn write_mmc1_control(bus: &mut Bus, value: u8) {
write_mmc1_register(bus, 0x8000, value);
}
fn assert_vertical_mirroring(ppu: &mut ppu::Ppu) {
ppu.write_address(0x20, false);
ppu.write_address(0x00, false);
ppu.write_data(0x42);
ppu.write_address(0x28, false);
ppu.write_address(0x00, false);
let _ = ppu.read_data();
assert_eq!(ppu.read_data(), 0x42);
}
fn assert_horizontal_mirroring(ppu: &mut ppu::Ppu) {
ppu.write_address(0x20, false);
ppu.write_address(0x00, false);
ppu.write_data(0x55);
ppu.write_address(0x24, false);
ppu.write_address(0x00, false);
let _ = ppu.read_data();
assert_eq!(ppu.read_data(), 0x55);
}
#[test]
fn test_restore_mapper_state_updates_ppu_mirroring() {
let ppu = Rc::new(RefCell::new(ppu::Ppu::new_for_testing(TimingMode::Ntsc)));
let apu = Rc::new(RefCell::new(crate::nes::apu::Apu::new()));
let app_context = Rc::new(RefCell::new(crate::platform::app_context::AppContext::new()));
let mut bus = Bus::new(ppu.clone(), apu, app_context.clone());
let rom = create_mmc1_rom();
let cartridge = Cartridge::load_from_file(&rom, "bus-mmc1-state-test.nes", None)
.expect("Failed to create MMC1 ROM");
bus.map_cartridge(cartridge);
write_mmc1_control(&mut bus, 0x1E); assert_vertical_mirroring(&mut ppu.borrow_mut());
let saved_state = bus.capture_mapper_state();
write_mmc1_control(&mut bus, 0x1F); assert_horizontal_mirroring(&mut ppu.borrow_mut());
bus.restore_mapper_state(&saved_state);
assert_vertical_mirroring(&mut ppu.borrow_mut());
}
fn create_mmc1_ines_rom_with_vertical_mirroring() -> Vec<u8> {
let prg_rom_banks = 2u8;
let chr_rom_banks = 1u8;
let flags6 = 0x10 | 0x01; let flags7 = 0x00;
let mut rom = vec![
b'N',
b'E',
b'S',
0x1A, prg_rom_banks, chr_rom_banks, flags6, flags7, 0, 0, 0, 0,
0,
0,
0,
0, ];
rom.extend(vec![0u8; prg_rom_banks as usize * 16 * 1024]);
rom.extend(vec![0u8; chr_rom_banks as usize * 8 * 1024]);
rom
}
fn create_nrom_rom() -> Vec<u8> {
let prg_rom_banks = 1u8;
let chr_rom_banks = 1u8;
let flags6 = 0x00;
let flags7 = 0x00;
let mut rom = vec![
b'N',
b'E',
b'S',
0x1A,
prg_rom_banks,
chr_rom_banks,
flags6,
flags7,
0,
0,
0,
0,
0,
0,
0,
0,
];
rom.extend(vec![0u8; prg_rom_banks as usize * 16 * 1024]);
rom.extend(vec![0u8; chr_rom_banks as usize * 8 * 1024]);
rom
}
fn create_test_memory() -> Bus {
let ppu = Rc::new(RefCell::new(ppu::Ppu::new_for_testing(TimingMode::Ntsc)));
let apu = Rc::new(RefCell::new(crate::nes::apu::Apu::new()));
let config = crate::nes::console::Config {
frontend: crate::platform::config::FrontendConfig {
ram_init_mode: crate::nes::console::RamInitMode::Zero,
..Default::default()
},
..Default::default()
};
let app_context = Rc::new(RefCell::new(
crate::platform::app_context::AppContext::new_with_config(config),
));
Bus::new(ppu, apu, app_context.clone())
}
fn create_test_memory_with_four_score_enabled() -> Bus {
let ppu = Rc::new(RefCell::new(ppu::Ppu::new_for_testing(TimingMode::Ntsc)));
let apu = Rc::new(RefCell::new(crate::nes::apu::Apu::new()));
let mut config = crate::nes::console::Config {
frontend: crate::platform::config::FrontendConfig {
ram_init_mode: crate::nes::console::RamInitMode::Zero,
..Default::default()
},
..Default::default()
};
config.nes.four_score_enabled = true;
let app_context = Rc::new(RefCell::new(
crate::platform::app_context::AppContext::new_with_config(config),
));
Bus::new(ppu, apu, app_context.clone())
}
fn read_24_bits(memory: &mut Bus, addr: u16) -> u32 {
let mut value = 0u32;
for bit in 0..24 {
let sample = memory.read(addr, false) & 0x01;
value |= (sample as u32) << bit;
}
value
}
#[test]
fn test_bus_device_dispatches_reads_and_writes() {
let mut memory = create_test_memory();
let last_write = Rc::new(RefCell::new(None));
memory.devices.insert(
0,
Box::new(TestBusDevice::new(
0x4100..=0x4101,
0xAB,
last_write.clone(),
)),
);
assert_eq!(memory.read(0x4100, false), 0xAB);
let dma = memory.write(0x4101, 0x55, false);
assert!(!dma);
assert_eq!(*last_write.borrow(), Some((0x4101, 0x55)));
}
#[test]
fn test_bus_prefers_device_for_joypad_registers() {
let mut memory = create_test_memory();
let last_write = Rc::new(RefCell::new(None));
memory.devices.insert(
0,
Box::new(TestBusDevice::new(
0x4016..=0x4016,
0xAA,
last_write.clone(),
)),
);
assert_eq!(memory.read(0x4016, false), 0xAA);
let dma = memory.write(0x4016, 0x55, false);
assert!(!dma);
assert_eq!(*last_write.borrow(), Some((0x4016, 0x55)));
}
#[test]
fn test_four_score_port1_includes_player3_button_state() {
let mut memory = create_test_memory_with_four_score_enabled();
memory.set_button(3, crate::nes::input::Button::A, true);
memory.write(0x4016, 0x01, false);
memory.write(0x4016, 0x00, false);
let bits = read_24_bits(&mut memory, 0x4016);
assert_eq!(bits & (1 << 8), 1 << 8);
}
#[test]
fn test_four_score_port2_includes_player4_button_state() {
let mut memory = create_test_memory_with_four_score_enabled();
memory.set_button(4, crate::nes::input::Button::B, true);
memory.write(0x4016, 0x01, false);
memory.write(0x4016, 0x00, false);
let bits = read_24_bits(&mut memory, 0x4017);
assert_eq!(bits & (1 << 9), 1 << 9);
}
#[test]
fn test_ram_device_is_registered() {
let memory = create_test_memory();
assert!(memory.has_device_for_address(0x0000));
assert!(memory.has_device_for_address(0x1FFF));
}
#[test]
fn test_ppu_device_is_registered() {
let memory = create_test_memory();
assert!(memory.has_device_for_address(0x2002));
}
#[test]
fn test_apu_device_is_registered() {
let memory = create_test_memory();
assert!(memory.has_device_for_address(0x4015));
assert!(memory.has_device_for_address(0x4017));
}
#[test]
fn test_joypad_device_is_registered() {
let memory = create_test_memory();
assert!(memory.has_device_for_address(0x4016));
assert!(memory.has_device_for_address(0x4017));
}
#[test]
fn test_mapper_device_is_registered() {
let memory = create_test_memory();
assert!(memory.has_device_for_address(0x5000));
assert!(memory.has_device_for_address(0x6000));
assert!(memory.has_device_for_address(0x8000));
}
#[test]
fn test_oam_dma_device_is_registered() {
let memory = create_test_memory();
assert!(memory.has_device_for_address(0x4014));
}
#[test]
fn test_open_bus_updates_after_read() {
let mut memory = create_test_memory();
memory.write(0x0000, 0x3C, false);
let value = memory.read(0x0000, false);
assert_eq!(value, 0x3C);
assert_eq!(memory.open_bus_value_for_test(), 0x3C);
}
#[test]
fn test_cpu_memory_map_io_and_mapper_ranges() {
let memory = create_test_memory();
assert!(memory.has_device_for_address(0x4018));
assert!(memory.has_device_for_address(0x401F));
assert!(memory.has_device_for_address(0x4020));
}
#[test]
fn test_oam_dma_write_is_dispatched_to_devices() {
let mut memory = create_test_memory();
let dma = memory.write(0x4014, 0x22, false);
assert!(dma);
assert!(memory.oam_dma_pending());
assert_eq!(memory.take_oam_dma_page(), Some(0x22));
}
#[test]
fn test_oam_dma_write_notifies_mapper_only_on_real_write() {
let ppu = Rc::new(RefCell::new(ppu::Ppu::new_for_testing(TimingMode::Ntsc)));
let apu = Rc::new(RefCell::new(crate::nes::apu::Apu::new()));
let app_context = Rc::new(RefCell::new(crate::platform::app_context::AppContext::new()));
let mut memory = Bus::new(ppu, apu, app_context.clone());
let oam_dma_calls = Rc::new(RefCell::new(0u32));
let mapper = Box::new(OamDmaCountingMapper::new(oam_dma_calls.clone()));
let cartridge = Cartridge::from_mapper_for_test(mapper);
memory.map_cartridge(cartridge);
memory.write(0x4014, 0x22, false);
assert_eq!(*oam_dma_calls.borrow(), 1);
memory.write(0x4014, 0x33, true);
assert_eq!(*oam_dma_calls.borrow(), 1);
}
#[test]
fn test_unmapped_cartridge_space_returns_open_bus() {
let mut memory = create_test_memory();
memory.write(0x0000, 0x3C, false);
let open_bus = memory.read(0x0000, false);
assert_eq!(open_bus, 0x3C);
assert_eq!(memory.read(0x4020, false), open_bus);
}
#[test]
fn test_unmapped_cartridge_space_returns_open_bus_with_mapper() {
let mut memory = create_test_memory();
let rom = create_mmc1_rom();
let cartridge =
crate::nes::cartridge::Cartridge::load_from_file(&rom, "bus-open-bus-mmc1.nes", None)
.expect("valid cartridge");
memory.map_cartridge(cartridge);
memory.write(0x0000, 0x5A, false);
let open_bus = memory.read(0x0000, false);
assert_eq!(open_bus, 0x5A);
assert_eq!(memory.read(0x4020, false), open_bus);
}
#[test]
fn test_unmapped_cartridge_space_returns_open_bus_with_nrom() {
let mut memory = create_test_memory();
let rom = create_nrom_rom();
let cartridge =
crate::nes::cartridge::Cartridge::load_from_file(&rom, "bus-open-bus-nrom.nes", None)
.expect("valid cartridge");
memory.map_cartridge(cartridge);
memory.write(0x0000, 0xA5, false);
let open_bus = memory.read(0x0000, false);
assert_eq!(open_bus, 0xA5);
assert_eq!(memory.read(0x4020, false), open_bus);
}
#[test]
fn test_bus_save_state_roundtrip_includes_internal_state() {
let mut memory = create_test_memory();
memory.write(0x0000, 0x3C, false);
memory.write(0x4014, 0x22, false);
memory.set_button(1, crate::nes::input::Button::A, true);
memory.set_button(1, crate::nes::input::Button::Right, true);
memory.write(0x4016, 0x01, false); memory.write(0x4016, 0x00, false); memory.read(0x4016, false); memory.read(0x4016, false);
let expected_open_bus = memory.open_bus_value_for_test();
let saved_state = memory.capture_state();
let mut restored = create_test_memory();
restored.restore_state(&saved_state);
assert_eq!(restored.open_bus_value_for_test(), expected_open_bus);
assert!(restored.oam_dma_pending());
assert_eq!(restored.take_oam_dma_page(), Some(0x22));
let expected_sequence = [0, 0, 0, 0, 0, 0]; for expected in expected_sequence {
assert_eq!(restored.read(0x4016, false) & 0x01, expected);
}
}
#[test]
fn test_bus_save_state_roundtrip_with_paddle() {
let mut memory = create_test_memory();
memory.set_controller_type(1, ControllerType::Arkanoid);
memory.set_mouse_x_position(0xA5);
memory.set_mouse_left_button(true);
memory.write(0x4016, 0x01, false);
memory.write(0x4016, 0x00, false);
let saved_state = memory.capture_state();
let mut restored = create_test_memory();
restored.restore_state(&saved_state);
restored.write(0x0000, 0x00, false);
restored.read(0x0000, false);
let restored_paddle = [
restored.read(0x4016, false) & 0x18,
restored.read(0x4016, false) & 0x18,
];
assert_eq!(restored_paddle, [0x00, 0x10]); }
#[test]
fn test_bus_save_state_roundtrip_with_power_pad() {
let mut memory = create_test_memory();
memory.set_controller_type(1, ControllerType::PowerPad);
assert!(memory.set_power_pad_button(1, crate::nes::input::PowerPadButton::One, true));
assert!(memory.set_power_pad_button(1, crate::nes::input::PowerPadButton::Four, true));
memory.write(0x4016, 0x01, false);
memory.write(0x4016, 0x00, false);
memory.read(0x4016, false);
memory.read(0x4016, false);
let saved_state = memory.capture_state();
let mut restored = create_test_memory();
restored.restore_state(&saved_state);
restored.write(0x4016, 0x01, false);
restored.write(0x4016, 0x00, false);
assert_eq!(restored.read(0x4016, false) & 0x18, 0x00); assert_eq!(restored.read(0x4016, false) & 0x18, 0x00); assert_eq!(restored.read(0x4016, false) & 0x18, 0x00); assert_eq!(restored.read(0x4016, false) & 0x18, 0x00); assert_eq!(restored.read(0x4016, false) & 0x18, 0x10); assert_eq!(restored.read(0x4016, false) & 0x18, 0x10); assert_eq!(restored.read(0x4016, false) & 0x18, 0x10); assert_eq!(restored.read(0x4016, false) & 0x18, 0x10); }
#[test]
fn test_bus_save_state_roundtrip_with_expansion_arkanoid() {
let ppu = Rc::new(RefCell::new(ppu::Ppu::new_for_testing(TimingMode::Ntsc)));
let apu = Rc::new(RefCell::new(crate::nes::apu::Apu::new()));
let config = crate::nes::console::Config {
frontend: crate::platform::config::FrontendConfig {
ram_init_mode: crate::nes::console::RamInitMode::Zero,
..Default::default()
},
..Default::default()
};
let mut config = config;
config.nes.hardware_mode = HardwareMode::Famicom;
config.nes.expansion_port = ExpansionPort::ArkanoidFamicom;
let app_context = Rc::new(RefCell::new(
crate::platform::app_context::AppContext::new_with_config(config),
));
let memory = Bus::new(ppu, apu, app_context.clone());
memory.expansion_arkanoid.borrow_mut().set_position(0xB0);
memory.expansion_arkanoid.borrow_mut().set_trigger(true);
let saved_state = memory.capture_state();
assert!(saved_state.expansion_arkanoid.is_some());
let arkanoid_state = saved_state.expansion_arkanoid.as_ref().unwrap();
assert_eq!(arkanoid_state.position, 0xB0);
assert!(arkanoid_state.trigger);
let mut restored = Bus::new(
Rc::new(RefCell::new(ppu::Ppu::new_for_testing(TimingMode::Ntsc))),
Rc::new(RefCell::new(crate::nes::apu::Apu::new())),
app_context,
);
restored.restore_state(&saved_state);
let restored_state = restored.expansion_arkanoid.borrow().capture_state();
assert_eq!(restored_state.position, 0xB0);
assert!(!restored_state.trigger);
}
#[test]
fn test_bus_save_state_omits_expansion_arkanoid_when_not_configured() {
let memory = create_test_memory();
let saved_state = memory.capture_state();
assert!(saved_state.expansion_arkanoid.is_none());
}
#[test]
fn test_mmc1_runtime_mirroring_change_propagates_to_ppu() {
let ppu = Rc::new(RefCell::new(ppu::Ppu::new_for_testing(TimingMode::Ntsc)));
let apu = Rc::new(RefCell::new(crate::nes::apu::Apu::new()));
let app_context = Rc::new(RefCell::new(crate::platform::app_context::AppContext::new()));
let mut mem = Bus::new(ppu.clone(), apu, app_context.clone());
let cart = Cartridge::load_from_file(
&create_mmc1_ines_rom_with_vertical_mirroring(),
"bus-mmc1-mirroring-runtime.nes",
None,
)
.expect("MMC1 test ROM should load");
mem.map_cartridge(cart);
{
let mut ppu = ppu.borrow_mut();
ppu.write_address(0x20, false);
ppu.write_address(0x00, false);
ppu.write_data(0xAA);
assert_eq!(ppu.read_nametable_for_debug(0x2000), 0xAA);
assert_eq!(ppu.read_nametable_for_debug(0x2800), 0xAA);
}
write_mmc1_control(&mut mem, 0b00011);
{
let mut ppu = ppu.borrow_mut();
ppu.write_address(0x20, false);
ppu.write_address(0x00, false);
ppu.write_data(0x33);
ppu.write_address(0x28, false);
ppu.write_address(0x00, false);
ppu.write_data(0x44);
assert_eq!(ppu.read_nametable_for_debug(0x2000), 0x33);
assert_eq!(ppu.read_nametable_for_debug(0x2400), 0x33);
}
}
#[test]
fn test_mmc1_wram_disabled_reads_return_open_bus() {
let mut mem = create_test_memory();
let cart = Cartridge::load_from_file(
&create_mmc1_ines_rom_with_vertical_mirroring(),
"bus-mmc1-wram-disable.nes",
None,
)
.expect("MMC1 test ROM should load");
mem.map_cartridge(cart);
write_mmc1_register(&mut mem, 0xE000, 0b10000);
mem.write(0x0000, 0xAB, false);
assert_eq!(mem.read(0x6000, false), 0xAB);
}
#[test]
fn test_debug_read_uses_open_bus_for_disabled_mmc1_wram() {
let mut mem = create_test_memory();
let cart = Cartridge::load_from_file(
&create_mmc1_ines_rom_with_vertical_mirroring(),
"bus-mmc1-debug-wram-disable.nes",
None,
)
.expect("MMC1 test ROM should load");
mem.map_cartridge(cart);
write_mmc1_register(&mut mem, 0xE000, 0b10000);
mem.write(0x0000, 0x5E, false);
let open_bus = mem.read(0x0000, false);
assert_eq!(open_bus, 0x5E);
assert_eq!(mem.read_cpu_for_debugger(0x6000), open_bus);
}
#[test]
fn test_new_memory_is_initialized() {
let mut memory = create_test_memory();
assert_eq!(memory.read(0x0000, false), 0);
assert_eq!(memory.read(0x1234, false), 0);
assert_eq!(memory.read(0x3FFF, false), 0);
}
#[test]
fn test_write_and_read_byte() {
let mut memory = create_test_memory();
let dma = memory.write(0x1234, 0x42, false);
assert!(!dma);
assert_eq!(memory.read(0x1234, false), 0x42);
}
#[test]
fn test_write_u16_little_endian() {
let mut memory = create_test_memory();
memory.write_u16(0x1234, 0xABCD);
assert_eq!(memory.read(0x1234, false), 0xCD); assert_eq!(memory.read(0x1235, false), 0xAB); }
#[test]
fn test_ram_mirror_0800() {
let mut memory = create_test_memory();
memory.write(0x0000, 0x42, false);
assert_eq!(memory.read(0x0800, false), 0x42);
assert_eq!(memory.read(0x1000, false), 0x42);
assert_eq!(memory.read(0x1800, false), 0x42);
}
#[test]
fn test_ram_mirror_write_to_mirror() {
let mut memory = create_test_memory();
memory.write(0x0800, 0x55, false);
assert_eq!(memory.read(0x0000, false), 0x55);
assert_eq!(memory.read(0x1000, false), 0x55);
assert_eq!(memory.read(0x1800, false), 0x55);
}
#[test]
fn test_ram_mirror_different_addresses() {
let mut memory = create_test_memory();
memory.write(0x01FF, 0xAA, false);
assert_eq!(memory.read(0x09FF, false), 0xAA);
assert_eq!(memory.read(0x11FF, false), 0xAA);
assert_eq!(memory.read(0x19FF, false), 0xAA);
}
#[test]
fn test_cartridge_prg_rom_16kb_read() {
use crate::nes::cartridge::Cartridge;
let mut memory = create_test_memory();
let mut prg_rom = vec![0; 0x4000]; prg_rom[0] = 0xAA; prg_rom[0x3FFF] = 0xBB;
let cartridge = Cartridge::from_parts(
prg_rom,
vec![],
crate::nes::cartridge::NametableLayout::Horizontal,
);
memory.map_cartridge(cartridge);
assert_eq!(memory.read(0x8000, false), 0xAA);
assert_eq!(memory.read(0xBFFF, false), 0xBB);
assert_eq!(memory.read(0xC000, false), 0xAA);
assert_eq!(memory.read(0xFFFF, false), 0xBB);
}
#[test]
fn test_cartridge_prg_rom_32kb_read() {
use crate::nes::cartridge::Cartridge;
let mut memory = create_test_memory();
let mut prg_rom = vec![0; 0x8000]; prg_rom[0] = 0xAA; prg_rom[0x4000] = 0xCC; prg_rom[0x7FFF] = 0xDD;
let cartridge = Cartridge::from_parts(
prg_rom,
vec![],
crate::nes::cartridge::NametableLayout::Horizontal,
);
memory.map_cartridge(cartridge);
assert_eq!(memory.read(0x8000, false), 0xAA);
assert_eq!(memory.read(0xC000, false), 0xCC);
assert_eq!(memory.read(0xFFFF, false), 0xDD);
}
#[test]
fn test_ram_still_writable_with_cartridge() {
use crate::nes::cartridge::Cartridge;
let mut memory = create_test_memory();
let cartridge = Cartridge::from_parts(
vec![0; 0x4000],
vec![],
crate::nes::cartridge::NametableLayout::Horizontal,
);
memory.map_cartridge(cartridge);
memory.write(0x0000, 0x55, false);
assert_eq!(memory.read(0x0000, false), 0x55);
memory.write(0x0100, 0x66, false);
assert_eq!(memory.read(0x0100, false), 0x66);
}
#[test]
fn test_write_to_ppudata_writes_to_ppu() {
let mut memory = create_test_memory();
memory.write(0x2006, 0x20, false);
memory.write(0x2006, 0x00, false);
memory.write(0x2007, 0x42, false);
memory.write(0x2006, 0x20, false);
memory.write(0x2006, 0x00, false);
memory.read(0x2007, false); assert_eq!(memory.read(0x2007, false), 0x42);
}
#[test]
fn test_write_to_oamaddr_sets_oam_address() {
let mut memory = create_test_memory();
memory.write(0x2003, 0x40, false);
memory.write(0x2004, 0xAA, false);
memory.write(0x2004, 0xBB, false);
memory.write(0x2003, 0x40, false);
assert_eq!(memory.read(0x2004, false), 0xAA);
assert_eq!(memory.read(0x2004, false), 0xAA); }
#[test]
fn test_write_to_oamdata_writes_and_increments() {
let mut memory = create_test_memory();
memory.write(0x2003, 0x00, false);
memory.write(0x2004, 0x11, false);
memory.write(0x2004, 0x22, false);
memory.write(0x2004, 0x33, false);
memory.write(0x2003, 0x00, false);
assert_eq!(memory.read(0x2004, false), 0x11);
memory.write(0x2003, 0x01, false);
assert_eq!(memory.read(0x2004, false), 0x22);
memory.write(0x2003, 0x02, false);
assert_eq!(memory.read(0x2004, false), 0x23);
}
#[test]
fn test_oamdata_write_wraps_at_256() {
let mut memory = create_test_memory();
memory.write(0x2003, 0xFF, false);
memory.write(0x2004, 0xAA, false);
memory.write(0x2004, 0xBB, false);
memory.write(0x2003, 0xFF, false);
assert_eq!(memory.read(0x2004, false), 0xAA);
memory.write(0x2003, 0x00, false);
assert_eq!(memory.read(0x2004, false), 0xBB);
}
#[test]
fn test_read_from_oamdata_does_not_increment() {
let mut memory = create_test_memory();
memory.write(0x2003, 0x10, false);
memory.write(0x2004, 0x88, false);
memory.write(0x2003, 0x10, false);
assert_eq!(memory.read(0x2004, false), 0x88);
assert_eq!(memory.read(0x2004, false), 0x88);
assert_eq!(memory.read(0x2004, false), 0x88);
}
#[test]
fn test_oam_full_sprite_write() {
let mut memory = create_test_memory();
memory.write(0x2003, 0x00, false);
memory.write(0x2004, 0x10, false); memory.write(0x2004, 0x20, false); memory.write(0x2004, 0xE3, false); memory.write(0x2004, 0x40, false);
memory.write(0x2003, 0x00, false);
assert_eq!(memory.read(0x2004, false), 0x10);
memory.write(0x2003, 0x01, false);
assert_eq!(memory.read(0x2004, false), 0x20);
memory.write(0x2003, 0x02, false);
assert_eq!(memory.read(0x2004, false), 0xE3);
memory.write(0x2003, 0x03, false);
assert_eq!(memory.read(0x2004, false), 0x40);
}
#[test]
fn test_prg_ram_write_and_read() {
let mut memory = create_test_memory();
let rom_data = create_nrom_rom_with_prg_ram();
let cartridge = Cartridge::load_from_file(&rom_data, "bus-prg-ram-rw.nes", None)
.expect("Failed to create cartridge");
memory.map_cartridge(cartridge);
memory.write(0x6000, 0x42, false);
memory.write(0x6001, 0x43, false);
memory.write(0x7FFF, 0xFF, false);
assert_eq!(
memory.read(0x6000, false),
0x42,
"PRG-RAM at $6000 should return written value"
);
assert_eq!(
memory.read(0x6001, false),
0x43,
"PRG-RAM at $6001 should return written value"
);
assert_eq!(
memory.read(0x7FFF, false),
0xFF,
"PRG-RAM at $7FFF should return written value"
);
}
#[test]
fn test_prg_ram_persistence() {
let mut memory = create_test_memory();
let rom_data = create_nrom_rom_with_prg_ram();
let cartridge = Cartridge::load_from_file(&rom_data, "bus-prg-ram-persistence.nes", None)
.expect("Failed to create cartridge");
memory.map_cartridge(cartridge);
memory.write(0x6100, 0xAB, false);
assert_eq!(memory.read(0x6100, false), 0xAB);
assert_eq!(memory.read(0x6100, false), 0xAB);
assert_eq!(memory.read(0x6100, false), 0xAB);
}
#[test]
fn test_prg_ram_8kb_size() {
let mut memory = create_test_memory();
let rom_data = create_nrom_rom_with_prg_ram();
let cartridge = Cartridge::load_from_file(&rom_data, "bus-prg-ram-size.nes", None)
.expect("Failed to create cartridge");
memory.map_cartridge(cartridge);
memory.write(0x6000, 0x01, false);
memory.write(0x7FFF, 0xFF, false);
assert_eq!(memory.read(0x6000, false), 0x01);
assert_eq!(memory.read(0x7FFF, false), 0xFF);
assert_ne!(memory.read(0x6000, false), memory.read(0x7FFF, false));
}
#[test]
fn test_prg_ram_initialized_to_zero() {
let mut memory = create_test_memory();
let rom_data = create_nrom_rom_with_prg_ram();
let cartridge = Cartridge::load_from_file(&rom_data, "bus-prg-ram-zero-init.nes", None)
.expect("Failed to create cartridge");
memory.map_cartridge(cartridge);
assert_eq!(memory.read(0x6000, false), 0x00);
assert_eq!(memory.read(0x6100, false), 0x00);
assert_eq!(memory.read(0x7000, false), 0x00);
assert_eq!(memory.read(0x7FFF, false), 0x00);
}
fn create_nrom_rom_with_prg_ram() -> Vec<u8> {
let mut rom = Vec::new();
rom.extend_from_slice(b"NES\x1A"); rom.push(2); rom.push(1); rom.push(0x02); rom.push(0x00); rom.extend_from_slice(&[0; 8]);
rom.extend_from_slice(&[0xEA; 32768]);
rom.extend_from_slice(&[0x00; 8192]);
rom
}
#[test]
fn test_read_apu_status_register() {
let mut memory = create_test_memory();
let status = memory.read(0x4015, false);
assert_eq!(status & 0b1101_1111, 0x00); assert_eq!(status & 0b0010_0000, 0x20); }
#[test]
fn test_read_apu_status_after_enable() {
let mut memory = create_test_memory();
{
let mut apu = memory.apu.borrow_mut();
apu.write_enable(0b0000_0001); apu.pulse1_mut()
.write_length_counter_timer_high(0b1111_1000);
apu.pulse1_mut().apply_pending_length_reload();
}
let status = memory.read(0x4015, false);
assert_eq!(status & 0b0000_0001, 0b0000_0001);
}
#[test]
fn test_apu_status_register_mirrored() {
let mut memory = create_test_memory();
let status = memory.read(0x4015, false);
assert_eq!(status & 0b1101_1111, 0x00);
}
#[test]
fn test_write_pulse1_registers() {
let mut memory = create_test_memory();
memory.write(0x4015, 0b00000001, false);
memory.write(0x4000, 0b10111111, false);
memory.write(0x4001, 0b10101010, false);
memory.write(0x4002, 0xAB, false);
memory.write(0x4003, 0b11111000, false);
memory
.apu
.borrow_mut()
.pulse1_mut()
.apply_pending_length_reload();
let apu = memory.apu.borrow();
assert!(apu.pulse1().get_length_counter() > 0);
}
#[test]
fn test_write_pulse2_registers() {
let mut memory = create_test_memory();
memory.write(0x4015, 0b00000010, false);
memory.write(0x4004, 0b11001111, false);
memory.write(0x4007, 0b11110000, false);
memory
.apu
.borrow_mut()
.pulse2_mut()
.apply_pending_length_reload();
let apu = memory.apu.borrow();
assert!(apu.pulse2().get_length_counter() > 0);
}
#[test]
fn test_write_triangle_registers() {
let mut memory = create_test_memory();
memory.write(0x4015, 0b00000100, false);
memory.write(0x4008, 0b11111111, false);
memory.write(0x400B, 0b11110000, false);
memory
.apu
.borrow_mut()
.triangle_mut()
.apply_pending_length_reload();
let apu = memory.apu.borrow();
assert!(apu.triangle().get_length_counter() > 0);
}
#[test]
fn test_triangle_period_sweep_via_bus_no_skipped_steps() {
let mut memory = create_test_memory();
memory.write(0x4015, 0b0000_0100, false);
memory.write(0x4008, 0x7F, false);
memory.write(0x400B, 0x08, false);
memory
.apu
.borrow_mut()
.triangle_mut()
.apply_pending_length_reload();
memory.write(0x4017, 0b1000_0000, false);
for _ in 0..4 {
memory.apu.borrow_mut().clock();
}
let periods = [0u8, 1, 2, 3, 7, 15, 31, 63];
for &period in &periods {
memory.write(0x400A, period, false);
let prev_pos = memory.apu.borrow().triangle().debug_sequence_position();
let mut prev_pos = prev_pos;
loop {
memory.apu.borrow_mut().clock();
let apu = memory.apu.borrow();
let pos = apu.triangle().debug_sequence_position();
if pos != prev_pos {
prev_pos = pos;
break;
}
}
let mut cycles_since_step: u32 = 0;
let mut steps_seen: u32 = 0;
while steps_seen < 64 {
memory.apu.borrow_mut().clock();
cycles_since_step += 1;
let apu = memory.apu.borrow();
let pos = apu.triangle().debug_sequence_position();
drop(apu);
if pos != prev_pos {
let expected = (prev_pos + 1) % 32;
assert_eq!(
pos, expected,
"period={}: step skipped: prev={}, got={}, expected={}",
period, prev_pos, pos, expected
);
let expected_cycles = period as u32 + 1;
assert_eq!(
cycles_since_step, expected_cycles,
"period={}: step interval mismatch",
period
);
prev_pos = pos;
cycles_since_step = 0;
steps_seen += 1;
}
}
}
}
#[test]
fn test_write_noise_registers() {
let mut memory = create_test_memory();
memory.write(0x4015, 0b00001000, false);
memory.write(0x400C, 0b00111111, false);
memory.write(0x400F, 0b11110000, false);
memory
.apu
.borrow_mut()
.noise_mut()
.apply_pending_length_reload();
let apu = memory.apu.borrow();
assert!(apu.noise().get_length_counter() > 0);
}
#[test]
fn test_write_dmc_registers() {
let mut memory = create_test_memory();
memory.write(0x4010, 0b00001111, false);
memory.write(0x4011, 0x40, false);
memory.write(0x4012, 0xC0, false);
memory.write(0x4013, 0xFF, false);
}
#[test]
fn test_write_apu_enable_register() {
let mut memory = create_test_memory();
memory.write(0x4015, 0b00000011, false);
memory.write(0x4003, 0b11110000, false);
memory.write(0x4007, 0b11110000, false);
{
let mut apu = memory.apu.borrow_mut();
apu.pulse1_mut().apply_pending_length_reload();
apu.pulse2_mut().apply_pending_length_reload();
}
let status = memory.read(0x4015, false);
assert_eq!(status & 0b00000011, 0b00000011);
}
#[test]
fn test_write_frame_counter_register() {
let mut memory = create_test_memory();
memory.write(0x4017, 0b10000000, false);
assert!(
!memory.apu.borrow().frame_counter().get_mode(),
"$4017 write should not take effect immediately"
);
for _ in 0..4 {
memory.apu.borrow_mut().clock();
}
assert!(
memory.apu.borrow().frame_counter().get_mode(),
"$4017 write should switch to 5-step mode after the delayed-write window"
);
}
#[test]
fn test_paddle_on_port_1() {
let mut memory = create_test_memory();
memory.set_controller_type(1, crate::nes::input::ControllerType::Arkanoid);
memory.set_mouse_x_position(0xA5);
memory.set_mouse_left_button(true);
memory.write(0x4016, 0x01, false);
memory.write(0x4016, 0x00, false);
let paddle_bits1 = memory.read(0x4016, false) & 0x18;
let paddle_bits2 = memory.read(0x4016, false) & 0x18;
assert_eq!(paddle_bits1, 0x08);
assert_eq!(paddle_bits2, 0x18);
}
#[test]
fn test_zapper_on_port_2_reports_trigger_and_light_bits() {
let mut memory = create_test_memory();
memory.set_controller_type(2, crate::nes::input::ControllerType::Zapper);
memory.set_mouse_x_position(0x10);
memory.set_mouse_y_position(0x20);
memory.set_mouse_left_button(true);
memory.write(0x4016, 0x01, false);
memory.write(0x4016, 0x00, false);
let zapper_bits = memory.read(0x4017, false) & 0x18;
assert_eq!(zapper_bits, 0x18);
}
#[test]
fn test_paddle_on_port_2() {
let mut memory = create_test_memory();
memory.set_controller_type(2, crate::nes::input::ControllerType::Arkanoid);
memory.set_mouse_x_position(0xB3);
memory.set_mouse_left_button(false);
memory.write(0x4016, 0x01, false);
memory.write(0x4016, 0x00, false);
let paddle_bits1 = memory.read(0x4017, false) & 0x18;
let paddle_bits2 = memory.read(0x4017, false) & 0x18;
assert_eq!(paddle_bits1, 0x00);
assert_eq!(paddle_bits2, 0x10);
}
#[test]
fn test_joypad_on_port_1_while_paddle_on_port_2() {
let mut memory = create_test_memory();
memory.set_controller_type(1, crate::nes::input::ControllerType::Joypad);
memory.set_controller_type(2, crate::nes::input::ControllerType::Arkanoid);
memory.set_button(1, crate::nes::input::Button::A, true);
memory.set_button(1, crate::nes::input::Button::B, true);
memory.write(0x4016, 0x01, false);
memory.write(0x4016, 0x00, false);
assert_eq!(memory.read(0x4016, false) & 0x01, 1); assert_eq!(memory.read(0x4016, false) & 0x01, 1);
memory.set_mouse_x_position(0xA5);
memory.set_mouse_left_button(true); memory.write(0x4016, 0x01, false);
memory.write(0x4016, 0x00, false);
let paddle_bits = memory.read(0x4017, false) & 0x18;
assert!(paddle_bits != 0); }
#[test]
fn test_controller_port_config_save_state_roundtrip() {
let mut memory = create_test_memory();
memory.set_controller_type(1, crate::nes::input::ControllerType::Joypad);
memory.set_controller_type(2, crate::nes::input::ControllerType::Arkanoid);
memory.set_mouse_x_position(0xC7);
memory.set_mouse_left_button(true);
let saved_state = memory.capture_state();
memory.set_controller_type(1, crate::nes::input::ControllerType::Arkanoid);
memory.set_controller_type(2, crate::nes::input::ControllerType::Joypad);
memory.restore_state(&saved_state);
memory.set_button(1, crate::nes::input::Button::A, true);
memory.write(0x4016, 0x01, false);
memory.write(0x4016, 0x00, false);
let joypad_bit = memory.read(0x4016, false) & 0x01;
assert_eq!(joypad_bit, 1);
let _ = memory.read(0x4017, false) & 0x18; let _ = memory.read(0x4017, false) & 0x18; let paddle_bit4 = memory.read(0x4017, false) & 0x10; assert_ne!(
paddle_bit4, 0,
"Port 2 position data (bit 4) should appear after restore"
);
}
#[test]
fn test_trainer_loaded_into_ram() {
const TEST_OFFSET: u8 = 0x42;
let mut memory = create_test_memory();
let mut rom = vec![
b'N', b'E', b'S', 0x1A, 1, 1, 0x04, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
for i in 0..512 {
rom.push((i as u8).wrapping_add(TEST_OFFSET));
}
rom.extend(vec![0xAA; 16 * 1024]);
rom.extend(vec![0xBB; 8 * 1024]);
let cartridge =
crate::nes::cartridge::Cartridge::load_from_file(&rom, "bus-trainer-load.nes", None)
.unwrap();
memory.map_cartridge(cartridge);
for i in 0..512 {
let addr = 0x7000 + i;
let expected = (i as u8).wrapping_add(TEST_OFFSET);
let actual = memory.read(addr, false);
assert_eq!(
actual, expected,
"Trainer data mismatch at ${:04X}: expected ${:02X}, got ${:02X}",
addr, expected, actual
);
}
}
#[test]
fn test_no_trainer_does_not_modify_ram() {
let mut memory = create_test_memory();
let mut rom = vec![
b'N', b'E', b'S', 0x1A, 1, 1, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
rom.extend(vec![0xAA; 16 * 1024]);
rom.extend(vec![0xBB; 8 * 1024]);
let cartridge =
crate::nes::cartridge::Cartridge::load_from_file(&rom, "bus-no-trainer.nes", None)
.unwrap();
memory.map_cartridge(cartridge);
for i in 0..512 {
let addr = 0x7000 + i;
let actual = memory.read(addr, false);
assert_eq!(
actual, 0,
"RAM at ${:04X} should be zero without trainer, got ${:02X}",
addr, actual
);
}
}
#[test]
fn test_mapper17_submapper1_trainer_loaded_at_5d00() {
let mut memory = create_test_memory();
let mut rom = vec![
b'N', b'E', b'S', 0x1A, 1, 0, 0x14, 0x18, 0x10, 0, 0, 0, 0, 0, 0, 0,
];
for i in 0..512u16 {
rom.push(i as u8); }
rom.extend(vec![0xFF; 16 * 1024]);
let cartridge = crate::nes::cartridge::Cartridge::load_from_file(
&rom,
"bus-m17-sub1-trainer.nes",
None,
)
.unwrap();
memory.map_cartridge(cartridge);
for i in 0..512u16 {
let addr = 0x5D00 + i;
assert_eq!(
memory.read(addr, false),
i as u8,
"trainer byte mismatch at ${:04X}",
addr
);
}
}
fn create_test_memory_with_vs_system() -> Bus {
let ppu = Rc::new(RefCell::new(ppu::Ppu::new_for_testing(TimingMode::Ntsc)));
let apu = Rc::new(RefCell::new(crate::nes::apu::Apu::new()));
let mut config = crate::nes::console::Config {
frontend: crate::platform::config::FrontendConfig {
ram_init_mode: crate::nes::console::RamInitMode::Zero,
..Default::default()
},
..Default::default()
};
config.nes.expansion_port = ExpansionPort::VsSystem;
let app_context = Rc::new(RefCell::new(
crate::platform::app_context::AppContext::new_with_config(config),
));
Bus::new(ppu, apu, app_context)
}
fn create_test_memory_with_vs_system_swapped() -> Bus {
let ppu = Rc::new(RefCell::new(ppu::Ppu::new_for_testing(TimingMode::Ntsc)));
let apu = Rc::new(RefCell::new(crate::nes::apu::Apu::new()));
let mut config = crate::nes::console::Config {
frontend: crate::platform::config::FrontendConfig {
ram_init_mode: crate::nes::console::RamInitMode::Zero,
..Default::default()
},
..Default::default()
};
config.nes.expansion_port = ExpansionPort::VsSystem;
config.nes.vs_controllers_swapped = true;
let app_context = Rc::new(RefCell::new(
crate::platform::app_context::AppContext::new_with_config(config),
));
Bus::new(ppu, apu, app_context)
}
fn read_joypad_button_from_port(bus: &mut Bus, port_addr: u16, button: Button) -> bool {
bus.write(0x4016, 0x01, false);
bus.write(0x4016, 0x00, false);
for _ in 0..button as u8 {
bus.read(port_addr, false);
}
(bus.read(port_addr, false) & 0x01) != 0
}
#[test]
fn vs_system_swaps_start_to_select_bit_position() {
let mut bus = create_test_memory_with_vs_system();
bus.set_button(1, Button::Start, true);
let select_bit = read_joypad_button_from_port(&mut bus, 0x4016, Button::Select);
assert!(select_bit, "VS: Start should appear at Select bit position");
let start_bit = read_joypad_button_from_port(&mut bus, 0x4016, Button::Start);
assert!(
!start_bit,
"VS: Start should NOT appear at Start bit position"
);
}
#[test]
fn vs_system_swaps_select_to_start_bit_position() {
let mut bus = create_test_memory_with_vs_system();
bus.set_button(1, Button::Select, true);
let start_bit = read_joypad_button_from_port(&mut bus, 0x4016, Button::Start);
assert!(start_bit, "VS: Select should appear at Start bit position");
}
#[test]
fn vs_system_does_not_swap_other_buttons() {
let mut bus = create_test_memory_with_vs_system();
bus.set_button(1, Button::A, true);
let a_bit = read_joypad_button_from_port(&mut bus, 0x4016, Button::A);
assert!(
a_bit,
"VS: A button should not be affected by Start/Select swap"
);
}
#[test]
fn vs_system_swaps_start_select_on_port2() {
let mut bus = create_test_memory_with_vs_system();
bus.set_button(2, Button::Start, true);
let select_bit = read_joypad_button_from_port(&mut bus, 0x4017, Button::Select);
assert!(
select_bit,
"VS: P2 Start should appear at Select bit on $4017"
);
}
#[test]
fn non_vs_system_does_not_swap_start_select() {
let mut bus = create_test_memory();
bus.set_button(1, Button::Start, true);
let start_bit = read_joypad_button_from_port(&mut bus, 0x4016, Button::Start);
assert!(
start_bit,
"Non-VS: Start should appear at Start bit position"
);
}
#[test]
fn vs_swapped_routes_p1_action_buttons_to_port2() {
let mut bus = create_test_memory_with_vs_system_swapped();
bus.set_button(1, Button::A, true);
let a_on_4017 = read_joypad_button_from_port(&mut bus, 0x4017, Button::A);
assert!(
a_on_4017,
"VS swapped: P1 A should appear on $4017 (left stick)"
);
let a_on_4016 = read_joypad_button_from_port(&mut bus, 0x4016, Button::A);
assert!(!a_on_4016, "VS swapped: P1 A should NOT appear on $4016");
}
#[test]
fn vs_swapped_routes_p2_action_buttons_to_port1() {
let mut bus = create_test_memory_with_vs_system_swapped();
bus.set_button(2, Button::A, true);
let a_on_4016 = read_joypad_button_from_port(&mut bus, 0x4016, Button::A);
assert!(
a_on_4016,
"VS swapped: P2 A should appear on $4016 (right stick)"
);
}
#[test]
fn vs_swapped_keeps_start_on_original_port() {
let mut bus = create_test_memory_with_vs_system_swapped();
bus.set_button(1, Button::Start, true);
let select_on_4016 = read_joypad_button_from_port(&mut bus, 0x4016, Button::Select);
assert!(
select_on_4016,
"VS swapped: P1 Start→Select on $4016 (original port)"
);
let select_on_4017 = read_joypad_button_from_port(&mut bus, 0x4017, Button::Select);
assert!(
!select_on_4017,
"VS swapped: P1 Start should NOT appear on $4017"
);
}
#[test]
fn vs_non_swapped_does_not_swap_ports() {
let mut bus = create_test_memory_with_vs_system();
bus.set_button(1, Button::A, true);
let a_on_4016 = read_joypad_button_from_port(&mut bus, 0x4016, Button::A);
assert!(a_on_4016, "VS non-swapped: P1 A should stay on $4016");
}
#[test]
fn map_cartridge_sets_vs_ppu_type_on_ppu() {
use crate::nes::cartridge::VsPpuType;
let ppu = Rc::new(RefCell::new(ppu::Ppu::new_for_testing(TimingMode::Ntsc)));
let apu = Rc::new(RefCell::new(crate::nes::apu::Apu::new()));
let app_context = Rc::new(RefCell::new(crate::platform::app_context::AppContext::new()));
let mut bus = Bus::new(ppu.clone(), apu, app_context);
let mut cartridge = Cartridge::from_parts(
vec![0u8; 32 * 1024],
vec![0u8; 8 * 1024],
NametableLayout::Horizontal,
);
cartridge.set_vs_ppu_type_for_test(Some(VsPpuType::Rp2c04_0002));
bus.map_cartridge(cartridge);
assert_eq!(ppu.borrow().vs_ppu_type(), Some(VsPpuType::Rp2c04_0002));
}
}