diff options
author | Astatin <[email protected]> | 2025-04-03 18:35:03 +0200 |
---|---|---|
committer | Astatin <[email protected]> | 2025-04-03 18:35:03 +0200 |
commit | 9a8e4117be8d30109229600346e7d9561c52a3e3 (patch) | |
tree | 6d2531b675e609d3d5d734732f1328769dddf557 | |
parent | c1fb610e198d785aa57440b86c69587e5caaf563 (diff) |
Separate core from desktop target
-rw-r--r-- | assets/Astatin-bootrom.gbasm | 1 | ||||
-rw-r--r-- | src/audio.rs | 51 | ||||
-rw-r--r-- | src/desktop/audio.rs | 54 | ||||
-rw-r--r-- | src/desktop/input.rs (renamed from src/gamepad.rs) | 12 | ||||
-rw-r--r-- | src/desktop/load_save.rs | 69 | ||||
-rw-r--r-- | src/desktop/mod.rs | 5 | ||||
-rw-r--r-- | src/desktop/serial.rs (renamed from src/serial.rs) | 11 | ||||
-rw-r--r-- | src/desktop/window.rs (renamed from src/window.rs) | 17 | ||||
-rw-r--r-- | src/interrupts_timers.rs | 3 | ||||
-rw-r--r-- | src/io.rs | 409 | ||||
-rw-r--r-- | src/main.rs | 129 | ||||
-rw-r--r-- | src/mmio.rs | 268 | ||||
-rw-r--r-- | src/opcodes.rs | 3 | ||||
-rw-r--r-- | src/state.rs | 83 |
14 files changed, 619 insertions, 496 deletions
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<usize> { - None - } - - fn channels(&self) -> u16 { - 1 - } - - fn sample_rate(&self) -> u32 { - SAMPLE_RATE - } - - fn total_duration(&self) -> Option<Duration> { - None - } -} - pub struct AudioSquareChannel { wave: Arc<Mutex<Option<Wave>>>, @@ -453,6 +434,10 @@ impl AudioSquareChannel { } } } + + pub fn get_wave_mutex(&self) -> Arc<Mutex<Option<Wave>>> { + return self.wave.clone(); + } } pub struct AudioCustomChannel { @@ -499,6 +484,10 @@ impl AudioCustomChannel { } } } + + pub fn get_wave_mutex(&self) -> Arc<Mutex<Option<Wave>>> { + return self.wave.clone(); + } } pub struct AudioNoiseChannel { @@ -549,11 +538,14 @@ impl AudioNoiseChannel { } } } + + pub fn get_wave_mutex(&self) -> Arc<Mutex<Option<NoiseWave>>> { + return self.wave.clone(); + } } -pub struct Audio { - _stream: OutputStream, - _sink: Sink, +pub struct Channels<A: Audio> { + _audio: A, pub ch1: AudioSquareChannel, pub ch2: AudioSquareChannel, @@ -561,18 +553,15 @@ pub struct Audio { pub ch4: AudioNoiseChannel, } -impl Audio { +impl<A: Audio> Channels<A> { 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: Iterator + Send + 'static>(W); + +impl<W: Iterator + Send + 'static> Iterator for RodioWave<W> where <W as Iterator>::Item: rodio::Sample { + type Item = W::Item; + + fn next(&mut self) -> Option<Self::Item> { + self.0.next() + } +} + +impl<W: Iterator + Send + 'static> Source for RodioWave<W> where <W as Iterator>::Item: rodio::Sample { + fn current_frame_len(&self) -> Option<usize> { + None + } + + fn channels(&self) -> u16 { + 1 + } + + fn sample_rate(&self) -> u32 { + SAMPLE_RATE + } + + fn total_duration(&self) -> Option<Duration> { + None + } +} + + +impl Audio for RodioAudio { + fn new<S: Iterator<Item = f32> + 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/gamepad.rs b/src/desktop/input.rs index 87c445c..1a2a212 100644 --- a/src/gamepad.rs +++ b/src/desktop/input.rs @@ -1,7 +1,9 @@ -use crate::window::Keys; -use gilrs::{Button, GamepadId, Gilrs}; 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 { @@ -9,12 +11,6 @@ pub struct Gamepad { gamepad_id: Option<GamepadId>, } -pub trait Input { - fn update_events(&mut self, cycles: u128) -> Option<u128>; - 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(); 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<String>, save_file: impl Into<String>) -> 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/serial.rs b/src/desktop/serial.rs index 65c8bfe..342f7f5 100644 --- a/src/serial.rs +++ b/src/desktop/serial.rs @@ -3,16 +3,7 @@ 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); -} +use crate::io::Serial; pub struct UnconnectedSerial {} diff --git a/src/window.rs b/src/desktop/window.rs index d139f1e..14ac3a7 100644 --- a/src/window.rs +++ b/src/desktop/window.rs @@ -1,9 +1,12 @@ -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 crate::io::{WindowSignal, Window}; + +use pixels::{Error, Pixels, SurfaceTexture}; use winit::dpi::LogicalSize; use winit::event::{Event, WindowEvent}; use winit::event_loop::EventLoop; @@ -17,7 +20,7 @@ const HEIGHT: u32 = 144; pub type Keys = Rc<RefCell<HashSet<KeyCode>>>; -pub struct Window<'a> { +pub struct DesktopWindow<'a> { event_loop: EventLoop<()>, input: WinitInputHelper, window: Arc<WinitWindow>, @@ -31,11 +34,7 @@ fn draw(frame: &mut [u8], fb: &[u32; 160 * 144]) { } } -pub enum WindowSignal { - Exit, -} - -impl<'a> Window<'a> { +impl<'a> DesktopWindow<'a> { pub fn new() -> Result<Self, Error> { let event_loop = EventLoop::new().unwrap(); let input = WinitInputHelper::new(); @@ -64,8 +63,10 @@ impl<'a> Window<'a> { keys: Rc::new(HashSet::new().into()), }) } +} - pub fn update(&mut self, fb: &[u32; 160 * 144]) -> Option<WindowSignal> { +impl<'a> Window for DesktopWindow<'a> { + fn update(&mut self, fb: &[u32; 160 * 144]) -> Option<WindowSignal> { let mut res = None; let mut keys = (*self.keys).borrow_mut(); self.event_loop 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<S: Serial, A: Audio> GBState<S, A> { pub fn check_interrupts(&mut self) -> Result<(), MemError> { if self.mem.ime { let interrupts = self.mem.io[0x0f] & self.mem.interrupts_register & 0b11111; @@ -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<u128>; + 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<T: Input + ?Sized> Input for Box<T> { + fn update_events(&mut self, cycles: u128) -> Option<u128> { + (**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<WindowSignal>; +} + +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<S: Iterator<Item = f32> + 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<S, A>, + load_save: LS, +} + +impl<I: Input, W: Window, S: Serial, A: Audio, LS: LoadSave> Gameboy<I, W, S, A, LS> { + pub fn new(input: I, window: W, serial: S, load_save: LS, speed: f64) -> Self { + Self { + input, + window, + speed, + state: GBState::<S, A>::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<u128> = 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<dyn Input> = 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<u128> = 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<S: Serial, A: Audio> Memory<S, A> { + 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<S: Serial, A: Audio> GBState<S, A> { fn r_16b_from_pc(&mut self) -> Result<u16, MemError> { 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/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<S: Serial, A: Audio> { + 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<A>, - pub serial: Box<dyn Serial>, + 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<dyn Serial>) -> Self { +impl<S: Serial, A: Audio> Memory<S, A> { + 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<u8, MemError> { 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<S: Serial, A: Audio> { pub cpu: CPU, - pub mem: Memory, + pub mem: Memory<S, A>, pub is_debug: bool, pub div_cycles: u64, pub tima_cycles: u64, } -impl GBState { - pub fn new(serial: Box<dyn Serial>) -> Self { +impl<S: Serial, A: Audio> GBState<S, A> { + pub fn new(serial: S) -> Self { let mem = Memory::new(serial); Self { |