[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:
parent
97101d7bc1
commit
bce4456f42
|
@ -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::*;
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
|
|
Reference in a new issue