#![feature(exclusive_range_pattern, let_chains, bigint_helper_methods, step_trait)] use crate::{processor::memory::Memory, util::pause}; use connect::{ AudioOutput, CameraWrapper, CameraWrapperRef, EmulatorMessage, EmulatorOptions, NoCamera, PocketCamera, Renderer, RomFile, SerialTarget, }; use once_cell::sync::OnceCell; use processor::{ memory::{mmio::gpu::Colour, Rom}, Cpu, CpuSaveState, }; use std::{ fs::{self}, io::{stdout, Write}, marker::PhantomData, path::PathBuf, process::exit, str::FromStr, sync::{mpsc::Receiver, Arc, Mutex}, }; pub mod connect; mod constants; mod processor; pub mod util; static VERBOSE: OnceCell = OnceCell::new(); pub const WIDTH: usize = 160; pub const HEIGHT: usize = 144; pub struct EmulatorCore where ColourFormat: From + Clone, R: Renderer, C: PocketCamera + Send + 'static, { receiver: Receiver, cpu: Cpu, spooky: PhantomData, } impl EmulatorCore where ColourFormat: From + Clone, R: Renderer, C: PocketCamera + Send + 'static, { pub fn init( receiver: Receiver, mut options: EmulatorOptions, ) -> Self { if options.verbose { VERBOSE.set(true).unwrap(); } let camera: CameraWrapperRef = Arc::new(Mutex::new(CameraWrapper::new(options.camera))); 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, camera.clone()), Err(e) => { println!("Error reading ROM: {e}"); exit(1); } } } RomFile::Raw(data) => Rom::load(data, None, camera.clone()), }; options.window.prepare(WIDTH, HEIGHT); options .window .set_title(format!("{} on {}", rom.get_title(), rom.mbc_type())); let bootrom_enabled = options.bootrom.is_some(); let bootrom: Option> = options.bootrom.map(|v| match v { RomFile::Path(path) => match fs::read(path) { Ok(data) => data, Err(e) => { println!("Error reading bootROM: {e}"); exit(1); } }, RomFile::Raw(data) => data, }); Self::new( receiver, Cpu::new( Memory::init( bootrom, rom, options.window, options.output, options.serial_target, options.tile_window, camera, ), bootrom_enabled, ), ) } fn new(receiver: Receiver, cpu: Cpu) -> Self { Self { receiver, cpu, spooky: PhantomData, } } pub fn replace_output(&mut self, new: AudioOutput) { self.cpu.memory.replace_output(new); } pub fn run(&mut self) { self.process_messages(); self.run_cycle(); } pub fn run_stepped(&mut self, step_size: usize) { loop { self.process_messages(); for _ in 0..step_size { self.run_cycle(); } 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) { self.cpu.exec_next(); } fn process_messages(&mut self) { while let Ok(msg) = self.receiver.try_recv() { match msg { EmulatorMessage::Stop => { self.cpu.memory.flush_rom(); exit(0); } } } } pub fn get_save_state(&self) -> CpuSaveState where ColourFormat: From + Clone, R: Renderer, { CpuSaveState::create(&self.cpu) } } impl EmulatorCore where ColourFormat: From + Clone, R: Renderer, { pub fn from_save_state( state: CpuSaveState, rom: RomFile, receiver: Receiver, window: R, output: AudioOutput, serial_target: SerialTarget, ) -> Self { let data = match rom { RomFile::Path(path) => match fs::read(path) { Ok(data) => data, Err(e) => { println!("Error reading ROM: {e}"); exit(1); } }, RomFile::Raw(data) => data, }; Self { receiver, cpu: Cpu::from_save_state( state, data, window, output, serial_target, Arc::new(Mutex::new(CameraWrapper::new(NoCamera::default()))), ), spooky: PhantomData, } } }