Newer
Older
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>>) {
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
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.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<(), 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],
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);
for _ in 0..(self.header.size / 3) {
let mut buffer = [0x00; 2];
data.read_exact(&mut buffer).unwrap();
let address = u16::from_le_bytes(buffer);
let mut buffer = [0x00; 1];
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, String> {
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<(), String> {
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<(), String> {
let mut file = match File::create(file_path) {
Ok(file) => file,
Err(_) => return Err(format!("Failed to open file: {}", file_path)),
};
let data = Self::save(gb, format)?;
file.write_all(&data).unwrap();
Ok(())
}
pub fn load_file(
file_path: &str,
gb: &mut GameBoy,
format: Option<SaveStateFormat>,
) -> Result<(), String> {
let mut file = match File::open(file_path) {
Ok(file) => file,
Err(_) => return Err(format!("Failed to open file: {}", file_path)),
};
let mut data = vec![];
file.read_to_end(&mut data).unwrap();
Self::load(&data, gb, format)?;
Ok(())
}
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl StateManager {
pub fn save(gb: &mut GameBoy, format: Option<SaveStateFormat>) -> Result<Vec<u8>, String> {
let mut data = Cursor::new(vec![]);
match format {
Some(SaveStateFormat::Bos) | None => {
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>,
) -> Result<(), String> {
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(String::from("Unknown save state file format"));
match format {
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)?;
}
}
pub fn read_bos(data: &[u8]) -> Result<BosState, String> {
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, String> {
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
/// stored in raw RGB format.
/// This operation is currently only supported for the BOS format.
pub fn thumbnail(data: &[u8], format: Option<SaveStateFormat>) -> Result<Vec<u8>, String> {
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(String::from("Unknown save state file format"));
}
}
};
match format {
SaveStateFormat::Bos => {
let mut state = BosState::default();
state.read(data);
Ok(state.image_buffer.unwrap().image.to_vec())
}
SaveStateFormat::Bess => Err(String::from("Format foes not support thumbnail")),
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
#[cfg(test)]
mod tests {
use super::*;
use crate::gb::GameBoy;
#[test]
fn test_load_bos() {
let mut gb = GameBoy::default();
gb.load(true);
gb.load_rom_file("res/roms/test/firstwhite.gb", None);
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(true);
gb.load_rom_file("res/roms/test/firstwhite.gb", None);
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();
}
}