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/io.rs | |
parent | c1fb610e198d785aa57440b86c69587e5caaf563 (diff) |
Separate core from desktop target
Diffstat (limited to 'src/io.rs')
-rw-r--r-- | src/io.rs | 409 |
1 files changed, 159 insertions, 250 deletions
@@ -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(()) } } |