feat/savestates: Implement save/load state API for GameBoyAdvance
Using serde & bincode encoding Former-commit-id: f5e4c599497f6bdf3096fa99f8b2d6ce89278ef7
This commit is contained in:
parent
16142ee99d
commit
f4460b2740
|
@ -1,6 +1,8 @@
|
||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod exec;
|
pub mod exec;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::alu::*;
|
use super::alu::*;
|
||||||
use crate::core::arm7tdmi::{Addr, InstructionDecoder, InstructionDecoderError};
|
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 {
|
pub enum ArmCond {
|
||||||
EQ = 0b0000,
|
EQ = 0b0000,
|
||||||
NE = 0b0001,
|
NE = 0b0001,
|
||||||
|
@ -57,7 +59,7 @@ pub enum ArmCond {
|
||||||
AL = 0b1110,
|
AL = 0b1110,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum ArmFormat {
|
pub enum ArmFormat {
|
||||||
/// Branch and Exchange
|
/// Branch and Exchange
|
||||||
|
@ -97,7 +99,7 @@ pub enum ArmHalfwordTransferType {
|
||||||
SignedHalfwords = 0b11,
|
SignedHalfwords = 0b11,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct ArmInstruction {
|
pub struct ArmInstruction {
|
||||||
pub cond: ArmCond,
|
pub cond: ArmCond,
|
||||||
pub fmt: ArmFormat,
|
pub fmt: ArmFormat,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use ansi_term::{Colour, Style};
|
use ansi_term::{Colour, Style};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub use super::exception::Exception;
|
pub use super::exception::Exception;
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -13,7 +14,7 @@ use crate::core::sysbus::{
|
||||||
MemoryAccess, MemoryAccessType, MemoryAccessType::*, MemoryAccessWidth::*, SysBus,
|
MemoryAccess, MemoryAccessType, MemoryAccessType::*, MemoryAccessWidth::*, SysBus,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum PipelineState {
|
pub enum PipelineState {
|
||||||
Refill1,
|
Refill1,
|
||||||
Refill2,
|
Refill2,
|
||||||
|
@ -26,7 +27,7 @@ impl Default for PipelineState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
pub struct Core {
|
pub struct Core {
|
||||||
pub pc: u32,
|
pub pc: u32,
|
||||||
pub gpr: [u32; 15],
|
pub gpr: [u32; 15],
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use num::Num;
|
use num::Num;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub mod arm;
|
pub mod arm;
|
||||||
pub mod thumb;
|
pub mod thumb;
|
||||||
|
@ -21,7 +22,7 @@ pub const REG_SP: usize = 13;
|
||||||
|
|
||||||
pub(self) use crate::core::{Addr, Bus};
|
pub(self) use crate::core::{Addr, Bus};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||||
pub enum DecodedInstruction {
|
pub enum DecodedInstruction {
|
||||||
Arm(ArmInstruction),
|
Arm(ArmInstruction),
|
||||||
Thumb(ThumbInstruction),
|
Thumb(ThumbInstruction),
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/// The program status register
|
/// The program status register
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::bit::BitIndex;
|
use crate::bit::BitIndex;
|
||||||
use crate::num::FromPrimitive;
|
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 {
|
pub struct RegPSR {
|
||||||
raw: u32,
|
raw: u32,
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ impl ThumbDecodeError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum ThumbFormat {
|
pub enum ThumbFormat {
|
||||||
/// Format 1
|
/// Format 1
|
||||||
MoveShiftedReg,
|
MoveShiftedReg,
|
||||||
|
@ -77,7 +77,7 @@ pub enum ThumbFormat {
|
||||||
BranchLongWithLink,
|
BranchLongWithLink,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct ThumbInstruction {
|
pub struct ThumbInstruction {
|
||||||
pub fmt: ThumbFormat,
|
pub fmt: ThumbFormat,
|
||||||
pub raw: u16,
|
pub raw: u16,
|
||||||
|
|
|
@ -4,6 +4,8 @@ use std::io::prelude::*;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
@ -35,7 +37,7 @@ use super::{Addr, Bus, GBAResult};
|
||||||
/// 0C6h 26 Not used (seems to be unused)
|
/// 0C6h 26 Not used (seems to be unused)
|
||||||
/// 0E0h 4 JOYBUS Entry Pt. (32bit ARM branch opcode, eg. "B joy_start")
|
/// 0E0h 4 JOYBUS Entry Pt. (32bit ARM branch opcode, eg. "B joy_start")
|
||||||
///
|
///
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct CartridgeHeader {
|
pub struct CartridgeHeader {
|
||||||
// rom_entry_point: Addr,
|
// rom_entry_point: Addr,
|
||||||
game_title: String,
|
game_title: String,
|
||||||
|
@ -69,7 +71,7 @@ impl CartridgeHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Cartridge {
|
pub struct Cartridge {
|
||||||
pub header: CartridgeHeader,
|
pub header: CartridgeHeader,
|
||||||
bytes: Box<[u8]>,
|
bytes: Box<[u8]>,
|
||||||
|
|
|
@ -2,16 +2,10 @@ use super::iodev::consts::{REG_FIFO_A, REG_FIFO_B};
|
||||||
use super::sysbus::SysBus;
|
use super::sysbus::SysBus;
|
||||||
use super::{Addr, Bus, Interrupt, IrqBitmask};
|
use super::{Addr, Bus, Interrupt, IrqBitmask};
|
||||||
|
|
||||||
use bit_set::BitSet;
|
|
||||||
use num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
enum DmaTransferType {
|
|
||||||
Xfer16bit,
|
|
||||||
Xfer32bit,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DmaChannel {
|
pub struct DmaChannel {
|
||||||
id: usize,
|
id: usize,
|
||||||
|
|
||||||
|
@ -30,7 +24,7 @@ pub struct DmaChannel {
|
||||||
irq: Interrupt,
|
irq: Interrupt,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
struct DmaInternalRegs {
|
struct DmaInternalRegs {
|
||||||
src_addr: u32,
|
src_addr: u32,
|
||||||
dst_addr: u32,
|
dst_addr: u32,
|
||||||
|
@ -172,10 +166,10 @@ impl DmaChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct DmaController {
|
pub struct DmaController {
|
||||||
pub channels: [DmaChannel; 4],
|
pub channels: [DmaChannel; 4],
|
||||||
pending_set: BitSet,
|
pending_set: u8,
|
||||||
cycles: usize,
|
cycles: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,20 +182,22 @@ impl DmaController {
|
||||||
DmaChannel::new(2),
|
DmaChannel::new(2),
|
||||||
DmaChannel::new(3),
|
DmaChannel::new(3),
|
||||||
],
|
],
|
||||||
pending_set: BitSet::with_capacity(4),
|
pending_set: 0,
|
||||||
cycles: 0,
|
cycles: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_active(&self) -> bool {
|
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) {
|
pub fn perform_work(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
||||||
for id in self.pending_set.iter() {
|
for id in 0..4 {
|
||||||
self.channels[id].xfer(sb, irqs);
|
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) {
|
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),
|
8 => self.channels[channel_id].write_word_count(value),
|
||||||
10 => {
|
10 => {
|
||||||
if self.channels[channel_id].write_dma_ctrl(value) {
|
if self.channels[channel_id].write_dma_ctrl(value) {
|
||||||
self.pending_set.insert(channel_id);
|
self.pending_set |= 1 << channel_id;
|
||||||
} else {
|
} else {
|
||||||
self.pending_set.remove(channel_id);
|
self.pending_set &= !(1 << channel_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => panic!("Invalid dma offset {:x}", ofs),
|
_ => panic!("Invalid dma offset {:x}", ofs),
|
||||||
|
@ -225,7 +221,7 @@ impl DmaController {
|
||||||
pub fn notify_vblank(&mut self) {
|
pub fn notify_vblank(&mut self) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if self.channels[i].ctrl.is_enabled() && self.channels[i].ctrl.timing() == 1 {
|
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) {
|
pub fn notify_hblank(&mut self) {
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
if self.channels[i].ctrl.is_enabled() && self.channels[i].ctrl.timing() == 2 {
|
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].ctrl.timing() == 3
|
||||||
&& self.channels[i].dst == fifo_addr
|
&& self.channels[i].dst == fifo_addr
|
||||||
{
|
{
|
||||||
self.pending_set.insert(i);
|
self.pending_set |= 1 << i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Default)]
|
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct DmaChannelCtrl(u16);
|
pub struct DmaChannelCtrl(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u16;
|
u16;
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use bincode;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::arm7tdmi::Core;
|
use super::arm7tdmi::Core;
|
||||||
use super::cartridge::Cartridge;
|
use super::cartridge::Cartridge;
|
||||||
use super::gpu::*;
|
use super::gpu::*;
|
||||||
|
@ -23,6 +26,12 @@ pub struct GameBoyAdvance {
|
||||||
cycles_to_next_event: usize,
|
cycles_to_next_event: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct SaveState {
|
||||||
|
sysbus: Box<SysBus>,
|
||||||
|
cpu: Core,
|
||||||
|
}
|
||||||
|
|
||||||
impl GameBoyAdvance {
|
impl GameBoyAdvance {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cpu: Core,
|
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]
|
#[inline]
|
||||||
pub fn key_poll(&mut self) {
|
pub fn key_poll(&mut self) {
|
||||||
self.sysbus.io.keyinput = self.input_device.borrow_mut().poll();
|
self.sysbus.io.keyinput = self.input_device.borrow_mut().poll();
|
||||||
|
|
|
@ -2,6 +2,8 @@ use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::super::VideoInterface;
|
use super::super::VideoInterface;
|
||||||
use super::interrupt::IrqBitmask;
|
use super::interrupt::IrqBitmask;
|
||||||
use super::sysbus::{BoxedMemory, SysBus};
|
use super::sysbus::{BoxedMemory, SysBus};
|
||||||
|
@ -50,7 +52,7 @@ pub enum PixelFormat {
|
||||||
BPP8 = 1,
|
BPP8 = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||||
pub enum GpuState {
|
pub enum GpuState {
|
||||||
HDraw = 0,
|
HDraw = 0,
|
||||||
HBlank,
|
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 struct Background {
|
||||||
pub bgcnt: BgControl,
|
pub bgcnt: BgControl,
|
||||||
pub bgvofs: u16,
|
pub bgvofs: u16,
|
||||||
|
@ -107,7 +109,7 @@ pub struct Background {
|
||||||
mosaic_first_row: Scanline,
|
mosaic_first_row: Scanline,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
pub left: u8,
|
pub left: u8,
|
||||||
pub right: u8,
|
pub right: u8,
|
||||||
|
@ -151,7 +153,7 @@ pub struct AffineMatrix {
|
||||||
pub pd: i32,
|
pub pd: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Default, Copy, Clone)]
|
||||||
pub struct BgAffine {
|
pub struct BgAffine {
|
||||||
pub pa: i16, // dx
|
pub pa: i16, // dx
|
||||||
pub pb: i16, // dmx
|
pub pb: i16, // dmx
|
||||||
|
@ -163,7 +165,7 @@ pub struct BgAffine {
|
||||||
pub internal_y: i32,
|
pub internal_y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
||||||
pub struct ObjBufferEntry {
|
pub struct ObjBufferEntry {
|
||||||
pub(super) color: Rgb15,
|
pub(super) color: Rgb15,
|
||||||
pub(super) priority: u16,
|
pub(super) priority: u16,
|
||||||
|
@ -182,7 +184,7 @@ impl Default for ObjBufferEntry {
|
||||||
|
|
||||||
type VideoDeviceRcRefCell = Rc<RefCell<dyn VideoInterface>>;
|
type VideoDeviceRcRefCell = Rc<RefCell<dyn VideoInterface>>;
|
||||||
|
|
||||||
#[derive(DebugStub)]
|
#[derive(Serialize, Deserialize, Clone, DebugStub)]
|
||||||
pub struct Gpu {
|
pub struct Gpu {
|
||||||
pub state: GpuState,
|
pub state: GpuState,
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use super::sfx::BldMode;
|
use super::sfx::BldMode;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub const SCREEN_BLOCK_SIZE: u32 = 0x800;
|
pub const SCREEN_BLOCK_SIZE: u32 = 0x800;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
@ -60,6 +62,7 @@ impl BgControl {
|
||||||
|
|
||||||
// struct definitions below because the bitfield! macro messes up syntax highlighting in vscode.
|
// struct definitions below because the bitfield! macro messes up syntax highlighting in vscode.
|
||||||
bitfield! {
|
bitfield! {
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct DisplayControl(u16);
|
pub struct DisplayControl(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u16;
|
u16;
|
||||||
|
@ -79,6 +82,7 @@ bitfield! {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct DisplayStatus(u16);
|
pub struct DisplayStatus(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u16;
|
u16;
|
||||||
|
@ -92,7 +96,7 @@ bitfield! {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
|
||||||
pub struct BgControl(u16);
|
pub struct BgControl(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u16;
|
u16;
|
||||||
|
@ -106,7 +110,7 @@ bitfield! {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
|
||||||
pub struct RegMosaic(u16);
|
pub struct RegMosaic(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u32;
|
u32;
|
||||||
|
@ -117,7 +121,7 @@ bitfield! {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
pub struct BlendFlags: u32 {
|
pub struct BlendFlags: u32 {
|
||||||
const BG0 = 0b00000001;
|
const BG0 = 0b00000001;
|
||||||
const BG1 = 0b00000010;
|
const BG1 = 0b00000010;
|
||||||
|
@ -148,7 +152,7 @@ impl BlendFlags {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
|
||||||
pub struct BlendControl(u16);
|
pub struct BlendControl(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
pub into BlendFlags, top, _: 5, 0;
|
pub into BlendFlags, top, _: 5, 0;
|
||||||
|
@ -157,7 +161,7 @@ bitfield! {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
|
||||||
pub struct BlendAlpha(u16);
|
pub struct BlendAlpha(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u16;
|
u16;
|
||||||
|
@ -166,7 +170,7 @@ bitfield! {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
pub struct WindowFlags: u32 {
|
pub struct WindowFlags: u32 {
|
||||||
const BG0 = 0b00000001;
|
const BG0 = 0b00000001;
|
||||||
const BG1 = 0b00000010;
|
const BG1 = 0b00000010;
|
||||||
|
@ -200,7 +204,7 @@ const BG_WIN_FLAG: [WindowFlags; 4] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
|
||||||
pub struct WindowReg(u16);
|
pub struct WindowReg(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u16;
|
u16;
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
//! Helper type to deal with the GBA's 15bit color
|
//! Helper type to deal with the GBA's 15bit color
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Copy, Clone, Default, PartialEq)]
|
#[derive(Serialize, Deserialize, Copy, Clone, Default, PartialEq)]
|
||||||
pub struct Rgb15(u16);
|
pub struct Rgb15(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
pub r, set_r: 4, 0;
|
pub r, set_r: 4, 0;
|
||||||
|
|
|
@ -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)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum Interrupt {
|
pub enum Interrupt {
|
||||||
LCD_VBlank = 0,
|
LCD_VBlank = 0,
|
||||||
|
@ -17,7 +19,7 @@ pub enum Interrupt {
|
||||||
GamePak = 13,
|
GamePak = 13,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
pub struct InterruptController {
|
pub struct InterruptController {
|
||||||
pub interrupt_master_enable: bool,
|
pub interrupt_master_enable: bool,
|
||||||
pub interrupt_enable: IrqBitmask,
|
pub interrupt_enable: IrqBitmask,
|
||||||
|
@ -48,7 +50,7 @@ impl IrqBitmask {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Default, Copy, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Default, Copy, Clone, PartialEq)]
|
||||||
pub struct IrqBitmask(u16);
|
pub struct IrqBitmask(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u16;
|
u16;
|
||||||
|
|
|
@ -7,15 +7,18 @@ use super::sound::SoundController;
|
||||||
use super::timer::Timers;
|
use super::timer::Timers;
|
||||||
use super::{Addr, Bus};
|
use super::{Addr, Bus};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use self::consts::*;
|
use self::consts::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum HaltState {
|
pub enum HaltState {
|
||||||
Running,
|
Running,
|
||||||
Halt, // In Halt mode, the CPU is paused as long as (IE AND IF)=0,
|
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
|
Stop, // In Stop mode, most of the hardware including sound and video are paused
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct IoDevices {
|
pub struct IoDevices {
|
||||||
pub intc: InterruptController,
|
pub intc: InterruptController,
|
||||||
pub gpu: Box<Gpu>,
|
pub gpu: Box<Gpu>,
|
||||||
|
@ -262,7 +265,7 @@ impl Bus for IoDevices {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Default, Copy, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Default, Copy, Clone, PartialEq)]
|
||||||
pub struct WaitControl(u16);
|
pub struct WaitControl(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u16;
|
u16;
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use crate::{AudioInterface, StereoSample};
|
use crate::{AudioInterface, StereoSample};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
const PI: f32 = std::f32::consts::PI;
|
const PI: f32 = std::f32::consts::PI;
|
||||||
|
|
||||||
pub trait Resampler {
|
pub trait Resampler {
|
||||||
fn push_sample(&mut self, s: StereoSample, audio: &mut dyn AudioInterface);
|
fn push_sample(&mut self, s: StereoSample, audio: &mut dyn AudioInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct CosineResampler {
|
pub struct CosineResampler {
|
||||||
last_in_sample: StereoSample,
|
last_in_sample: StereoSample,
|
||||||
phase: f32,
|
phase: f32,
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// TODO write tests or replace with a crate
|
// TODO write tests or replace with a crate
|
||||||
const SOUND_FIFO_CAPACITY: usize = 32;
|
const SOUND_FIFO_CAPACITY: usize = 32;
|
||||||
|
|
||||||
#[derive(Debug)]
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct SoundFifo {
|
pub struct SoundFifo {
|
||||||
wr_pos: usize,
|
wr_pos: usize,
|
||||||
rd_pos: usize,
|
rd_pos: usize,
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use bit::BitIndex;
|
use bit::BitIndex;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::dma::DmaController;
|
use super::dma::DmaController;
|
||||||
use super::iodev::consts::*;
|
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 DMA_TIMERS: [usize; 2] = [0, 1];
|
||||||
const DUTY_RATIOS: [f32; 4] = [0.125, 0.25, 0.5, 0.75];
|
const DUTY_RATIOS: [f32; 4] = [0.125, 0.25, 0.5, 0.75];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
struct NoiseChannel {}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct DmaSoundChannel {
|
struct DmaSoundChannel {
|
||||||
value: i8,
|
value: i8,
|
||||||
volume_shift: i16,
|
volume_shift: i16,
|
||||||
|
@ -62,7 +60,7 @@ const REG_FIFO_B_H: u32 = REG_FIFO_B + 2;
|
||||||
|
|
||||||
type AudioDeviceRcRefCell = Rc<RefCell<dyn AudioInterface>>;
|
type AudioDeviceRcRefCell = Rc<RefCell<dyn AudioInterface>>;
|
||||||
|
|
||||||
#[derive(DebugStub)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct SoundController {
|
pub struct SoundController {
|
||||||
sample_rate_to_cpu_freq: usize, // how many "cycles" are a sample?
|
sample_rate_to_cpu_freq: usize, // how many "cycles" are a sample?
|
||||||
cycles: usize, // cycles count when we last provided a new sample.
|
cycles: usize, // cycles count when we last provided a new sample.
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::cartridge::Cartridge;
|
use super::cartridge::Cartridge;
|
||||||
use super::gpu::{GpuState, VIDEO_RAM_SIZE};
|
use super::gpu::{GpuState, VIDEO_RAM_SIZE};
|
||||||
use super::iodev::IoDevices;
|
use super::iodev::IoDevices;
|
||||||
|
@ -64,7 +66,7 @@ impl fmt::Display for MemoryAccess {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct BoxedMemory {
|
pub struct BoxedMemory {
|
||||||
pub mem: Box<[u8]>,
|
pub mem: Box<[u8]>,
|
||||||
}
|
}
|
||||||
|
@ -77,15 +79,17 @@ impl BoxedMemory {
|
||||||
|
|
||||||
impl Bus for BoxedMemory {
|
impl Bus for BoxedMemory {
|
||||||
fn read_8(&self, addr: Addr) -> u8 {
|
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) {
|
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]);
|
struct DummyBus([u8; 4]);
|
||||||
|
|
||||||
impl Bus for DummyBus {
|
impl Bus for DummyBus {
|
||||||
|
@ -96,6 +100,7 @@ impl Bus for DummyBus {
|
||||||
fn write_8(&mut self, _addr: Addr, _value: u8) {}
|
fn write_8(&mut self, _addr: Addr, _value: u8) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct SysBus {
|
pub struct SysBus {
|
||||||
pub io: IoDevices,
|
pub io: IoDevices,
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,10 @@ use super::interrupt::{Interrupt, IrqBitmask};
|
||||||
use super::iodev::consts::*;
|
use super::iodev::consts::*;
|
||||||
use super::sysbus::SysBus;
|
use super::sysbus::SysBus;
|
||||||
|
|
||||||
use bit_set::BitSet;
|
|
||||||
|
|
||||||
use num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Timer {
|
pub struct Timer {
|
||||||
// registers
|
// registers
|
||||||
pub ctl: TimerCtl,
|
pub ctl: TimerCtl,
|
||||||
|
@ -66,10 +65,10 @@ impl Timer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Timers {
|
pub struct Timers {
|
||||||
timers: [Timer; 4],
|
timers: [Timer; 4],
|
||||||
running_timers: BitSet,
|
running_timers: u8,
|
||||||
pub trace: bool,
|
pub trace: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +89,7 @@ impl Timers {
|
||||||
pub fn new() -> Timers {
|
pub fn new() -> Timers {
|
||||||
Timers {
|
Timers {
|
||||||
timers: [Timer::new(0), Timer::new(1), Timer::new(2), Timer::new(3)],
|
timers: [Timer::new(0), Timer::new(1), Timer::new(2), Timer::new(3)],
|
||||||
running_timers: BitSet::with_capacity(4),
|
running_timers: 0,
|
||||||
trace: false,
|
trace: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,9 +100,9 @@ impl Timers {
|
||||||
let new_enabled = self[id].ctl.enabled();
|
let new_enabled = self[id].ctl.enabled();
|
||||||
let cascade = self.timers[id].ctl.cascade();
|
let cascade = self.timers[id].ctl.cascade();
|
||||||
if new_enabled && !cascade {
|
if new_enabled && !cascade {
|
||||||
self.running_timers.insert(id);
|
self.running_timers |= 1 << id;
|
||||||
} else {
|
} else {
|
||||||
self.running_timers.remove(id);
|
self.running_timers &= !(1 << id);
|
||||||
}
|
}
|
||||||
if self.trace && old_enabled != new_enabled {
|
if self.trace && old_enabled != new_enabled {
|
||||||
println!(
|
println!(
|
||||||
|
@ -158,7 +157,11 @@ impl Timers {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
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() {
|
if !self.timers[id].ctl.cascade() {
|
||||||
let timer = &mut self.timers[id];
|
let timer = &mut self.timers[id];
|
||||||
let num_overflows = timer.update(cycles, irqs);
|
let num_overflows = timer.update(cycles, irqs);
|
||||||
|
@ -181,7 +184,7 @@ impl Timers {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Default)]
|
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct TimerCtl(u16);
|
pub struct TimerCtl(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
u16;
|
u16;
|
||||||
|
|
Reference in a new issue