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,
/// Magic string for the BOS (Boytacean Save State) format.
pub const BOS_MAGIC: &'static 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 State) format.
pub const BOS_VERSION: u8 = 1;
/// Magic number for the BESS file format.
pub const BESS_MAGIC: u32 = 0x53534542;
pub enum SaveStateFormat {
Bos,
Bess,
}
pub enum BosBlockKind {
Name = 0x01,
ImageBuffer = 0x02,
SystemInfo = 0x03,
}
/// 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>;
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#[derive(Default)]
pub struct BosState {
magic: u32,
version: u8,
blocks: Vec<BosBlock>,
bess: BessState,
}
impl BosState {
/// Checks if the data contained in the provided
/// buffer represents a valid BOS (Boytacean Save State)
/// 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(())
}
}
impl Serialize for BosState {
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();
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);
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,
blocks: vec![],
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,
buffer: Vec<u8>,
}
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();
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; 2];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 2];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 1];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 1];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 1];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 1];
data.read_exact(&mut buffer).unwrap();
data.read_exact(&mut self.io_registers).unwrap();
self.ram.read(data);
self.vram.read(data);
self.mbc_ram.read(data);
self.oam.read(data);
self.hram.read(data);
self.background_palettes.read(data);
self.object_palettes.read(data);
fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
let mut core = Self::new(
gb.cpu_i().pc(),
gb.cpu_i().af(),
gb.cpu_i().bc(),
gb.cpu_i().de(),
gb.cpu_i().hl(),
gb.cpu_i().sp(),
gb.cpu_i().ime(),
gb.mmu_i().ie,
// @TODO: these registers cannot be totally retrieved
// because of that some audio noise exists
// The loading of the registers should be done in a much
// more manual way like SameBoy does here
// https://github.com/LIJI32/SameBoy/blob/7e6f1f866e89430adaa6be839aecc4a2ccabd69c/Core/save_state.c#L673
gb.mmu().read_many_unsafe(0xff00, 128).try_into().unwrap(),
core.vram.fill_buffer(gb.ppu().vram_device());
core.mbc_ram.fill_buffer(gb.rom_i().ram_data());
core.oam.fill_buffer(&gb.mmu().read_many(0xfe00, 0x00a0));
core.hram.fill_buffer(&gb.mmu().read_many(0xff80, 0x007f));
if gb.is_cgb() {
core.background_palettes
.fill_buffer(&gb.ppu_i().palettes_color()[0]);
core.object_palettes
.fill_buffer(&gb.ppu_i().palettes_color()[1]);
}
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), String> {
gb.cpu().set_pc(self.pc);
gb.cpu().set_af(self.af);
gb.cpu().set_bc(self.bc);
gb.cpu().set_de(self.de);
gb.cpu().set_hl(self.hl);
gb.cpu().set_sp(self.sp);
gb.cpu().set_ime(self.ime);
gb.mmu().ie = self.ie;
match self.execution_mode {
0 => gb.cpu().set_halted(false),
1 => gb.cpu().set_halted(true),
2 => gb.cpu().stop(),
_ => unimplemented!(),
}
// @TODO: we need to be careful about this writing and
// should make this a bit more robust, to handle this
// special case/situations
// The registers should be handled in a more manual manner
// to avoid unwanted side effects
// https://github.com/LIJI32/SameBoy/blob/7e6f1f866e89430adaa6be839aecc4a2ccabd69c/Core/save_state.c#L1003
gb.mmu().write_many_unsafe(0xff00, &self.io_registers);
gb.mmu().set_ram(self.ram.buffer.to_vec());
gb.ppu().set_vram(&self.vram.buffer);
gb.rom().set_ram_data(&self.mbc_ram.buffer);
gb.mmu().write_many(0xfe00, &self.oam.buffer);
gb.mmu().write_many(0xff80, &self.hram.buffer);
// updates the internal palettes for the CGB with the values
gb.ppu().set_palettes_color([
self.background_palettes.buffer.to_vec().try_into().unwrap(),
self.object_palettes.buffer.to_vec().try_into().unwrap(),
]);
// updates the speed of the CGB according to the KEY1 register
let is_double = self.io_registers[0x4d_usize] & 0x80 == 0x80;
gb.mmu().set_speed(if is_double {
GameBoySpeed::Double
} else {
GameBoySpeed::Normal
});
// need to disable DMA transfer to avoid unwanted
// DMA transfers when loading the state
gb.dma().set_active(false);
}
String::from("GD "),
0x0000_u16,
0x0000_u16,
0x0000_u16,
0x0000_u16,
0x0000_u16,
0x0000_u16,
false,
0x00,
0,
[0x00; 128],
pub fn new(address: u16, value: u8) -> Self {
Self { address, value }
}
}
pub struct BessMbc {
header: BessBlockHeader,
registers: Vec<BessMbrRegister>,