Skip to main content

nes_sim/
lib.rs

1pub mod api;
2mod apu;
3mod bus;
4pub mod cartridge;
5mod cpu;
6mod dma;
7pub mod headless;
8mod input;
9mod ppu;
10mod ppu_memory;
11pub mod runtime;
12pub mod savestate;
13pub mod video;
14
15pub use api::{
16    AudioBatch, CoreCommand, CoreEvent, CoreResponse, CpuDebugSnapshot, DebugSnapshot, PixelFormat,
17    PpuDebugSnapshot, VIDEO_FRAME_PITCH, VideoFrame,
18};
19#[cfg(feature = "debug")]
20pub use api::{Breakpoint, Debugger, DisassembledInstruction, DisassemblyResult, MemorySnapshot};
21pub use apu::ExpansionAudioChip;
22pub use cartridge::{Cartridge, CartridgeError, Mirroring, TVSystem};
23pub use input::{ControllerButton, ControllerState};
24pub use ppu::{FRAME_HEIGHT, FRAME_WIDTH};
25pub use runtime::{
26    ExecutionTarget, FrontendInput, FrontendRuntime, RunMode, RuntimeSnapshot, RuntimeStatus,
27};
28pub use savestate::SaveStateError;
29use savestate::{StateReader, StateWriter};
30
31const PAL_CPU_SCHEDULE: [u8; 5] = [3, 3, 3, 3, 4];
32
33pub struct NES {
34    cpu: cpu::CPU,
35    bus: bus::NESBus,
36    master_clock: u64,
37    cpu_ppu_counter: u8,
38    cpu_schedule_index: usize,
39    cached_tv_system: TVSystem,
40    #[cfg(feature = "debug")]
41    breakpoints: Vec<Breakpoint>,
42    #[cfg(feature = "debug")]
43    cached_mem_breakpoints: Vec<Breakpoint>,
44    #[cfg(feature = "debug")]
45    paused: bool,
46    #[cfg(feature = "debug")]
47    breakpoint_hit: Option<Breakpoint>,
48}
49
50impl NES {
51    pub fn new() -> Self {
52        Self {
53            cpu: cpu::CPU::new(),
54            bus: bus::NESBus::new(),
55            master_clock: 0,
56            cpu_ppu_counter: 0,
57            cpu_schedule_index: 0,
58            cached_tv_system: TVSystem::NTSC,
59            #[cfg(feature = "debug")]
60            breakpoints: Vec::new(),
61            #[cfg(feature = "debug")]
62            cached_mem_breakpoints: Vec::new(),
63            #[cfg(feature = "debug")]
64            paused: false,
65            #[cfg(feature = "debug")]
66            breakpoint_hit: None,
67        }
68    }
69
70    pub fn reset(&mut self) {
71        self.bus.reset();
72        self.reset_cpu_schedule();
73        self.cpu.reset(&mut self.bus);
74        self.cpu.set_nmi(self.bus.ppu_nmi_line());
75    }
76
77    pub fn insert_cartridge(&mut self, cartridge: Cartridge) {
78        self.bus.insert_cartridge(cartridge);
79        self.reset_cpu_schedule();
80        self.reset();
81    }
82
83    pub fn load_cartridge_ines(&mut self, rom: &[u8]) -> Result<(), CartridgeError> {
84        self.bus.load_cartridge_ines(rom)?;
85        self.reset_cpu_schedule();
86        Ok(())
87    }
88
89    pub fn set_controller_state(&mut self, port: usize, state: ControllerState) {
90        self.bus.set_controller_state(port, state);
91    }
92
93    pub fn clock(&mut self) {
94        #[cfg(feature = "debug")]
95        if self.paused() {
96            return;
97        }
98
99        self.master_clock += 1;
100        self.bus.tick_ppu();
101
102        #[cfg(feature = "debug")]
103        {
104            if self.check_ppu_breakpoints() {
105                return;
106            }
107        }
108
109        let threshold = match self.cached_tv_system {
110            TVSystem::NTSC | TVSystem::DENDY => (3, 1),
111            TVSystem::PAL => (PAL_CPU_SCHEDULE[self.cpu_schedule_index], 5),
112        };
113        self.cpu_ppu_counter += 1;
114        if self.cpu_ppu_counter >= threshold.0 {
115            self.cpu_ppu_counter = 0;
116            self.cpu_schedule_index = (self.cpu_schedule_index + 1) % threshold.1;
117            // Update NMI/IRQ only around CPU step, not every PPU cycle
118            self.cpu.set_nmi(self.bus.ppu_nmi_line());
119            self.bus.advance_dma_cpu_phase();
120            self.bus.tick_apu_cpu_cycle();
121
122            #[cfg(feature = "debug")]
123            {
124                self.bus
125                    .set_debug_mem_breakpoints(&self.cached_mem_breakpoints);
126            }
127
128            self.cpu.clock(&mut self.bus);
129
130            #[cfg(feature = "debug")]
131            {
132                if let Some(bp) = self.bus.take_mem_breakpoint_hit() {
133                    self.breakpoint_hit = Some(bp);
134                    return;
135                }
136            }
137
138            self.cpu.irq_set_level(0x01, self.bus.apu_irq_line());
139            self.cpu.irq_set_level(0x02, self.bus.cartridge_irq_line());
140            self.cpu.set_nmi(self.bus.ppu_nmi_line());
141
142            #[cfg(feature = "debug")]
143            {
144                if self.check_breakpoints() {
145                    return;
146                }
147            }
148        }
149    }
150
151    pub fn run_frame(&mut self) {
152        let start_frame = self.frame_number();
153        while self.frame_number() == start_frame {
154            self.clock();
155        }
156    }
157
158    pub fn step_cpu_instruction(&mut self) {
159        let start_instruction = self.cpu.instruction_counter();
160        while self.cpu.instruction_counter() == start_instruction {
161            self.clock();
162        }
163    }
164
165    pub fn execute(&mut self, command: CoreCommand) -> CoreResponse {
166        let event = match command {
167            CoreCommand::Reset => {
168                self.reset();
169                CoreEvent::ResetComplete
170            }
171            CoreCommand::SetControllerState { port, state } => {
172                self.set_controller_state(port, state);
173                CoreEvent::ControllerStateUpdated { port }
174            }
175            CoreCommand::RunFrame => {
176                self.run_frame();
177                CoreEvent::FrameReady {
178                    frame_number: self.frame_number(),
179                }
180            }
181            CoreCommand::StepCpuInstruction => {
182                self.step_cpu_instruction();
183                CoreEvent::CpuInstructionComplete {
184                    instruction_counter: self.cpu.instruction_counter(),
185                }
186            }
187            #[cfg(feature = "debug")]
188            CoreCommand::AddBreakpoint(bp) => {
189                self.add_breakpoint(bp);
190                CoreEvent::None
191            }
192            #[cfg(feature = "debug")]
193            CoreCommand::RemoveBreakpoint(bp) => {
194                self.remove_breakpoint(&bp);
195                CoreEvent::None
196            }
197            #[cfg(feature = "debug")]
198            CoreCommand::SetPaused(paused) => {
199                self.set_paused(paused);
200                CoreEvent::None
201            }
202        };
203
204        CoreResponse {
205            event,
206            master_clock: self.master_clock,
207        }
208    }
209
210    pub fn master_clock(&self) -> u64 {
211        self.master_clock
212    }
213
214    pub fn frame_number(&self) -> u64 {
215        self.bus.ppu_frame()
216    }
217
218    pub fn frame_pixels(&self) -> &[u8] {
219        self.bus.ppu().frame_pixels()
220    }
221
222    pub fn video_frame(&self) -> VideoFrame<'_> {
223        VideoFrame {
224            width: FRAME_WIDTH,
225            height: FRAME_HEIGHT,
226            pitch: VIDEO_FRAME_PITCH,
227            format: PixelFormat::Indexed8,
228            frame_number: self.frame_number(),
229            pixels: self.frame_pixels(),
230        }
231    }
232
233    pub fn audio_batch(&self) -> AudioBatch<'_> {
234        AudioBatch {
235            channels: 1,
236            sample_rate: self.bus.apu_sample_rate(),
237            samples: self.bus.apu_audio_samples(),
238        }
239    }
240
241    pub fn apu_audio_samples(&self) -> &[f32] {
242        self.bus.apu_audio_samples()
243    }
244
245    pub fn apu_sample_rate(&self) -> u32 {
246        self.bus.apu_sample_rate()
247    }
248
249    pub fn clear_apu_audio_samples(&mut self) {
250        self.bus.clear_apu_audio_samples();
251    }
252
253    pub fn add_expansion_audio_chip(&mut self, chip: Box<dyn ExpansionAudioChip>) {
254        self.bus.add_expansion_audio_chip(chip);
255    }
256
257    pub fn debug_snapshot(&self) -> DebugSnapshot {
258        let ppu = self.bus.ppu();
259        DebugSnapshot {
260            master_clock: self.master_clock,
261            cpu: self.cpu.debug_snapshot(),
262            #[cfg(feature = "debug")]
263            ppu: PpuDebugSnapshot {
264                frame: ppu.frame(),
265                scanline: ppu.scanline(),
266                in_vblank: ppu.in_vblank(),
267                nmi_line: ppu.nmi_line(),
268                oam_addr: ppu.oam_addr(),
269                cycles: ppu.cycles(),
270                ctrl: ppu.debug_ctrl(),
271                mask: ppu.debug_mask(),
272                status: ppu.debug_status(),
273                fine_x: ppu.debug_fine_x(),
274                vram_addr: ppu.debug_vram_addr(),
275                temp_vram_addr: ppu.debug_temp_vram_addr(),
276                write_latch: ppu.debug_write_latch(),
277                bg_on: ppu.bg_on(),
278                sprites_on: ppu.sprites_on(),
279                rendering_on: ppu.rendering_on(),
280                odd_frame: ppu.debug_odd_frame(),
281            },
282            #[cfg(not(feature = "debug"))]
283            ppu: PpuDebugSnapshot {
284                frame: ppu.frame(),
285                scanline: ppu.scanline(),
286                in_vblank: ppu.in_vblank(),
287                nmi_line: ppu.nmi_line(),
288                oam_addr: ppu.oam_addr(),
289            },
290        }
291    }
292
293    #[cfg(feature = "debug")]
294    pub fn debug_memory_snapshot(&self) -> MemorySnapshot<'_> {
295        self.bus.debug_memory_snapshot()
296    }
297
298    pub fn mirroring(&self) -> Mirroring {
299        self.bus.mirroring()
300    }
301
302    #[cfg(feature = "debug")]
303    pub fn debug_read_chr(&mut self) -> [u8; 0x2000] {
304        self.bus.debug_read_chr()
305    }
306
307    #[cfg(feature = "debug")]
308    pub fn debug_disassemble(&mut self, rows: usize) -> DisassemblyResult {
309        let pc = self.cpu.pc();
310        cpu::disassemble_range(&mut self.bus, pc, rows, rows)
311    }
312
313    pub fn save_state(&self) -> Result<Vec<u8>, SaveStateError> {
314        let mut writer = StateWriter::new();
315        writer.write_u64(self.master_clock);
316        writer.write_u8(self.cpu_ppu_counter);
317        writer.write_u64(self.cpu_schedule_index as u64);
318        self.cpu.save_state(&mut writer);
319        self.bus.save_state(&mut writer)?;
320        Ok(writer.finish())
321    }
322
323    pub fn load_state(&mut self, bytes: &[u8]) -> Result<(), SaveStateError> {
324        let mut reader = StateReader::new(bytes)?;
325        self.master_clock = reader.read_u64()?;
326        self.cpu_ppu_counter = reader.read_u8()?;
327        self.cpu_schedule_index = reader.read_u64()? as usize;
328        self.cpu.load_state(&mut reader)?;
329        self.bus.load_state(&mut reader)?;
330        self.cached_tv_system = self.bus.ppu().tv_system();
331        self.cpu.set_nmi(self.bus.ppu_nmi_line());
332        reader.finish()
333    }
334
335    pub fn clear_audio_samples(&mut self) {
336        self.bus.clear_apu_audio_samples();
337    }
338
339    pub fn set_apu_sample_rate(&mut self, sample_rate: u32) {
340        self.bus.set_apu_sample_rate(sample_rate);
341    }
342
343    pub fn set_apu_debug_mute_mask(&mut self, mask: u8) {
344        self.bus.set_apu_debug_mute_mask(mask);
345    }
346
347    pub fn apu_debug_mute_mask(&self) -> u8 {
348        self.bus.apu_debug_mute_mask()
349    }
350
351    #[cfg(feature = "debug")]
352    fn update_mem_breakpoint_cache(&mut self) {
353        self.cached_mem_breakpoints = self
354            .breakpoints
355            .iter()
356            .copied()
357            .filter(|bp| matches!(bp, Breakpoint::MemoryRead(_) | Breakpoint::MemoryWrite(_)))
358            .collect();
359    }
360
361    #[cfg(feature = "debug")]
362    pub fn add_breakpoint(&mut self, bp: Breakpoint) {
363        if !self.breakpoints.contains(&bp) {
364            self.breakpoints.push(bp);
365            self.update_mem_breakpoint_cache();
366        }
367    }
368
369    #[cfg(feature = "debug")]
370    pub fn remove_breakpoint(&mut self, bp: &Breakpoint) {
371        self.breakpoints.retain(|b| b != bp);
372        self.update_mem_breakpoint_cache();
373    }
374
375    #[cfg(feature = "debug")]
376    pub fn clear_breakpoints(&mut self) {
377        self.breakpoints.clear();
378        self.cached_mem_breakpoints.clear();
379    }
380
381    #[cfg(feature = "debug")]
382    pub fn breakpoints(&self) -> &[Breakpoint] {
383        &self.breakpoints
384    }
385
386    #[cfg(feature = "debug")]
387    pub fn set_paused(&mut self, paused: bool) {
388        self.paused = paused;
389        self.breakpoint_hit = None;
390    }
391
392    #[cfg(feature = "debug")]
393    pub fn paused(&self) -> bool {
394        self.paused || self.breakpoint_hit.is_some()
395    }
396
397    #[cfg(feature = "debug")]
398    pub fn breakpoint_hit(&self) -> Option<Breakpoint> {
399        self.breakpoint_hit
400    }
401
402    #[cfg(feature = "debug")]
403    fn check_breakpoints(&mut self) -> bool {
404        if self.breakpoints.is_empty() {
405            return false;
406        }
407
408        let pc = self.cpu.pc();
409
410        for &bp in &self.breakpoints {
411            match bp {
412                Breakpoint::Address(addr) if pc == addr => {
413                    self.breakpoint_hit = Some(bp);
414                    return true;
415                }
416                _ => {}
417            }
418        }
419        false
420    }
421
422    #[cfg(feature = "debug")]
423    fn check_ppu_breakpoints(&mut self) -> bool {
424        if self.breakpoints.is_empty() {
425            return false;
426        }
427
428        let ppu = self.bus.ppu();
429        let scanline = ppu.scanline();
430        let in_vblank = ppu.in_vblank();
431
432        for &bp in &self.breakpoints {
433            match bp {
434                Breakpoint::PpuScanline(sl) if scanline == sl => {
435                    self.breakpoint_hit = Some(bp);
436                    return true;
437                }
438                Breakpoint::Vblank if in_vblank => {
439                    self.breakpoint_hit = Some(bp);
440                    return true;
441                }
442                _ => {}
443            }
444        }
445        false
446    }
447
448    fn reset_cpu_schedule(&mut self) {
449        self.cached_tv_system = self.bus.ppu().tv_system();
450        self.cpu_ppu_counter = 0;
451        self.cpu_schedule_index = 0;
452    }
453}
454
455impl Default for NES {
456    fn default() -> Self {
457        Self::new()
458    }
459}
460
461#[cfg(test)]
462mod tests;