From 9a8e4117be8d30109229600346e7d9561c52a3e3 Mon Sep 17 00:00:00 2001 From: Astatin Date: Thu, 3 Apr 2025 18:35:03 +0200 Subject: Separate core from desktop target --- assets/Astatin-bootrom.gbasm | 1 - src/audio.rs | 51 ++---- src/desktop/audio.rs | 54 ++++++ src/desktop/input.rs | 287 ++++++++++++++++++++++++++++++ src/desktop/load_save.rs | 69 ++++++++ src/desktop/mod.rs | 5 + src/desktop/serial.rs | 130 ++++++++++++++ src/desktop/window.rs | 125 +++++++++++++ src/gamepad.rs | 291 ------------------------------ src/interrupts_timers.rs | 3 +- src/io.rs | 409 +++++++++++++++++-------------------------- src/main.rs | 129 ++------------ src/mmio.rs | 268 ++++++++++++++++++++++++++++ src/opcodes.rs | 3 +- src/serial.rs | 139 --------------- src/state.rs | 83 ++------- src/window.rs | 124 ------------- 17 files changed, 1147 insertions(+), 1024 deletions(-) create mode 100644 src/desktop/audio.rs create mode 100644 src/desktop/input.rs create mode 100644 src/desktop/load_save.rs create mode 100644 src/desktop/mod.rs create mode 100644 src/desktop/serial.rs create mode 100644 src/desktop/window.rs delete mode 100644 src/gamepad.rs create mode 100644 src/mmio.rs delete mode 100644 src/serial.rs delete mode 100644 src/window.rs diff --git a/assets/Astatin-bootrom.gbasm b/assets/Astatin-bootrom.gbasm index d51e1ab..e1ed375 100644 --- a/assets/Astatin-bootrom.gbasm +++ b/assets/Astatin-bootrom.gbasm @@ -1,4 +1,3 @@ -; .INCLUDE "minimal.gbasm" LD SP,$fffe EmptyVRAM: diff --git a/src/audio.rs b/src/audio.rs index 0a152a6..3e36790 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,12 +1,11 @@ // You are entering a very scary territory of magic numbers and arbitrary math operations. // I don't remember why I did all of this but it works I guess :3 -use rodio::{OutputStream, Sink, Source}; +use crate::io::Audio; use std::sync::{Arc, Mutex}; -use std::time::Duration; -const SAMPLE_RATE: u32 = 65536; +pub const SAMPLE_RATE: u32 = 65536; const SAMPLE_AVERAGING: usize = 5; //20; @@ -381,24 +380,6 @@ impl Iterator for MutableWave { } } -impl Source for MutableWave { - fn current_frame_len(&self) -> Option { - None - } - - fn channels(&self) -> u16 { - 1 - } - - fn sample_rate(&self) -> u32 { - SAMPLE_RATE - } - - fn total_duration(&self) -> Option { - None - } -} - pub struct AudioSquareChannel { wave: Arc>>, @@ -453,6 +434,10 @@ impl AudioSquareChannel { } } } + + pub fn get_wave_mutex(&self) -> Arc>> { + return self.wave.clone(); + } } pub struct AudioCustomChannel { @@ -499,6 +484,10 @@ impl AudioCustomChannel { } } } + + pub fn get_wave_mutex(&self) -> Arc>> { + return self.wave.clone(); + } } pub struct AudioNoiseChannel { @@ -549,11 +538,14 @@ impl AudioNoiseChannel { } } } + + pub fn get_wave_mutex(&self) -> Arc>> { + return self.wave.clone(); + } } -pub struct Audio { - _stream: OutputStream, - _sink: Sink, +pub struct Channels { + _audio: A, pub ch1: AudioSquareChannel, pub ch2: AudioSquareChannel, @@ -561,18 +553,15 @@ pub struct Audio { pub ch4: AudioNoiseChannel, } -impl Audio { +impl Channels { pub fn new() -> Self { - let (stream, stream_handle) = OutputStream::try_default().unwrap(); - - let sink = Sink::try_new(&stream_handle).unwrap(); let wave_ch1 = Arc::new(Mutex::new(None)); let wave_ch2 = Arc::new(Mutex::new(None)); let wave_ch3 = Arc::new(Mutex::new(None)); let wave_ch4 = Arc::new(Mutex::new(None)); - sink.append(MutableWave::new( + let audio = A::new(MutableWave::new( wave_ch1.clone(), wave_ch2.clone(), wave_ch3.clone(), @@ -580,9 +569,7 @@ impl Audio { )); Self { - _stream: stream, - _sink: sink, - + _audio: audio, ch1: AudioSquareChannel::new(wave_ch1), ch2: AudioSquareChannel::new(wave_ch2), ch3: AudioCustomChannel::new(wave_ch3), diff --git a/src/desktop/audio.rs b/src/desktop/audio.rs new file mode 100644 index 0000000..60d32df --- /dev/null +++ b/src/desktop/audio.rs @@ -0,0 +1,54 @@ +use rodio::{OutputStream, Sink, Source}; + +use crate::io::Audio; +use crate::audio::SAMPLE_RATE; +use std::time::Duration; + +pub struct RodioAudio { + _stream: OutputStream, + _sink: Sink, +} + +struct RodioWave(W); + +impl Iterator for RodioWave where ::Item: rodio::Sample { + type Item = W::Item; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl Source for RodioWave where ::Item: rodio::Sample { + fn current_frame_len(&self) -> Option { + None + } + + fn channels(&self) -> u16 { + 1 + } + + fn sample_rate(&self) -> u32 { + SAMPLE_RATE + } + + fn total_duration(&self) -> Option { + None + } +} + + +impl Audio for RodioAudio { + fn new + Send + 'static>(wave: S) -> Self { + let (stream, stream_handle) = OutputStream::try_default().unwrap(); + + let sink = Sink::try_new(&stream_handle).unwrap(); + sink.append(RodioWave(wave)); + + + RodioAudio { + _stream: stream, + _sink: sink, + } + } +} diff --git a/src/desktop/input.rs b/src/desktop/input.rs new file mode 100644 index 0000000..1a2a212 --- /dev/null +++ b/src/desktop/input.rs @@ -0,0 +1,287 @@ +use std::fs::File; +use std::io::{ErrorKind, Read, Write}; + +use gilrs::{Button, GamepadId, Gilrs}; +use crate::desktop::window::Keys; +use crate::io::Input; +use winit::keyboard::KeyCode; + +pub struct Gamepad { + gilrs: Gilrs, + gamepad_id: Option, +} + +impl Gamepad { + pub fn new() -> Self { + let gilrs = Gilrs::new().unwrap(); + + let gamepad_id = if let Some((gamepad_id, _gamepad)) = gilrs.gamepads().next() { + println!("Found Gamepad id: {:?}", gamepad_id); + Some(gamepad_id) + } else { + println!("No gamepad found"); + None + }; + + Self { gilrs, gamepad_id } + } + + pub fn check_special_actions(&self, is_debug: &mut bool) { + if let Some(gamepad_id) = self.gamepad_id { + if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { + *is_debug = gamepad.is_pressed(Button::West); + } + } + } +} + +impl Input for Gamepad { + fn update_events(&mut self, _cycles: u128) -> Option { + while let Some(_) = self.gilrs.next_event() {} + None + } + + fn get_action_gamepad_reg(&self) -> u8 { + let mut res = 0xf; + + if let Some(gamepad_id) = self.gamepad_id { + if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { + if gamepad.is_pressed(Button::East) { + res &= 0b1110; + } + + if gamepad.is_pressed(Button::South) { + res &= 0b1101; + } + + if gamepad.is_pressed(Button::Select) { + res &= 0b1011; + } + + if gamepad.is_pressed(Button::Start) { + res &= 0b0111; + } + } + } + + res + } + + fn get_direction_gamepad_reg(&self) -> u8 { + let mut res = 0xf; + + if let Some(gamepad_id) = self.gamepad_id { + if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { + if gamepad.is_pressed(Button::DPadRight) { + res &= 0b1110; + } + + if gamepad.is_pressed(Button::DPadLeft) { + res &= 0b1101; + } + + if gamepad.is_pressed(Button::DPadUp) { + res &= 0b1011; + } + + if gamepad.is_pressed(Button::DPadDown) { + res &= 0b0111; + } + } + } + + res + } +} + +pub struct Keyboard { + keys: Keys, + action_reg: u8, + direction_reg: u8, +} + +impl Keyboard { + pub fn new(keys: Keys) -> Self { + Self { + keys, + action_reg: 0, + direction_reg: 0, + } + } +} + +impl Input for Keyboard { + fn update_events(&mut self, _cycles: u128) -> Option { + let mut res = 0xf; + let keys = self.keys.borrow(); + + if keys.contains(&KeyCode::KeyA) { + res &= 0b1110; + } + + if keys.contains(&KeyCode::KeyB) { + res &= 0b1101; + } + + if keys.contains(&KeyCode::Backspace) { + res &= 0b1011; + } + + if keys.contains(&KeyCode::Enter) { + res &= 0b0111; + } + + self.action_reg = res; + + let mut res = 0xf; + + if keys.contains(&KeyCode::ArrowRight) { + res &= 0b1110; + } + + if keys.contains(&KeyCode::ArrowLeft) { + res &= 0b1101; + } + + if keys.contains(&KeyCode::ArrowUp) { + res &= 0b1011; + } + + if keys.contains(&KeyCode::ArrowDown) { + res &= 0b0111; + } + + self.direction_reg = res; + + None + } + + fn get_action_gamepad_reg(&self) -> u8 { + self.action_reg + } + + fn get_direction_gamepad_reg(&self) -> u8 { + self.direction_reg + } +} + +pub struct GamepadRecorder { + input: Box, + record_file: File, + action_reg: u8, + direction_reg: u8, +} + +impl GamepadRecorder { + pub fn new(input: Box, record_file: String) -> Self { + Self { + input, + record_file: File::create(record_file).expect("Couldn't create gamepad record file"), + action_reg: 0xff, + direction_reg: 0xff, + } + } +} + +impl Input for GamepadRecorder { + fn update_events(&mut self, cycles: u128) -> Option { + self.input.update_events(cycles); + + let new_action_reg = self.input.get_action_gamepad_reg(); + let new_direction_reg = self.input.get_direction_gamepad_reg(); + + if self.action_reg != new_action_reg || self.direction_reg != new_direction_reg { + println!( + "input update on cycle {} ! 0x{:02x} 0x{:02x}", + cycles, new_action_reg, new_direction_reg + ); + if let Err(err) = self.record_file.write_all(&cycles.to_le_bytes()) { + eprintln!("Failed to write to record file: {}", err); + }; + if let Err(err) = self + .record_file + .write_all(&[new_action_reg, new_direction_reg]) + { + eprintln!("Failed to write to record file: {}", err); + } + if let Err(err) = self.record_file.flush() { + eprintln!("Failed to flush record file writes: {}", err); + } + } + + self.action_reg = new_action_reg; + self.direction_reg = new_direction_reg; + None + } + + fn get_action_gamepad_reg(&self) -> u8 { + self.action_reg + } + + fn get_direction_gamepad_reg(&self) -> u8 { + self.direction_reg + } +} + +pub struct GamepadReplay { + record_file: File, + action_reg: u8, + direction_reg: u8, + next_cycle_update: Option, +} + +impl GamepadReplay { + pub fn new(record_file: String) -> Self { + let mut file = File::open(record_file).expect("Couldn't open gamepad record file"); + + let mut cycles_le: [u8; 16] = [0; 16]; + + let next_cycle_update = match file.read_exact(&mut cycles_le) { + Err(err) if err.kind() == ErrorKind::UnexpectedEof => None, + Err(err) => panic!("{}", err), + Ok(_) => Some(u128::from_le_bytes(cycles_le)), + }; + + Self { + record_file: file, + action_reg: 0xff, + direction_reg: 0xff, + next_cycle_update, + } + } +} + +impl Input for GamepadReplay { + fn update_events(&mut self, cycles: u128) -> Option { + if let Some(next_cycle_update) = self.next_cycle_update { + if cycles >= next_cycle_update { + let mut inputs: [u8; 2] = [0; 2]; + + self.record_file + .read_exact(&mut inputs) + .expect("Unexpected EOF after cycle but before input"); + + self.action_reg = inputs[0]; + self.direction_reg = inputs[1]; + + let mut cycles_le: [u8; 16] = [0; 16]; + + self.next_cycle_update = match self.record_file.read_exact(&mut cycles_le) { + Err(err) if err.kind() == ErrorKind::UnexpectedEof => None, + Err(err) => panic!("{}", err), + Ok(_) => Some(u128::from_le_bytes(cycles_le)), + }; + } + } + + return self.next_cycle_update; + } + + fn get_action_gamepad_reg(&self) -> u8 { + self.action_reg + } + + fn get_direction_gamepad_reg(&self) -> u8 { + self.direction_reg + } +} diff --git a/src/desktop/load_save.rs b/src/desktop/load_save.rs new file mode 100644 index 0000000..dfe2083 --- /dev/null +++ b/src/desktop/load_save.rs @@ -0,0 +1,69 @@ +use std::fs::File; +use std::io::{Write, Read}; +use crate::io::LoadSave; + +pub struct FSLoadSave { + rom_file: String, + save_file: String, +} + +impl FSLoadSave { + pub fn new(rom_file: impl Into, save_file: impl Into) -> Self { + Self { + rom_file: rom_file.into(), + save_file: save_file.into(), + } + } +} + +impl LoadSave for FSLoadSave { + type Error = std::io::Error; + + fn load_rom(&self, rom: &mut [u8]) -> Result<(), std::io::Error> { + let mut f = File::open(&self.rom_file)?; + + f.read(rom)?; + + return Ok(()); + } + + fn load_bootrom(&self, boot_rom: &mut [u8]) -> Result<(), std::io::Error> { + println!("MBC: {:02x}", boot_rom[0x147]); + println!("CGB: {:02x}", boot_rom[0x143]); + + if boot_rom[0x143] == 0x80 || boot_rom[0x143] == 0xc0 { + unimplemented!("CGB Boot rom is not implemented"); + // let bytes = include_bytes!("../assets/cgb_boot.bin"); + + // self.boot_rom[..0x900].copy_from_slice(bytes); + // self.cgb_mode = true; + // self.display.cgb_mode = true; + } else { + let bytes = include_bytes!("../../assets/dmg_boot.bin"); + + boot_rom[..0x100].copy_from_slice(bytes); + } + + Ok(()) + } + + fn load_external_ram(&self, external_ram: &mut [u8]) -> Result<(), std::io::Error> { + let mut f = File::open(&self.save_file)?; + + f.read(external_ram)?; + + println!("Save file loaded from \"{}\"!", self.save_file); + + Ok(()) + } + + fn save_external_ram(&self, external_ram: &[u8]) -> Result<(), std::io::Error> { + let mut f = File::create(&self.save_file)?; + + f.write_all(&external_ram)?; + + println!("Save written to \"{}\"!", self.save_file); + + Ok(()) + } +} diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs new file mode 100644 index 0000000..af773b6 --- /dev/null +++ b/src/desktop/mod.rs @@ -0,0 +1,5 @@ +pub mod window; +pub mod input; +pub mod audio; +pub mod load_save; +pub mod serial; diff --git a/src/desktop/serial.rs b/src/desktop/serial.rs new file mode 100644 index 0000000..342f7f5 --- /dev/null +++ b/src/desktop/serial.rs @@ -0,0 +1,130 @@ +use std::fs::File; +use std::io::{Read, Write}; +use std::sync::mpsc::{self, Receiver, Sender}; +use std::thread; + +use crate::io::Serial; + +pub struct UnconnectedSerial {} + +impl Serial for UnconnectedSerial { + fn write(&mut self, byte: u8) { + println!("Writing {} to unconnected serial", byte); + } + + fn read(&mut self) -> u8 { + println!("Reading 0 from unconnected serial"); + 0 + } + + fn new_transfer(&mut self) -> bool { + false + } + + fn clock_master(&mut self) -> bool { + false + } + + fn set_clock_master(&mut self, _clock_master: bool) {} +} + +pub struct FIFOPackage { + t: bool, + value: u8, +} + +pub struct FIFOSerial { + input: Receiver, + output: Sender, + clock_change: Receiver, + last_read_byte: u8, + clock_master: bool, +} + +impl FIFOSerial { + pub fn new(input_path: String, output_path: String) -> FIFOSerial { + let (tx, input) = mpsc::channel::(); + let (clock_tx, clock_change) = mpsc::channel::(); + thread::spawn(move || { + let mut input_f = File::open(input_path).unwrap(); + loop { + let mut byte = [0, 0]; + + input_f.read(&mut byte).unwrap(); + if byte[0] == 1 { + tx.send(byte[1]).unwrap(); + } else { + clock_tx.send(byte[1] == 0).unwrap(); + } + } + }); + + let (output, rx) = mpsc::channel::(); + thread::spawn(move || { + let mut output_f = File::create(output_path).unwrap(); + for b in rx.iter() { + if b.t { + output_f.write(&[1, b.value]).unwrap(); + } else { + output_f.write(&[0, b.value]).unwrap(); + } + } + }); + + FIFOSerial { + input, + output, + clock_change, + last_read_byte: 0xff, + clock_master: false, + } + } +} + +impl Serial for FIFOSerial { + fn write(&mut self, byte: u8) { + println!("Writing {} to fifo serial", byte); + if let Err(err) = self.output.send(FIFOPackage { + t: true, + value: byte, + }) { + eprintln!("Error while sending serial package: {}", err); + }; + } + + fn read(&mut self) -> u8 { + println!("Reading {} from fifo serial", self.last_read_byte); + self.last_read_byte + } + + fn new_transfer(&mut self) -> bool { + match self.input.try_recv() { + Ok(byte) => { + println!("Received: {}", byte); + self.last_read_byte = byte; + true + } + _ => false, + } + } + fn clock_master(&mut self) -> bool { + match self.clock_change.try_recv() { + Ok(byte) => { + println!("Received clock change, master: {}", byte); + self.clock_master = byte; + } + _ => {} + }; + self.clock_master + } + + fn set_clock_master(&mut self, clock_master: bool) { + self.clock_master = clock_master; + if let Err(err) = self.output.send(FIFOPackage { + t: false, + value: (if clock_master { 1 } else { 0 }), + }) { + eprintln!("Error while sending serial package: {}", err); + } + } +} diff --git a/src/desktop/window.rs b/src/desktop/window.rs new file mode 100644 index 0000000..14ac3a7 --- /dev/null +++ b/src/desktop/window.rs @@ -0,0 +1,125 @@ +use std::cell::RefCell; +use std::collections::HashSet; +use std::rc::Rc; +use std::sync::Arc; +use std::time::Duration; + +use crate::io::{WindowSignal, Window}; + +use pixels::{Error, Pixels, SurfaceTexture}; +use winit::dpi::LogicalSize; +use winit::event::{Event, WindowEvent}; +use winit::event_loop::EventLoop; +use winit::keyboard::{KeyCode, PhysicalKey}; +use winit::platform::pump_events::EventLoopExtPumpEvents; +use winit::window::{Window as WinitWindow, WindowBuilder}; +use winit_input_helper::WinitInputHelper; + +const WIDTH: u32 = 160; +const HEIGHT: u32 = 144; + +pub type Keys = Rc>>; + +pub struct DesktopWindow<'a> { + event_loop: EventLoop<()>, + input: WinitInputHelper, + window: Arc, + pixels: Pixels<'a>, + pub keys: Keys, +} + +fn draw(frame: &mut [u8], fb: &[u32; 160 * 144]) { + for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { + pixel.copy_from_slice(&((fb[i] << 8) | 0xff).to_be_bytes()) + } +} + +impl<'a> DesktopWindow<'a> { + pub fn new() -> Result { + let event_loop = EventLoop::new().unwrap(); + let input = WinitInputHelper::new(); + let window = Arc::new({ + let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64); + WindowBuilder::new() + .with_title("Gameboy Emulator") + .with_inner_size(size) + .with_min_inner_size(size) + .build(&event_loop) + .unwrap() + }); + + let pixels = { + let window_size = window.inner_size(); + let surface_texture = + SurfaceTexture::new(window_size.width, window_size.height, window.clone()); + Pixels::new(WIDTH, HEIGHT, surface_texture)? + }; + + Ok(Self { + event_loop, + input, + window, + pixels, + keys: Rc::new(HashSet::new().into()), + }) + } +} + +impl<'a> Window for DesktopWindow<'a> { + fn update(&mut self, fb: &[u32; 160 * 144]) -> Option { + let mut res = None; + let mut keys = (*self.keys).borrow_mut(); + self.event_loop + .pump_events(Some(Duration::ZERO), |event, elwt| { + if let Event::WindowEvent { + event: WindowEvent::RedrawRequested, + .. + } = event + { + draw(self.pixels.frame_mut(), fb); + if let Err(err) = self.pixels.render() { + eprintln!("Error during render: {}", err); + return; + } + } + + if let Event::WindowEvent { + window_id: _, + event: + WindowEvent::KeyboardInput { + device_id: _, + event: ref keyboard_event, + is_synthetic: _, + }, + } = event + { + if let PhysicalKey::Code(keycode) = keyboard_event.physical_key { + if keyboard_event.state.is_pressed() { + keys.insert(keycode); + } else { + keys.remove(&keycode); + } + } + } + + if self.input.update(&event) { + if self.input.close_requested() { + elwt.exit(); + res = Some(WindowSignal::Exit); + return; + } + + if let Some(size) = self.input.window_resized() { + if let Err(err) = self.pixels.resize_surface(size.width, size.height) { + eprintln!("Error during resize: {}", err); + return; + } + } + + self.window.request_redraw(); + } + }); + + res + } +} diff --git a/src/gamepad.rs b/src/gamepad.rs deleted file mode 100644 index 87c445c..0000000 --- a/src/gamepad.rs +++ /dev/null @@ -1,291 +0,0 @@ -use crate::window::Keys; -use gilrs::{Button, GamepadId, Gilrs}; -use std::fs::File; -use std::io::{ErrorKind, Read, Write}; -use winit::keyboard::KeyCode; - -pub struct Gamepad { - gilrs: Gilrs, - gamepad_id: Option, -} - -pub trait Input { - fn update_events(&mut self, cycles: u128) -> Option; - fn get_action_gamepad_reg(&self) -> u8; - fn get_direction_gamepad_reg(&self) -> u8; -} - -impl Gamepad { - pub fn new() -> Self { - let gilrs = Gilrs::new().unwrap(); - - let gamepad_id = if let Some((gamepad_id, _gamepad)) = gilrs.gamepads().next() { - println!("Found Gamepad id: {:?}", gamepad_id); - Some(gamepad_id) - } else { - println!("No gamepad found"); - None - }; - - Self { gilrs, gamepad_id } - } - - pub fn check_special_actions(&self, is_debug: &mut bool) { - if let Some(gamepad_id) = self.gamepad_id { - if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { - *is_debug = gamepad.is_pressed(Button::West); - } - } - } -} - -impl Input for Gamepad { - fn update_events(&mut self, _cycles: u128) -> Option { - while let Some(_) = self.gilrs.next_event() {} - None - } - - fn get_action_gamepad_reg(&self) -> u8 { - let mut res = 0xf; - - if let Some(gamepad_id) = self.gamepad_id { - if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { - if gamepad.is_pressed(Button::East) { - res &= 0b1110; - } - - if gamepad.is_pressed(Button::South) { - res &= 0b1101; - } - - if gamepad.is_pressed(Button::Select) { - res &= 0b1011; - } - - if gamepad.is_pressed(Button::Start) { - res &= 0b0111; - } - } - } - - res - } - - fn get_direction_gamepad_reg(&self) -> u8 { - let mut res = 0xf; - - if let Some(gamepad_id) = self.gamepad_id { - if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { - if gamepad.is_pressed(Button::DPadRight) { - res &= 0b1110; - } - - if gamepad.is_pressed(Button::DPadLeft) { - res &= 0b1101; - } - - if gamepad.is_pressed(Button::DPadUp) { - res &= 0b1011; - } - - if gamepad.is_pressed(Button::DPadDown) { - res &= 0b0111; - } - } - } - - res - } -} - -pub struct Keyboard { - keys: Keys, - action_reg: u8, - direction_reg: u8, -} - -impl Keyboard { - pub fn new(keys: Keys) -> Self { - Self { - keys, - action_reg: 0, - direction_reg: 0, - } - } -} - -impl Input for Keyboard { - fn update_events(&mut self, _cycles: u128) -> Option { - let mut res = 0xf; - let keys = self.keys.borrow(); - - if keys.contains(&KeyCode::KeyA) { - res &= 0b1110; - } - - if keys.contains(&KeyCode::KeyB) { - res &= 0b1101; - } - - if keys.contains(&KeyCode::Backspace) { - res &= 0b1011; - } - - if keys.contains(&KeyCode::Enter) { - res &= 0b0111; - } - - self.action_reg = res; - - let mut res = 0xf; - - if keys.contains(&KeyCode::ArrowRight) { - res &= 0b1110; - } - - if keys.contains(&KeyCode::ArrowLeft) { - res &= 0b1101; - } - - if keys.contains(&KeyCode::ArrowUp) { - res &= 0b1011; - } - - if keys.contains(&KeyCode::ArrowDown) { - res &= 0b0111; - } - - self.direction_reg = res; - - None - } - - fn get_action_gamepad_reg(&self) -> u8 { - self.action_reg - } - - fn get_direction_gamepad_reg(&self) -> u8 { - self.direction_reg - } -} - -pub struct GamepadRecorder { - input: Box, - record_file: File, - action_reg: u8, - direction_reg: u8, -} - -impl GamepadRecorder { - pub fn new(input: Box, record_file: String) -> Self { - Self { - input, - record_file: File::create(record_file).expect("Couldn't create gamepad record file"), - action_reg: 0xff, - direction_reg: 0xff, - } - } -} - -impl Input for GamepadRecorder { - fn update_events(&mut self, cycles: u128) -> Option { - self.input.update_events(cycles); - - let new_action_reg = self.input.get_action_gamepad_reg(); - let new_direction_reg = self.input.get_direction_gamepad_reg(); - - if self.action_reg != new_action_reg || self.direction_reg != new_direction_reg { - println!( - "input update on cycle {} ! 0x{:02x} 0x{:02x}", - cycles, new_action_reg, new_direction_reg - ); - if let Err(err) = self.record_file.write_all(&cycles.to_le_bytes()) { - eprintln!("Failed to write to record file: {}", err); - }; - if let Err(err) = self - .record_file - .write_all(&[new_action_reg, new_direction_reg]) - { - eprintln!("Failed to write to record file: {}", err); - } - if let Err(err) = self.record_file.flush() { - eprintln!("Failed to flush record file writes: {}", err); - } - } - - self.action_reg = new_action_reg; - self.direction_reg = new_direction_reg; - None - } - - fn get_action_gamepad_reg(&self) -> u8 { - self.action_reg - } - - fn get_direction_gamepad_reg(&self) -> u8 { - self.direction_reg - } -} - -pub struct GamepadReplay { - record_file: File, - action_reg: u8, - direction_reg: u8, - next_cycle_update: Option, -} - -impl GamepadReplay { - pub fn new(record_file: String) -> Self { - let mut file = File::open(record_file).expect("Couldn't open gamepad record file"); - - let mut cycles_le: [u8; 16] = [0; 16]; - - let next_cycle_update = match file.read_exact(&mut cycles_le) { - Err(err) if err.kind() == ErrorKind::UnexpectedEof => None, - Err(err) => panic!("{}", err), - Ok(_) => Some(u128::from_le_bytes(cycles_le)), - }; - - Self { - record_file: file, - action_reg: 0xff, - direction_reg: 0xff, - next_cycle_update, - } - } -} - -impl Input for GamepadReplay { - fn update_events(&mut self, cycles: u128) -> Option { - if let Some(next_cycle_update) = self.next_cycle_update { - if cycles >= next_cycle_update { - let mut inputs: [u8; 2] = [0; 2]; - - self.record_file - .read_exact(&mut inputs) - .expect("Unexpected EOF after cycle but before input"); - - self.action_reg = inputs[0]; - self.direction_reg = inputs[1]; - - let mut cycles_le: [u8; 16] = [0; 16]; - - self.next_cycle_update = match self.record_file.read_exact(&mut cycles_le) { - Err(err) if err.kind() == ErrorKind::UnexpectedEof => None, - Err(err) => panic!("{}", err), - Ok(_) => Some(u128::from_le_bytes(cycles_le)), - }; - } - } - - return self.next_cycle_update; - } - - fn get_action_gamepad_reg(&self) -> u8 { - self.action_reg - } - - fn get_direction_gamepad_reg(&self) -> u8 { - self.direction_reg - } -} diff --git a/src/interrupts_timers.rs b/src/interrupts_timers.rs index 95a0757..f4ac3c2 100644 --- a/src/interrupts_timers.rs +++ b/src/interrupts_timers.rs @@ -1,9 +1,10 @@ use crate::display::DisplayInterrupt; use crate::state::{GBState, MemError}; +use crate::io::{Serial, Audio}; const TIMA_TIMER_SPEEDS: [u64; 4] = [1024, 16, 64, 256]; -impl GBState { +impl GBState { pub fn check_interrupts(&mut self) -> Result<(), MemError> { if self.mem.ime { let interrupts = self.mem.io[0x0f] & self.mem.interrupts_register & 0b11111; diff --git a/src/io.rs b/src/io.rs index 935109b..3e7330e 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,267 +1,176 @@ -use crate::state::{MemError, Memory}; +use std::time::SystemTime; +use std::{thread, time}; -impl Memory { - pub fn r_io(&self, addr: u8) -> u8 { - if addr > 0x50 { - println!("Reading from 0xff{:02x} not implemented yet", addr); - } - match addr { - 0x00 => { - if self.joypad_is_action { - (self.joypad_reg >> 4) | 0b11010000 - } else { - (self.joypad_reg & 0xf) | 0b11100000 - } - } - 0x01 => self.serial_data, - 0x02 => self.serial_control, - 0x04 => self.div, - 0x0f => self.io[0x0f], - 0x40 => self.display.lcdc, - 0x42 => self.display.viewport_y, - 0x43 => self.display.viewport_x, - 0x41 => { - let mut ret = 0b00001000 << self.display.lcd_interrupt_mode; +use crate::state::GBState; +use crate::consts; - ret |= if self.display.ly >= 0x90 { - 1 - } else if self.display.stat < 80 { - 2 - } else if self.display.stat < 280 { - 3 - } else { - 0 - }; +pub trait Input { + fn update_events(&mut self, cycles: u128) -> Option; + fn get_action_gamepad_reg(&self) -> u8; + fn get_direction_gamepad_reg(&self) -> u8; +} - if self.display.ly == self.display.lyc + 1 { - ret |= 0b100; - } +impl Input for Box { + fn update_events(&mut self, cycles: u128) -> Option { + (**self).update_events(cycles) + } + fn get_action_gamepad_reg(&self) -> u8 { + (**self).get_action_gamepad_reg() + } + fn get_direction_gamepad_reg(&self) -> u8 { + (**self).get_direction_gamepad_reg() + } +} - ret - } - 0x44 => self.display.ly, - 0x45 => self.display.lyc, - 0x47 => self.display.bg_palette, - 0x48 => self.display.obj_palettes[0], - 0x49 => self.display.obj_palettes[1], - 0x4a => self.display.window_y, - 0x4b => self.display.window_x, - 0x50 => { - if self.boot_rom_on { - 0xfe - } else { - 0xff - } - } - _ => { - println!("Reading from 0xff{:02x} not implemented yet", addr); - self.io[addr as usize] - } +pub enum WindowSignal { + Exit, +} + +pub trait Window { + fn update(&mut self, fb: &[u32; 160 * 144]) -> Option; +} + +pub trait Serial { + // Should not be blocking + fn write(&mut self, byte: u8); + fn read(&mut self) -> u8; + + fn new_transfer(&mut self) -> bool; // since last read + fn clock_master(&mut self) -> bool; + + fn set_clock_master(&mut self, clock_master: bool); +} + +pub trait Audio { + fn new + Send + 'static>(wave: S) -> Self; +} + +pub trait LoadSave where Self::Error: std::fmt::Display, Self::Error: std::fmt::Debug { + type Error; + fn load_bootrom(&self, boot_rom: &mut [u8]) -> Result<(), Self::Error>; + fn load_rom(&self, rom: &mut [u8]) -> Result<(), Self::Error>; + fn load_external_ram(&self, external_ram: &mut [u8]) -> Result<(), Self::Error>; + fn save_external_ram(&self, external_ram: &[u8]) -> Result<(), Self::Error>; +} + +pub struct Gameboy< + I: Input, + W: Window, + S: Serial, + A: Audio, + LS: LoadSave, +> { + input: I, + window: W, + speed: f64, + state: GBState, + load_save: LS, +} + +impl Gameboy { + pub fn new(input: I, window: W, serial: S, load_save: LS, speed: f64) -> Self { + Self { + input, + window, + speed, + state: GBState::::new(serial), + load_save, } } - pub fn w_io(&mut self, addr: u8, value: u8) -> Result<(), MemError> { - match addr { - 0x00 => { - self.joypad_is_action = !value & 0b00100000 != 0; - } - 0x01 => { - self.serial_data = value; - } - 0x02 => { - if value & 0x01 != 0 { - self.serial.set_clock_master(true); - println!("Set as master"); - } else if value & 0x01 != 0 { - self.serial.set_clock_master(false); - println!("Set as slave"); - } - self.serial_control = value; - } - 0x04 => { - self.div = 0; - } - 0x05 => { - self.tima = value; - } - 0x06 => { - self.tma = value; - } - 0x07 => { - self.timer_enabled = value & 0b100 != 0; - self.timer_speed = value & 0b11; - } - 0x0f => { - self.io[0x0f] = value; - } - 0x10 => { - self.audio.ch1.period_sweep_pace = (0b1110000 & value) >> 4; - self.audio.ch1.period_sweep_direction = (0b1000 & value) >> 3; - self.audio.ch1.period_sweep_slope = 0b111 & value; - } - 0x11 => { - self.audio.ch1.duty = value >> 6; - self.audio.ch1.length_timer = value & 0b111111; - } - 0x12 => { - self.audio.ch1.initial_volume = value >> 4; - self.audio.ch1.env_direction = (value & 0xf) >> 3; - self.audio.ch1.sweep = value & 0b111; - } - 0x13 => { - self.audio.ch1.period_value &= 0xff00; - self.audio.ch1.period_value |= value as u16; - } - 0x14 => { - self.audio.ch1.period_value &= 0xff; - self.audio.ch1.period_value |= ((value & 0b111) as u16) << 8; - self.audio.ch1.length_timer_enabled = value & 0b01000000 != 0; - if value >> 7 == 1 { - self.audio.ch1.update(); - } - } - 0x16 => { - self.audio.ch2.duty = value >> 6; - self.audio.ch2.length_timer = value & 0b111111; - } - 0x17 => { - self.audio.ch2.initial_volume = value >> 4; - self.audio.ch2.env_direction = (value & 0xf) >> 3; - self.audio.ch2.sweep = value & 0b111; - } - 0x18 => { - self.audio.ch2.period_value &= 0xff00; - self.audio.ch2.period_value |= value as u16; - } - 0x19 => { - self.audio.ch2.period_value &= 0xff; - self.audio.ch2.period_value |= ((value & 0b111) as u16) << 8; - self.audio.ch2.length_timer_enabled = value & 0b01000000 != 0; - if value >> 7 == 1 { - self.audio.ch2.update(); - } - } - 0x1a => { - if value & 0b10000000 != 0 { - self.audio.ch3.on = true; - } else { - self.audio.ch3.on = false; - } - self.audio.ch3.update(); - } - 0x1b => { - self.audio.ch3.length_timer = value & 0b111111; - } - 0x1c => { - let s = (value >> 5) & 0b11; - if s == 0 { - self.audio.ch3.initial_volume = 0; - } else { - self.audio.ch3.initial_volume = 0xf >> (s - 1); - } - } - 0x1d => { - self.audio.ch3.period_value &= 0xff00; - self.audio.ch3.period_value |= value as u16; - } - 0x1e => { - self.audio.ch3.period_value &= 0xff; - self.audio.ch3.period_value |= ((value & 0b111) as u16) << 8; - self.audio.ch3.period_value /= 2; - self.audio.ch3.length_timer_enabled = value & 0b01000000 != 0; - if value >> 7 == 1 { - self.audio.ch3.update(); - } - } - 0x20 => { - self.audio.ch4.length_timer = value & 0b111111; - } - 0x21 => { - self.audio.ch4.initial_volume = value >> 4; - self.audio.ch4.env_direction = (value & 0xf) >> 3; - self.audio.ch4.sweep = value & 0b111; - } - 0x22 => { - self.audio.ch4.clock_shift = value >> 4; - self.audio.ch4.lsfr_width = (value & 0xf) >> 3; - self.audio.ch4.clock_divider = value & 0b111; - } - 0x23 => { - self.audio.ch4.length_timer_enabled = value & 0b01000000 != 0; - if value >> 7 == 1 { - self.audio.ch4.update(); - } - } - 0x40 => self.display.lcdc = value, - 0x41 => { - if value & 0b01000000 != 0 { - self.display.lcd_interrupt_mode = 3; - } else if value & 0b00100000 != 0 { - self.display.lcd_interrupt_mode = 2; - } else if value & 0b00010000 != 0 { - self.display.lcd_interrupt_mode = 1; - } else if value & 0b00001000 != 0 { - self.display.lcd_interrupt_mode = 0; + + pub fn start(self) { + let Self { + mut window, + mut input, + speed, + mut state, + load_save, + } = self; + + load_save.load_bootrom(&mut state.mem.boot_rom).unwrap(); + load_save.load_rom(&mut state.mem.rom).unwrap(); + + if let Err(err) = load_save.load_external_ram(&mut state.mem.external_ram) { + println!( + "Loading save failed ({}). Initializing new external ram.", + err + ); + } + let mut total_cycle_counter: u128 = 0; + let mut nanos_sleep: f64 = 0.0; + let mut halt_time = 0; + let mut was_previously_halted = false; + + let mut last_ram_bank_enabled = false; + let mut now = SystemTime::now(); + let mut next_precise_gamepad_update: Option = None; + + loop { + if was_previously_halted && !state.mem.halt { + println!("Halt cycles {}", halt_time); + halt_time = 0; + } + was_previously_halted = state.mem.halt; + let c = if !state.mem.halt { + state.exec_opcode().unwrap() + } else { + halt_time += 4; + 4 + }; + + state.cpu.dbg_cycle_counter += c; + total_cycle_counter += c as u128; + + state.div_timer(c); + state.tima_timer(c); + state.update_display_interrupts(c); + state.check_interrupts().unwrap(); + state.mem.update_serial(); + + nanos_sleep += c as f64 * (consts::CPU_CYCLE_LENGTH_NANOS as f64 / speed) as f64; + + if nanos_sleep >= 0.0 || next_precise_gamepad_update.map_or(false, |c| (c >= total_cycle_counter)) { + next_precise_gamepad_update = input.update_events(total_cycle_counter); + + let (action_button_reg, direction_button_reg) = ( + input.get_action_gamepad_reg(), + input.get_direction_gamepad_reg(), + ); + + if state.mem.joypad_is_action + && (action_button_reg & (state.mem.joypad_reg >> 4)) != (state.mem.joypad_reg >> 4) + || (!state.mem.joypad_is_action + && (direction_button_reg & state.mem.joypad_reg & 0b1111) + != (state.mem.joypad_reg & 0b1111)) + { + state.mem.io[0x0f] |= 0b10000; } + + state.mem.joypad_reg = direction_button_reg | (action_button_reg << 4); } - 0x45 => self.display.lyc = value, - 0x42 => self.display.viewport_y = value, - 0x43 => self.display.viewport_x = value, - 0x46 => { - if value < 0xe0 { - let addr = (value as u16) << 8; - for i in 0..0xa0 { - self.w(0xfe00 | i, self.r(addr | i)?)?; + + if nanos_sleep > 0.0 { + if let Some(fb) = state.mem.display.redraw_request { + if let Some(WindowSignal::Exit) = window.update(&fb) { + break; } } - } - 0x47 => self.display.bg_palette = value, - 0x48 => self.display.obj_palettes[0] = value, - 0x49 => self.display.obj_palettes[1] = value, - 0x4a => self.display.window_y = value, - 0x4b => self.display.window_x = value, - 0x4f => self.display.vram_bank = value & 1, - 0x50 => self.boot_rom_on = value & 1 == 0 && self.boot_rom_on, - 0x68 => { - self.bgcram_pointer = 0b111111 & value; - self.bgcram_pointer_autoincrement = value & 0b10000000 != 0; - } - 0x69 => { - self.display.cram[self.bgcram_pointer as usize] = value; - if self.bgcram_pointer_autoincrement { - self.bgcram_pointer += 1; - self.bgcram_pointer &= 0b111111; - } - } - 0x6a => { - self.obcram_pointer = 0b111111 & value; - self.obcram_pointer_autoincrement = value & 0b10000000 != 0; - } - 0x6b => { - self.display.cram[self.obcram_pointer as usize + 0x40] = value; - if self.obcram_pointer_autoincrement { - self.obcram_pointer += 1; - self.obcram_pointer &= 0b111111; - } - } - _ => { - if addr != 0x25 && addr != 0x24 && addr != 0x26 && addr < 0x30 && addr > 0x3f { - println!( - "Writing to 0xff{:02x} not implemented yet ({:02x})", - addr, value - ); + + thread::sleep(time::Duration::from_nanos(nanos_sleep as u64 / 10)); + + nanos_sleep = + nanos_sleep - SystemTime::now().duration_since(now).unwrap().as_nanos() as f64; + now = SystemTime::now(); + + if last_ram_bank_enabled && !state.mem.ram_bank_enabled { + if let Err(err) = load_save.save_external_ram(&state.mem.external_ram) { + println!("Failed to save external RAM ({})", err); + } } + last_ram_bank_enabled = state.mem.ram_bank_enabled; } } - self.io[addr as usize] = value; - - if addr >= 0x30 && addr <= 0x3f { - let i = (addr - 0x30) as usize; - self.audio.ch3.wave_pattern[i * 2] = value >> 4; - self.audio.ch3.wave_pattern[i * 2 + 1] = value & 0xf; - } - - Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 7798bc3..1057634 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,17 @@ pub mod audio; pub mod consts; pub mod display; -pub mod gamepad; -pub mod interrupts_timers; pub mod io; +pub mod interrupts_timers; +pub mod mmio; pub mod opcodes; -pub mod serial; pub mod state; -pub mod window; +pub mod desktop; -use crate::gamepad::{Gamepad, GamepadRecorder, GamepadReplay, Input, Keyboard}; -use crate::state::GBState; +use crate::io::Input; +use crate::desktop::input::{Gamepad, GamepadRecorder, GamepadReplay, Keyboard}; +use crate::desktop::load_save::FSLoadSave; use clap::Parser; -use std::time::SystemTime; -use std::{thread, time}; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -49,32 +47,11 @@ struct Cli { fn main() { let cli = Cli::parse(); - let mut total_cycle_counter: u128 = 0; - - println!("Initializing Gamepad..."); println!("Starting {:?}...", &cli.rom); - let mut state = match (cli.fifo_input, cli.fifo_output) { - (Some(fifo_input), Some(fifo_output)) => { - GBState::new(Box::new(serial::FIFOSerial::new(fifo_input, fifo_output))) - } - (None, None) => GBState::new(Box::new(serial::UnconnectedSerial {})), - _ => panic!("If using fifo serial, both input and output should be set"), - }; - - let save_file = format!("{}.sav", &cli.rom); - - state.mem.load_rom(&cli.rom).unwrap(); - - if let Err(_) = state.mem.load_external_ram(&save_file) { - println!( - "\"{}\" not found. Initializing new external ram.", - save_file - ); - } - - let mut window = window::Window::new().unwrap(); + let serial = desktop::serial::UnconnectedSerial{}; + let window = desktop::window::DesktopWindow::new().unwrap(); let mut gamepad: Box = if let Some(record_file) = cli.replay_input { Box::new(GamepadReplay::new(record_file)) @@ -88,90 +65,8 @@ fn main() { gamepad = Box::new(GamepadRecorder::new(gamepad, record_file)); }; - state.is_debug = cli.debug; - - let mut nanos_sleep: f64 = 0.0; - let mut halt_time = 0; - let mut was_previously_halted = false; - - let mut last_ram_bank_enabled = false; - let mut now = SystemTime::now(); - let mut next_precise_gamepad_update: Option = None; - - loop { - if was_previously_halted && !state.mem.halt { - println!("Halt cycles {}", halt_time); - halt_time = 0; - } - was_previously_halted = state.mem.halt; - let c = if !state.mem.halt { - state.exec_opcode().unwrap() - } else { - halt_time += 4; - 4 - }; - - state.cpu.dbg_cycle_counter += c; - total_cycle_counter += c as u128; - - state.div_timer(c); - state.tima_timer(c); - state.update_display_interrupts(c); - state.check_interrupts().unwrap(); - state.mem.update_serial(); - - nanos_sleep += c as f64 * (consts::CPU_CYCLE_LENGTH_NANOS as f64 / cli.speed as f64) as f64; - - if nanos_sleep >= 0.0 || next_precise_gamepad_update.map_or(false, |c| (c >= total_cycle_counter)) { - next_precise_gamepad_update = gamepad.update_events(total_cycle_counter); - - let (action_button_reg, direction_button_reg) = ( - gamepad.get_action_gamepad_reg(), - gamepad.get_direction_gamepad_reg(), - ); - - if state.mem.joypad_is_action - && (action_button_reg & (state.mem.joypad_reg >> 4)) != (state.mem.joypad_reg >> 4) - || (!state.mem.joypad_is_action - && (direction_button_reg & state.mem.joypad_reg & 0b1111) - != (state.mem.joypad_reg & 0b1111)) - { - state.mem.io[0x0f] |= 0b10000; - } - - state.mem.joypad_reg = direction_button_reg | (action_button_reg << 4); - } - - - if nanos_sleep > 0.0 { - if let Some(fb) = state.mem.display.redraw_request { - if let Some(window::WindowSignal::Exit) = window.update(&fb) { - break; - } - } - - if !cli.loop_lock_timing { - thread::sleep(time::Duration::from_nanos(nanos_sleep as u64 / 10)); - } else { - while SystemTime::now().duration_since(now).unwrap().as_nanos() - < nanos_sleep as u128 - { - for _ in 0..100_000_000 {} - } - } - - nanos_sleep = - nanos_sleep - SystemTime::now().duration_since(now).unwrap().as_nanos() as f64; - now = SystemTime::now(); - - if last_ram_bank_enabled && !state.mem.ram_bank_enabled { - println!("Saving to \"{}\"...", save_file); - - if let Err(_) = state.mem.save_external_ram(&save_file) { - println!("Failed to save external RAM"); - } - } - last_ram_bank_enabled = state.mem.ram_bank_enabled; - } - } + io::Gameboy::<_, _, _, desktop::audio::RodioAudio, _>::new( + gamepad, window, serial, + FSLoadSave::new(&cli.rom, format!("{}.sav", &cli.rom)), + cli.speed as f64).start(); } diff --git a/src/mmio.rs b/src/mmio.rs new file mode 100644 index 0000000..e669ff8 --- /dev/null +++ b/src/mmio.rs @@ -0,0 +1,268 @@ +use crate::state::{MemError, Memory}; +use crate::io::{Serial, Audio}; + +impl Memory { + pub fn r_io(&self, addr: u8) -> u8 { + if addr > 0x50 { + println!("Reading from 0xff{:02x} not implemented yet", addr); + } + match addr { + 0x00 => { + if self.joypad_is_action { + (self.joypad_reg >> 4) | 0b11010000 + } else { + (self.joypad_reg & 0xf) | 0b11100000 + } + } + 0x01 => self.serial_data, + 0x02 => self.serial_control, + 0x04 => self.div, + 0x0f => self.io[0x0f], + 0x40 => self.display.lcdc, + 0x42 => self.display.viewport_y, + 0x43 => self.display.viewport_x, + 0x41 => { + let mut ret = 0b00001000 << self.display.lcd_interrupt_mode; + + ret |= if self.display.ly >= 0x90 { + 1 + } else if self.display.stat < 80 { + 2 + } else if self.display.stat < 280 { + 3 + } else { + 0 + }; + + if self.display.ly == self.display.lyc + 1 { + ret |= 0b100; + } + + ret + } + 0x44 => self.display.ly, + 0x45 => self.display.lyc, + 0x47 => self.display.bg_palette, + 0x48 => self.display.obj_palettes[0], + 0x49 => self.display.obj_palettes[1], + 0x4a => self.display.window_y, + 0x4b => self.display.window_x, + 0x50 => { + if self.boot_rom_on { + 0xfe + } else { + 0xff + } + } + _ => { + println!("Reading from 0xff{:02x} not implemented yet", addr); + self.io[addr as usize] + } + } + } + + pub fn w_io(&mut self, addr: u8, value: u8) -> Result<(), MemError> { + match addr { + 0x00 => { + self.joypad_is_action = !value & 0b00100000 != 0; + } + 0x01 => { + self.serial_data = value; + } + 0x02 => { + if value & 0x01 != 0 { + self.serial.set_clock_master(true); + println!("Set as master"); + } else if value & 0x01 != 0 { + self.serial.set_clock_master(false); + println!("Set as slave"); + } + self.serial_control = value; + } + 0x04 => { + self.div = 0; + } + 0x05 => { + self.tima = value; + } + 0x06 => { + self.tma = value; + } + 0x07 => { + self.timer_enabled = value & 0b100 != 0; + self.timer_speed = value & 0b11; + } + 0x0f => { + self.io[0x0f] = value; + } + 0x10 => { + self.audio.ch1.period_sweep_pace = (0b1110000 & value) >> 4; + self.audio.ch1.period_sweep_direction = (0b1000 & value) >> 3; + self.audio.ch1.period_sweep_slope = 0b111 & value; + } + 0x11 => { + self.audio.ch1.duty = value >> 6; + self.audio.ch1.length_timer = value & 0b111111; + } + 0x12 => { + self.audio.ch1.initial_volume = value >> 4; + self.audio.ch1.env_direction = (value & 0xf) >> 3; + self.audio.ch1.sweep = value & 0b111; + } + 0x13 => { + self.audio.ch1.period_value &= 0xff00; + self.audio.ch1.period_value |= value as u16; + } + 0x14 => { + self.audio.ch1.period_value &= 0xff; + self.audio.ch1.period_value |= ((value & 0b111) as u16) << 8; + self.audio.ch1.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch1.update(); + } + } + 0x16 => { + self.audio.ch2.duty = value >> 6; + self.audio.ch2.length_timer = value & 0b111111; + } + 0x17 => { + self.audio.ch2.initial_volume = value >> 4; + self.audio.ch2.env_direction = (value & 0xf) >> 3; + self.audio.ch2.sweep = value & 0b111; + } + 0x18 => { + self.audio.ch2.period_value &= 0xff00; + self.audio.ch2.period_value |= value as u16; + } + 0x19 => { + self.audio.ch2.period_value &= 0xff; + self.audio.ch2.period_value |= ((value & 0b111) as u16) << 8; + self.audio.ch2.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch2.update(); + } + } + 0x1a => { + if value & 0b10000000 != 0 { + self.audio.ch3.on = true; + } else { + self.audio.ch3.on = false; + } + self.audio.ch3.update(); + } + 0x1b => { + self.audio.ch3.length_timer = value & 0b111111; + } + 0x1c => { + let s = (value >> 5) & 0b11; + if s == 0 { + self.audio.ch3.initial_volume = 0; + } else { + self.audio.ch3.initial_volume = 0xf >> (s - 1); + } + } + 0x1d => { + self.audio.ch3.period_value &= 0xff00; + self.audio.ch3.period_value |= value as u16; + } + 0x1e => { + self.audio.ch3.period_value &= 0xff; + self.audio.ch3.period_value |= ((value & 0b111) as u16) << 8; + self.audio.ch3.period_value /= 2; + self.audio.ch3.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch3.update(); + } + } + 0x20 => { + self.audio.ch4.length_timer = value & 0b111111; + } + 0x21 => { + self.audio.ch4.initial_volume = value >> 4; + self.audio.ch4.env_direction = (value & 0xf) >> 3; + self.audio.ch4.sweep = value & 0b111; + } + 0x22 => { + self.audio.ch4.clock_shift = value >> 4; + self.audio.ch4.lsfr_width = (value & 0xf) >> 3; + self.audio.ch4.clock_divider = value & 0b111; + } + 0x23 => { + self.audio.ch4.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch4.update(); + } + } + 0x40 => self.display.lcdc = value, + 0x41 => { + if value & 0b01000000 != 0 { + self.display.lcd_interrupt_mode = 3; + } else if value & 0b00100000 != 0 { + self.display.lcd_interrupt_mode = 2; + } else if value & 0b00010000 != 0 { + self.display.lcd_interrupt_mode = 1; + } else if value & 0b00001000 != 0 { + self.display.lcd_interrupt_mode = 0; + } + } + 0x45 => self.display.lyc = value, + 0x42 => self.display.viewport_y = value, + 0x43 => self.display.viewport_x = value, + 0x46 => { + if value < 0xe0 { + let addr = (value as u16) << 8; + + for i in 0..0xa0 { + self.w(0xfe00 | i, self.r(addr | i)?)?; + } + } + } + 0x47 => self.display.bg_palette = value, + 0x48 => self.display.obj_palettes[0] = value, + 0x49 => self.display.obj_palettes[1] = value, + 0x4a => self.display.window_y = value, + 0x4b => self.display.window_x = value, + 0x4f => self.display.vram_bank = value & 1, + 0x50 => self.boot_rom_on = value & 1 == 0 && self.boot_rom_on, + 0x68 => { + self.bgcram_pointer = 0b111111 & value; + self.bgcram_pointer_autoincrement = value & 0b10000000 != 0; + } + 0x69 => { + self.display.cram[self.bgcram_pointer as usize] = value; + if self.bgcram_pointer_autoincrement { + self.bgcram_pointer += 1; + self.bgcram_pointer &= 0b111111; + } + } + 0x6a => { + self.obcram_pointer = 0b111111 & value; + self.obcram_pointer_autoincrement = value & 0b10000000 != 0; + } + 0x6b => { + self.display.cram[self.obcram_pointer as usize + 0x40] = value; + if self.obcram_pointer_autoincrement { + self.obcram_pointer += 1; + self.obcram_pointer &= 0b111111; + } + } + _ => { + if addr != 0x25 && addr != 0x24 && addr != 0x26 && addr < 0x30 && addr > 0x3f { + println!( + "Writing to 0xff{:02x} not implemented yet ({:02x})", + addr, value + ); + } + } + } + self.io[addr as usize] = value; + + if addr >= 0x30 && addr <= 0x3f { + let i = (addr - 0x30) as usize; + self.audio.ch3.wave_pattern[i * 2] = value >> 4; + self.audio.ch3.wave_pattern[i * 2 + 1] = value & 0xf; + } + + Ok(()) + } +} diff --git a/src/opcodes.rs b/src/opcodes.rs index 6c41449..26e98c5 100644 --- a/src/opcodes.rs +++ b/src/opcodes.rs @@ -1,8 +1,9 @@ use crate::state::{flag, reg, GBState, MemError}; +use crate::io::{Serial, Audio}; // The opcodes functions are returning the number of cycles used. -impl GBState { +impl GBState { fn r_16b_from_pc(&mut self) -> Result { let p: u16 = self.mem.r(self.cpu.pc)? as u16 | ((self.mem.r(self.cpu.pc + 1)? as u16) << 8); self.cpu.pc += 2; diff --git a/src/serial.rs b/src/serial.rs deleted file mode 100644 index 65c8bfe..0000000 --- a/src/serial.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::fs::File; -use std::io::{Read, Write}; -use std::sync::mpsc::{self, Receiver, Sender}; -use std::thread; - -pub trait Serial { - // Should not be blocking - fn write(&mut self, byte: u8); - fn read(&mut self) -> u8; - - fn new_transfer(&mut self) -> bool; // since last read - fn clock_master(&mut self) -> bool; - - fn set_clock_master(&mut self, clock_master: bool); -} - -pub struct UnconnectedSerial {} - -impl Serial for UnconnectedSerial { - fn write(&mut self, byte: u8) { - println!("Writing {} to unconnected serial", byte); - } - - fn read(&mut self) -> u8 { - println!("Reading 0 from unconnected serial"); - 0 - } - - fn new_transfer(&mut self) -> bool { - false - } - - fn clock_master(&mut self) -> bool { - false - } - - fn set_clock_master(&mut self, _clock_master: bool) {} -} - -pub struct FIFOPackage { - t: bool, - value: u8, -} - -pub struct FIFOSerial { - input: Receiver, - output: Sender, - clock_change: Receiver, - last_read_byte: u8, - clock_master: bool, -} - -impl FIFOSerial { - pub fn new(input_path: String, output_path: String) -> FIFOSerial { - let (tx, input) = mpsc::channel::(); - let (clock_tx, clock_change) = mpsc::channel::(); - thread::spawn(move || { - let mut input_f = File::open(input_path).unwrap(); - loop { - let mut byte = [0, 0]; - - input_f.read(&mut byte).unwrap(); - if byte[0] == 1 { - tx.send(byte[1]).unwrap(); - } else { - clock_tx.send(byte[1] == 0).unwrap(); - } - } - }); - - let (output, rx) = mpsc::channel::(); - thread::spawn(move || { - let mut output_f = File::create(output_path).unwrap(); - for b in rx.iter() { - if b.t { - output_f.write(&[1, b.value]).unwrap(); - } else { - output_f.write(&[0, b.value]).unwrap(); - } - } - }); - - FIFOSerial { - input, - output, - clock_change, - last_read_byte: 0xff, - clock_master: false, - } - } -} - -impl Serial for FIFOSerial { - fn write(&mut self, byte: u8) { - println!("Writing {} to fifo serial", byte); - if let Err(err) = self.output.send(FIFOPackage { - t: true, - value: byte, - }) { - eprintln!("Error while sending serial package: {}", err); - }; - } - - fn read(&mut self) -> u8 { - println!("Reading {} from fifo serial", self.last_read_byte); - self.last_read_byte - } - - fn new_transfer(&mut self) -> bool { - match self.input.try_recv() { - Ok(byte) => { - println!("Received: {}", byte); - self.last_read_byte = byte; - true - } - _ => false, - } - } - fn clock_master(&mut self) -> bool { - match self.clock_change.try_recv() { - Ok(byte) => { - println!("Received clock change, master: {}", byte); - self.clock_master = byte; - } - _ => {} - }; - self.clock_master - } - - fn set_clock_master(&mut self, clock_master: bool) { - self.clock_master = clock_master; - if let Err(err) = self.output.send(FIFOPackage { - t: false, - value: (if clock_master { 1 } else { 0 }), - }) { - eprintln!("Error while sending serial package: {}", err); - } - } -} diff --git a/src/state.rs b/src/state.rs index 6379e2f..0a18af0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,9 +1,7 @@ -use crate::audio::Audio; use crate::consts::{PROGRAM_START_ADDRESS, STACK_START_ADDRESS}; use crate::display::Display; -use crate::serial::Serial; -use std::fs::File; -use std::io::{Read, Write}; +use crate::audio::Channels; +use crate::io::{Serial, Audio}; pub mod reg { pub const B: u8 = 0; @@ -105,8 +103,8 @@ impl CPU { } } -pub struct Memory { - boot_rom: [u8; 0x900], +pub struct Memory { + pub boot_rom: [u8; 0x900], pub cgb_mode: bool, @@ -127,7 +125,7 @@ pub struct Memory { pub ram_bank_enabled: bool, // 32 KiB ROM bank 00 - rom: [u8; 0x200000], + pub rom: [u8; 0x200000], // 4 KiB Work RAM 00 wram_00: [u8; 0x1000], @@ -136,7 +134,7 @@ pub struct Memory { wram_01: [u8; 0x1000], // External RAM - external_ram: [u8; 0x8000], + pub external_ram: [u8; 0x8000], // 8 KiB Video RAM pub display: Display, @@ -146,9 +144,9 @@ pub struct Memory { // High RAM hram: [u8; 0x7f], - pub audio: Audio, + pub audio: Channels, - pub serial: Box, + pub serial: S, pub ime: bool, @@ -187,8 +185,8 @@ mod serial_control_flags { pub const TRANSFER_ENABLE: u8 = 0b10000000; } -impl Memory { - pub fn new(serial: Box) -> Self { +impl Memory { + pub fn new(serial: S) -> Self { let mut display = Display::new(); display.cls(); @@ -211,7 +209,7 @@ impl Memory { display, io: [0; 0x80], hram: [0; 0x7f], - audio: Audio::new(), + audio: Channels::new(), ime: false, interrupts_register: 0, joypad_is_action: false, @@ -251,57 +249,6 @@ impl Memory { } } - pub fn load_dmg_boot_rom(&mut self) { - let bytes = include_bytes!("../assets/dmg_boot.bin"); - - self.boot_rom[..0x100].copy_from_slice(bytes); - } - - pub fn load_cgb_boot_rom(&mut self) { - let bytes = include_bytes!("../assets/cgb_boot.bin"); - - self.boot_rom[..0x900].copy_from_slice(bytes); - } - - pub fn load_rom(&mut self, file: &str) -> Result<(), std::io::Error> { - let mut f = File::open(file)?; - - f.read(&mut self.rom)?; - - println!("MBC: {:02x}", self.rom[0x147]); - println!("CGB: {:02x}", self.rom[0x143]); - - if self.rom[0x143] == 0x80 || self.rom[0x143] == 0xc0 { - self.load_cgb_boot_rom(); - self.cgb_mode = true; - self.display.cgb_mode = true; - } else { - self.load_dmg_boot_rom(); - } - - Ok(()) - } - - pub fn load_external_ram(&mut self, file: &str) -> Result<(), std::io::Error> { - let mut f = File::open(file)?; - - f.read(&mut self.external_ram)?; - - println!("Save file loaded from \"{}\"!", file); - - Ok(()) - } - - pub fn save_external_ram(&self, file: &str) -> Result<(), std::io::Error> { - let mut f = File::create(file)?; - - f.write_all(&self.external_ram)?; - - println!("Save written to \"{}\"!", file); - - Ok(()) - } - pub fn r(&self, addr: u16) -> Result { if (addr < 0x100 || (addr >= 0x200 && addr < 0x900)) && self.boot_rom_on { Ok(self.boot_rom[addr as usize]) @@ -379,17 +326,17 @@ impl Memory { } } -pub struct GBState { +pub struct GBState { pub cpu: CPU, - pub mem: Memory, + pub mem: Memory, pub is_debug: bool, pub div_cycles: u64, pub tima_cycles: u64, } -impl GBState { - pub fn new(serial: Box) -> Self { +impl GBState { + pub fn new(serial: S) -> Self { let mem = Memory::new(serial); Self { diff --git a/src/window.rs b/src/window.rs deleted file mode 100644 index d139f1e..0000000 --- a/src/window.rs +++ /dev/null @@ -1,124 +0,0 @@ -use pixels::{Error, Pixels, SurfaceTexture}; -use std::cell::RefCell; -use std::collections::HashSet; -use std::rc::Rc; -use std::sync::Arc; -use std::time::Duration; -use winit::dpi::LogicalSize; -use winit::event::{Event, WindowEvent}; -use winit::event_loop::EventLoop; -use winit::keyboard::{KeyCode, PhysicalKey}; -use winit::platform::pump_events::EventLoopExtPumpEvents; -use winit::window::{Window as WinitWindow, WindowBuilder}; -use winit_input_helper::WinitInputHelper; - -const WIDTH: u32 = 160; -const HEIGHT: u32 = 144; - -pub type Keys = Rc>>; - -pub struct Window<'a> { - event_loop: EventLoop<()>, - input: WinitInputHelper, - window: Arc, - pixels: Pixels<'a>, - pub keys: Keys, -} - -fn draw(frame: &mut [u8], fb: &[u32; 160 * 144]) { - for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { - pixel.copy_from_slice(&((fb[i] << 8) | 0xff).to_be_bytes()) - } -} - -pub enum WindowSignal { - Exit, -} - -impl<'a> Window<'a> { - pub fn new() -> Result { - let event_loop = EventLoop::new().unwrap(); - let input = WinitInputHelper::new(); - let window = Arc::new({ - let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64); - WindowBuilder::new() - .with_title("Gameboy Emulator") - .with_inner_size(size) - .with_min_inner_size(size) - .build(&event_loop) - .unwrap() - }); - - let pixels = { - let window_size = window.inner_size(); - let surface_texture = - SurfaceTexture::new(window_size.width, window_size.height, window.clone()); - Pixels::new(WIDTH, HEIGHT, surface_texture)? - }; - - Ok(Self { - event_loop, - input, - window, - pixels, - keys: Rc::new(HashSet::new().into()), - }) - } - - pub fn update(&mut self, fb: &[u32; 160 * 144]) -> Option { - let mut res = None; - let mut keys = (*self.keys).borrow_mut(); - self.event_loop - .pump_events(Some(Duration::ZERO), |event, elwt| { - if let Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - } = event - { - draw(self.pixels.frame_mut(), fb); - if let Err(err) = self.pixels.render() { - eprintln!("Error during render: {}", err); - return; - } - } - - if let Event::WindowEvent { - window_id: _, - event: - WindowEvent::KeyboardInput { - device_id: _, - event: ref keyboard_event, - is_synthetic: _, - }, - } = event - { - if let PhysicalKey::Code(keycode) = keyboard_event.physical_key { - if keyboard_event.state.is_pressed() { - keys.insert(keycode); - } else { - keys.remove(&keycode); - } - } - } - - if self.input.update(&event) { - if self.input.close_requested() { - elwt.exit(); - res = Some(WindowSignal::Exit); - return; - } - - if let Some(size) = self.input.window_resized() { - if let Err(err) = self.pixels.resize_surface(size.width, size.height) { - eprintln!("Error during resize: {}", err); - return; - } - } - - self.window.request_redraw(); - } - }); - - res - } -} -- cgit v1.2.3-70-g09d2