nes-sim 0.1.3

A NES (Famicom) emulator core library written in pure Rust.
Documentation
use crate::apu::APU;
use crate::cartridge::{Cartridge, CartridgeError, Mirroring};
use crate::dma::{DmaBusRequest, DmaController};
use crate::input::{ControllerState, Joypad};
use crate::ppu::PPU;
#[cfg(feature = "debug")]
use crate::ppu::PPUBus;
use crate::ppu_memory::PPUMemory;
use crate::savestate::{SaveStateError, StateReader, StateWriter};

#[cfg(feature = "debug")]
use crate::api::{Breakpoint, MemorySnapshot};

pub trait CPUBus {
    fn cpu_read(&mut self, addr: u16) -> u8;
    fn cpu_write(&mut self, addr: u16, data: u8);
    fn cpu_read_timed(&mut self, addr: u16, cycle_offset: u8) -> u8 {
        let _ = cycle_offset;
        self.cpu_read(addr)
    }
    fn cpu_write_timed(&mut self, addr: u16, data: u8, cycle_offset: u8) {
        let _ = cycle_offset;
        self.cpu_write(addr, data);
    }
    fn try_dma(&mut self) -> bool {
        false
    }

    fn cpu_read_u16(&mut self, addr: u16) -> u16 {
        let lo = self.cpu_read(addr) as u16;
        let hi = self.cpu_read(addr.wrapping_add(1)) as u16;
        (hi << 8) | lo
    }
}

pub struct NESBus {
    pub ram: [u8; 0x800],
    ppu: PPU,
    ppu_memory: PPUMemory,
    apu: APU,
    dma: DmaController,
    controllers: [Joypad; 2],
    cpu_open_bus: u8,
    #[cfg(feature = "debug")]
    debug_mem_breakpoints: Vec<Breakpoint>,
    #[cfg(feature = "debug")]
    debug_mem_breakpoint_hit: Option<Breakpoint>,
    // Additional components: APU, cartridge, etc. can be added here
}

impl NESBus {
    pub fn new() -> Self {
        NESBus {
            ram: [0; 0x800],
            ppu: PPU::new(),
            ppu_memory: PPUMemory::new(),
            apu: APU::new(),
            dma: DmaController::new(),
            controllers: [Joypad::new(), Joypad::new()],
            cpu_open_bus: 0,
            #[cfg(feature = "debug")]
            debug_mem_breakpoints: Vec::new(),
            #[cfg(feature = "debug")]
            debug_mem_breakpoint_hit: None,
        }
    }

    pub fn ppu(&self) -> &PPU {
        &self.ppu
    }

    pub fn add_expansion_audio_chip(&mut self, chip: Box<dyn crate::apu::ExpansionAudioChip>) {
        self.apu.add_expansion_chip(chip);
    }

    pub fn set_controller_state(&mut self, port: usize, state: ControllerState) {
        if let Some(controller) = self.controllers.get_mut(port) {
            controller.set_state(state);
        }
    }

    pub fn insert_cartridge(&mut self, mut cartridge: Cartridge) {
        let tv_system = cartridge.tv_system();
        self.ppu.set_parameters(tv_system);
        self.apu.set_tv_system(tv_system);
        let chips = cartridge.take_expansion_audio_chips();
        self.ppu_memory.insert_cartridge(cartridge);
        for chip in chips {
            self.apu.add_expansion_chip(chip);
        }
    }

    pub fn load_cartridge_ines(&mut self, rom: &[u8]) -> Result<(), CartridgeError> {
        let cartridge: Cartridge = Cartridge::from_ines(rom)?;
        self.insert_cartridge(cartridge);
        Ok(())
    }

    pub fn reset(&mut self) {
        self.ppu.reset();
        self.apu.reset();
        self.dma = DmaController::new();
        self.cpu_open_bus = 0;
    }

    pub fn tick_ppu(&mut self) {
        let ppu = &mut self.ppu;
        let ppu_memory = &mut self.ppu_memory;
        ppu.clock(ppu_memory);
    }

    pub fn ppu_nmi_line(&self) -> bool {
        self.ppu.nmi_line()
    }

    pub fn ppu_frame(&self) -> u64 {
        self.ppu.frame()
    }

    pub fn tick_apu_cpu_cycle(&mut self) {
        self.apu.tick_cpu_cycle();
        self.ppu_memory.cartridge_tick_cpu_cycle();
    }

