Newer
Older
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, Error> {
&gb.cartridge_i().rom_data()[0x0134..=0x0143],
&gb.cartridge_i().rom_data()[0x014e..=0x014f],
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), Error> {
if self.title() != gb.rom_i().title() {
return Err(Error::CustomError(format!(
"Invalid ROM loaded, expected '{}' (len {}) got '{}' (len {})",
self.title(),
self.title().len(),
gb.rom_i().title(),
gb.rom_i().title().len(),
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
execution_mode: u8,
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<(), Error> {
if self.header.magic != "CORE" {
return Err(Error::CustomError(String::from("Invalid magic")));
return Err(Error::CustomError(String::from("Invalid OAM size")));
}
if self.hram.size != 0x7f {
return Err(Error::CustomError(String::from("Invalid HRAM size")));
if (self.is_cgb() && self.background_palettes.size != 0x40)
|| (self.is_dmg() && self.background_palettes.size != 0x00)
{
return Err(Error::CustomError(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(Error::CustomError(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>>) {
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
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; size_of::<u16>()];
let mut buffer = [0x00; size_of::<u16>()];
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
self.model = String::from_utf8(Vec::from(buffer)).unwrap();
let mut buffer = [0x00; size_of::<u16>()];
let mut buffer = [0x00; size_of::<u16>()];
let mut buffer = [0x00; size_of::<u16>()];
let mut buffer = [0x00; size_of::<u16>()];
let mut buffer = [0x00; size_of::<u16>()];
let mut buffer = [0x00; size_of::<u16>()];
let mut buffer = [0x00; 1];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; size_of::<u8>()];
let mut buffer = [0x00; size_of::<u8>()];
let mut buffer = [0x00; size_of::<u8>()];
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, Error> {
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 completely retrieved
// and because of that some audio noise is played
// 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.ram.fill_buffer(gb.mmu().ram());
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<(), Error> {
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
String::from("GD "),
0x0000_u16,
0x0000_u16,
0x0000_u16,
0x0000_u16,
0x0000_u16,
0x0000_u16,
false,
0x00,
0,
[0x00; 128],
address: u16,
value: u8,
}
pub fn new(address: u16, value: u8) -> Self {
Self { address, value }
}
}
pub struct BessMbc {
header: BessBlockHeader,
registers: Vec<BessMbrRegister>,
impl BessMbc {
pub fn new(registers: Vec<BessMbrRegister>) -> Self {
String::from("MBC "),
((size_of::<u8>() + size_of::<u16>()) * registers.len()) as u32,
),
registers,
}
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
for register in self.registers.iter() {
buffer.write_all(®ister.address.to_le_bytes()).unwrap();
buffer.write_all(®ister.value.to_le_bytes()).unwrap();
}
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
let mut buffer = [0x00; size_of::<u16>()];
data.read_exact(&mut buffer).unwrap();
let address = u16::from_le_bytes(buffer);
let mut buffer = [0x00; size_of::<u8>()];
data.read_exact(&mut buffer).unwrap();
let value = u8::from_le_bytes(buffer);
self.registers.push(BessMbrRegister::new(address, value));
fn from_gb(gb: &mut GameBoy) -> Result<Self, Error> {
let mut registers = vec![];
match gb.cartridge().rom_type().mbc_type() {
MbcType::NoMbc => (),
MbcType::Mbc1 => {
0x0000,
if gb.rom().ram_enabled() {
0x0a_u8
} else {
0x00_u8
},
));
0x2000,
gb.rom().rom_bank() as u8 & 0x1f,
));
registers.push(BessMbrRegister::new(0x4000, gb.rom().ram_bank()));
registers.push(BessMbrRegister::new(0x6000, 0x00_u8));
0x0000,
if gb.rom().ram_enabled() {
0x0a_u8
} else {
0x00_u8
},
));
registers.push(BessMbrRegister::new(0x2000, gb.rom().rom_bank() as u8));
registers.push(BessMbrRegister::new(0x4000, gb.rom().ram_bank()));
0x0000,
if gb.rom().ram_enabled() {
0x0a_u8
} else {
0x00_u8
},
));
registers.push(BessMbrRegister::new(0x2000, gb.rom().rom_bank() as u8));
registers.push(BessMbrRegister::new(
0x3000,
(gb.rom().rom_bank() >> 8) as u8 & 0x01,
));
registers.push(BessMbrRegister::new(0x4000, gb.rom().ram_bank()));
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), Error> {
for register in self.registers.iter() {
gb.mmu().write(register.address, register.value);
}
Ok(())
}
}
fn default() -> Self {
Self::new(vec![])
}
}
/// Top level manager structure containing the
/// entrypoint static methods for saving and loading
/// [BESS](https://github.com/LIJI32/SameBoy/blob/master/BESS.md) state
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub struct StateManager;
impl StateManager {
pub fn save_file(
file_path: &str,
gb: &mut GameBoy,
format: Option<SaveStateFormat>,
) -> Result<(), Error> {
let mut file = File::create(file_path)
.map_err(|_| Error::CustomError(format!("Failed to create file: {}", file_path)))?;
let data = Self::save(gb, format)?;
file.write_all(&data)
.map_err(|_| Error::CustomError(format!("Failed to write to file: {}", file_path)))?;
file.flush()
.map_err(|_| Error::CustomError(format!("Failed to flush file: {}", file_path)))?;
pub fn load_file(
file_path: &str,
gb: &mut GameBoy,
format: Option<SaveStateFormat>,
) -> Result<(), Error> {
let mut file = File::open(file_path)
.map_err(|_| Error::CustomError(format!("Failed to open file: {}", file_path)))?;
file.read_to_end(&mut data)
.map_err(|_| Error::CustomError(format!("Failed to read from file: {}", file_path)))?;
Self::load(&data, gb, format)?;
Ok(())
}
}
impl StateManager {
pub fn save(gb: &mut GameBoy, format: Option<SaveStateFormat>) -> Result<Vec<u8>, Error> {
let mut data = Cursor::new(vec![]);
match format {
Some(SaveStateFormat::Bosc) | None => {
let mut state = BoscState::from_gb(gb)?;
state.write(&mut data);
}
Some(SaveStateFormat::Bos) => {
let mut state = BosState::from_gb(gb)?;
state.write(&mut data);
}
Some(SaveStateFormat::Bess) => {
let mut state = BessState::from_gb(gb)?;
state.write(&mut data);
}
}
Ok(data.into_inner())
pub fn load(
data: &[u8],
gb: &mut GameBoy,
format: Option<SaveStateFormat>,
let data = &mut Cursor::new(data.to_vec());
let format = match format {
Some(format) => format,
None => {
if BoscState::is_bosc(data) {
} else if BosState::is_bos(data) {
SaveStateFormat::Bos
} else if BessState::is_bess(data) {
SaveStateFormat::Bess
} else {
return Err(Error::CustomError(String::from(
"Unknown save state file format",
)));
SaveStateFormat::Bosc => {
let mut state = BoscState::default();
state.read(data);
state.to_gb(gb)?;
}
SaveStateFormat::Bos => {
let mut state = BosState::default();
state.read(data);
state.to_gb(gb)?;
}
SaveStateFormat::Bess => {
let mut state = BessState::default();
state.read(data);
state.to_gb(gb)?;
}
}
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
pub fn read_bos_auto(data: &[u8]) -> Result<BosState, Error> {
let data = &mut Cursor::new(data.to_vec());
let format = if BoscState::is_bosc(data) {
SaveStateFormat::Bosc
} else if BosState::is_bos(data) {
SaveStateFormat::Bos
} else if BessState::is_bess(data) {
return Err(Error::CustomError(String::from(
"Incompatible save state file format (BESS)",
)));
} else {
return Err(Error::CustomError(String::from(
"Unknown save state file format",
)));
};
match format {
SaveStateFormat::Bosc => {
let mut state = BoscState::default();
state.read(data);
Ok(state.bos)
}
SaveStateFormat::Bos => {
let mut state = BosState::default();
state.read(data);
Ok(state)
}
_ => unreachable!(),
}
}
pub fn read_bosc(data: &[u8]) -> Result<BoscState, Error> {
let data = &mut Cursor::new(data.to_vec());
let mut state = BoscState::default();
state.read(data);
Ok(state)
}
pub fn read_bos(data: &[u8]) -> Result<BosState, Error> {
let data = &mut Cursor::new(data.to_vec());
let mut state = BosState::default();
state.read(data);
Ok(state)
}
pub fn read_bess(data: &[u8]) -> Result<BessState, Error> {
let data = &mut Cursor::new(data.to_vec());
let mut state = BessState::default();
state.read(data);
Ok(state)
}
/// Obtains the thumbnail of the save state file, this thumbnail is
/// This operation is currently only supported for the BOS format.
pub fn thumbnail(data: &[u8], format: Option<SaveStateFormat>) -> Result<Vec<u8>, Error> {
let data = &mut Cursor::new(data.to_vec());
let format = match format {
Some(format) => format,
None => {
if BosState::is_bos(data) {
SaveStateFormat::Bos
} else if BessState::is_bess(data) {
SaveStateFormat::Bess
} else {
return Err(Error::CustomError(String::from(
"Unknown save state file format",
)));
}
}
};
match format {
SaveStateFormat::Bosc => {
let mut state = BoscState::default();
state.read(data);
Ok(state.bos.image_buffer.unwrap().image.to_vec())
}
SaveStateFormat::Bos => {
let mut state = BosState::default();
state.read(data);
Ok(state.image_buffer.unwrap().image.to_vec())
}
SaveStateFormat::Bess => Err(Error::CustomError(String::from(
"Format foes not support thumbnail",
))),
#[cfg(feature = "wasm")]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl StateManager {
pub fn save_wa(gb: &mut GameBoy, format: Option<SaveStateFormat>) -> Result<Vec<u8>, String> {
Self::save(gb, format).map_err(|e| e.to_string())
}
data: &[u8],
gb: &mut GameBoy,
format: Option<SaveStateFormat>,
) -> Result<(), String> {
Self::load(data, gb, format).map_err(|e| e.to_string())
}
pub fn read_bos_auto_wa(data: &[u8]) -> Result<BosState, String> {
Self::read_bos_auto(data).map_err(|e| e.to_string())
}
pub fn read_bosc_wa(data: &[u8]) -> Result<BoscState, String> {
Self::read_bosc(data).map_err(|e| e.to_string())
}
pub fn read_bos_wa(data: &[u8]) -> Result<BosState, String> {
Self::read_bos(data).map_err(|e| e.to_string())
}
pub fn read_bess_wa(data: &[u8]) -> Result<BessState, String> {
Self::read_bess(data).map_err(|e| e.to_string())
}
pub fn thumbnail_wa(data: &[u8], format: Option<SaveStateFormat>) -> Result<Vec<u8>, String> {
Self::thumbnail(data, format).map_err(|e| e.to_string())
}
}
use boytacean_encoding::zippy::{decode_zippy, encode_zippy};
use crate::{gb::GameBoy, state::State};
use super::{BessCore, SaveStateFormat, StateManager};
#[test]
fn test_bess_core() {
let mut gb = GameBoy::default();
gb.load(true).unwrap();
gb.load_rom_file("res/roms/test/firstwhite.gb", None)
.unwrap();
let bess_core = BessCore::from_gb(&mut gb).unwrap();
assert_eq!(bess_core.model, "GDB ");
assert_eq!(bess_core.pc, 0x0000);
assert_eq!(bess_core.af, 0x0000);
assert_eq!(bess_core.bc, 0x0000);
assert_eq!(bess_core.de, 0x0000);
assert_eq!(bess_core.hl, 0x0000);
assert_eq!(bess_core.sp, 0x0000);
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
assert_eq!(bess_core.ie, 0x00);
assert_eq!(bess_core.execution_mode, 0);
assert_eq!(bess_core.io_registers.len(), 128);
assert_eq!(
bess_core.io_registers,
[
63, 0, 0, 255, 0, 0, 0, 248, 255, 255, 255, 255, 255, 255, 255, 224, 128, 63, 0,
255, 191, 255, 63, 0, 255, 191, 127, 255, 159, 255, 191, 255, 255, 0, 0, 191, 0, 0,
240, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 134, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 255, 254, 0, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 249, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
]
);
assert_eq!(bess_core.ram.size, 0x2000);
assert_eq!(bess_core.vram.size, 0x2000);
assert_eq!(bess_core.mbc_ram.size, 0x2000);
assert_eq!(bess_core.oam.size, 0x00a0);
assert_eq!(bess_core.hram.size, 0x007f);
assert_eq!(bess_core.background_palettes.size, 0x0000);
assert_eq!(bess_core.object_palettes.size, 0x0000);
}
#[test]
fn test_load_bosc() {
let mut gb = GameBoy::default();
gb.load(true).unwrap();
gb.load_rom_file("res/roms/test/firstwhite.gb", None)
.unwrap();
let data = StateManager::save(&mut gb, Some(SaveStateFormat::Bosc)).unwrap();
StateManager::load(&data, &mut gb, Some(SaveStateFormat::Bosc)).unwrap();
StateManager::load(&data, &mut gb, None).unwrap();
}
#[test]
fn test_load_bos() {
let mut gb = GameBoy::default();
gb.load_rom_file("res/roms/test/firstwhite.gb", None)
.unwrap();
let data = StateManager::save(&mut gb, Some(SaveStateFormat::Bos)).unwrap();
StateManager::load(&data, &mut gb, Some(SaveStateFormat::Bos)).unwrap();
StateManager::load(&data, &mut gb, None).unwrap();
}
#[test]
fn test_load_bess() {
let mut gb = GameBoy::default();
gb.load_rom_file("res/roms/test/firstwhite.gb", None)
.unwrap();
let data = StateManager::save(&mut gb, Some(SaveStateFormat::Bess)).unwrap();
StateManager::load(&data, &mut gb, Some(SaveStateFormat::Bess)).unwrap();
StateManager::load(&data, &mut gb, None).unwrap();
}
#[test]
fn test_compression() {
let mut gb = GameBoy::default();
gb.load(true).unwrap();
gb.load_rom_file("res/roms/test/firstwhite.gb", None)
.unwrap();
gb.step_to(0x0100);
let data = StateManager::save(&mut gb, Some(SaveStateFormat::Bess)).unwrap();
let encoded = encode_zippy(&data).unwrap();
let decoded = decode_zippy(&encoded).unwrap();
assert_eq!(data, decoded);
assert_eq!(decoded.len(), 25154);
}