mod cartridge; mod bus; mod cpu; mod mapper; mod palette; mod render; mod ppu; mod dump; mod input; use crate::bus::{Bus, NesBus}; use crate::cartridge::Cartridge; use crate::cpu::Cpu; use crate::render::{PpmRenderer, Renderer}; use eframe::egui; use crate::render::WindowRenderer; fn main() { let args: Vec = std::env::args().collect(); match args.len() { 1 => run_nestest(), 3 if args[1] == "blargg" => run_blargg(&args[2]), 3 if args[1] == "dbg" => run_dbg(&args[2]), 3 if args[1] == "gui" => run_gui(&args[2]), 2 => run_cart(&args[1]), _ => eprintln!("usage: rnes [ | blargg | dbg | gui ]"), } } fn run_cart(path: &str) { let cart = Cartridge::from_file(path).unwrap(); let mut bus = NesBus::new(cart); let mut cpu = Cpu::new(); cpu.reset(&mut bus); let mut renderer = PpmRenderer::new(); let frames: u32 = 180; for _ in 0..frames { run_one_frame(&mut bus, &mut cpu); renderer.present(bus.framebuffer(), 256, 240); } } fn run_blargg(path: &str) { let cart = Cartridge::from_file(path).unwrap(); let mut bus = NesBus::new(cart); let mut cpu = Cpu::new(); cpu.reset(&mut bus); // boots from $FFFC-$FFFD via MMC1 PRG banking let cycle_cap: u64 = 5_000_000; // blargg tests complete well under this; tune if needed while cpu.cycles < cycle_cap { cpu.step(&mut bus); } let r0 = bus.read(0x6000); let r1 = bus.read(0x6001); let r2 = bus.read(0x6002); let r3 = bus.read(0x6003); println!("result: $6000={:02X} $6001={:02X} $6002={:02X} $6003={:02X}", r0, r1, r2, r3); if r0 == 0 { println!("PASS"); } else { println!("FAIL (error {:#04X})", r0); } } fn run_nestest() { let cart: Cartridge = Cartridge::from_file("roms/test/cpu/nestest/nestest.nes").unwrap(); let mut bus: NesBus = NesBus::new(cart); let mut cpu = Cpu::new(); cpu.set_test_start(0xC000, 7); loop { println!("{}", cpu.trace(&mut bus)); cpu.step(&mut bus); } } fn run_dbg(path: &str) { let cart = Cartridge::from_file(path).unwrap(); let mut bus = NesBus::new(cart); let mut cpu = Cpu::new(); cpu.reset(&mut bus); let mut dumper = dump::Dumper::new(); let mut renderer = PpmRenderer::new(); // <-- add this let frames: u32 = 1800; for f in 0..frames { while !bus.frame_done() { let cycles = cpu.step(&mut bus); bus.tick_ppu(cycles as u64); if bus.take_nmi() { cpu.set_nmi(); } } renderer.present(bus.framebuffer(), 256, 240); // <-- and this dumper.snapshot(f, &bus, &cpu); bus.begin_frame(); } println!("wrote debug_out/ + renders/, {frames} frames"); } fn run_one_frame(bus: &mut NesBus, cpu: &mut Cpu) { while !bus.frame_done() { let cycles = cpu.step(bus); bus.tick_ppu(cycles as u64); if bus.take_nmi() { cpu.set_nmi(); } } bus.begin_frame(); } struct GuiApp { bus: NesBus, cpu: Cpu, renderer: WindowRenderer, } impl GuiApp { fn new(cc: &eframe::CreationContext<'_>, cart: Cartridge) -> Self { let mut bus = NesBus::new(cart); let mut cpu = Cpu::new(); cpu.reset(&mut bus); let renderer = WindowRenderer::new(cc, 256, 240); Self { bus, cpu, renderer } } } impl eframe::App for GuiApp { // Non-UI work: emulation runs here, before painting. fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.renderer.poll_input(self.bus.controller_mut()); // keyboard -> $4016/$4017 run_one_frame(&mut self.bus, &mut self.cpu); // one emulated frame self.renderer.present(self.bus.framebuffer(), 256, 240); ctx.request_repaint(); // keep it running at 60fps } // Painting: draw the framebuffer texture. fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { self.renderer.draw(ui); } } fn run_gui(path: &str) { let cart = Cartridge::from_file(path).unwrap(); let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_inner_size([1024.0, 960.0]) .with_title("rnes"), ..Default::default() }; eframe::run_native( "rnes", options, Box::new(move |cc| Ok(Box::new(GuiApp::new(cc, cart)))), ).expect("failed to run GUI"); }