2023-02-25 09:43:39 +11:00
|
|
|
#![feature(
|
|
|
|
exclusive_range_pattern,
|
|
|
|
let_chains,
|
|
|
|
slice_flatten,
|
|
|
|
async_closure,
|
2023-03-08 11:01:18 +11:00
|
|
|
bigint_helper_methods,
|
|
|
|
associated_type_defaults
|
2023-02-25 09:43:39 +11:00
|
|
|
)]
|
|
|
|
|
2023-03-06 19:20:47 +11:00
|
|
|
use crate::{processor::memory::Memory, util::pause};
|
2023-03-06 17:23:46 +11:00
|
|
|
use connect::{AudioOutput, EmulatorMessage, Renderer, RomFile};
|
2023-02-25 09:43:39 +11:00
|
|
|
use once_cell::sync::OnceCell;
|
2023-03-08 11:01:18 +11:00
|
|
|
use processor::{
|
|
|
|
memory::{mmio::gpu::Colour, Rom},
|
|
|
|
Cpu,
|
|
|
|
};
|
2023-02-26 09:42:15 +11:00
|
|
|
use std::{
|
2023-03-02 11:29:54 +11:00
|
|
|
fs::{self},
|
2023-02-26 09:42:15 +11:00
|
|
|
io::{stdout, Write},
|
2023-03-02 11:29:54 +11:00
|
|
|
path::PathBuf,
|
|
|
|
process::exit,
|
|
|
|
str::FromStr,
|
|
|
|
sync::mpsc::Receiver,
|
2023-02-26 09:42:15 +11:00
|
|
|
};
|
|
|
|
use util::pause_then_step;
|
2023-02-25 09:43:39 +11:00
|
|
|
|
2023-02-26 10:18:58 +11:00
|
|
|
pub mod connect;
|
2023-02-25 09:43:39 +11:00
|
|
|
mod constants;
|
|
|
|
mod processor;
|
2023-02-26 09:42:15 +11:00
|
|
|
pub mod util;
|
|
|
|
|
|
|
|
pub struct Options {
|
2023-03-06 17:23:46 +11:00
|
|
|
pub rom: RomFile,
|
2023-03-02 19:10:50 +11:00
|
|
|
pub save_path: Option<String>,
|
2023-03-03 09:40:16 +11:00
|
|
|
pub no_save: bool,
|
2023-02-26 09:42:15 +11:00
|
|
|
pub bootrom_path: Option<String>,
|
|
|
|
pub connect_serial: bool,
|
|
|
|
pub verbose: bool,
|
|
|
|
pub cycle_count: bool,
|
2023-02-25 09:43:39 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
static mut PAUSE_ENABLED: bool = false;
|
|
|
|
static mut PAUSE_QUEUED: bool = false;
|
|
|
|
|
|
|
|
static VERBOSE: OnceCell<bool> = OnceCell::new();
|
|
|
|
|
|
|
|
pub const WIDTH: usize = 160;
|
|
|
|
pub const HEIGHT: usize = 144;
|
|
|
|
|
2023-03-08 11:01:18 +11:00
|
|
|
pub struct EmulatorCore<ColourFormat: From<Colour> + Clone> {
|
2023-03-02 11:29:54 +11:00
|
|
|
receiver: Receiver<EmulatorMessage>,
|
2023-03-08 11:01:18 +11:00
|
|
|
cpu: Cpu<ColourFormat>,
|
2023-03-06 19:20:47 +11:00
|
|
|
cycle_num: usize,
|
|
|
|
cycle_count: bool,
|
|
|
|
}
|
|
|
|
|
2023-03-08 11:01:18 +11:00
|
|
|
impl<ColourFormat: From<Colour> + Clone> EmulatorCore<ColourFormat> {
|
2023-03-06 19:20:47 +11:00
|
|
|
pub fn init(
|
|
|
|
receiver: Receiver<EmulatorMessage>,
|
|
|
|
options: Options,
|
2023-03-08 11:01:18 +11:00
|
|
|
mut window: Box<dyn Renderer<ColourFormat>>,
|
2023-03-06 19:20:47 +11:00
|
|
|
output: AudioOutput,
|
2023-03-08 11:01:18 +11:00
|
|
|
tile_window: Option<Box<dyn Renderer<ColourFormat>>>,
|
2023-03-06 19:20:47 +11:00
|
|
|
) -> Self {
|
|
|
|
VERBOSE.set(options.verbose).unwrap();
|
|
|
|
|
|
|
|
let rom = match options.rom {
|
|
|
|
RomFile::Path(path) => {
|
|
|
|
let maybe_save = if options.no_save {
|
|
|
|
None
|
2023-03-06 17:23:46 +11:00
|
|
|
} else {
|
2023-03-06 19:20:47 +11:00
|
|
|
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()));
|
2023-03-02 11:29:54 +11:00
|
|
|
|
2023-03-06 19:20:47 +11:00
|
|
|
let bootrom_enabled = options.bootrom_path.is_some();
|
|
|
|
let bootrom: Option<Vec<u8>> = if let Some(path) = options.bootrom_path {
|
2023-03-06 17:23:46 +11:00
|
|
|
match fs::read(path) {
|
2023-03-06 19:20:47 +11:00
|
|
|
Ok(data) => Some(data),
|
2023-03-06 17:23:46 +11:00
|
|
|
Err(e) => {
|
2023-03-06 19:20:47 +11:00
|
|
|
println!("Error reading bootROM: {e}");
|
|
|
|
exit(1);
|
2023-03-06 17:23:46 +11:00
|
|
|
}
|
|
|
|
}
|
2023-03-06 19:20:47 +11:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
Self::new(
|
|
|
|
receiver,
|
|
|
|
Cpu::new(
|
|
|
|
Memory::init(
|
|
|
|
bootrom,
|
|
|
|
rom,
|
|
|
|
window,
|
|
|
|
output,
|
|
|
|
options.connect_serial,
|
|
|
|
tile_window,
|
|
|
|
),
|
|
|
|
bootrom_enabled,
|
|
|
|
),
|
|
|
|
options.cycle_count,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-03-08 11:01:18 +11:00
|
|
|
fn new(receiver: Receiver<EmulatorMessage>, cpu: Cpu<ColourFormat>, cycle_count: bool) -> Self {
|
2023-03-06 19:20:47 +11:00
|
|
|
Self {
|
|
|
|
receiver,
|
|
|
|
cpu,
|
|
|
|
cycle_num: 0,
|
|
|
|
cycle_count,
|
2023-02-25 09:43:39 +11:00
|
|
|
}
|
2023-03-06 19:20:47 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run(&mut self) {
|
|
|
|
self.process_messages();
|
|
|
|
self.cycle_num += 1;
|
|
|
|
if self.cycle_count {
|
|
|
|
self.print_cycles();
|
2023-02-25 09:43:39 +11:00
|
|
|
}
|
2023-03-06 19:20:47 +11:00
|
|
|
self.run_cycle();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run_stepped(&mut self, step_size: usize) {
|
|
|
|
loop {
|
|
|
|
self.process_messages();
|
2023-02-25 09:43:39 +11:00
|
|
|
for _ in 0..step_size {
|
2023-03-06 19:20:47 +11:00
|
|
|
self.cycle_num += 1;
|
|
|
|
if self.cycle_count {
|
|
|
|
self.print_cycles();
|
2023-02-25 09:43:39 +11:00
|
|
|
}
|
2023-03-06 19:20:47 +11:00
|
|
|
self.run_cycle();
|
2023-02-25 09:43:39 +11:00
|
|
|
}
|
2023-03-06 19:20:47 +11:00
|
|
|
print!(
|
|
|
|
" ...{} cycles - press enter to continue\r",
|
|
|
|
self.cycle_num
|
|
|
|
);
|
2023-02-25 09:43:39 +11:00
|
|
|
stdout().flush().unwrap();
|
|
|
|
pause();
|
2023-03-06 19:20:47 +11:00
|
|
|
}
|
2023-02-25 09:43:39 +11:00
|
|
|
}
|
|
|
|
|
2023-03-06 19:20:47 +11:00
|
|
|
pub fn run_until_buffer_full(&mut self) {
|
|
|
|
while !self.cpu.memory.is_audio_buffer_full() {
|
2023-03-07 09:53:56 +11:00
|
|
|
self.run();
|
2023-03-03 11:20:47 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-06 19:20:47 +11:00
|
|
|
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();
|
|
|
|
}
|
2023-02-25 09:43:39 +11:00
|
|
|
}
|
2023-03-06 19:20:47 +11:00
|
|
|
|
|
|
|
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();
|
2023-02-25 09:43:39 +11:00
|
|
|
}
|
|
|
|
}
|