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_encoding::zippy::{decode_zippy, encode_zippy};
io::{Cursor, Read, Seek, SeekFrom, Write},
mem::size_of,
use crate::{
gb::{GameBoy, GameBoySpeed},
info::Info,
ppu::{DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_SIZE},
#[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.
/// 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)]
/// Boytacean Save format (uncompressed) (BOS).
/// Boytacean Save Compressed format (BOSC).
/// The format uses the Zippy compression algorithm.
Bosc,
/// Best Effort Save State format (BESS).
Unknown = 0xff,
}
impl BosBlockKind {
fn from_u8(value: u8) -> Self {
match value {
0x02 => Self::ImageBuffer,
_ => Self::Unknown,
}
}
impl From<u8> for BosBlockKind {
fn from(value: u8) -> Self {
Self::from_u8(value)
}
}
/// 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>>);
/// 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>;
}
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#[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...