Skip to content
Snippets Groups Projects
state.rs 72.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • //! 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::{
    
        data::{
    
            read_bytes, read_into, read_u16, read_u32, read_u64, read_u8, write_bytes, write_u16,
            write_u32, write_u64, write_u8,
    
        error::Error,
    
    João Magalhães's avatar
    João Magalhães committed
        util::{save_bmp, timestamp},
    
    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,
    
        disable_pedantic, enable_pedantic,
    
        gb::{GameBoy, GameBoyDevice, GameBoyMode, GameBoySpeed},
    
        ppu::{DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_SIZE},
    
    João Magalhães's avatar
    João Magalhães committed
        rom::{CgbMode, MbcType},
    
    #[cfg(feature = "wasm")]
    use wasm_bindgen::prelude::*;
    
    
    /// Magic string for the BOSC (Boytacean Save Compressed) format.
    
    pub const BOSC_MAGIC: &str = "BOSC";
    
    
    /// Magic string ("BOSC") in little endian unsigned 32 bit format.
    pub const BOSC_MAGIC_UINT: u32 = 0x43534f42;
    
    
    /// 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 Compressed) format.
    pub const BOSC_VERSION: u8 = 1;
    
    
    /// 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;
    
    
    /// Represents the different formats for the state storage
    /// and retrieval.
    ///
    /// Different formats will have different levels of detail
    /// and will require different amounts of data to be
    /// stored and retrieved.
    #[derive(Clone, Copy, PartialEq, Eq, Debug)]
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    pub enum StateFormat {
        /// Minimal state format, meaning that only the most basic
        /// elements of the component will be stored and retrieved.
        Minimal = 1,
    
        /// Partial state format, meaning that only the essential
        /// elements of the component will be stored and retrieved.
        /// All the remaining data, should inferred or computed.
        Partial = 2,
    
        /// Full state format, meaning that every single element
        /// of the component will be stored and retrieved. This
        /// should included redundant and calculated data.
        Full = 3,
    }
    
    impl From<u8> for StateFormat {
        fn from(value: u8) -> Self {
            match value {
                1 => Self::Minimal,
                2 => Self::Partial,
                3 => Self::Full,
                _ => Self::Partial,
            }
        }
    }
    
    
    /// Represents a component that is able to store and retrieve
    /// the state of its internal structure.
    
    João Magalhães's avatar
    João Magalhães committed
    ///
    
    /// This trait is used to define the behavior of the state
    /// components that are used to store the emulator state.
    
    João Magalhães's avatar
    João Magalhães committed
    ///
    
    /// Ideally each of Game Boy's components should implement
    /// this trait to allow the state to be saved and restored
    /// in a consistent way.
    pub trait StateComponent {
    
        fn state(&self, format: Option<StateFormat>) -> Result<Vec<u8>, Error>;
        fn set_state(&mut self, data: &[u8], format: Option<StateFormat>) -> Result<(), Error>;
    
    #[derive(Clone, Copy, PartialEq, Eq, Debug)]
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    
    pub enum SaveStateFormat {
    
        /// Boytacean Save Compressed format (BOSC).
    
        /// This format uses the Zippy compression algorithm
        /// to compress the underlying BOS contents.
    
        /// Boytacean Save format (uncompressed) (BOS).
    
        /// Best Effort Save State format (BESS).
    
    João Magalhães's avatar
    João Magalhães committed
    impl SaveStateFormat {
        pub fn description(&self) -> String {
            match self {
                Self::Bosc => String::from("BOSC"),
                Self::Bos => String::from("BOS"),
                Self::Bess => String::from("BESS"),
            }
        }
    
    
    João Magalhães's avatar
    João Magalhães committed
        pub fn from_string(value: &str) -> Self {
    
    João Magalhães's avatar
    João Magalhães committed
            match value {
                "BOSC" => Self::Bosc,
                "BOS" => Self::Bos,
                "BESS" => Self::Bess,
                _ => Self::Bos,
            }
        }
    }
    
    impl From<&str> for SaveStateFormat {
        fn from(value: &str) -> Self {
    
    João Magalhães's avatar
    João Magalhães committed
            Self::from_string(value)
    
    João Magalhães's avatar
    João Magalhães committed
        }
    }
    
    impl Display for SaveStateFormat {
        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
            write!(f, "{}", self.description())
        }
    }
    
    
    #[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,
    
                0x03 => Self::DeviceState,
    
    
        pub fn description(&self) -> String {
            match self {
                Self::Info => String::from("Info"),
                Self::ImageBuffer => String::from("ImageBuffer"),
                Self::DeviceState => String::from("DeviceState"),
                Self::Unknown => String::from("Unknown"),
            }
        }
    }
    
    impl Display for BosBlockKind {
        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
            write!(f, "{}", self.description())
        }
    
    impl From<u8> for BosBlockKind {
        fn from(value: u8) -> Self {
            Self::from_u8(value)
        }
    }
    
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    pub struct FromGbOptions {
        thumbnail: bool,
    
        state_format: Option<StateFormat>,
    
    João Magalhães's avatar
    João Magalhães committed
        agent: Option<String>,
        agent_version: Option<String>,
    
    }
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    impl FromGbOptions {
    
        pub fn new(
            thumbnail: bool,
            state_format: Option<StateFormat>,
            agent: Option<String>,
            agent_version: Option<String>,
        ) -> Self {
    
    João Magalhães's avatar
    João Magalhães committed
            Self {
                thumbnail,
    
    João Magalhães's avatar
    João Magalhães committed
                agent,
                agent_version,
            }
    
        }
    }
    
    impl Default for FromGbOptions {
        fn default() -> Self {
    
    João Magalhães's avatar
    João Magalhães committed
            Self {
                thumbnail: true,
    
                state_format: None,
    
    João Magalhães's avatar
    João Magalhães committed
                agent: None,
                agent_version: None,
            }
    
        }
    }
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    
    pub struct ToGbOptions {
        reload: bool,
    }
    
    impl ToGbOptions {
        pub fn new(reload: bool) -> Self {
            Self { reload }
        }
    }
    
    impl Default for ToGbOptions {
        fn default() -> Self {
            Self { reload: true }
        }
    }
    
    pub trait Serialize {
    
        /// Writes the data from the internal structure into the
        /// provided buffer.
    
        fn write<W: Write + Seek>(&mut self, writer: &mut W) -> Result<(), Error>;
    
    
        /// Reads the data from the provided buffer and populates
        /// the internal structure with it.
    
        fn read<R: Read + Seek>(&mut self, reader: &mut R) -> Result<(), Error>;
    
    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, options: &FromGbOptions) -> Result<Box<Self>, Error>
    
        where
            Self: Sized;
    
        /// Applies the state to the provided `GameBoy` instance.
    
        fn to_gb(&self, gb: &mut GameBoy, options: &ToGbOptions) -> Result<(), Error>;
    
    pub trait StateConfig {
    
        /// Obtains the Game Boy execution mode expected by the
        /// state instance.
        fn mode(&self) -> Result<GameBoyMode, Error>;
    
    pub trait StateInfo {
        fn timestamp(&self) -> Result<u64, Error>;
        fn agent(&self) -> Result<String, Error>;
        fn model(&self) -> Result<String, Error>;
    
        fn title(&self) -> Result<String, Error>;
    
        fn image_eager(&self) -> Result<Vec<u8>, Error>;
        fn has_image(&self) -> bool;
    }
    
    
    #[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<R: Read + Seek>(reader: &mut R) -> Result<bool, Error> {
            let magic = read_u32(reader)?;
            reader.rewind()?;
    
            Ok(magic == BOSC_MAGIC_UINT)
    
        }
    
        pub fn verify(&self) -> Result<(), Error> {
            if self.magic != BOSC_MAGIC_UINT {
    
                return Err(Error::DataError(String::from("Invalid magic")));
            }
            if self.version != BOSC_VERSION {
                return Err(Error::DataError(format!(
                    "Invalid version, expected {BOS_VERSION}, got {}",
                    self.version
                )));
    
            }
            self.bos.verify()?;
            Ok(())
        }
    }
    
    impl Serialize for BoscState {
    
        fn write<W: Write + Seek>(&mut self, writer: &mut W) -> Result<(), Error> {
            write_u32(writer, self.magic)?;
            write_u8(writer, self.version)?;
    
    
            let mut cursor = Cursor::new(vec![]);
    
            self.bos.write(&mut cursor)?;
    
            let bos_compressed = encode_zippy(&cursor.into_inner(), None, None)?;
    
            write_bytes(writer, &bos_compressed)?;
    
        fn read<R: Read + Seek>(&mut self, reader: &mut R) -> Result<(), Error> {
            self.magic = read_u32(reader)?;
            self.version = read_u8(reader)?;
    
    
            let mut bos_compressed = vec![];
    
            reader.read_to_end(&mut bos_compressed)?;
    
            let bos_buffer = decode_zippy(&bos_compressed, None)?;
    
            let mut bos_cursor = Cursor::new(bos_buffer);
    
    
            self.bos.read(&mut bos_cursor)?;
    
            Ok(())
    
        }
    }
    
    impl StateBox for BoscState {
    
        fn from_gb(gb: &mut GameBoy, options: &FromGbOptions) -> Result<Box<Self>, Error> {
    
            Ok(Box::new(Self {
                magic: BOSC_MAGIC_UINT,
                version: BOSC_VERSION,
    
                bos: *BosState::from_gb(gb, options)?,
    
        fn to_gb(&self, gb: &mut GameBoy, options: &ToGbOptions) -> Result<(), Error> {
    
            self.bos.to_gb(gb, options)?;
    
    impl StateConfig for BoscState {
    
        fn mode(&self) -> Result<GameBoyMode, Error> {
    
    João Magalhães's avatar
    João Magalhães committed
            self.bos.mode()
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    
    #[derive(Default)]
    pub struct BosState {
        magic: u32,
        version: u8,
    
        info: Option<BosInfo>,
    
        image_buffer: Option<BosImageBuffer>,
    
        device_states: Vec<BosDeviceState>,
    
        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<R: Read + Seek>(reader: &mut R) -> Result<bool, Error> {
            let magic = read_u32(reader)?;
            reader.rewind()?;
    
            Ok(magic == BOS_MAGIC_UINT)
    
        pub fn verify(&self) -> Result<(), Error> {
    
            if self.magic != BOS_MAGIC_UINT {
    
                return Err(Error::CustomError(String::from("Invalid magic")));
    
            if self.version != BOS_VERSION {
                return Err(Error::CustomError(format!(
                    "Invalid version, expected {BOS_VERSION}, got {}",
                    self.version
                )));
            }
    
            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 += self.device_states.len() as u8;
    
    impl StateInfo for BosState {
        fn timestamp(&self) -> Result<u64, Error> {
    
            if let Some(info) = &self.info {
                Ok(info.timestamp)
            } else {
    
                Err(Error::CustomError(String::from("No timestamp available")))
    
        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")))
    
        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")))
    
        fn title(&self) -> Result<String, Error> {
            self.bess.title()
        }
    
    
        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")))
    
        fn has_image(&self) -> bool {
    
            self.image_buffer.is_some()
        }
    
    #[cfg(feature = "wasm")]
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    
    impl BosState {
        pub fn timestamp_wa(&self) -> Result<u64, String> {
    
            Ok(Self::timestamp(self)?)
    
        pub fn agent_wa(&self) -> Result<String, String> {
    
            Ok(Self::agent(self)?)
    
        pub fn model_wa(&self) -> Result<String, String> {
    
            Ok(Self::model(self)?)
    
        pub fn title_wa(&self) -> Result<String, String> {
            Ok(Self::title(self)?)
        }
    
    
        pub fn image_eager_wa(&self) -> Result<Vec<u8>, String> {
    
            Ok(Self::image_eager(self)?)
        }
    
    
        pub fn has_image_wa(&self) -> bool {
    
            self.has_image()
    
    impl Serialize for BosState {
    
        fn write<W: Write + Seek>(&mut self, writer: &mut W) -> Result<(), Error> {
    
            self.block_count = self.build_block_count();
    
    
            write_u32(writer, self.magic)?;
            write_u8(writer, self.version)?;
            write_u8(writer, self.block_count)?;
    
            if let Some(info) = &mut self.info {
    
                info.write(writer)?;
    
            if let Some(image_buffer) = &mut self.image_buffer {
    
                image_buffer.write(writer)?;
    
            for device_state in &mut self.device_states {
    
                device_state.write(writer)?;
    
            self.bess.write(writer)?;
    
        fn read<R: Read + Seek>(&mut self, reader: &mut R) -> Result<(), Error> {
            self.magic = read_u32(reader)?;
            self.version = read_u8(reader)?;
            self.block_count = read_u8(reader)?;
    
    
            for _ in 0..self.block_count {
    
                let block = BosBlock::from_data(reader)?;
    
                let offset = -((size_of::<u8>() + size_of::<u16>() + size_of::<u32>()) as i64);
    
                reader.seek(SeekFrom::Current(offset))?;
    
                    BosBlockKind::Info => {
    
                        self.info = Some(BosInfo::from_data(reader)?);
    
                    BosBlockKind::ImageBuffer => {
    
                        self.image_buffer = Some(BosImageBuffer::from_data(reader)?);
    
                    BosBlockKind::DeviceState => {
    
                        self.device_states.push(BosDeviceState::from_data(reader)?);
    
                        reader.seek(SeekFrom::Current(-offset))?;
                        reader.seek(SeekFrom::Current(block.size as i64))?;
    
                    }
                }
            }
    
            self.block_count = self.build_block_count();
    
    
            self.bess.read(reader)?;
    
    impl StateBox for BosState {
    
        fn from_gb(gb: &mut GameBoy, options: &FromGbOptions) -> Result<Box<Self>, Error> {
    
            Ok(Box::new(Self {
    
                magic: BOS_MAGIC_UINT,
                version: BOS_VERSION,
    
                block_count: 2,
    
                info: Some(*<BosInfo as StateBox>::from_gb(gb, options)?),
    
                image_buffer: if options.thumbnail {
                    Some(BosImageBuffer::from_gb(gb)?)
                } else {
                    None
                },
    
                device_states: vec![
    
                    BosDeviceState::from_gb(gb, GameBoyDevice::Cpu, options)?,
                    BosDeviceState::from_gb(gb, GameBoyDevice::Ppu, options)?,
                    BosDeviceState::from_gb(gb, GameBoyDevice::Apu, options)?,
                    BosDeviceState::from_gb(gb, GameBoyDevice::Dma, options)?,
                    BosDeviceState::from_gb(gb, GameBoyDevice::Pad, options)?,
                    BosDeviceState::from_gb(gb, GameBoyDevice::Timer, options)?,
    
                bess: *BessState::from_gb(gb, options)?,
    
        fn to_gb(&self, gb: &mut GameBoy, options: &ToGbOptions) -> Result<(), Error> {
    
            self.verify()?;
    
            self.bess.to_gb(gb, options)?;
    
            for device_state in &self.device_states {
    
                device_state.to_gb(gb, options)?;
    
    impl StateConfig for BosState {
    
        fn mode(&self) -> Result<GameBoyMode, Error> {
    
    João Magalhães's avatar
    João Magalhães committed
            self.bess.mode()
    
    }
    
    pub struct BosBlock {
        kind: BosBlockKind,
    
        pub fn new(kind: BosBlockKind, version: u16, size: u32) -> Self {
            Self {
                kind,
                version,
                size,
            }
    
        pub fn from_data<R: Read + Seek>(reader: &mut R) -> Result<Self, Error> {
    
            let mut instance = Self::default();
    
            instance.read(reader)?;
    
    
        pub fn description(&self) -> String {
            format!("{} version={} size={}", self.kind, self.version, self.size)
        }
    
    }
    
    impl Serialize for BosBlock {
    
        fn write<W: Write + Seek>(&mut self, writer: &mut W) -> Result<(), Error> {
            write_u8(writer, self.kind as u8)?;
    
            write_u16(writer, self.version)?;
    
            write_u32(writer, self.size)?;
    
        fn read<R: Read + Seek>(&mut self, reader: &mut R) -> Result<(), Error> {
    
            let check = self.version != 0;
            let expected_version = self.version;
    
    
            self.kind = read_u8(reader)?.into();
    
            self.version = read_u16(reader)?;
    
            self.size = read_u32(reader)?;
    
    
            if check && self.version != expected_version {
                return Err(Error::DataError(format!(
                    "Invalid version, expected {expected_version}, got {} for block ({})",
                    self.version, self
                )));
            }
    
        }
    }
    
    impl Default for BosBlock {
        fn default() -> Self {
    
            Self::new(BosBlockKind::Info, 0, 0)
        }
    }
    
    impl Display for BosBlock {
        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
            write!(f, "{}", self.description())
    
        }
    }
    
    pub struct BosInfo {
        header: BosBlock,
        timestamp: u64,
        agent: String,
        agent_version: String,
        model: String,
    }
    
    impl BosInfo {
        pub fn new(model: String, timestamp: u64, agent: String, agent_version: String) -> Self {
            Self {
                header: BosBlock::new(
                    BosBlockKind::Info,
    
                    (size_of::<u64>()
                        + size_of::<u8>() * agent.len()
                        + size_of::<u8>() * agent_version.len()
                        + size_of::<u8>() * model.len()
                        + size_of::<u32>() * 4) as u32,
                ),
                model,
                timestamp,
                agent,
                agent_version,
            }
        }
    
    
        pub fn from_data<R: Read + Seek>(reader: &mut R) -> Result<Self, Error> {
    
            let mut instance = Self::default();
    
            instance.read(reader)?;
    
        }
    }
    
    impl Serialize for BosInfo {
    
        fn write<W: Write + Seek>(&mut self, writer: &mut W) -> Result<(), Error> {
            self.header.write(writer)?;
    
            write_u32(writer, size_of::<u64>() as u32)?;
            write_u64(writer, self.timestamp)?;
    
            write_u32(writer, self.agent.as_bytes().len() as u32)?;
            write_bytes(writer, self.agent.as_bytes())?;
    
            write_u32(writer, self.agent_version.as_bytes().len() as u32)?;
            write_bytes(writer, self.agent_version.as_bytes())?;
    
            write_u32(writer, self.model.as_bytes().len() as u32)?;
            write_bytes(writer, self.model.as_bytes())?;
    
        fn read<R: Read + Seek>(&mut self, reader: &mut R) -> Result<(), Error> {
            self.header.read(reader)?;
    
            read_u32(reader)?;
            self.timestamp = read_u64(reader)?;
    
            let buffer_len = read_u32(reader)? as usize;
            self.agent = String::from_utf8(read_bytes(reader, buffer_len)?)?;
    
            let buffer_len = read_u32(reader)? as usize;
            self.agent_version = String::from_utf8(read_bytes(reader, buffer_len)?)?;
    
            let buffer_len = read_u32(reader)? as usize;
            self.model = String::from_utf8(read_bytes(reader, buffer_len)?)?;
    
        }
    }
    
    impl State for BosInfo {
    
        fn from_gb(gb: &mut GameBoy) -> Result<Self, Error> {
    
            let timestamp = timestamp();
    
            Ok(Self::new(
                gb.mode().to_string(Some(true)),
                timestamp,
    
                Info::name_lower(),
    
        fn to_gb(&self, _gb: &mut GameBoy) -> Result<(), Error> {
    
    impl StateBox for BosInfo {
        fn from_gb(gb: &mut GameBoy, options: &FromGbOptions) -> Result<Box<Self>, Error>
        where
            Self: Sized,
        {
            let timestamp = timestamp();
            Ok(Box::new(Self::new(
                gb.mode().to_string(Some(true)),
                timestamp,
                options.agent.clone().unwrap_or(Info::name_lower()),
                options.agent_version.clone().unwrap_or(Info::version()),
            )))
        }
    
        fn to_gb(&self, _gb: &mut GameBoy, _options: &ToGbOptions) -> Result<(), Error> {
            Ok(())
        }
    }
    
    
    impl Default for BosInfo {
        fn default() -> Self {
            Self::new(String::from(""), 0, String::from(""), String::from(""))
    
        }
    }
    
    pub struct BosImageBuffer {
        header: BosBlock,
        image: [u8; FRAME_BUFFER_SIZE],
    }
    
    impl BosImageBuffer {
        pub fn new(image: [u8; FRAME_BUFFER_SIZE]) -> Self {
            Self {
                header: BosBlock::new(
                    BosBlockKind::ImageBuffer,
    
                    (size_of::<u8>() * FRAME_BUFFER_SIZE) as u32,
                ),
                image,
            }
        }
    
    
        pub fn from_data<R: Read + Seek>(reader: &mut R) -> Result<Self, Error> {
    
            let mut instance = Self::default();
    
            instance.read(reader)?;
    
        pub fn save_bmp(&self, file_path: &str) -> Result<(), Error> {
    
            save_bmp(
                file_path,
                &self.image,
                DISPLAY_WIDTH as u32,
                DISPLAY_HEIGHT as u32,
            )?;
            Ok(())
        }
    }
    
    impl Serialize for BosImageBuffer {
    
        fn write<W: Write + Seek>(&mut self, writer: &mut W) -> Result<(), Error> {
            self.header.write(writer)?;
            write_bytes(writer, &self.image)?;
    
        fn read<R: Read + Seek>(&mut self, reader: &mut R) -> Result<(), Error> {
            self.header.read(reader)?;
            read_into(reader, &mut self.image)?;
    
        }
    }
    
    impl State for BosImageBuffer {
    
        fn from_gb(gb: &mut GameBoy) -> Result<Self, Error> {
    
            Ok(Self::new(gb.ppu_i().frame_buffer_raw()))
    
        fn to_gb(&self, _gb: &mut GameBoy) -> Result<(), Error> {
    
            Ok(())
        }
    }
    
    impl Default for BosImageBuffer {
        fn default() -> Self {
            Self::new([0x00; FRAME_BUFFER_SIZE])
        }
    
    pub struct BosDeviceState {
        header: BosBlock,
        device: GameBoyDevice,
    
        format: StateFormat,
    
        state: Vec<u8>,
    }
    
    impl BosDeviceState {
    
        pub fn new(device: GameBoyDevice, format: StateFormat, state: Vec<u8>) -> Self {
    
            Self {
                header: BosBlock::new(
    
                    BosBlockKind::DeviceState,
    
                    1,
                    (size_of::<u8>() + size_of::<u8>() + state.len()) as u32,
    
        pub fn from_data<R: Read + Seek>(reader: &mut R) -> Result<Self, Error> {
    
            let mut instance = Self::default();
    
            instance.read(reader)?;
    
        fn from_gb(
            gb: &mut GameBoy,
            device: GameBoyDevice,
            options: &FromGbOptions,
        ) -> Result<Self, Error> {
            let format: StateFormat = options.state_format.unwrap_or(StateFormat::Partial);
    
                GameBoyDevice::Cpu => Ok(Self::new(device, format, gb.cpu_i().state(Some(format))?)),
                GameBoyDevice::Ppu => Ok(Self::new(device, format, gb.ppu_i().state(Some(format))?)),
                GameBoyDevice::Apu => Ok(Self::new(device, format, gb.apu_i().state(Some(format))?)),
                GameBoyDevice::Dma => Ok(Self::new(device, format, gb.dma_i().state(Some(format))?)),
                GameBoyDevice::Pad => Ok(Self::new(device, format, gb.pad_i().state(Some(format))?)),
                GameBoyDevice::Timer => {
                    Ok(Self::new(device, format, gb.timer_i().state(Some(format))?))
                }
    
                _ => Err(Error::NotImplemented),
            }
        }
    
    
        fn to_gb(&self, gb: &mut GameBoy, _options: &ToGbOptions) -> Result<(), Error> {
    
            match self.device {
    
                GameBoyDevice::Cpu => gb.cpu().set_state(&self.state, Some(self.format))?,
                GameBoyDevice::Ppu => gb.ppu().set_state(&self.state, Some(self.format))?,
                GameBoyDevice::Apu => gb.apu().set_state(&self.state, Some(self.format))?,
                GameBoyDevice::Dma => gb.dma().set_state(&self.state, Some(self.format))?,
                GameBoyDevice::Pad => gb.pad().set_state(&self.state, Some(self.format))?,
                GameBoyDevice::Timer => gb.timer().set_state(&self.state, Some(self.format))?,
    
                _ => return Err(Error::NotImplemented),
            }
            Ok(())
        }
    
    }
    
    impl Serialize for BosDeviceState {
    
        fn write<W: Write + Seek>(&mut self, writer: &mut W) -> Result<(), Error> {
            self.header.write(writer)?;
            write_u8(writer, self.device as u8)?;
    
            write_u8(writer, self.format as u8)?;
    
            write_bytes(writer, &self.state)?;
    
        fn read<R: Read + Seek>(&mut self, reader: &mut R) -> Result<(), Error> {
            self.header.read(reader)?;
            self.device = read_u8(reader)?.into();
    
            self.format = read_u8(reader)?.into();
            let state_len = self.header.size as usize - size_of::<u8>() - size_of::<u8>();
    
            self.state.append(&mut read_bytes(reader, state_len)?);
    
            Ok(())
        }
    }
    
    impl Default for BosDeviceState {
        fn default() -> Self {
    
            Self::new(GameBoyDevice::Unknown, StateFormat::Partial, vec![])
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    
    João Magalhães's avatar
    João Magalhães committed
    #[derive(Default)]
    
    pub struct BessState {
        footer: BessFooter,
        name: BessName,
        info: BessInfo,
        core: BessCore,
        mbc: BessMbc,
        end: BessBlock,
    
    impl BessState {
    
        /// Checks if the data contained in the provided
        /// buffer represents a valid BESS (Best Effort Save State)
        /// file structure, thought magic string validation.
    
        pub fn is_bess<R: Read + Seek>(reader: &mut R) -> Result<bool, Error> {
            reader.seek(SeekFrom::End(-4))?;
            let magic = read_u32(reader)?;
            reader.rewind()?;
    
            Ok(magic == BESS_MAGIC)
    
        pub fn description(&self, column_length: usize) -> String {
            let emulator_l = format!("{:width$}", "Emulator", width = column_length);
            let title_l: String = format!("{:width$}", "Title", width = column_length);
    
            let version_l: String = format!("{:width$}", "Version", width = column_length);
            let model_l: String = format!("{:width$}", "Model", width = column_length);
    
            let ram_l: String = format!("{:width$}", "RAM", width = column_length);
            let vram_l: String = format!("{:width$}", "VRAM", width = column_length);
    
            let pc_l: String = format!("{:width$}", "PC", width = column_length);
            let sp_l: String = format!("{:width$}", "SP", width = column_length);
    
                "{}  {}\n{}  {}\n{}  {}.{}\n{}  {}\n{}  {}\n{}  {}\n{}  0x{:04X}\n{}  0x{:04X}\n",
    
                emulator_l,
                self.name.name,
                title_l,
                self.info.title(),
    
                version_l,
                self.core.major,
                self.core.minor,
                model_l,
                self.core.model,
    
                ram_l,
                self.core.ram.size,
                vram_l,
                self.core.vram.size,
    
                pc_l,
                self.core.pc,
                sp_l,
                self.core.sp
    
        pub fn verify(&self) -> Result<(), Error> {
    
    João Magalhães's avatar
    João Magalhães committed
            self.footer.verify()?;
            self.core.verify()?;
    
        /// Dumps the core data into the provided buffer and returns.
        /// This will effectively populate the majority of the save
        /// file with the core emulator contents.
    
        fn dump_core<W: Write + Seek>(&mut self, writer: &mut W) -> Result<(), Error> {
    
            let mut buffers = [
    
                &mut self.core.ram,
                &mut self.core.vram,
                &mut self.core.mbc_ram,
                &mut self.core.oam,
                &mut self.core.hram,
                &mut self.core.background_palettes,