aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAstatin <[email protected]>2025-04-03 18:35:03 +0200
committerAstatin <[email protected]>2025-04-03 18:35:03 +0200
commit9a8e4117be8d30109229600346e7d9561c52a3e3 (patch)
tree6d2531b675e609d3d5d734732f1328769dddf557
parentc1fb610e198d785aa57440b86c69587e5caaf563 (diff)
Separate core from desktop target
-rw-r--r--assets/Astatin-bootrom.gbasm1
-rw-r--r--src/audio.rs51
-rw-r--r--src/desktop/audio.rs54
-rw-r--r--src/desktop/input.rs (renamed from src/gamepad.rs)12
-rw-r--r--src/desktop/load_save.rs69
-rw-r--r--src/desktop/mod.rs5
-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.rs3
-rw-r--r--src/io.rs409
-rw-r--r--src/main.rs129
-rw-r--r--src/mmio.rs268
-rw-r--r--src/opcodes.rs3
-rw-r--r--src/state.rs83
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;
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<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 {