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,
version: u8,
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; size_of::<u32>()];
data.read_exact(&mut buffer).unwrap();
let magic = u32::from_le_bytes(buffer);
pub fn verify(&self) -> Result<(), Error> {
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
}
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);
}
if let Some(image_buffer) = &mut self.image_buffer {
image_buffer.write(buffer);
}
self.bess.write(buffer);
}
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 buffer = [0x00; size_of::<u8>()];
data.read_exact(&mut buffer).unwrap();
self.block_count = u8::from_le_bytes(buffer);
for _ in 0..self.block_count {
let block = BosBlock::from_data(data);
let offset = -((size_of::<u8>() + size_of::<u32>()) as i64);
data.seek(SeekFrom::Current(offset)).unwrap();
match block.kind {
BosBlockKind::Info => {
self.info = Some(BosInfo::from_data(data));
}
BosBlockKind::ImageBuffer => {
self.image_buffer = Some(BosImageBuffer::from_data(data));
}
_ => {
data.seek(SeekFrom::Current(-offset)).unwrap();
data.seek(SeekFrom::Current(block.size as i64)).unwrap();
}
}
}
self.block_count = self.build_block_count();
self.bess.read(data);
}
}
impl StateBox for BosState {
fn from_gb(gb: &mut GameBoy) -> Result<Box<Self>, Error> {
Ok(Box::new(Self {
magic: BOS_MAGIC_UINT,
version: BOS_VERSION,
block_count: 2,
info: Some(BosInfo::from_gb(gb)?),
image_buffer: Some(BosImageBuffer::from_gb(gb)?),
bess: *BessState::from_gb(gb)?,
}))
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), Error> {
self.verify()?;
self.bess.to_gb(gb)?;
Ok(())
}
}
pub struct BosBlock {
kind: BosBlockKind,
size: u32,
}
impl BosBlock {
pub fn new(kind: BosBlockKind, size: u32) -> Self {
Self { kind, size }
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
instance.read(data);
instance
}
}
impl Serialize for BosBlock {
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&(self.kind as u8).to_le_bytes()).unwrap();
buffer.write_all(&self.size.to_le_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
let mut buffer = [0x00; size_of::<u8>()];
data.read_exact(&mut buffer).unwrap();
self.kind = BosBlockKind::from_u8(u8::from_le_bytes(buffer));
let mut buffer = [0x00; size_of::<u32>()];
data.read_exact(&mut buffer).unwrap();
self.size = u32::from_le_bytes(buffer);
}
}
impl Default for BosBlock {
fn default() -> Self {
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
Self::new(BosBlockKind::Info, 0)
}
}
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(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
instance.read(data);
instance
}
}
impl Serialize for BosInfo {
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
self.header.write(buffer);
buffer
.write_all(&(size_of::<u64>() as u32).to_le_bytes())
.unwrap();
buffer.write_all(&self.timestamp.to_le_bytes()).unwrap();
buffer
.write_all(&(self.agent.as_bytes().len() as u32).to_le_bytes())
.unwrap();
buffer.write_all(self.agent.as_bytes()).unwrap();
buffer
.write_all(&(self.agent_version.as_bytes().len() as u32).to_le_bytes())
.unwrap();
buffer.write_all(self.agent_version.as_bytes()).unwrap();
buffer
.write_all(&(self.model.as_bytes().len() as u32).to_le_bytes())
.unwrap();
buffer.write_all(self.model.as_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
let mut buffer = [0x00; size_of::<u32>()];
data.read_exact(&mut buffer).unwrap();
let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
data.read_exact(&mut buffer).unwrap();
self.timestamp = u64::from_le_bytes(buffer.try_into().unwrap());
let mut buffer = [0x00; size_of::<u32>()];
data.read_exact(&mut buffer).unwrap();
let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
data.read_exact(&mut buffer).unwrap();
self.agent = String::from_utf8(buffer).unwrap();
let mut buffer = [0x00; size_of::<u32>()];
data.read_exact(&mut buffer).unwrap();
let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
data.read_exact(&mut buffer).unwrap();
self.agent_version = String::from_utf8(buffer).unwrap();
let mut buffer = [0x00; size_of::<u32>()];
data.read_exact(&mut buffer).unwrap();
let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
data.read_exact(&mut buffer).unwrap();
self.model = String::from_utf8(buffer).unwrap();
}
}
impl State for BosInfo {
fn from_gb(gb: &mut GameBoy) -> Result<Self, Error> {
let timestamp = get_timestamp();
Ok(Self::new(
gb.mode().to_string(Some(true)),
timestamp,
Info::name(),
Info::version(),
))
}
fn to_gb(&self, _gb: &mut GameBoy) -> 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(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
instance.read(data);
instance
}
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(&mut self, buffer: &mut Cursor<Vec<u8>>) {
self.header.write(buffer);
buffer.write_all(&self.image).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
data.read_exact(&mut self.image).unwrap();
}
}
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])
}
#[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(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);
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(&mut self, buffer: &mut Cursor<Vec<u8>>) {
&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,
&mut self.core.object_palettes,
];
for item in buffers.iter_mut() {
item.offset = buffer.position() as u32;
buffer.write_all(&item.buffer).unwrap();
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
self.dump_core(buffer);
self.footer.start_offset = buffer.position() as u32;
self.name.write(buffer);
self.info.write(buffer);
self.core.write(buffer);
self.mbc.write(buffer);
self.end.write(buffer);
self.footer.write(buffer);
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
// moves the cursor to the end of the file
// to read the footer, and then places the
// according to the footer information
data.seek(SeekFrom::End(-8)).unwrap();
data.seek(SeekFrom::Start(self.footer.start_offset as u64))
.unwrap();
// reads the block header information and then moves the
// cursor back to the original position to be able to
// re-read the block data
let offset = -((size_of::<u32>() * 2) as i64);
data.seek(SeekFrom::Current(offset)).unwrap();
"NAME" => self.name = BessName::from_data(data),
"INFO" => self.info = BessInfo::from_data(data),
"CORE" => self.core = BessCore::from_data(data),
"MBC " => self.mbc = BessMbc::from_data(data),
"END " => self.end = BessBlock::from_data(data),
if block.is_end() {
break;
}
}
impl StateBox for BessState {
fn from_gb(gb: &mut GameBoy) -> Result<Box<Self>, Error> {
Ok(Box::new(Self {
footer: BessFooter::default(),
name: BessName::from_gb(gb)?,
info: BessInfo::from_gb(gb)?,
core: BessCore::from_gb(gb)?,
mbc: BessMbc::from_gb(gb)?,
end: BessBlock::from_magic(String::from("END ")),
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), Error> {
self.verify()?;
self.name.to_gb(gb)?;
self.info.to_gb(gb)?;
self.core.to_gb(gb)?;
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description(9))
magic: String,
size: u32,
pub fn new(magic: String, size: u32) -> Self {
Self { magic, size }
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
instance
}
pub fn is_end(&self) -> bool {
self.magic == "END "
}
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.size.to_le_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
self.magic = String::from_utf8(Vec::from(buffer)).unwrap();
let mut buffer = [0x00; size_of::<u32>()];
fn default() -> Self {
Self::new(String::from(" "), 0)
}
}
pub struct BessBlock {
header: BessBlockHeader,
impl BessBlock {
pub fn new(header: BessBlockHeader, buffer: Vec<u8>) -> Self {
Self { header, buffer }
}
pub fn from_magic(magic: String) -> Self {
Self::new(BessBlockHeader::new(magic, 0), vec![])
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
instance
}
pub fn magic(&self) -> &String {
&self.header.magic
}
pub fn is_end(&self) -> bool {
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.buffer).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
self.buffer.reserve_exact(self.header.size as usize);
data.read_exact(&mut self.buffer).unwrap();
size: u32,
offset: u32,
pub fn new(size: u32, offset: u32, buffer: Vec<u8>) -> Self {
Self {
size,
offset,
buffer,
}
}
/// Fills the buffer with new data and updating the size
/// value accordingly.
fn fill_buffer(&mut self, data: &[u8]) {
self.size = data.len() as u32;
self.buffer = data.to_vec();
}
/// Loads the internal buffer structure with the provided
/// data according to the size and offset defined.
fn load_buffer(&self, data: &mut Cursor<Vec<u8>>) -> Vec<u8> {
let mut buffer = vec![0x00; self.size as usize];
let position = data.position();
data.seek(SeekFrom::Start(self.offset as u64)).unwrap();
data.read_exact(&mut buffer).unwrap();
data.set_position(position);
buffer
}
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.size.to_le_bytes()).unwrap();
buffer.write_all(&self.offset.to_le_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
let mut buffer = [0x00; size_of::<u32>()];
let mut buffer = [0x00; size_of::<u32>()];
self.buffer = self.load_buffer(data);
}
}
fn default() -> Self {
Self::new(0, 0, vec![])
start_offset: u32,
magic: u32,
pub fn new(start_offset: u32, magic: u32) -> Self {
Self {
start_offset,
magic,
}
}
pub fn verify(&self) -> Result<(), Error> {
return Err(Error::CustomError(String::from("Invalid magic")));
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.start_offset.to_le_bytes()).unwrap();
buffer.write_all(&self.magic.to_le_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
let mut buffer = [0x00; size_of::<u32>()];
let mut buffer = [0x00; size_of::<u32>()];
pub struct BessName {
header: BessBlockHeader,
pub fn new(name: String) -> Self {
Self {
header: BessBlockHeader::new(String::from("NAME"), name.len() as u32),
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(self.name.as_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
let mut buffer = vec![0x00; self.header.size as usize];
fn from_gb(_gb: &mut GameBoy) -> Result<Self, Error> {
Ok(Self::new(format!("{} v{}", Info::name(), Info::version())))
fn to_gb(&self, _gb: &mut GameBoy) -> Result<(), Error> {
fn default() -> Self {
Self::new(String::from(""))
}
}
pub struct BessInfo {
header: BessBlockHeader,
title: [u8; 16],
checksum: [u8; 2],
pub fn new(title: &[u8], checksum: &[u8]) -> Self {
Self {
String::from("INFO"),
title.len() as u32 + checksum.len() as u32,
),
title: title.try_into().unwrap(),
checksum: checksum.try_into().unwrap(),
}
}