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("Invalid state file"));
}
}
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 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("Invalid state file"));
}
}
};
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")),