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.
read_bytes, read_into, read_u16, read_u32, read_u64, read_u8, write_bytes, write_u16,
write_u32, write_u64, write_u8,
use boytacean_encoding::zippy::{decode_zippy, encode_zippy};
io::{Cursor, Read, Seek, SeekFrom, Write},
mem::size_of,
gb::{GameBoy, GameBoyDevice, GameBoyMode, GameBoySpeed},
ppu::{DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_SIZE},
#[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;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/// 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.
/// This trait is used to define the behavior of the state
/// components that are used to store the emulator state.
/// 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)]
/// 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).
impl SaveStateFormat {
pub fn description(&self) -> String {
match self {
Self::Bosc => String::from("BOSC"),
Self::Bos => String::from("BOS"),
Self::Bess => String::from("BESS"),
}
}
match value {
"BOSC" => Self::Bosc,
"BOS" => Self::Bos,
"BESS" => Self::Bess,
_ => Self::Bos,
}
}
}
impl From<&str> for SaveStateFormat {
fn from(value: &str) -> Self {
}
}
impl Display for SaveStateFormat {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
DeviceState = 0x03,
Unknown = 0xff,
}
impl BosBlockKind {
fn from_u8(value: u8) -> Self {
match value {
0x02 => Self::ImageBuffer,
0x03 => Self::DeviceState,
_ => Self::Unknown,
}
}
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>,
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 {
}
}
impl Default for FromGbOptions {
fn default() -> Self {
}
}
#[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 }
}
}
/// 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>;
/// 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>;
/// 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> {
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Default)]
pub struct BosState {
magic: u32,
version: u8,
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> {
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> {
pub fn agent_wa(&self) -> Result<String, String> {
pub fn model_wa(&self) -> Result<String, String> {
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)?)
}
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 {
if let Some(image_buffer) = &mut self.image_buffer {
for device_state in &mut self.device_states {
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))?;
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();
fn from_gb(gb: &mut GameBoy, options: &FromGbOptions) -> Result<Box<Self>, Error> {
magic: BOS_MAGIC_UINT,
version: BOS_VERSION,
info: Some(*<BosInfo as StateBox>::from_gb(gb, options)?),
image_buffer: if options.thumbnail {
Some(BosImageBuffer::from_gb(gb)?)
} else {
None
},
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.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> {
}
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();
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();
}
}
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> {
Ok(Self::new(
gb.mode().to_string(Some(true)),
timestamp,
Info::version(),
))
}
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();
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,
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,
state,
}
}
pub fn from_data<R: Read + Seek>(reader: &mut R) -> Result<Self, Error> {
let mut instance = Self::default();
Ok(instance)
}
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> {
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)]
pub struct BessState {
footer: BessFooter,
name: BessName,
info: BessInfo,
core: BessCore,
mbc: BessMbc,
end: BessBlock,
/// 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> {
/// 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> {
&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,