diff --git a/src/bus.rs b/src/bus.rs index 8dfb79b..1b7f4d8 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -7,14 +7,13 @@ use crate::ppu::{Ppu, PpuMode}; use crate::serial::Serial; use crate::timer::Timer; use crate::work_ram::{VariableWorkRam, WorkRam}; -use std::{fs::File, io::Read}; -const BOOT_ROM_SIZE: usize = 0x100; +pub(crate) const BOOT_SIZE: usize = 0x100; #[derive(Debug)] pub struct Bus { - boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long - cartridge: Option, + boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long + cart: Option, pub(crate) ppu: Ppu, work_ram: WorkRam, var_ram: VariableWorkRam, @@ -30,7 +29,7 @@ impl Default for Bus { fn default() -> Self { Self { boot: None, - cartridge: None, + cart: None, ppu: Default::default(), work_ram: Default::default(), var_ram: Default::default(), @@ -45,25 +44,19 @@ impl Default for Bus { } impl Bus { - pub(crate) fn with_boot(path: &str) -> anyhow::Result { - let mut file = File::open(path)?; - let mut boot_rom = [0u8; 256]; - - file.read_exact(&mut boot_rom)?; - - Ok(Self { - boot: Some(boot_rom), + pub(crate) fn with_boot(rom: [u8; 256]) -> Self { + Self { + boot: Some(rom), ..Default::default() - }) + } } - pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { - self.cartridge = Some(Cartridge::new(path)?); - Ok(()) + pub(crate) fn load_cart(&mut self, rom: Vec) { + self.cart = Some(Cartridge::new(rom)); } - pub(crate) fn rom_title(&self) -> Option<&str> { - self.cartridge.as_ref()?.title() + pub(crate) fn cart_title(&self) -> Option<&str> { + self.cart.as_ref()?.title() } #[allow(dead_code)] @@ -104,13 +97,13 @@ impl Bus { } } - match self.cartridge.as_ref() { + match self.cart.as_ref() { Some(cart) => cart.read_byte(addr), None => panic!("Tried to read from a non-existent cartridge"), } } 0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM - 0xA000..=0xBFFF => match self.cartridge.as_ref() { + 0xA000..=0xBFFF => match self.cart.as_ref() { // 8KB External RAM Some(cart) => cart.read_byte(addr), None => panic!("Tried to read from a non-existent cartridge"), @@ -157,7 +150,7 @@ impl BusIo for Bus { } } - match self.cartridge.as_ref() { + match self.cart.as_ref() { Some(cart) => cart.read_byte(addr), None => panic!("Tried to read from a non-existent cartridge"), } @@ -169,7 +162,7 @@ impl BusIo for Bus { _ => self.ppu.read_byte(addr), } } - 0xA000..=0xBFFF => match self.cartridge.as_ref() { + 0xA000..=0xBFFF => match self.cart.as_ref() { // 8KB External RAM Some(cart) => cart.read_byte(addr), None => panic!("Tried to read from a non-existent cartridge"), @@ -262,7 +255,7 @@ impl BusIo for Bus { 0x0000..=0x7FFF => { // 16KB ROM bank 00 (ends at 0x3FFF) // and 16KB ROM Bank 01 -> NN (switchable via MB) - match self.cartridge.as_mut() { + match self.cart.as_mut() { Some(cart) => cart.write_byte(addr, byte), None => panic!("Tried to write into non-existent cartridge"), } @@ -276,7 +269,7 @@ impl BusIo for Bus { } 0xA000..=0xBFFF => { // 8KB External RAM - match self.cartridge.as_mut() { + match self.cart.as_mut() { Some(cart) => cart.write_byte(addr, byte), None => panic!("Tried to write into non-existent cartridge"), } diff --git a/src/cartridge.rs b/src/cartridge.rs index 0416145..e7bdc50 100644 --- a/src/cartridge.rs +++ b/src/cartridge.rs @@ -1,7 +1,3 @@ -use std::fs::File; -use std::io::{self, Read}; -use std::path::Path; - use crate::bus::BusIo; const RAM_SIZE_ADDRESS: usize = 0x0149; @@ -17,19 +13,15 @@ pub(crate) struct Cartridge { } impl Cartridge { - pub(crate) fn new + ?Sized>(path: &P) -> io::Result { - let mut memory = vec![]; - let mut rom = File::open(path)?; - rom.read_to_end(&mut memory)?; - + pub(crate) fn new(memory: Vec) -> Self { let title = Self::find_title(&memory); eprintln!("Cartridge Title: {:?}", title); - Ok(Self { + Self { mbc: Self::detect_mbc(&memory), title, memory, - }) + } } fn detect_mbc(memory: &[u8]) -> Box { diff --git a/src/cpu.rs b/src/cpu.rs index e82b16e..5b1ab1b 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,9 +1,6 @@ -use crate::apu::Apu; -use crate::bus::{Bus, BusIo}; +use crate::bus::{Bus, BusIo, BOOT_SIZE}; use crate::instruction::Instruction; use crate::interrupt::{InterruptEnable, InterruptFlag}; -use crate::joypad::Joypad; -use crate::ppu::Ppu; use crate::Cycle; use bitfield::bitfield; use std::fmt::{Display, Formatter, Result as FmtResult}; @@ -18,7 +15,7 @@ pub struct Cpu { } impl Cpu { - pub fn new() -> Self { + pub(crate) fn without_boot() -> Self { Self { reg: Registers { a: 0x01, @@ -36,11 +33,11 @@ impl Cpu { } } - pub fn boot_new(path: &str) -> anyhow::Result { - Ok(Self { - bus: Bus::with_boot(path)?, + pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self { + Self { + bus: Bus::with_boot(rom), ..Default::default() - }) + } } pub(crate) fn ime(&self) -> ImeState { @@ -72,14 +69,6 @@ impl Cpu { _ => None, } } - - pub fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { - self.bus.load_cartridge(path) - } - - pub fn rom_title(&self) -> Option<&str> { - self.bus.rom_title() - } } impl Cpu { @@ -167,16 +156,12 @@ impl BusIo for Cpu { } impl Cpu { - pub fn ppu(&mut self) -> &Ppu { - &self.bus.ppu + pub(crate) fn bus(&self) -> &Bus { + &self.bus } - pub fn apu_mut(&mut self) -> &mut Apu { - &mut self.bus.apu - } - - pub(crate) fn joypad_mut(&mut self) -> &mut Joypad { - &mut self.bus.joypad + pub(crate) fn bus_mut(&mut self) -> &mut Bus { + &mut self.bus } fn handle_ei(&mut self) { diff --git a/src/emu.rs b/src/emu.rs index 7fd18aa..d62d787 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -1,8 +1,7 @@ -use crate::cpu::Cpu as SM83; -use crate::joypad; -use crate::ppu::Ppu; -use crate::Cycle; -use anyhow::Result; +use crate::apu::gen::SampleProducer; +use crate::cpu::Cpu; +use crate::joypad::{self, Joypad}; +use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; use gilrs::Gilrs; use std::time::Duration; use winit_input_helper::WinitInputHelper; @@ -12,56 +11,111 @@ pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz const DEFAULT_TITLE: &str = "DMG-01 Emulator"; -pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result { - let mut cpu = match boot_path { - Some(path) => SM83::boot_new(path)?, - None => SM83::new(), - }; - - eprintln!("Initialized GB Emulator"); - - cpu.load_cartridge(rom_path)?; - Ok(cpu) -} - -pub fn rom_title(game_boy: &SM83) -> &str { - game_boy.rom_title().unwrap_or(DEFAULT_TITLE) -} - -pub fn run( - game_boy: &mut SM83, - gamepad: &mut Gilrs, - input: &WinitInputHelper, - target: Cycle, -) -> Cycle { +pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle { let mut elapsed = 0; if let Some(event) = gamepad.next_event() { - joypad::handle_gamepad_input(game_boy.joypad_mut(), event); - } - joypad::handle_keyboard_input(game_boy.joypad_mut(), input); - - while elapsed < target { - elapsed += game_boy.step(); + joypad::handle_gamepad_input(emu.joyp_mut(), event); } - elapsed -} - -pub fn run_frame(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle { - let mut elapsed = 0; - - if let Some(event) = gamepad.next_event() { - joypad::handle_gamepad_input(game_boy.joypad_mut(), event); - } - joypad::handle_keyboard_input(game_boy.joypad_mut(), input); + joypad::handle_keyboard_input(emu.joyp_mut(), key); while elapsed < CYCLES_IN_FRAME { - elapsed += game_boy.step(); + elapsed += emu.step(); } elapsed } -pub fn draw(ppu: &Ppu, frame: &mut [u8]) { - ppu.copy_to_gui(frame); +pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) { + buf.copy_from_slice(emu.cpu.bus().ppu.frame_buf()); +} + +pub struct Emulator { + cpu: Cpu, + timestamp: Cycle, +} + +impl Emulator { + fn new(cpu: Cpu) -> Self { + Self { + cpu, + timestamp: Default::default(), + } + } + + fn step(&mut self) -> Cycle { + self.cpu.step() + } + + fn load_cart(&mut self, rom: Vec) { + self.cpu.bus_mut().load_cart(rom) + } + + fn joyp_mut(&mut self) -> &mut Joypad { + &mut self.cpu.bus_mut().joypad + } + + pub fn set_prod(&mut self, prod: SampleProducer) { + self.cpu.bus_mut().apu.attach_producer(prod) + } + + pub fn title(&self) -> &str { + self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE) + } +} + +pub mod build { + use std::fs::File; + use std::io::{Read, Result}; + use std::path::Path; + + use crate::bus::BOOT_SIZE; + use crate::cpu::Cpu; + + use super::Emulator; + + #[derive(Debug, Default)] + pub struct EmulatorBuilder { + boot: Option<[u8; BOOT_SIZE]>, + cart: Option>, + } + + impl EmulatorBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn with_boot>(mut self, path: P) -> Result { + let mut file = File::open(path.as_ref())?; + + let mut buf = [0x00; BOOT_SIZE]; + file.read_exact(&mut buf)?; + + self.boot = Some(buf); + Ok(self) + } + + pub fn with_cart>(mut self, path: P) -> Result { + let mut file = File::open(path.as_ref())?; + + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + + self.cart = Some(buf); + Ok(self) + } + + pub fn finish(mut self) -> Emulator { + let mut emu = Emulator::new(match self.boot { + Some(rom) => Cpu::with_boot(rom), + None => Cpu::without_boot(), + }); + + if let Some(rom) = self.cart.take() { + emu.load_cart(rom) + } + + emu + } + } } diff --git a/src/main.rs b/src/main.rs index dbe7b0a..41c0b63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,8 @@ +use std::convert::TryInto; + use anyhow::{anyhow, Result}; use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; +use gb::emu::build::EmulatorBuilder; use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH}; use gilrs::Gilrs; use pixels::{PixelsBuilder, SurfaceTexture}; @@ -38,13 +41,15 @@ fn main() -> Result<()> { ) .get_matches(); - let rom_path = m - .value_of("rom") - .expect("Required value 'rom' was provided"); + let mut emu_build = + EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?; - let mut game_boy = - gb::emu::init(m.value_of("boot"), rom_path).expect("Initialize DMG-01 Emulator"); - let rom_title = gb::emu::rom_title(&game_boy); + if let Some(path) = m.value_of("boot") { + emu_build = emu_build.with_boot(path)?; + } + + let mut emu = emu_build.finish(); + let rom_title = emu.title(); let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); @@ -68,10 +73,14 @@ fn main() -> Result<()> { if AUDIO_ENABLED { let spsc: AudioSPSC = Default::default(); let (prod, cons) = spsc.init(); - let sink = Sink::try_new(&stream_handle)?; - sink.append(cons); - sink.set_volume(0.1); // TODO: Is this the right way to go about this? - game_boy.apu_mut().attach_producer(prod); + let sink = { + let s = Sink::try_new(&stream_handle)?; + s.append(cons); + s.set_volume(0.1); + s + }; + + emu.set_prod(prod); std::thread::spawn(move || { sink.sleep_until_end(); @@ -102,12 +111,17 @@ fn main() -> Result<()> { pixels.resize_surface(size.width, size.height); } - cycle_count += gb::emu::run_frame(&mut game_boy, &mut gamepad, &input); + cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input); if cycle_count >= gb::emu::CYCLES_IN_FRAME { cycle_count %= gb::emu::CYCLES_IN_FRAME; - gb::emu::draw(game_boy.ppu(), pixels.get_frame()); + let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels + .get_frame() + .try_into() + .expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4"); + + gb::emu::draw_frame(&emu, buf); window.request_redraw(); } } diff --git a/src/ppu.rs b/src/ppu.rs index 9a3f439..4cc7491 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -418,8 +418,8 @@ impl Ppu { self.frame_buf.swap_with_slice(&mut blank); } - pub fn copy_to_gui(&self, frame: &mut [u8]) { - frame.copy_from_slice(self.frame_buf.as_ref()); + pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { + &self.frame_buf } fn clock_fifo(&mut self) -> Option {