Newer
Older
//! System save state (BOS and [BESS](https://github.com/LIJI32/SameBoy/blob/master/BESS.md) formats) functions and structures.
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;
/// 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,
}
pub enum BosBlockKind {
Name = 0x01,
ImageBuffer = 0x02,
SystemInfo = 0x03,
Unknown = 0xff,
}
impl BosBlockKind {
fn from_u8(value: u8) -> Self {
match value {
0x01 => Self::Name,
0x02 => Self::ImageBuffer,
0x03 => Self::SystemInfo,
_ => Self::Unknown,
}
}
/// 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, String>
where
Self: Sized;
/// Applies the state to the provided `GameBoy` instance.
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), String>;
#[derive(Default)]
pub struct BosState {
magic: u32,
version: u8,
block_count: 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; 4];
data.read_exact(&mut buffer).unwrap();
let magic = u32::from_le_bytes(buffer);
data.seek(SeekFrom::Start(0)).unwrap();
magic == BOS_MAGIC_UINT
}
pub fn verify(&self) -> Result<(), String> {
if self.magic != BOS_MAGIC_UINT {
return Err(String::from("Invalid magic"));
}
self.bess.verify()?;
Ok(())
}
pub fn save_bmp(&self, file_path: &str) -> Result<(), String> {
if let Some(image_buffer) = &self.image_buffer {
image_buffer.save_bmp(file_path)?;
Ok(())
} else {
Err(String::from("No image buffer found"))
}
}
fn build_block_count(&self) -> u8 {
let mut count = 0_u8;
if self.image_buffer.is_some() {
count += 1;
}
count
}
}
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(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; 4];
data.read_exact(&mut buffer).unwrap();
self.magic = u32::from_le_bytes(buffer);
let mut buffer = [0x00; 1];
data.read_exact(&mut buffer).unwrap();
self.version = u8::from_le_bytes(buffer);
let mut buffer = [0x00; 1];
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::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 State for BosState {
fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
Ok(Self {
magic: BOS_MAGIC_UINT,
version: BOS_VERSION,
block_count: 1,
image_buffer: Some(BosImageBuffer::from_gb(gb)?),
bess: BessState::from_gb(gb)?,
})
}
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), String> {
self.verify()?;
self.bess.to_gb(gb)?;
Ok(())
}
}
pub struct BosBlock {
kind: BosBlockKind,
size: u32,
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
}
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; 1];
data.read_exact(&mut buffer).unwrap();
self.kind = BosBlockKind::from_u8(u8::from_le_bytes(buffer));
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
self.size = u32::from_le_bytes(buffer);
}
}
impl Default for BosBlock {
fn default() -> Self {
Self::new(BosBlockKind::Name, 0)
}
}
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<(), String> {
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, String> {
Ok(Self::new(*gb.ppu_i().frame_buffer))
}
fn to_gb(&self, _gb: &mut GameBoy) -> Result<(), String> {
Ok(())
}
}
impl Default for BosImageBuffer {
fn default() -> Self {
Self::new([0x00; FRAME_BUFFER_SIZE])
}
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 {
data.seek(SeekFrom::End(-8)).unwrap();
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
let magic = u32::from_le_bytes(buffer);
data.seek(SeekFrom::Start(0)).unwrap();
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<(), String> {
/// 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>>) {
let mut buffers = vec![
&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;
}
}
fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
Ok(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<(), String> {
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();
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; 4];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
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<(), String> {
return Err(String::from("Invalid magic"));
}
Ok(())
}
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; 4];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
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, String> {
Ok(Self::new(format!("{} v{}", Info::name(), Info::version())))
fn to_gb(&self, _gb: &mut GameBoy) -> Result<(), String> {
Ok(())
}
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(),
}
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
for (offset, byte) in self.title.iter().enumerate() {
final_index = offset;
break;
}
// in we're at the final byte of the title and the value
// is one that is reserved for CGB compatibility testing
// then we must ignore it for title processing purposes
if offset > 14
&& (*byte == CgbMode::CgbCompatible as u8 || *byte == CgbMode::CgbOnly as u8)
{
final_index = offset;
String::from_utf8(Vec::from(&self.title[..final_index]))
.unwrap()
.trim_matches(char::from(0))
.trim(),
)
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.title).unwrap();
buffer.write_all(&self.checksum).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
data.read_exact(&mut self.title).unwrap();
data.read_exact(&mut self.checksum).unwrap();
fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
Ok(Self::new(
&gb.cartridge_i().rom_data()[0x134..=0x143],
&gb.cartridge_i().rom_data()[0x14e..=0x14f],
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), String> {
if self.title() != gb.rom_i().title() {
return Err(format!(
"Invalid ROM loaded, expected '{}' (len {}) got '{}' (len {})",
self.title(),
self.title().len(),
gb.rom_i().title(),
gb.rom_i().title().len(),
));
}
Ok(())
}
fn default() -> Self {
Self::new(&[0_u8; 16], &[0_u8; 2])
}
}
pub struct BessCore {
header: BessBlockHeader,
major: u16,
minor: u16,
pc: u16,
af: u16,
bc: u16,
de: u16,
hl: u16,
sp: u16,
// 0 = running; 1 = halted; 2 = stopped
io_registers: [u8; 128],
ram: BessBuffer,
vram: BessBuffer,
mbc_ram: BessBuffer,
oam: BessBuffer,
hram: BessBuffer,
background_palettes: BessBuffer,
object_palettes: BessBuffer,
pub fn new(
model: String,
pc: u16,
af: u16,
bc: u16,
de: u16,
hl: u16,
sp: u16,
ime: bool,
ie: u8,
execution_mode: u8,
io_registers: [u8; 128],
) -> Self {
String::from("CORE"),
((size_of::<u16>() * 2)
+ size_of::<u32>()
+ (size_of::<u16>() * 6)
+ (size_of::<u8>() * 4)
+ (size_of::<u8>() * 128)
+ ((size_of::<u32>() + size_of::<u32>()) * 7)) as u32,
),
major: 1,
minor: 1,
model,
pc,
af,
bc,
de,
hl,
sp,
ram: BessBuffer::default(),
vram: BessBuffer::default(),
mbc_ram: BessBuffer::default(),
oam: BessBuffer::default(),
hram: BessBuffer::default(),
background_palettes: BessBuffer::default(),
object_palettes: BessBuffer::default(),
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
pub fn verify(&self) -> Result<(), String> {
if self.header.magic != "CORE" {
return Err(String::from("Invalid magic"));
if self.oam.size != 0xa0 {
return Err(String::from("Invalid OAM size"));
}
if self.hram.size != 0x7f {
return Err(String::from("Invalid HRAM size"));
}
if (self.is_cgb() && self.background_palettes.size != 0x40)
|| (self.is_dmg() && self.background_palettes.size != 0x00)
{
return Err(String::from("Invalid background palettes size"));
}
if (self.is_cgb() && self.object_palettes.size != 0x40)
|| (self.is_dmg() && self.object_palettes.size != 0x00)
{
return Err(String::from("Invalid object palettes size"));
}
/// Obtains the BESS (Game Boy) model string using the
let mut buffer = [0x00_u8; 4];
if gb.is_dmg() {
buffer[0] = b'C';
} else if gb.is_sgb() {
buffer[0] = b'S';
buffer[1] = b'C';
} else if gb.is_sgb() {
buffer[1] = b'N';
buffer[2] = b'B';
} else if gb.is_cgb() {
buffer[2] = b'A';
} else {
buffer[2] = b' ';
}
String::from_utf8(Vec::from(buffer)).unwrap()
}
fn is_dmg(&self) -> bool {
if let Some(first_char) = self.model.chars().next() {
return first_char == 'G';
}
false
}
fn is_cgb(&self) -> bool {
if let Some(first_char) = self.model.chars().next() {
return first_char == 'C';
}
false
}
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.major.to_le_bytes()).unwrap();
buffer.write_all(&self.minor.to_le_bytes()).unwrap();
buffer.write_all(self.model.as_bytes()).unwrap();
buffer.write_all(&self.pc.to_le_bytes()).unwrap();
buffer.write_all(&self.af.to_le_bytes()).unwrap();
buffer.write_all(&self.bc.to_le_bytes()).unwrap();
buffer.write_all(&self.de.to_le_bytes()).unwrap();
buffer.write_all(&self.hl.to_le_bytes()).unwrap();
buffer.write_all(&self.sp.to_le_bytes()).unwrap();
buffer.write_all(&(self.ime as u8).to_le_bytes()).unwrap();
buffer.write_all(&self.ie.to_le_bytes()).unwrap();
buffer
.write_all(&self.execution_mode.to_le_bytes())
.unwrap();
buffer.write_all(&self._padding.to_le_bytes()).unwrap();
buffer.write_all(&self.io_registers).unwrap();
self.ram.write(buffer);
self.vram.write(buffer);
self.mbc_ram.write(buffer);
self.oam.write(buffer);
self.hram.write(buffer);
self.background_palettes.write(buffer);
self.object_palettes.write(buffer);
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
let mut buffer = [0x00; 2];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 2];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
self.model = String::from_utf8(Vec::from(buffer)).unwrap();
let mut buffer = [0x00; 2];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 2];
data.read_exact(&mut buffer).unwrap();