    pub fn apu_sample_rate(&self) -> u32 {
        self.apu.sample_rate()
    }

    pub fn set_apu_sample_rate(&mut self, sample_rate: u32) {
        self.apu.set_sample_rate(sample_rate);
    }

    pub fn apu_audio_samples(&self) -> &[f32] {
        self.apu.audio_samples()
    }

    pub fn clear_apu_audio_samples(&mut self) {
        self.apu.clear_audio_samples();
    }

    pub fn set_apu_debug_mute_mask(&mut self, mask: u8) {
        self.apu.set_debug_mute_mask(mask);
    }

    pub fn apu_debug_mute_mask(&self) -> u8 {
        self.apu.debug_mute_mask()
    }

    pub fn apu_irq_line(&self) -> bool {
        self.apu.irq_line()
    }

    pub fn cartridge_irq_line(&self) -> bool {
        self.ppu_memory.cartridge_irq_line()
    }

    pub fn mirroring(&self) -> Mirroring {
        self.ppu_memory.mirroring()
    }

    #[allow(dead_code)]
    pub fn dma_in_progress(&self) -> bool {
        self.dma.in_progress()
    }

    pub fn advance_dma_cpu_phase(&mut self) {
        self.dma.advance_cpu_phase();
    }

    #[cfg(feature = "debug")]
    pub fn debug_read_chr(&mut self) -> [u8; 0x2000] {
        let mut chr = [0u8; 0x2000];
        for i in 0..0x2000 {
            chr[i] = self.ppu_memory.ppu_read(i as u16);
        }
        chr
    }

