[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)]
pub struct Cartridge {
pub header: CartridgeHeader,
#[serde(skip)]
bytes: Box<[u8]>,
#[serde(skip)]
size: usize,
gpio: Option<Gpio>,
symbols: Option<SymbolTable>, // TODO move it somewhere else
@ -52,6 +54,34 @@ impl Cartridge {
pub fn get_gpio(&self) -> &Option<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::*;

View file

@ -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<SysBus>,
pub cpu: arm7tdmi::Core,
pub sysbus: Box<SysBus>,
io_devs: Shared<IoDevices>,
#[cfg(not(feature = "no_video_interface"))]
pub video_device: Rc<RefCell<dyn VideoInterface>>,
@ -39,8 +41,11 @@ pub struct GameBoyAdvance {
#[derive(Serialize, Deserialize)]
struct SaveState {
sysbus: Box<SysBus>,
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<RefCell<dyn VideoInterface>>,
audio_device: Rc<RefCell<dyn AudioInterface>>,
input_device: Rc<RefCell<dyn InputInterface>>,
@ -118,17 +131,28 @@ impl GameBoyAdvance {
let decoded: Box<SaveState> = 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<Vec<u8>> {
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<SaveState> = 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(())
}

View file

@ -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};
}

View file

@ -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<Event>,
}
// 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<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 type SharedScheduler = Shared<Scheduler>;
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) {

View file

@ -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<IoDevices>,
scheduler: Shared<Scheduler>,
bios: BoxedMemory,
onboard_work_ram: BoxedMemory,
@ -181,24 +183,70 @@ pub struct SysBus {
pub type SysBusPtr = WeakPointer<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();
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<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
pub fn created(&mut self) {
let ptr = SysBusPtr::new(self as *mut SysBus);

View file

@ -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);
}
}
}

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)]
#[repr(transparent)]
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_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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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");