Skip to content
Snippets Groups Projects
state.rs 55.7 KiB
Newer Older
//! System save state (BOS and [BESS](https://github.com/LIJI32/SameBoy/blob/master/BESS.md) formats) functions and structures.
//!
//! The BOS (Boytacean Save) format is a custom save state format that contains the emulator state and the frame buffer.
//! Its serialization includes header, info, image buffer and then a BESS (Best Effort Save State) footer with the state itself.
//!
//! The [BESS](https://github.com/LIJI32/SameBoy/blob/master/BESS.md) format is a format developed by the [SameBoy](https://sameboy.github.io/) emulator and is used to store the emulator state
//! in agnostic and compatible way.
use boytacean_common::error::Error;
use boytacean_encoding::zippy::{decode_zippy, encode_zippy};
use std::{
    convert::TryInto,
    fmt::{self, Display, Formatter},
    fs::File,
    io::{Cursor, Read, Seek, SeekFrom, Write},
    mem::size_of,
use crate::{
    gb::{GameBoy, GameBoySpeed},
    info::Info,
    ppu::{DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_SIZE},
João Magalhães's avatar
João Magalhães committed
    rom::{CgbMode, MbcType},
    util::{get_timestamp, save_bmp},
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;

/// Magic string for the BOS (Boytacean Save) format.
pub const BOS_MAGIC: &str = "BOS\0";

/// Magic string ("BOS\0") in little endian unsigned 32 bit format.
pub const BOS_MAGIC_UINT: u32 = 0x00534f42;

/// Magic string for the BOSC (Boytacean Save Compressed) format.
pub const BOSC_MAGIC: &str = "BOSC\0";

/// Magic string ("BOSC") in little endian unsigned 32 bit format.
pub const BOSC_MAGIC_UINT: u32 = 0x43534f42;

/// Current version of the BOS (Boytacean Save) format.
pub const BOS_VERSION: u8 = 1;

/// Current version of the BOS (Boytacean Save Compressed) format.
pub const BOSC_VERSION: u8 = 1;

/// Magic number for the BESS file format.
pub const BESS_MAGIC: u32 = 0x53534542;

#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub enum SaveStateFormat {
    /// Boytacean Save format (uncompressed) (BOS).

    /// Boytacean Save Compressed format (BOSC).
    /// The format uses the Zippy compression algorithm.
    Bosc,

    /// Best Effort Save State format (BESS).
#[derive(Clone, Copy)]
pub enum BosBlockKind {
    ImageBuffer = 0x02,
    Unknown = 0xff,
}

impl BosBlockKind {
    fn from_u8(value: u8) -> Self {
        match value {
            0x01 => Self::Info,
            0x02 => Self::ImageBuffer,
            _ => Self::Unknown,
        }
    }
impl From<u8> for BosBlockKind {
    fn from(value: u8) -> Self {
        Self::from_u8(value)
    }
}

pub trait Serialize {
    /// Writes the data from the internal structure into the
    /// provided buffer.
    fn write(&mut self, buffer: &mut Cursor<Vec<u8>>);

    /// Reads the data from the provided buffer and populates
    /// the internal structure with it.
    fn read(&mut self, data: &mut Cursor<Vec<u8>>);
pub trait State {
    /// Obtains a new instance of the state from the provided
    /// `GameBoy` instance and returns it.
    fn from_gb(gb: &mut GameBoy) -> Result<Self, Error>

    /// Applies the state to the provided `GameBoy` instance.
    fn to_gb(&self, gb: &mut GameBoy) -> Result<(), Error>;
pub trait StateBox {
    /// Obtains a new instance of the state from the provided
    /// `GameBoy` instance and returns it as a boxed value.
    fn from_gb(gb: &mut GameBoy) -> Result<Box<Self>, Error>
    where
        Self: Sized;

    /// Applies the state to the provided `GameBoy` instance.
    fn to_gb(&self, gb: &mut GameBoy) -> Result<(), Error>;
}

#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Default)]
pub struct BoscState {
    magic: u32,
    version: u8,
    bos: BosState,
}

impl BoscState {
    /// Checks if the data contained in the provided
    /// buffer represents a valid BOSC (Boytacean Save
    /// Compressed) file structure, thought magic
    /// string validation.
    pub fn is_bosc(data: &mut Cursor<Vec<u8>>) -> bool {
        let mut buffer = [0x00; size_of::<u32>()];
        data.read_exact(&mut buffer).unwrap();
        let magic = u32::from_le_bytes(buffer);
        data.rewind().unwrap();
        magic == BOSC_MAGIC_UINT
    }

    pub fn verify(&self) -> Result<(), Error> {
        if self.magic != BOSC_MAGIC_UINT {
            return Err(Error::CustomError(String::from("Invalid magic")));
        }
        self.bos.verify()?;
        Ok(())
    }
}

impl Serialize for BoscState {
    fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
        buffer.write_all(&self.magic.to_le_bytes()).unwrap();
        buffer.write_all(&self.version.to_le_bytes()).unwrap();

        let mut cursor = Cursor::new(vec![]);
        self.bos.write(&mut cursor);
        cursor.rewind().unwrap();
        let mut bos_buffer = vec![];
        cursor.read_to_end(&mut bos_buffer).unwrap();

        let bos_compressed = encode_zippy(&bos_buffer).unwrap();
        buffer.write_all(&bos_compressed).unwrap();
    }

    fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
        let mut buffer = [0x00; size_of::<u32>()];
        data.read_exact(&mut buffer).unwrap();
        self.magic = u32::from_le_bytes(buffer);
        let mut buffer = [0x00; size_of::<u8>()];
        data.read_exact(&mut buffer).unwrap();
        self.version = u8::from_le_bytes(buffer);

        let mut bos_compressed = vec![];
        data.read_to_end(&mut bos_compressed).unwrap();
        let bos_buffer = decode_zippy(&bos_compressed).unwrap();
        let mut bos_cursor = Cursor::new(bos_buffer);

        self.bos.read(&mut bos_cursor);
    }
}

impl StateBox for BoscState {
    fn from_gb(gb: &mut GameBoy) -> Result<Box<Self>, Error> {
        Ok(Box::new(Self {
            magic: BOSC_MAGIC_UINT,
            version: BOSC_VERSION,
            bos: *BosState::from_gb(gb)?,
        }))
    }

    fn to_gb(&self, gb: &mut GameBoy) -> Result<(), Error> {
        self.verify()?;
        self.bos.to_gb(gb)?;
        Ok(())
    }
}

#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Default)]
pub struct BosState {
    magic: u32,
Loading
Loading full blame...