[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
This commit is contained in:
Michel Heily 2020-10-10 11:06:11 -07:00 committed by MishMish
parent 97101d7bc1
commit bce4456f42
8 changed files with 211 additions and 67 deletions

View file

@ -38,7 +38,9 @@ pub type SymbolTable = HashMap<String, u32>;
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Cartridge { pub struct Cartridge {
pub header: CartridgeHeader, pub header: CartridgeHeader,
#[serde(skip)]
bytes: Box<[u8]>, bytes: Box<[u8]>,
#[serde(skip)]
size: usize, size: usize,
gpio: Option<Gpio>, gpio: Option<Gpio>,
symbols: Option<SymbolTable>, // TODO move it somewhere else symbols: Option<SymbolTable>, // TODO move it somewhere else
@ -52,6 +54,34 @@ impl Cartridge {
pub fn get_gpio(&self) -> &Option<Gpio> { pub fn get_gpio(&self) -> &Option<Gpio> {
&self.gpio &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::*; use super::sysbus::consts::*;

View file

@ -15,14 +15,16 @@ use super::sched::{EventHandler, EventType, Scheduler, SharedScheduler};
use super::sound::SoundController; use super::sound::SoundController;
use super::sysbus::SysBus; use super::sysbus::SysBus;
use super::timer::Timers; use super::timer::Timers;
use super::util::Shared;
#[cfg(not(feature = "no_video_interface"))] #[cfg(not(feature = "no_video_interface"))]
use super::VideoInterface; use super::VideoInterface;
use super::{AudioInterface, InputInterface}; use super::{AudioInterface, InputInterface};
pub struct GameBoyAdvance { pub struct GameBoyAdvance {
pub sysbus: Box<SysBus>,
pub cpu: arm7tdmi::Core, pub cpu: arm7tdmi::Core,
pub sysbus: Box<SysBus>,
io_devs: Shared<IoDevices>,
#[cfg(not(feature = "no_video_interface"))] #[cfg(not(feature = "no_video_interface"))]
pub video_device: Rc<RefCell<dyn VideoInterface>>, pub video_device: Rc<RefCell<dyn VideoInterface>>,
@ -39,8 +41,11 @@ pub struct GameBoyAdvance {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct SaveState { struct SaveState {
sysbus: Box<SysBus>,
scheduler: Scheduler, scheduler: Scheduler,
io_devs: IoDevices,
cartridge: Cartridge,
ewram: Box<[u8]>,
iwram: Box<[u8]>,
interrupt_flags: u16, interrupt_flags: u16,
cpu: arm7tdmi::Core, cpu: arm7tdmi::Core,
} }
@ -83,14 +88,20 @@ impl GameBoyAdvance {
scheduler.clone(), scheduler.clone(),
audio_device.borrow().get_sample_rate() as f32, audio_device.borrow().get_sample_rate() as f32,
)); ));
let io = IoDevices::new(intc, gpu, dmac, timers, sound_controller); let io_devs = Shared::new(IoDevices::new(intc, gpu, dmac, timers, sound_controller));
let sysbus = Box::new(SysBus::new(io, bios_rom, gamepak)); let sysbus = Box::new(SysBus::new(
scheduler.clone(),
io_devs.clone(),
bios_rom,
gamepak,
));
let cpu = arm7tdmi::Core::new(); let cpu = arm7tdmi::Core::new();
let mut gba = GameBoyAdvance { let mut gba = GameBoyAdvance {
cpu: cpu, cpu,
sysbus: sysbus, sysbus,
io_devs,
#[cfg(not(feature = "no_video_interface"))] #[cfg(not(feature = "no_video_interface"))]
video_device: video_device, video_device: video_device,
@ -111,6 +122,8 @@ impl GameBoyAdvance {
pub fn from_saved_state( pub fn from_saved_state(
savestate: &[u8], savestate: &[u8],
bios: Box<[u8]>,
rom: Box<[u8]>,
#[cfg(not(feature = "no_video_interface"))] video_device: Rc<RefCell<dyn VideoInterface>>, #[cfg(not(feature = "no_video_interface"))] video_device: Rc<RefCell<dyn VideoInterface>>,
audio_device: Rc<RefCell<dyn AudioInterface>>, audio_device: Rc<RefCell<dyn AudioInterface>>,
input_device: Rc<RefCell<dyn InputInterface>>, input_device: Rc<RefCell<dyn InputInterface>>,
@ -118,17 +131,28 @@ impl GameBoyAdvance {
let decoded: Box<SaveState> = bincode::deserialize_from(savestate)?; let decoded: Box<SaveState> = bincode::deserialize_from(savestate)?;
let arm7tdmi = decoded.cpu; let arm7tdmi = decoded.cpu;
let mut sysbus = decoded.sysbus;
let interrupts = Rc::new(Cell::new(IrqBitmask(decoded.interrupt_flags))); let interrupts = Rc::new(Cell::new(IrqBitmask(decoded.interrupt_flags)));
let scheduler = decoded.scheduler.make_shared(); 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()); let sysbus = Box::new(SysBus::new_with_memories(
sysbus.io.sound.set_scheduler(scheduler.clone()); scheduler.clone(),
sysbus.io.connect_irq(interrupts.clone()); io_devs.clone(),
cartridge,
bios,
decoded.ewram,
decoded.iwram,
));
Ok(GameBoyAdvance { Ok(GameBoyAdvance {
cpu: arm7tdmi, cpu: arm7tdmi,
sysbus: sysbus, sysbus: sysbus,
io_devs,
interrupt_flags: interrupts, interrupt_flags: interrupts,
@ -141,37 +165,44 @@ impl GameBoyAdvance {
overshoot_cycles: 0, overshoot_cycles: 0,
scheduler: scheduler, scheduler,
}) })
} }
pub fn save_state(&self) -> bincode::Result<Vec<u8>> { pub fn save_state(&self) -> bincode::Result<Vec<u8>> {
let s = SaveState { let s = SaveState {
cpu: self.cpu.clone(), 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(), interrupt_flags: self.interrupt_flags.get().value(),
scheduler: (*self.scheduler).clone(), scheduler: self.scheduler.clone_inner(),
}; };
bincode::serialize(&s) 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<SaveState> = bincode::deserialize_from(bytes)?; let decoded: Box<SaveState> = bincode::deserialize_from(bytes)?;
self.cpu = decoded.cpu; self.cpu = decoded.cpu;
self.sysbus = decoded.sysbus;
self.scheduler = Scheduler::make_shared(decoded.scheduler); self.scheduler = Scheduler::make_shared(decoded.scheduler);
self.interrupt_flags = Rc::new(Cell::new(IrqBitmask(decoded.interrupt_flags))); 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 // Redistribute shared pointers
self.sysbus.io.connect_irq(self.interrupt_flags.clone()); self.io_devs.connect_irq(self.interrupt_flags.clone());
self.sysbus.io.gpu.set_scheduler(self.scheduler.clone()); self.io_devs.gpu.set_scheduler(self.scheduler.clone());
self.sysbus.io.sound.set_scheduler(self.scheduler.clone()); self.io_devs.sound.set_scheduler(self.scheduler.clone());
self.sysbus.set_scheduler(self.scheduler.clone());
self.cycles_to_next_event = 1; self.sysbus.set_io_devices(self.io_devs.clone());
self.sysbus.cartridge.update_from(decoded.cartridge);
self.sysbus.created(); self.sysbus.created();
self.cycles_to_next_event = 1;
Ok(()) Ok(())
} }

View file

@ -138,8 +138,8 @@ pub mod prelude {
pub use super::gpu::{DISPLAY_HEIGHT, DISPLAY_WIDTH}; pub use super::gpu::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
pub use super::util::{read_bin_file, write_bin_file}; pub use super::util::{read_bin_file, write_bin_file};
pub use super::Bus; pub use super::Bus;
pub use super::{AudioInterface, InputInterface, StereoSample};
#[cfg(not(feature = "no_video_interface"))] #[cfg(not(feature = "no_video_interface"))]
pub use super::VideoInterface; pub use super::VideoInterface;
pub use super::{AudioInterface, InputInterface, StereoSample};
pub use super::{GBAError, GBAResult, GameBoyAdvance}; pub use super::{GBAError, GBAResult, GameBoyAdvance};
} }

View file

@ -1,5 +1,4 @@
use std::cell::UnsafeCell; use super::util::Shared;
use std::rc::Rc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -55,31 +54,7 @@ pub struct Scheduler {
events: Vec<Event>, events: Vec<Event>,
} }
// Opt-out of runtime borrow checking by using unsafe cell pub type SharedScheduler = Shared<Scheduler>;
// 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<UnsafeCell<Scheduler>>);
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 trait EventHandler { pub trait EventHandler {
/// Handle the scheduler event /// Handle the scheduler event
@ -92,11 +67,11 @@ impl Scheduler {
timestamp: 0, timestamp: 0,
events: Vec::with_capacity(NUM_EVENTS), events: Vec::with_capacity(NUM_EVENTS),
}; };
SharedScheduler(Rc::new(UnsafeCell::new(sched))) SharedScheduler::new(sched)
} }
pub fn make_shared(self) -> SharedScheduler { pub fn make_shared(self) -> SharedScheduler {
SharedScheduler(Rc::new(UnsafeCell::new(self))) SharedScheduler::new(self)
} }
pub fn schedule(&mut self, typ: EventType, cycles: usize) { pub fn schedule(&mut self, typ: EventType, cycles: usize) {

View file

@ -6,7 +6,8 @@ use super::bus::*;
use super::cartridge::Cartridge; use super::cartridge::Cartridge;
use super::dma::DmaNotifer; use super::dma::DmaNotifer;
use super::iodev::{IoDevices, WaitControl}; use super::iodev::{IoDevices, WaitControl};
use super::util::{BoxedMemory, WeakPointer}; use super::sched::Scheduler;
use super::util::{BoxedMemory, Shared, WeakPointer};
pub mod consts { pub mod consts {
pub const WORK_RAM_SIZE: usize = 256 * 1024; pub const WORK_RAM_SIZE: usize = 256 * 1024;
@ -164,9 +165,10 @@ impl CycleLookupTables {
} }
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Clone)]
pub struct SysBus { pub struct SysBus {
pub io: IoDevices, pub io: Shared<IoDevices>,
scheduler: Shared<Scheduler>,
bios: BoxedMemory, bios: BoxedMemory,
onboard_work_ram: BoxedMemory, onboard_work_ram: BoxedMemory,
@ -181,24 +183,70 @@ pub struct SysBus {
pub type SysBusPtr = WeakPointer<SysBus>; pub type SysBusPtr = WeakPointer<SysBus>;
impl SysBus { impl SysBus {
pub fn new(io: IoDevices, bios_rom: Box<[u8]>, cartridge: Cartridge) -> SysBus { pub fn new_with_memories(
scheduler: Shared<Scheduler>,
io: Shared<IoDevices>,
cartridge: Cartridge,
bios_rom: Box<[u8]>,
ewram: Box<[u8]>,
iwram: Box<[u8]>,
) -> SysBus {
let mut luts = CycleLookupTables::default(); let mut luts = CycleLookupTables::default();
luts.init(); luts.init();
luts.update_gamepak_waitstates(io.waitcnt); luts.update_gamepak_waitstates(io.waitcnt);
SysBus { SysBus {
io, io,
scheduler,
cartridge,
bios: BoxedMemory::new(bios_rom), bios: BoxedMemory::new(bios_rom),
onboard_work_ram: BoxedMemory::new(vec![0; WORK_RAM_SIZE].into_boxed_slice()), onboard_work_ram: BoxedMemory::new(ewram),
internal_work_ram: BoxedMemory::new(vec![0; INTERNAL_RAM_SIZE].into_boxed_slice()), internal_work_ram: BoxedMemory::new(iwram),
cartridge: cartridge,
cycle_luts: luts, cycle_luts: luts,
trace_access: false, trace_access: false,
} }
} }
pub fn new(
scheduler: Shared<Scheduler>,
io: Shared<IoDevices>,
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<Scheduler>) {
self.scheduler = s;
}
pub fn set_io_devices(&mut self, io_devs: Shared<IoDevices>) {
self.io = io_devs;
}
/// must be called whenever this object is instanciated /// must be called whenever this object is instanciated
pub fn created(&mut self) { pub fn created(&mut self) {
let ptr = SysBusPtr::new(self as *mut SysBus); let ptr = SysBusPtr::new(self as *mut SysBus);

View file

@ -200,9 +200,9 @@ impl Timers {
} }
} }
if id == 0 || id == 1 { if id == 0 || id == 1 {
sb.io let io = unsafe { sb.io.inner_unsafe() };
.sound io.sound
.handle_timer_overflow(&mut sb.io.dmac, id, num_overflows); .handle_timer_overflow(&mut io.dmac, id, num_overflows);
} }
} }
} }

View file

@ -201,6 +201,66 @@ impl<T> Default for WeakPointer<T> {
} }
} }
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<T>(Rc<UnsafeCell<T>>);
impl<T> std::ops::Deref for Shared<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { &(*self.0.get()) }
}
}
impl<T> std::ops::DerefMut for Shared<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut (*self.0.get()) }
}
}
impl<T> Clone for Shared<T> {
#[inline]
fn clone(&self) -> Shared<T> {
Shared(self.0.clone())
}
}
impl<T> Shared<T> {
pub fn new(t: T) -> Shared<T> {
Shared(Rc::new(UnsafeCell::new(t)))
}
pub unsafe fn inner_unsafe(&self) -> &mut T {
&mut (*self.0.get())
}
}
impl<T> Shared<T>
where
T: Clone,
{
pub fn clone_inner(&self) -> T {
self.deref().clone()
}
}
impl<T> Default for Shared<T>
where
T: Default,
{
fn default() -> Shared<T> {
Shared::new(Default::default())
}
}
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[repr(transparent)] #[repr(transparent)]
pub struct BoxedMemory { pub struct BoxedMemory {

View file

@ -121,7 +121,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let bios_path = Path::new(matches.value_of("bios").unwrap_or_default()); let bios_path = Path::new(matches.value_of("bios").unwrap_or_default());
let bios_bin = match read_bin_file(bios_path) { let bios_bin = match read_bin_file(bios_path) {
Ok(bios) => bios, Ok(bios) => bios.into_boxed_slice(),
_ => { _ => {
ask_download_bios(); ask_download_bios();
std::process::exit(0); std::process::exit(0);
@ -203,7 +203,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let gamepak = builder.build()?; let gamepak = builder.build()?;
let mut gba = GameBoyAdvance::new( let mut gba = GameBoyAdvance::new(
bios_bin.into_boxed_slice(), bios_bin.clone(),
gamepak, gamepak,
video.clone(), video.clone(),
audio.clone(), audio.clone(),
@ -276,7 +276,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
if savestate_path.is_file() { if savestate_path.is_file() {
let save = read_bin_file(&savestate_path)?; let save = read_bin_file(&savestate_path)?;
info!("Restoring state from {:?}...", savestate_path); info!("Restoring state from {:?}...", savestate_path);
gba.restore_state(&save)?; gba.restore_state(&save, bios_bin.clone())?;
info!("Restored!"); info!("Restored!");
} else { } else {
info!("Savestate not created, please create one by pressing F5"); info!("Savestate not created, please create one by pressing F5");