From bce4456f4242465876fd9ae306a329332f8f74f2 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sat, 10 Oct 2020 11:06:11 -0700 Subject: [PATCH] [breaking-change] Remove game ROM and bios from savestate file. This breaks the API of GameBoyAdvanvce::save_state and restore_state methods. Currently as WIP only SDL2 frontend will adjust. Former-commit-id: 1df15c8697fef0f6adddb07a6d653947c622ba12 Former-commit-id: 2ea339dc6a0d1e7539d167c4df29694b408303da --- core/src/cartridge/mod.rs | 30 ++++++++++ core/src/gba.rs | 75 +++++++++++++++++------- core/src/lib.rs | 2 +- core/src/sched.rs | 33 ++--------- core/src/sysbus.rs | 66 ++++++++++++++++++--- core/src/timer.rs | 6 +- core/src/util.rs | 60 +++++++++++++++++++ platform/rustboyadvance-sdl2/src/main.rs | 6 +- 8 files changed, 211 insertions(+), 67 deletions(-) diff --git a/core/src/cartridge/mod.rs b/core/src/cartridge/mod.rs index 30ea04d..f5babb8 100644 --- a/core/src/cartridge/mod.rs +++ b/core/src/cartridge/mod.rs @@ -38,7 +38,9 @@ pub type SymbolTable = HashMap; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Cartridge { pub header: CartridgeHeader, + #[serde(skip)] bytes: Box<[u8]>, + #[serde(skip)] size: usize, gpio: Option, symbols: Option, // TODO move it somewhere else @@ -52,6 +54,34 @@ impl Cartridge { pub fn get_gpio(&self) -> &Option { &self.gpio } + + pub fn set_rom_bytes(&mut self, bytes: Box<[u8]>) { + self.size = bytes.len(); + self.bytes = bytes; + } + + pub fn get_rom_bytes(&self) -> &[u8] { + &self.bytes + } + + // 'clones' the cartridge without the ROM buffer + pub fn thin_copy(&self) -> Cartridge { + Cartridge { + header: self.header.clone(), + bytes: Default::default(), + size: 0, + gpio: self.gpio.clone(), + symbols: self.symbols.clone(), + backup: self.backup.clone(), + } + } + + pub fn update_from(&mut self, other: Cartridge) { + self.header = other.header; + self.gpio = other.gpio; + self.symbols = other.symbols; + self.backup = other.backup; + } } use super::sysbus::consts::*; diff --git a/core/src/gba.rs b/core/src/gba.rs index 955eb19..7e902e0 100644 --- a/core/src/gba.rs +++ b/core/src/gba.rs @@ -15,14 +15,16 @@ use super::sched::{EventHandler, EventType, Scheduler, SharedScheduler}; use super::sound::SoundController; use super::sysbus::SysBus; use super::timer::Timers; +use super::util::Shared; #[cfg(not(feature = "no_video_interface"))] use super::VideoInterface; use super::{AudioInterface, InputInterface}; pub struct GameBoyAdvance { - pub sysbus: Box, pub cpu: arm7tdmi::Core, + pub sysbus: Box, + io_devs: Shared, #[cfg(not(feature = "no_video_interface"))] pub video_device: Rc>, @@ -39,8 +41,11 @@ pub struct GameBoyAdvance { #[derive(Serialize, Deserialize)] struct SaveState { - sysbus: Box, scheduler: Scheduler, + io_devs: IoDevices, + cartridge: Cartridge, + ewram: Box<[u8]>, + iwram: Box<[u8]>, interrupt_flags: u16, cpu: arm7tdmi::Core, } @@ -83,14 +88,20 @@ impl GameBoyAdvance { scheduler.clone(), audio_device.borrow().get_sample_rate() as f32, )); - let io = IoDevices::new(intc, gpu, dmac, timers, sound_controller); - let sysbus = Box::new(SysBus::new(io, bios_rom, gamepak)); + let io_devs = Shared::new(IoDevices::new(intc, gpu, dmac, timers, sound_controller)); + let sysbus = Box::new(SysBus::new( + scheduler.clone(), + io_devs.clone(), + bios_rom, + gamepak, + )); let cpu = arm7tdmi::Core::new(); let mut gba = GameBoyAdvance { - cpu: cpu, - sysbus: sysbus, + cpu, + sysbus, + io_devs, #[cfg(not(feature = "no_video_interface"))] video_device: video_device, @@ -111,6 +122,8 @@ impl GameBoyAdvance { pub fn from_saved_state( savestate: &[u8], + bios: Box<[u8]>, + rom: Box<[u8]>, #[cfg(not(feature = "no_video_interface"))] video_device: Rc>, audio_device: Rc>, input_device: Rc>, @@ -118,17 +131,28 @@ impl GameBoyAdvance { let decoded: Box = bincode::deserialize_from(savestate)?; let arm7tdmi = decoded.cpu; - let mut sysbus = decoded.sysbus; let interrupts = Rc::new(Cell::new(IrqBitmask(decoded.interrupt_flags))); let scheduler = decoded.scheduler.make_shared(); + let mut io_devs = Shared::new(decoded.io_devs); + let mut cartridge = decoded.cartridge; + cartridge.set_rom_bytes(rom); + io_devs.connect_irq(interrupts.clone()); + io_devs.gpu.set_scheduler(scheduler.clone()); + io_devs.sound.set_scheduler(scheduler.clone()); - sysbus.io.gpu.set_scheduler(scheduler.clone()); - sysbus.io.sound.set_scheduler(scheduler.clone()); - sysbus.io.connect_irq(interrupts.clone()); + let sysbus = Box::new(SysBus::new_with_memories( + scheduler.clone(), + io_devs.clone(), + cartridge, + bios, + decoded.ewram, + decoded.iwram, + )); Ok(GameBoyAdvance { cpu: arm7tdmi, sysbus: sysbus, + io_devs, interrupt_flags: interrupts, @@ -141,37 +165,44 @@ impl GameBoyAdvance { overshoot_cycles: 0, - scheduler: scheduler, + scheduler, }) } pub fn save_state(&self) -> bincode::Result> { let s = SaveState { cpu: self.cpu.clone(), - sysbus: self.sysbus.clone(), + io_devs: self.io_devs.clone_inner(), + cartridge: self.sysbus.cartridge.thin_copy(), + iwram: Box::from(self.sysbus.get_iwram()), + ewram: Box::from(self.sysbus.get_ewram()), interrupt_flags: self.interrupt_flags.get().value(), - scheduler: (*self.scheduler).clone(), + scheduler: self.scheduler.clone_inner(), }; bincode::serialize(&s) } - pub fn restore_state(&mut self, bytes: &[u8]) -> bincode::Result<()> { + pub fn restore_state(&mut self, bytes: &[u8], bios: Box<[u8]>) -> bincode::Result<()> { let decoded: Box = bincode::deserialize_from(bytes)?; self.cpu = decoded.cpu; - self.sysbus = decoded.sysbus; self.scheduler = Scheduler::make_shared(decoded.scheduler); self.interrupt_flags = Rc::new(Cell::new(IrqBitmask(decoded.interrupt_flags))); - + self.io_devs = Shared::new(decoded.io_devs); + // Restore memory state + self.sysbus.set_bios(bios); + self.sysbus.set_iwram(decoded.iwram); + self.sysbus.set_ewram(decoded.ewram); // Redistribute shared pointers - self.sysbus.io.connect_irq(self.interrupt_flags.clone()); - self.sysbus.io.gpu.set_scheduler(self.scheduler.clone()); - self.sysbus.io.sound.set_scheduler(self.scheduler.clone()); - - self.cycles_to_next_event = 1; - + self.io_devs.connect_irq(self.interrupt_flags.clone()); + self.io_devs.gpu.set_scheduler(self.scheduler.clone()); + self.io_devs.sound.set_scheduler(self.scheduler.clone()); + self.sysbus.set_scheduler(self.scheduler.clone()); + self.sysbus.set_io_devices(self.io_devs.clone()); + self.sysbus.cartridge.update_from(decoded.cartridge); self.sysbus.created(); + self.cycles_to_next_event = 1; Ok(()) } diff --git a/core/src/lib.rs b/core/src/lib.rs index b290e1f..4702525 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -138,8 +138,8 @@ pub mod prelude { pub use super::gpu::{DISPLAY_HEIGHT, DISPLAY_WIDTH}; pub use super::util::{read_bin_file, write_bin_file}; pub use super::Bus; - pub use super::{AudioInterface, InputInterface, StereoSample}; #[cfg(not(feature = "no_video_interface"))] pub use super::VideoInterface; + pub use super::{AudioInterface, InputInterface, StereoSample}; pub use super::{GBAError, GBAResult, GameBoyAdvance}; } diff --git a/core/src/sched.rs b/core/src/sched.rs index ba69e37..5bc8d64 100644 --- a/core/src/sched.rs +++ b/core/src/sched.rs @@ -1,5 +1,4 @@ -use std::cell::UnsafeCell; -use std::rc::Rc; +use super::util::Shared; use serde::{Deserialize, Serialize}; @@ -55,31 +54,7 @@ pub struct Scheduler { events: Vec, } -// Opt-out of runtime borrow checking by using unsafe cell -// SAFETY: We need to make sure that the scheduler event queue is not modified while iterating it. -#[repr(transparent)] -#[derive(Debug)] -pub struct SharedScheduler(Rc>); - -impl std::ops::Deref for SharedScheduler { - type Target = Scheduler; - - fn deref(&self) -> &Self::Target { - unsafe { &(*self.0.get()) } - } -} - -impl std::ops::DerefMut for SharedScheduler { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut (*self.0.get()) } - } -} - -impl Clone for SharedScheduler { - fn clone(&self) -> SharedScheduler { - SharedScheduler(self.0.clone()) - } -} +pub type SharedScheduler = Shared; pub trait EventHandler { /// Handle the scheduler event @@ -92,11 +67,11 @@ impl Scheduler { timestamp: 0, events: Vec::with_capacity(NUM_EVENTS), }; - SharedScheduler(Rc::new(UnsafeCell::new(sched))) + SharedScheduler::new(sched) } pub fn make_shared(self) -> SharedScheduler { - SharedScheduler(Rc::new(UnsafeCell::new(self))) + SharedScheduler::new(self) } pub fn schedule(&mut self, typ: EventType, cycles: usize) { diff --git a/core/src/sysbus.rs b/core/src/sysbus.rs index 2b641a5..10cdf03 100644 --- a/core/src/sysbus.rs +++ b/core/src/sysbus.rs @@ -6,7 +6,8 @@ use super::bus::*; use super::cartridge::Cartridge; use super::dma::DmaNotifer; use super::iodev::{IoDevices, WaitControl}; -use super::util::{BoxedMemory, WeakPointer}; +use super::sched::Scheduler; +use super::util::{BoxedMemory, Shared, WeakPointer}; pub mod consts { pub const WORK_RAM_SIZE: usize = 256 * 1024; @@ -164,9 +165,10 @@ impl CycleLookupTables { } } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Clone)] pub struct SysBus { - pub io: IoDevices, + pub io: Shared, + scheduler: Shared, bios: BoxedMemory, onboard_work_ram: BoxedMemory, @@ -181,24 +183,70 @@ pub struct SysBus { pub type SysBusPtr = WeakPointer; impl SysBus { - pub fn new(io: IoDevices, bios_rom: Box<[u8]>, cartridge: Cartridge) -> SysBus { + pub fn new_with_memories( + scheduler: Shared, + io: Shared, + cartridge: Cartridge, + bios_rom: Box<[u8]>, + ewram: Box<[u8]>, + iwram: Box<[u8]>, + ) -> SysBus { let mut luts = CycleLookupTables::default(); luts.init(); luts.update_gamepak_waitstates(io.waitcnt); SysBus { io, + scheduler, + cartridge, + bios: BoxedMemory::new(bios_rom), - onboard_work_ram: BoxedMemory::new(vec![0; WORK_RAM_SIZE].into_boxed_slice()), - internal_work_ram: BoxedMemory::new(vec![0; INTERNAL_RAM_SIZE].into_boxed_slice()), - cartridge: cartridge, - + onboard_work_ram: BoxedMemory::new(ewram), + internal_work_ram: BoxedMemory::new(iwram), cycle_luts: luts, - trace_access: false, } } + pub fn new( + scheduler: Shared, + io: Shared, + bios_rom: Box<[u8]>, + cartridge: Cartridge, + ) -> SysBus { + let ewram = vec![0; WORK_RAM_SIZE].into_boxed_slice(); + let iwram = vec![0; INTERNAL_RAM_SIZE].into_boxed_slice(); + SysBus::new_with_memories(scheduler, io, cartridge, bios_rom, ewram, iwram) + } + + pub fn set_bios(&mut self, buffer: Box<[u8]>) { + self.bios.mem = buffer; + } + + pub fn set_ewram(&mut self, buffer: Box<[u8]>) { + self.onboard_work_ram.mem = buffer; + } + + pub fn set_iwram(&mut self, buffer: Box<[u8]>) { + self.internal_work_ram.mem = buffer; + } + + pub fn get_ewram(&self) -> &[u8] { + &self.onboard_work_ram.mem + } + + pub fn get_iwram(&self) -> &[u8] { + &self.internal_work_ram.mem + } + + pub fn set_scheduler(&mut self, s: Shared) { + self.scheduler = s; + } + + pub fn set_io_devices(&mut self, io_devs: Shared) { + self.io = io_devs; + } + /// must be called whenever this object is instanciated pub fn created(&mut self) { let ptr = SysBusPtr::new(self as *mut SysBus); diff --git a/core/src/timer.rs b/core/src/timer.rs index df5eeb0..074c696 100644 --- a/core/src/timer.rs +++ b/core/src/timer.rs @@ -200,9 +200,9 @@ impl Timers { } } if id == 0 || id == 1 { - sb.io - .sound - .handle_timer_overflow(&mut sb.io.dmac, id, num_overflows); + let io = unsafe { sb.io.inner_unsafe() }; + io.sound + .handle_timer_overflow(&mut io.dmac, id, num_overflows); } } } diff --git a/core/src/util.rs b/core/src/util.rs index e7d0b6c..2cd0c1a 100644 --- a/core/src/util.rs +++ b/core/src/util.rs @@ -201,6 +201,66 @@ impl Default for WeakPointer { } } +use std::cell::UnsafeCell; +use std::rc::Rc; + +/// Opt-out of runtime borrow checking of RefCell by using UnsafeCell +/// SAFETY: Up to the user to make sure the usage of the shared object is safe +#[repr(transparent)] +#[derive(Debug)] +pub struct Shared(Rc>); + +impl std::ops::Deref for Shared { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { &(*self.0.get()) } + } +} + +impl std::ops::DerefMut for Shared { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut (*self.0.get()) } + } +} + +impl Clone for Shared { + #[inline] + fn clone(&self) -> Shared { + Shared(self.0.clone()) + } +} + +impl Shared { + pub fn new(t: T) -> Shared { + Shared(Rc::new(UnsafeCell::new(t))) + } + + pub unsafe fn inner_unsafe(&self) -> &mut T { + &mut (*self.0.get()) + } +} + +impl Shared +where + T: Clone, +{ + pub fn clone_inner(&self) -> T { + self.deref().clone() + } +} + +impl Default for Shared +where + T: Default, +{ + fn default() -> Shared { + Shared::new(Default::default()) + } +} + #[derive(Serialize, Deserialize, Clone, Debug)] #[repr(transparent)] pub struct BoxedMemory { diff --git a/platform/rustboyadvance-sdl2/src/main.rs b/platform/rustboyadvance-sdl2/src/main.rs index 3e908de..c30d33e 100644 --- a/platform/rustboyadvance-sdl2/src/main.rs +++ b/platform/rustboyadvance-sdl2/src/main.rs @@ -121,7 +121,7 @@ fn main() -> Result<(), Box> { let bios_path = Path::new(matches.value_of("bios").unwrap_or_default()); let bios_bin = match read_bin_file(bios_path) { - Ok(bios) => bios, + Ok(bios) => bios.into_boxed_slice(), _ => { ask_download_bios(); std::process::exit(0); @@ -203,7 +203,7 @@ fn main() -> Result<(), Box> { let gamepak = builder.build()?; let mut gba = GameBoyAdvance::new( - bios_bin.into_boxed_slice(), + bios_bin.clone(), gamepak, video.clone(), audio.clone(), @@ -276,7 +276,7 @@ fn main() -> Result<(), Box> { if savestate_path.is_file() { let save = read_bin_file(&savestate_path)?; info!("Restoring state from {:?}...", savestate_path); - gba.restore_state(&save)?; + gba.restore_state(&save, bios_bin.clone())?; info!("Restored!"); } else { info!("Savestate not created, please create one by pressing F5");