212 lines
5.6 KiB
Rust
212 lines
5.6 KiB
Rust
#![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<bool> = OnceCell::new();
|
|
|
|
pub const WIDTH: usize = 160;
|
|
pub const HEIGHT: usize = 144;
|
|
|
|
pub struct EmulatorCore<ColourFormat, R, C>
|
|
where
|
|
ColourFormat: From<Colour> + Clone,
|
|
R: Renderer<ColourFormat>,
|
|
C: PocketCamera + Send + 'static,
|
|
{
|
|
receiver: Receiver<EmulatorMessage>,
|
|
cpu: Cpu<ColourFormat, R, C>,
|
|
spooky: PhantomData<C>,
|
|
}
|
|
|
|
impl<ColourFormat, R, C> EmulatorCore<ColourFormat, R, C>
|
|
where
|
|
ColourFormat: From<Colour> + Clone,
|
|
R: Renderer<ColourFormat>,
|
|
C: PocketCamera + Send + 'static,
|
|
{
|
|
pub fn init(
|
|
receiver: Receiver<EmulatorMessage>,
|
|
mut options: EmulatorOptions<ColourFormat, R, C>,
|
|
) -> Self {
|
|
if options.verbose {
|
|
VERBOSE.set(true).unwrap();
|
|
}
|
|
|
|
let camera: CameraWrapperRef<C> = 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<Vec<u8>> = 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<EmulatorMessage>, cpu: Cpu<ColourFormat, R, C>) -> 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<ColourFormat, R>
|
|
where
|
|
ColourFormat: From<Colour> + Clone,
|
|
R: Renderer<ColourFormat>,
|
|
{
|
|
CpuSaveState::create(&self.cpu)
|
|
}
|
|
}
|
|
|
|
impl<ColourFormat, R> EmulatorCore<ColourFormat, R, NoCamera>
|
|
where
|
|
ColourFormat: From<Colour> + Clone,
|
|
R: Renderer<ColourFormat>,
|
|
{
|
|
pub fn from_save_state(
|
|
state: CpuSaveState<ColourFormat, R>,
|
|
rom: RomFile,
|
|
receiver: Receiver<EmulatorMessage>,
|
|
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,
|
|
}
|
|
}
|
|
}
|