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 /src/desktop | |
parent | c1fb610e198d785aa57440b86c69587e5caaf563 (diff) |
Separate core from desktop target
Diffstat (limited to 'src/desktop')
-rw-r--r-- | src/desktop/audio.rs | 54 | ||||
-rw-r--r-- | src/desktop/input.rs | 287 | ||||
-rw-r--r-- | src/desktop/load_save.rs | 69 | ||||
-rw-r--r-- | src/desktop/mod.rs | 5 | ||||
-rw-r--r-- | src/desktop/serial.rs | 130 | ||||
-rw-r--r-- | src/desktop/window.rs | 125 |
6 files changed, 670 insertions, 0 deletions
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/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<GamepadId>, +} + +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<u128> { + 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<u128> { + 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<dyn Input>, + record_file: File, + action_reg: u8, + direction_reg: u8, +} + +impl GamepadRecorder { + pub fn new(input: Box<dyn Input>, 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<u128> { + 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<u128>, +} + +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<u128> { + 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<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/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<u8>, + output: Sender<FIFOPackage>, + clock_change: Receiver<bool>, + last_read_byte: u8, + clock_master: bool, +} + +impl FIFOSerial { + pub fn new(input_path: String, output_path: String) -> FIFOSerial { + let (tx, input) = mpsc::channel::<u8>(); + let (clock_tx, clock_change) = mpsc::channel::<bool>(); + 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::<FIFOPackage>(); + 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<RefCell<HashSet<KeyCode>>>; + +pub struct DesktopWindow<'a> { + event_loop: EventLoop<()>, + input: WinitInputHelper, + window: Arc<WinitWindow>, + 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<Self, Error> { + 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<WindowSignal> { + 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 + } +} |