    #[cfg(feature = "debug")]
    pub fn debug_memory_snapshot(&self) -> MemorySnapshot<'_> {
        MemorySnapshot {
            ram: &self.ram,
            vram: self.ppu_memory.debug_vram_snapshot(),
            chr: self.ppu_memory.debug_chr_snapshot(),
            palette: self.ppu_memory.debug_palette_snapshot(),
            oam: self.ppu.debug_oam_snapshot(),
        }
    }

    #[cfg(feature = "debug")]
    pub fn set_debug_mem_breakpoints(&mut self, breakpoints: &[Breakpoint]) {
        self.debug_mem_breakpoints.clear();
        self.debug_mem_breakpoints.extend_from_slice(breakpoints);
        self.debug_mem_breakpoint_hit = None;
    }

    #[cfg(feature = "debug")]
    pub fn take_mem_breakpoint_hit(&mut self) -> Option<Breakpoint> {
        self.debug_mem_breakpoint_hit.take()
    }

    #[cfg(feature = "debug")]
    fn check_mem_breakpoint(&mut self, addr: u16, is_write: bool) {
        if self.debug_mem_breakpoint_hit.is_some() {
            return;
        }
        for &bp in &self.debug_mem_breakpoints {
            match bp {
                Breakpoint::MemoryRead(target) if !is_write && addr == target => {
                    self.debug_mem_breakpoint_hit = Some(bp);
                    return;
                }
                Breakpoint::MemoryWrite(target) if is_write && addr == target => {
                    self.debug_mem_breakpoint_hit = Some(bp);
                    return;
                }
                _ => {}
            }
        }
    }

    pub(crate) fn save_state(&self, writer: &mut StateWriter) -> Result<(), SaveStateError> {
        writer.write_bytes(&self.ram);
        self.ppu.save_state(writer);
        self.ppu_memory.save_state(writer)?;
        self.apu.save_state(writer);
        self.dma.save_state(writer);
        for controller in &self.controllers {
            controller.save_state(writer);
        }
        writer.write_u8(self.cpu_open_bus);
        Ok(())
    }

    pub(crate) fn load_state(
        &mut self,
        reader: &mut StateReader<'_>,
    ) -> Result<(), SaveStateError> {
        reader.read_bytes_into(&mut self.ram)?;
        self.ppu.load_state(reader)?;
        self.ppu_memory.load_state(reader)?;
        self.apu.load_state(reader)?;
        self.dma.load_state(reader)?;
        for controller in &mut self.controllers {
            controller.load_state(reader)?;
        }
        self.cpu_open_bus = reader.read_u8()?;
        Ok(())
    }

    fn latched_cpu_read(&mut self, data: u8) -> u8 {
        self.cpu_open_bus = data;
        data
    }

    fn cpu_read_internal(&mut self, addr: u16, cycle_offset: u8) -> u8 {
        #[cfg(feature = "debug")]
        self.check_mem_breakpoint(addr, false);

        match addr {
            0x0000..=0x1FFF => self.latched_cpu_read(self.ram[(addr & 0x7FF) as usize]),
            0x2000..=0x3FFF => {
                let ppu = &mut self.ppu;
                let ppu_memory = &mut self.ppu_memory;
                let data =
                    ppu.cpu_read_register_timed(ppu_memory, 0x2000 | (addr & 0x0007), cycle_offset);
                self.latched_cpu_read(data)
            }
            0x4015 => {
                let data =
                    self.apu.read_status_at_offset(cycle_offset) | (self.cpu_open_bus & 0x20);
                self.latched_cpu_read(data)
            }
            0x4016 => {
                let data = (self.cpu_open_bus & 0xE0) | self.controllers[0].read();
                self.latched_cpu_read(data)
            }
            0x4017 => {
                let data = (self.cpu_open_bus & 0xE0) | self.controllers[1].read();
                self.latched_cpu_read(data)
            }
            0x4020..=0xFFFF => {
                let data = self
                    .ppu_memory
                    .cartridge_cpu_read(addr)
                    .unwrap_or(self.cpu_open_bus);
                self.latched_cpu_read(data)
            }
            // Handle other address ranges (APU, cartridge, etc.)
            _ => self.cpu_open_bus,
        }
    }

    fn cpu_write_internal(&mut self, addr: u16, data: u8, cycle_offset: u8) {
        self.cpu_open_bus = data;
        #[cfg(feature = "debug")]
        self.check_mem_breakpoint(addr, true);
        match addr {
            0x0000..=0x1FFF => self.ram[(addr & 0x7FF) as usize] = data,
            0x2000..=0x3FFF => {
                let ppu = &mut self.ppu;
                let ppu_memory = &mut self.ppu_memory;
                ppu.cpu_write_register_timed(
                    ppu_memory,
                    0x2000 | (addr & 0x0007),
                    data,
                    cycle_offset,
                );
            }
            0x4014 => self.dma.request_oam_dma(data),
            0x4000..=0x4013 | 0x4015 => self.apu.write_register_at_offset(addr, data, cycle_offset),
            0x4016 => {
                self.controllers[0].write(data);
                self.controllers[1].write(data);
            }
            0x4017 => self.apu.write_register_at_offset(addr, data, cycle_offset),
            0x4020..=0xFFFF => {
                let _ = self.ppu_memory.cartridge_cpu_write(addr, data);
            }
            // Handle other address ranges (APU, cartridge, etc.)
            _ => {}
        }
    }

    fn tick_dma_cpu_cycle(&mut self) -> bool {
        let dmc_request = self.apu.take_dmc_dma_request();
        let (consumed, request) = self.dma.tick_cpu_cycle(dmc_request);
        match request {
            DmaBusRequest::None => {}
            DmaBusRequest::DmcRead { addr } => {
                let data = self.cpu_read_internal(addr, 0);
                self.apu.submit_dmc_dma_sample(data);
                self.dma.apply_dmc_read();
            }
            DmaBusRequest::OamRead { addr } => {
                let data = self.cpu_read_internal(addr, 0);
                self.dma.apply_oam_read(data);
            }
            DmaBusRequest::OamWrite { data } => {
                self.ppu.write_oam_dma(data);
                self.dma.apply_oam_write();
            }
        }
        consumed
    }
}

impl CPUBus for NESBus {
    fn cpu_read(&mut self, addr: u16) -> u8 {
        self.cpu_read_internal(addr, 0)
    }

    fn cpu_write(&mut self, addr: u16, data: u8) {
        self.cpu_write_internal(addr, data, 0);
    }

    fn cpu_read_timed(&mut self, addr: u16, cycle_offset: u8) -> u8 {
        self.cpu_read_internal(addr, cycle_offset)
    }

    fn cpu_write_timed(&mut self, addr: u16, data: u8, cycle_offset: u8) {
        self.cpu_write_internal(addr, data, cycle_offset);
    }

    fn try_dma(&mut self) -> bool {
        self.tick_dma_cpu_cycle()
    }
}

impl Default for NESBus {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests;