#![feature( exclusive_range_pattern, let_chains, slice_flatten, async_closure, bigint_helper_methods )] use crate::{processor::memory::Memory, util::pause}; use connect::{AudioOutput, EmulatorMessage, Renderer, RomFile}; use once_cell::sync::OnceCell; use processor::{memory::Rom, Cpu}; use std::{ fs::{self}, io::{stdout, Write}, path::PathBuf, process::exit, str::FromStr, sync::mpsc::Receiver, }; use util::pause_then_step; pub mod connect; mod constants; mod processor; pub mod util; pub struct Options { pub rom: RomFile, pub save_path: Option, pub no_save: bool, pub bootrom_path: Option, pub connect_serial: bool, pub verbose: bool, pub cycle_count: bool, } static mut PAUSE_ENABLED: bool = false; static mut PAUSE_QUEUED: bool = false; static VERBOSE: OnceCell = OnceCell::new(); pub const WIDTH: usize = 160; pub const HEIGHT: usize = 144; pub struct EmulatorCore { receiver: Receiver, cpu: Cpu, cycle_num: usize, cycle_count: bool, } impl EmulatorCore { pub fn init( receiver: Receiver, options: Options, mut window: Box, output: AudioOutput, tile_window: Option>, ) -> Self { VERBOSE.set(options.verbose).unwrap(); let rom = match options.rom { RomFile::Path(path) => { let maybe_save = if options.no_save { None } else { Some(if let Some(path) = options.save_path { PathBuf::from_str(&path).unwrap() } else { PathBuf::from_str(&path).unwrap().with_extension("sav") }) }; match fs::read(path) { Ok(data) => Rom::load(data, maybe_save), Err(e) => { println!("Error reading ROM: {e}"); exit(1); } } } RomFile::Raw(data) => Rom::load(data, None), }; window.prepare(WIDTH, HEIGHT); window.set_title(format!("{} on {}", rom.get_title(), rom.mbc_type())); let bootrom_enabled = options.bootrom_path.is_some(); let bootrom: Option> = if let Some(path) = options.bootrom_path { match fs::read(path) { Ok(data) => Some(data), Err(e) => { println!("Error reading bootROM: {e}"); exit(1); } } } else { None }; Self::new( receiver, Cpu::new( Memory::init( bootrom, rom, window, output, options.connect_serial, tile_window, ), bootrom_enabled, ), options.cycle_count, ) } fn new(receiver: Receiver, cpu: Cpu, cycle_count: bool) -> Self { Self { receiver, cpu, cycle_num: 0, cycle_count, } } pub fn run(&mut self) { self.process_messages(); self.cycle_num += 1; if self.cycle_count { self.print_cycles(); } self.run_cycle(); } pub fn run_stepped(&mut self, step_size: usize) { loop { self.process_messages(); for _ in 0..step_size { self.cycle_num += 1; if self.cycle_count { self.print_cycles(); } self.run_cycle(); } print!( " ...{} cycles - press enter to continue\r", self.cycle_num ); stdout().flush().unwrap(); pause(); } } pub fn run_until_buffer_full(&mut self) { while !self.cpu.memory.is_audio_buffer_full() { self.run(); } } fn run_cycle(&mut self) { let will_pause = unsafe { PAUSE_QUEUED }; let pause_enabled = unsafe { PAUSE_ENABLED }; self.cpu.exec_next(); if !pause_enabled && self.cpu.reg.pc >= 0x100 { unsafe { PAUSE_ENABLED = true }; } if will_pause { pause_then_step(); } } fn process_messages(&mut self) { while let Ok(msg) = self.receiver.try_recv() { match msg { EmulatorMessage::Stop => { self.cpu.memory.flush_rom(); exit(0); } } } } fn print_cycles(&self) { if self.cycle_num % 45678 != 0 { return; } let instructions_per_second = 400000; print!( "cycle {} - approx {} seconds on real hardware\r", self.cycle_num, self.cycle_num / instructions_per_second ); stdout().flush().unwrap(); } }