248 lines
6.6 KiB
Rust
248 lines
6.6 KiB
Rust
#![feature(exclusive_range_pattern, let_chains, bigint_helper_methods)]
|
|
|
|
use crate::processor::{memory::Memory, Flags};
|
|
use connect::{
|
|
AudioOutput, CameraWrapper, EmulatorCoreTrait, EmulatorMessage, EmulatorOptions, PocketCamera,
|
|
RomFile,
|
|
};
|
|
use processor::{
|
|
memory::{mmio::gpu::Colour, rom::CgbRomType, OutputTargets},
|
|
Cpu,
|
|
};
|
|
use std::{
|
|
marker::PhantomData,
|
|
sync::{mpsc::Receiver, Arc, Mutex},
|
|
};
|
|
|
|
#[cfg(all(feature = "vulkan", feature = "pixels"))]
|
|
compile_error!("select only one rendering backend!");
|
|
|
|
#[allow(unused_attributes)]
|
|
#[cfg(any(feature = "vulkan-renderer", feature = "pixels-renderer"))]
|
|
#[cfg_attr(feature = "pixels-renderer", path = "renderer/pixels.rs")]
|
|
#[cfg_attr(feature = "vulkan-renderer", path = "renderer/vulkan/vulkan.rs")]
|
|
pub mod renderer;
|
|
|
|
#[cfg(feature = "config")]
|
|
pub mod config;
|
|
pub mod connect;
|
|
mod constants;
|
|
mod processor;
|
|
pub mod util;
|
|
|
|
pub const WIDTH: usize = 160;
|
|
pub const HEIGHT: usize = 144;
|
|
|
|
pub struct EmulatorCore<ColourFormat, C>
|
|
where
|
|
ColourFormat: From<Colour> + Copy,
|
|
C: PocketCamera + Send + 'static,
|
|
{
|
|
receiver: Receiver<EmulatorMessage>,
|
|
cpu: Cpu<ColourFormat, C>,
|
|
paused: bool,
|
|
spooky: PhantomData<C>,
|
|
}
|
|
|
|
impl<ColourFormat, C> EmulatorCore<ColourFormat, C>
|
|
where
|
|
ColourFormat: From<Colour> + Copy,
|
|
C: PocketCamera + Send + 'static,
|
|
{
|
|
pub fn init(
|
|
paused: bool,
|
|
receiver: Receiver<EmulatorMessage>,
|
|
options: EmulatorOptions<ColourFormat, C>,
|
|
camera: Arc<Mutex<CameraWrapper<C>>>,
|
|
) -> Self {
|
|
let rom = options.rom;
|
|
|
|
let is_cgb_mode = rom.rom_type == CgbRomType::CgbOnly || options.cgb_mode;
|
|
let bootrom = if is_cgb_mode {
|
|
options.cgb_bootrom.unwrap_or(RomFile::Raw(
|
|
include_bytes!("../../sameboy-bootroms/cgb_boot.bin").to_vec(),
|
|
))
|
|
} else {
|
|
options.dmg_bootrom.unwrap_or(RomFile::Raw(
|
|
include_bytes!("../../sameboy-bootroms/dmg_boot.bin").to_vec(),
|
|
))
|
|
}
|
|
.load_data()
|
|
.expect("Error loading bootrom!");
|
|
|
|
options
|
|
.window
|
|
.send(connect::RendererMessage::Prepare {
|
|
width: WIDTH,
|
|
height: HEIGHT,
|
|
})
|
|
.expect("message error");
|
|
options
|
|
.window
|
|
.send(connect::RendererMessage::SetTitle {
|
|
title: format!(
|
|
"{} on {} on {}",
|
|
rom.get_title(),
|
|
rom.mbc_type(),
|
|
if is_cgb_mode { "CGB" } else { "DMG" }
|
|
),
|
|
})
|
|
.expect("message error");
|
|
|
|
Self::new(
|
|
paused,
|
|
receiver,
|
|
Cpu::new(
|
|
Memory::init(
|
|
is_cgb_mode,
|
|
bootrom,
|
|
rom,
|
|
OutputTargets::new(
|
|
options.window,
|
|
options.output,
|
|
options.serial_target,
|
|
options.tile_window,
|
|
options.layer_window,
|
|
camera,
|
|
),
|
|
),
|
|
options.show_bootrom,
|
|
),
|
|
)
|
|
}
|
|
|
|
fn new(paused: bool, receiver: Receiver<EmulatorMessage>, cpu: Cpu<ColourFormat, C>) -> Self {
|
|
Self {
|
|
receiver,
|
|
cpu,
|
|
paused,
|
|
spooky: PhantomData,
|
|
}
|
|
}
|
|
|
|
fn print_flags(&self) -> String {
|
|
format!(
|
|
"{}{}{}{}",
|
|
if self.cpu.is_flag(Flags::Zero) {
|
|
"Z"
|
|
} else {
|
|
"-"
|
|
},
|
|
if self.cpu.is_flag(Flags::NSubtract) {
|
|
"N"
|
|
} else {
|
|
"-"
|
|
},
|
|
if self.cpu.is_flag(Flags::HalfCarry) {
|
|
"H"
|
|
} else {
|
|
"-"
|
|
},
|
|
if self.cpu.is_flag(Flags::Carry) {
|
|
"C"
|
|
} else {
|
|
"-"
|
|
},
|
|
)
|
|
}
|
|
|
|
fn run_cycle(&mut self) {
|
|
self.cpu.exec_next();
|
|
}
|
|
|
|
fn process_messages(&mut self) -> bool {
|
|
while let Ok(msg) = self.receiver.try_recv() {
|
|
if self.process_message(msg) {
|
|
return true;
|
|
}
|
|
}
|
|
while self.paused {
|
|
match self.receiver.recv() {
|
|
Ok(msg) => {
|
|
if self.process_message(msg) {
|
|
return true;
|
|
}
|
|
}
|
|
Err(e) => panic!("no message sender! error {e:#?}"),
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
fn process_message(&mut self, msg: EmulatorMessage) -> bool {
|
|
match msg {
|
|
EmulatorMessage::Exit => {
|
|
self.cpu.memory.flush_rom();
|
|
return true;
|
|
}
|
|
EmulatorMessage::Start => self.paused = false,
|
|
EmulatorMessage::Pause => self.paused = true,
|
|
EmulatorMessage::JoypadUpdate(new_state) => {
|
|
self.cpu.next_joypad_state = Some(new_state)
|
|
}
|
|
}
|
|
false
|
|
}
|
|
}
|
|
|
|
impl<ColourFormat, C> EmulatorCoreTrait for EmulatorCore<ColourFormat, C>
|
|
where
|
|
ColourFormat: From<Colour> + Copy,
|
|
C: PocketCamera + Send + 'static,
|
|
{
|
|
fn replace_output(&mut self, new: AudioOutput) {
|
|
self.cpu.memory.replace_output(new);
|
|
}
|
|
|
|
fn cycle_count(&self) -> usize {
|
|
self.cpu.cycle_count
|
|
}
|
|
|
|
fn pc(&self) -> u16 {
|
|
self.cpu.reg.pc
|
|
}
|
|
|
|
fn print_reg(&self) -> String {
|
|
format!(
|
|
"A:{:0>2X}, F:{}, BC:{:0>4X}, DE:{:0>4X}, HL:{:0>4X}, SP:{:0>4X}, PC:{:0>4X}\nLast instruction: {:0>2X} from 0x{:0>4X}",
|
|
self.cpu.reg.get_8(processor::Reg8::A),
|
|
self.print_flags(),
|
|
self.cpu.reg.bc,
|
|
self.cpu.reg.de,
|
|
self.cpu.reg.hl,
|
|
self.cpu.reg.sp,
|
|
self.cpu.reg.pc,
|
|
self.cpu.last_instruction,
|
|
self.cpu.last_instruction_addr
|
|
)
|
|
}
|
|
|
|
fn get_memory(&self, address: u16) -> u8 {
|
|
self.cpu.memory.get(address)
|
|
}
|
|
|
|
fn run(&mut self, cycles: usize) -> bool {
|
|
if self.process_messages() {
|
|
return true;
|
|
}
|
|
if !self.paused {
|
|
for _ in 0..cycles {
|
|
self.run_cycle();
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
fn run_until_buffer_full(&mut self) {
|
|
if self.process_messages() {
|
|
return;
|
|
}
|
|
while !self.cpu.memory.is_audio_buffer_full() {
|
|
self.run_cycle();
|
|
}
|
|
}
|
|
|
|
fn process_messages(&mut self) -> bool {
|
|
self.process_messages()
|
|
}
|
|
}
|