Skip to content
Snippets Groups Projects
state.rs 48.2 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 std::{
    convert::TryInto,
    fmt::{self, Display, Formatter},
    fs::File,
    io::{Cursor, Read, Seek, SeekFrom, Write},
    mem::size_of,
    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;

/// Current version of the BOS (Boytacean Save) format.
pub const BOS_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 {
    Bos,
    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,
        }
    }
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>;
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Default)]
pub struct BosState {
    magic: u32,
    version: u8,
    info: Option<BosInfo>,
    image_buffer: Option<BosImageBuffer>,
    bess: BessState,
}

impl BosState {
    /// Checks if the data contained in the provided
    /// buffer represents a valid BOS (Boytacean Save)
    /// file structure, thought magic string validation.
    pub fn is_bos(data: &mut Cursor<Vec<u8>>) -> bool {
        let mut buffer = [0x00; 4];
        data.read_exact(&mut buffer).unwrap();
        let magic = u32::from_le_bytes(buffer);
João Magalhães's avatar
João Magalhães committed
        data.rewind().unwrap();
        magic == BOS_MAGIC_UINT
    }

    pub fn verify(&self) -> Result<(), Error> {
        if self.magic != BOS_MAGIC_UINT {
            return Err(Error::CustomError(String::from("Invalid magic")));
        }
        self.bess.verify()?;
        Ok(())
    }
    pub fn save_image_bmp(&self, file_path: &str) -> Result<(), Error> {
        if let Some(image_buffer) = &self.image_buffer {
            image_buffer.save_bmp(file_path)?;
            Ok(())
        } else {
            Err(Error::CustomError(String::from("No image buffer found")))
        }
    }

    fn build_block_count(&self) -> u8 {
        let mut count = 0_u8;
        if self.info.is_some() {
            count += 1;
        }
        if self.image_buffer.is_some() {
            count += 1;
        }
        count
    }
impl BosState {
    pub fn timestamp(&self) -> Result<u64, Error> {
        if let Some(info) = &self.info {
            Ok(info.timestamp)
        } else {
            Err(Error::CustomError(String::from("No timestamp available")))
    pub fn agent(&self) -> Result<String, Error> {
        if let Some(info) = &self.info {
            Ok(format!("{}/{}", info.agent, info.agent_version))
        } else {
            Err(Error::CustomError(String::from("No agent available")))
    pub fn model(&self) -> Result<String, Error> {
        if let Some(info) = &self.info {
            Ok(info.model.clone())
        } else {
            Err(Error::CustomError(String::from("No model available")))
    pub fn image_eager(&self) -> Result<Vec<u8>, Error> {
        if let Some(image_buffer) = &self.image_buffer {
            Ok(image_buffer.image.to_vec())
        } else {
            Err(Error::CustomError(String::from("No image available")))
#[cfg(feature = "wasm")]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl BosState {
    pub fn timestamp_wa(&self) -> Result<u64, String> {
        Self::timestamp(self).map_err(|e| e.to_string())
    }

    pub fn agent_wa(&self) -> Result<String, String> {
        Self::agent(self).map_err(|e| e.to_string())
    }

    pub fn model_wa(&self) -> Result<String, String> {
        Self::model(self).map_err(|e| e.to_string())
    }

    pub fn image_eager_wa(&self) -> Result<Vec<u8>, String> {
        Self::image_eager(self).map_err(|e| e.to_string())
    }
}

impl Serialize for BosState {
    fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
        self.block_count = self.build_block_count();

        buffer.write_all(&self.magic.to_le_bytes()).unwrap();
        buffer.write_all(&self.version.to_le_bytes()).unwrap();
        buffer.write_all(&self.block_count.to_le_bytes()).unwrap();

        if let Some(info) = &mut self.info {
            info.write(buffer);
Loading
Loading full blame...