feat/savestates: Implement save/load state API for GameBoyAdvance

Using serde & bincode encoding


Former-commit-id: f5e4c599497f6bdf3096fa99f8b2d6ce89278ef7
This commit is contained in:
Michel Heily 2020-01-16 20:06:22 +02:00
parent 16142ee99d
commit f4460b2740
18 changed files with 128 additions and 73 deletions

View file

@ -1,6 +1,8 @@
pub mod display;
pub mod exec;
use serde::{Deserialize, Serialize};
use super::alu::*;
use crate::core::arm7tdmi::{Addr, InstructionDecoder, InstructionDecoderError};
@ -38,7 +40,7 @@ impl ArmDecodeError {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Primitive)]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Primitive)]
pub enum ArmCond {
EQ = 0b0000,
NE = 0b0001,
@ -57,7 +59,7 @@ pub enum ArmCond {
AL = 0b1110,
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
#[allow(non_camel_case_types)]
pub enum ArmFormat {
/// Branch and Exchange
@ -97,7 +99,7 @@ pub enum ArmHalfwordTransferType {
SignedHalfwords = 0b11,
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
pub struct ArmInstruction {
pub cond: ArmCond,
pub fmt: ArmFormat,

View file

@ -1,6 +1,7 @@
use std::fmt;
use ansi_term::{Colour, Style};
use serde::{Deserialize, Serialize};
pub use super::exception::Exception;
use super::{
@ -13,7 +14,7 @@ use crate::core::sysbus::{
MemoryAccess, MemoryAccessType, MemoryAccessType::*, MemoryAccessWidth::*, SysBus,
};
#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
pub enum PipelineState {
Refill1,
Refill2,
@ -26,7 +27,7 @@ impl Default for PipelineState {
}
}
#[derive(Debug, Default)]
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct Core {
pub pc: u32,
pub gpr: [u32; 15],

View file

@ -1,6 +1,7 @@
use std::fmt;
use num::Num;
use serde::{Deserialize, Serialize};
pub mod arm;
pub mod thumb;
@ -21,7 +22,7 @@ pub const REG_SP: usize = 13;
pub(self) use crate::core::{Addr, Bus};
#[derive(Debug, PartialEq, Copy, Clone)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
pub enum DecodedInstruction {
Arm(ArmInstruction),
Thumb(ThumbInstruction),

View file

@ -1,6 +1,8 @@
/// The program status register
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::bit::BitIndex;
use crate::num::FromPrimitive;
@ -27,7 +29,7 @@ impl From<bool> for CpuState {
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
pub struct RegPSR {
raw: u32,
}

View file

@ -35,7 +35,7 @@ impl ThumbDecodeError {
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
pub enum ThumbFormat {
/// Format 1
MoveShiftedReg,
@ -77,7 +77,7 @@ pub enum ThumbFormat {
BranchLongWithLink,
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
pub struct ThumbInstruction {
pub fmt: ThumbFormat,
pub raw: u16,

View file

@ -4,6 +4,8 @@ use std::io::prelude::*;
use std::path::Path;
use std::str::from_utf8;
use serde::{Deserialize, Serialize};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use zip::ZipArchive;
@ -35,7 +37,7 @@ use super::{Addr, Bus, GBAResult};
/// 0C6h 26 Not used (seems to be unused)
/// 0E0h 4 JOYBUS Entry Pt. (32bit ARM branch opcode, eg. "B joy_start")
///
#[derive(Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct CartridgeHeader {
// rom_entry_point: Addr,
game_title: String,
@ -69,7 +71,7 @@ impl CartridgeHeader {
}
}
#[derive(Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Cartridge {
pub header: CartridgeHeader,
bytes: Box<[u8]>,

View file

@ -2,16 +2,10 @@ use super::iodev::consts::{REG_FIFO_A, REG_FIFO_B};
use super::sysbus::SysBus;
use super::{Addr, Bus, Interrupt, IrqBitmask};
use bit_set::BitSet;
use num::FromPrimitive;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
enum DmaTransferType {
Xfer16bit,
Xfer32bit,
}
#[derive(Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DmaChannel {
id: usize,
@ -30,7 +24,7 @@ pub struct DmaChannel {
irq: Interrupt,
}
#[derive(Debug, Default)]
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
struct DmaInternalRegs {
src_addr: u32,
dst_addr: u32,
@ -172,10 +166,10 @@ impl DmaChannel {
}
}
#[derive(Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DmaController {
pub channels: [DmaChannel; 4],
pending_set: BitSet,
pending_set: u8,
cycles: usize,
}
@ -188,20 +182,22 @@ impl DmaController {
DmaChannel::new(2),
DmaChannel::new(3),
],
pending_set: BitSet::with_capacity(4),
pending_set: 0,
cycles: 0,
}
}
pub fn is_active(&self) -> bool {
!self.pending_set.is_empty()
self.pending_set != 0
}
pub fn perform_work(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) {
for id in self.pending_set.iter() {
self.channels[id].xfer(sb, irqs);
for id in 0..4 {
if self.pending_set & (1 << id) != 0 {
self.channels[id].xfer(sb, irqs);
}
}
self.pending_set.clear();
self.pending_set = 0;
}
pub fn write_16(&mut self, channel_id: usize, ofs: u32, value: u16) {
@ -213,9 +209,9 @@ impl DmaController {
8 => self.channels[channel_id].write_word_count(value),
10 => {
if self.channels[channel_id].write_dma_ctrl(value) {
self.pending_set.insert(channel_id);
self.pending_set |= 1 << channel_id;
} else {
self.pending_set.remove(channel_id);
self.pending_set &= !(1 << channel_id);
}
}
_ => panic!("Invalid dma offset {:x}", ofs),
@ -225,7 +221,7 @@ impl DmaController {
pub fn notify_vblank(&mut self) {
for i in 0..4 {
if self.channels[i].ctrl.is_enabled() && self.channels[i].ctrl.timing() == 1 {
self.pending_set.insert(i);
self.pending_set |= 1 << i;
}
}
}
@ -233,7 +229,7 @@ impl DmaController {
pub fn notify_hblank(&mut self) {
for i in 0..4 {
if self.channels[i].ctrl.is_enabled() && self.channels[i].ctrl.timing() == 2 {
self.pending_set.insert(i);
self.pending_set |= 1 << i;
}
}
}
@ -245,14 +241,14 @@ impl DmaController {
&& self.channels[i].ctrl.timing() == 3
&& self.channels[i].dst == fifo_addr
{
self.pending_set.insert(i);
self.pending_set |= 1 << i;
}
}
}
}
bitfield! {
#[derive(Default)]
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct DmaChannelCtrl(u16);
impl Debug;
u16;

View file

@ -2,6 +2,9 @@
use std::cell::RefCell;
use std::rc::Rc;
use bincode;
use serde::{Deserialize, Serialize};
use super::arm7tdmi::Core;
use super::cartridge::Cartridge;
use super::gpu::*;
@ -23,6 +26,12 @@ pub struct GameBoyAdvance {
cycles_to_next_event: usize,
}
#[derive(Serialize, Deserialize)]
struct SaveState {
sysbus: Box<SysBus>,
cpu: Core,
}
impl GameBoyAdvance {
pub fn new(
cpu: Core,
@ -49,6 +58,25 @@ impl GameBoyAdvance {
}
}
pub fn save_state(&self) -> bincode::Result<Vec<u8>> {
let s = SaveState {
cpu: self.cpu.clone(),
sysbus: self.sysbus.clone(),
};
bincode::serialize(&s)
}
pub fn restore_state(&mut self, bytes: &[u8]) -> bincode::Result<()> {
let decoded: Box<SaveState> = bincode::deserialize_from(bytes)?;
self.cpu = decoded.cpu;
self.sysbus = decoded.sysbus;
self.cycles_to_next_event = 1;
Ok(())
}
#[inline]
pub fn key_poll(&mut self) {
self.sysbus.io.keyinput = self.input_device.borrow_mut().poll();

View file

@ -2,6 +2,8 @@ use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
use serde::{Deserialize, Serialize};
use super::super::VideoInterface;
use super::interrupt::IrqBitmask;
use super::sysbus::{BoxedMemory, SysBus};
@ -50,7 +52,7 @@ pub enum PixelFormat {
BPP8 = 1,
}
#[derive(Debug, PartialEq, Copy, Clone)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
pub enum GpuState {
HDraw = 0,
HBlank,
@ -95,7 +97,7 @@ impl std::ops::IndexMut<usize> for Scanline {
}
}
#[derive(Debug, Default, Copy, Clone)]
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct Background {
pub bgcnt: BgControl,
pub bgvofs: u16,
@ -107,7 +109,7 @@ pub struct Background {
mosaic_first_row: Scanline,
}
#[derive(Debug, Default)]
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct Window {
pub left: u8,
pub right: u8,
@ -151,7 +153,7 @@ pub struct AffineMatrix {
pub pd: i32,
}
#[derive(Debug, Default, Copy, Clone)]
#[derive(Serialize, Deserialize, Debug, Default, Copy, Clone)]
pub struct BgAffine {
pub pa: i16, // dx
pub pb: i16, // dmx
@ -163,7 +165,7 @@ pub struct BgAffine {
pub internal_y: i32,
}
#[derive(Debug, Copy, Clone)]
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub struct ObjBufferEntry {
pub(super) color: Rgb15,
pub(super) priority: u16,
@ -182,7 +184,7 @@ impl Default for ObjBufferEntry {
type VideoDeviceRcRefCell = Rc<RefCell<dyn VideoInterface>>;
#[derive(DebugStub)]
#[derive(Serialize, Deserialize, Clone, DebugStub)]
pub struct Gpu {
pub state: GpuState,

View file

@ -1,6 +1,8 @@
use super::sfx::BldMode;
use super::*;
use serde::{Deserialize, Serialize};
pub const SCREEN_BLOCK_SIZE: u32 = 0x800;
#[derive(Debug, PartialEq)]
@ -60,6 +62,7 @@ impl BgControl {
// struct definitions below because the bitfield! macro messes up syntax highlighting in vscode.
bitfield! {
#[derive(Serialize, Deserialize, Clone)]
pub struct DisplayControl(u16);
impl Debug;
u16;
@ -79,6 +82,7 @@ bitfield! {
}
bitfield! {
#[derive(Serialize, Deserialize, Clone)]
pub struct DisplayStatus(u16);
impl Debug;
u16;
@ -92,7 +96,7 @@ bitfield! {
}
bitfield! {
#[derive(Default, Copy, Clone)]
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct BgControl(u16);
impl Debug;
u16;
@ -106,7 +110,7 @@ bitfield! {
}
bitfield! {
#[derive(Default, Copy, Clone)]
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct RegMosaic(u16);
impl Debug;
u32;
@ -117,7 +121,7 @@ bitfield! {
}
bitflags! {
#[derive(Default)]
#[derive(Serialize, Deserialize, Default)]
pub struct BlendFlags: u32 {
const BG0 = 0b00000001;
const BG1 = 0b00000010;
@ -148,7 +152,7 @@ impl BlendFlags {
}
bitfield! {
#[derive(Default, Copy, Clone)]
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct BlendControl(u16);
impl Debug;
pub into BlendFlags, top, _: 5, 0;
@ -157,7 +161,7 @@ bitfield! {
}
bitfield! {
#[derive(Default, Copy, Clone)]
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct BlendAlpha(u16);
impl Debug;
u16;
@ -166,7 +170,7 @@ bitfield! {
}
bitflags! {
#[derive(Default)]
#[derive(Serialize, Deserialize, Default)]
pub struct WindowFlags: u32 {
const BG0 = 0b00000001;
const BG1 = 0b00000010;
@ -200,7 +204,7 @@ const BG_WIN_FLAG: [WindowFlags; 4] = [
];
bitfield! {
#[derive(Default, Copy, Clone)]
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct WindowReg(u16);
impl Debug;
u16;

View file

@ -1,8 +1,10 @@
//! Helper type to deal with the GBA's 15bit color
use serde::{Deserialize, Serialize};
bitfield! {
#[repr(transparent)]
#[derive(Copy, Clone, Default, PartialEq)]
#[derive(Serialize, Deserialize, Copy, Clone, Default, PartialEq)]
pub struct Rgb15(u16);
impl Debug;
pub r, set_r: 4, 0;

View file

@ -1,4 +1,6 @@
#[derive(Debug, Primitive, Copy, Clone, PartialEq)]
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Primitive, Copy, Clone, PartialEq)]
#[allow(non_camel_case_types)]
pub enum Interrupt {
LCD_VBlank = 0,
@ -17,7 +19,7 @@ pub enum Interrupt {
GamePak = 13,
}
#[derive(Debug, Default)]
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct InterruptController {
pub interrupt_master_enable: bool,
pub interrupt_enable: IrqBitmask,
@ -48,7 +50,7 @@ impl IrqBitmask {
}
bitfield! {
#[derive(Default, Copy, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Default, Copy, Clone, PartialEq)]
pub struct IrqBitmask(u16);
impl Debug;
u16;

View file

@ -7,15 +7,18 @@ use super::sound::SoundController;
use super::timer::Timers;
use super::{Addr, Bus};
use serde::{Deserialize, Serialize};
use self::consts::*;
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum HaltState {
Running,
Halt, // In Halt mode, the CPU is paused as long as (IE AND IF)=0,
Stop, // In Stop mode, most of the hardware including sound and video are paused
}
#[derive(Clone, Serialize, Deserialize)]
pub struct IoDevices {
pub intc: InterruptController,
pub gpu: Box<Gpu>,
@ -262,7 +265,7 @@ impl Bus for IoDevices {
}
bitfield! {
#[derive(Default, Copy, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Default, Copy, Clone, PartialEq)]
pub struct WaitControl(u16);
impl Debug;
u16;

View file

@ -1,12 +1,14 @@
use crate::{AudioInterface, StereoSample};
use serde::{Deserialize, Serialize};
const PI: f32 = std::f32::consts::PI;
pub trait Resampler {
fn push_sample(&mut self, s: StereoSample, audio: &mut dyn AudioInterface);
}
#[derive(Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct CosineResampler {
last_in_sample: StereoSample,
phase: f32,

View file

@ -1,7 +1,9 @@
// TODO write tests or replace with a crate
const SOUND_FIFO_CAPACITY: usize = 32;
#[derive(Debug)]
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SoundFifo {
wr_pos: usize,
rd_pos: usize,

View file

@ -2,6 +2,7 @@ use std::cell::RefCell;
use std::rc::Rc;
use bit::BitIndex;
use serde::{Deserialize, Serialize};
use super::dma::DmaController;
use super::iodev::consts::*;
@ -18,10 +19,7 @@ const DMG_RATIOS: [f32; 4] = [0.25, 0.5, 1.0, 0.0];
const DMA_TIMERS: [usize; 2] = [0, 1];
const DUTY_RATIOS: [f32; 4] = [0.125, 0.25, 0.5, 0.75];
#[derive(Debug)]
struct NoiseChannel {}
#[derive(Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
struct DmaSoundChannel {
value: i8,
volume_shift: i16,
@ -62,7 +60,7 @@ const REG_FIFO_B_H: u32 = REG_FIFO_B + 2;
type AudioDeviceRcRefCell = Rc<RefCell<dyn AudioInterface>>;
#[derive(DebugStub)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SoundController {
sample_rate_to_cpu_freq: usize, // how many "cycles" are a sample?
cycles: usize, // cycles count when we last provided a new sample.

View file

@ -1,6 +1,8 @@
use std::fmt;
use std::ops::Add;
use serde::{Deserialize, Serialize};
use super::cartridge::Cartridge;
use super::gpu::{GpuState, VIDEO_RAM_SIZE};
use super::iodev::IoDevices;
@ -64,7 +66,7 @@ impl fmt::Display for MemoryAccess {
}
}
#[derive(Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BoxedMemory {
pub mem: Box<[u8]>,
}
@ -77,15 +79,17 @@ impl BoxedMemory {
impl Bus for BoxedMemory {
fn read_8(&self, addr: Addr) -> u8 {
self.mem[addr as usize]
unsafe { *self.mem.get_unchecked(addr as usize) }
}
fn write_8(&mut self, addr: Addr, value: u8) {
self.mem[addr as usize] = value;
unsafe {
*self.mem.get_unchecked_mut(addr as usize) = value;
}
}
}
#[derive(Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
struct DummyBus([u8; 4]);
impl Bus for DummyBus {
@ -96,6 +100,7 @@ impl Bus for DummyBus {
fn write_8(&mut self, _addr: Addr, _value: u8) {}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct SysBus {
pub io: IoDevices,

View file

@ -2,11 +2,10 @@ use super::interrupt::{Interrupt, IrqBitmask};
use super::iodev::consts::*;
use super::sysbus::SysBus;
use bit_set::BitSet;
use num::FromPrimitive;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Timer {
// registers
pub ctl: TimerCtl,
@ -66,10 +65,10 @@ impl Timer {
}
}
#[derive(Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Timers {
timers: [Timer; 4],
running_timers: BitSet,
running_timers: u8,
pub trace: bool,
}
@ -90,7 +89,7 @@ impl Timers {
pub fn new() -> Timers {
Timers {
timers: [Timer::new(0), Timer::new(1), Timer::new(2), Timer::new(3)],
running_timers: BitSet::with_capacity(4),
running_timers: 0,
trace: false,
}
}
@ -101,9 +100,9 @@ impl Timers {
let new_enabled = self[id].ctl.enabled();
let cascade = self.timers[id].ctl.cascade();
if new_enabled && !cascade {
self.running_timers.insert(id);
self.running_timers |= 1 << id;
} else {
self.running_timers.remove(id);
self.running_timers &= !(1 << id);
}
if self.trace && old_enabled != new_enabled {
println!(
@ -158,7 +157,11 @@ impl Timers {
}
pub fn update(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask) {
for id in self.running_timers.iter() {
for id in 0..4 {
if self.running_timers & (1 << id) == 0 {
continue;
}
if !self.timers[id].ctl.cascade() {
let timer = &mut self.timers[id];
let num_overflows = timer.update(cycles, irqs);
@ -181,7 +184,7 @@ impl Timers {
}
bitfield! {
#[derive(Default)]
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct TimerCtl(u16);
impl Debug;
u16;