Skip to content
Snippets Groups Projects
state.rs 45.9 KiB
Newer Older
//! System save state (BOS and [BESS](https://github.com/LIJI32/SameBoy/blob/master/BESS.md) formats) functions and structures.
use std::{
    convert::TryInto,
    fmt::{self, Display, Formatter},
    fs::File,
    io::{Cursor, Read, Seek, SeekFrom, Write},
    mem::size_of,
use crate::{
    gb::{GameBoy, GameBoySpeed},
    info::Info,
    ppu::{DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_SIZE},
João Magalhães's avatar
João Magalhães committed
    rom::{CgbMode, MbcType},
    util::{get_timestamp, save_bmp},
#[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,
}

#[derive(Clone, Copy)]
pub enum BosBlockKind {
    ImageBuffer = 0x02,
    Unknown = 0xff,
}

impl BosBlockKind {
    fn from_u8(value: u8) -> Self {
        match value {
            0x01 => Self::Info,
            0x02 => Self::ImageBuffer,
            _ => Self::Unknown,
        }
    }
pub trait Serialize {
    /// 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>>);
pub trait State {
    /// 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>;
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Default)]
pub struct BosState {
    magic: u32,
    version: u8,
    info: Option<BosInfo>,
    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);
João Magalhães's avatar
João Magalhães committed
        data.rewind().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_image_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.info.is_some() {
            count += 1;
        }
        if self.image_buffer.is_some() {
            count += 1;
        }
        count
    }
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl BosState {
    pub fn timestamp(&self) -> Result<u64, String> {
        if let Some(info) = &self.info {
            Ok(info.timestamp)
        } else {
            Err(String::from("No timestamp available"))
        }
    }

    pub fn agent(&self) -> Result<String, String> {
        if let Some(info) = &self.info {
            Ok(format!("{}/{}", info.agent, info.agent_version))
        } else {
            Err(String::from("No agent available"))
        }
    }

    pub fn model(&self) -> Result<String, String> {
        if let Some(info) = &self.info {
            Ok(info.model.clone())
        } else {
            Err(String::from("No model available"))
        }
    }

    pub fn image_eager(&self) -> Result<Vec<u8>, String> {
        if let Some(image_buffer) = &self.image_buffer {
            Ok(image_buffer.image.to_vec())
        } else {
            Err(String::from("No image available"))
        }
    }
}

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(info) = &mut self.info {
            info.write(buffer);
        }
        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::Info => {
                    self.info = Some(BosInfo::from_data(data));
                }
                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: 2,
            info: Some(BosInfo::from_gb(gb)?),
            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,
}

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::Info, 0)
    }
}

pub struct BosInfo {
    header: BosBlock,
    timestamp: u64,
    agent: String,
    agent_version: String,
    model: String,
}

impl BosInfo {
    pub fn new(model: String, timestamp: u64, agent: String, agent_version: String) -> Self {
        Self {
            header: BosBlock::new(
                BosBlockKind::Info,
                (size_of::<u64>()
                    + size_of::<u8>() * agent.len()
                    + size_of::<u8>() * agent_version.len()
                    + size_of::<u8>() * model.len()
                    + size_of::<u32>() * 4) as u32,
            ),
            model,
            timestamp,
            agent,
            agent_version,
        }
    }

    pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
        let mut instance = Self::default();
        instance.read(data);
        instance
    }
}

impl Serialize for BosInfo {
    fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
        self.header.write(buffer);

        buffer
            .write_all(&(size_of::<u64>() as u32).to_le_bytes())
            .unwrap();
        buffer.write_all(&self.timestamp.to_le_bytes()).unwrap();

        buffer
            .write_all(&(self.agent.as_bytes().len() as u32).to_le_bytes())
            .unwrap();
        buffer.write_all(self.agent.as_bytes()).unwrap();

        buffer
            .write_all(&(self.agent_version.as_bytes().len() as u32).to_le_bytes())
            .unwrap();
        buffer.write_all(self.agent_version.as_bytes()).unwrap();

        buffer
            .write_all(&(self.model.as_bytes().len() as u32).to_le_bytes())
            .unwrap();
        buffer.write_all(self.model.as_bytes()).unwrap();
    }

    fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
        self.header.read(data);

        let mut buffer = [0x00; size_of::<u32>()];
        data.read_exact(&mut buffer).unwrap();
        let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
        data.read_exact(&mut buffer).unwrap();
        self.timestamp = u64::from_le_bytes(buffer.try_into().unwrap());

        let mut buffer = [0x00; size_of::<u32>()];
        data.read_exact(&mut buffer).unwrap();
        let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
        data.read_exact(&mut buffer).unwrap();
        self.agent = String::from_utf8(buffer).unwrap();

        let mut buffer = [0x00; size_of::<u32>()];
        data.read_exact(&mut buffer).unwrap();
        let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
        data.read_exact(&mut buffer).unwrap();
        self.agent_version = String::from_utf8(buffer).unwrap();

        let mut buffer = [0x00; size_of::<u32>()];
        data.read_exact(&mut buffer).unwrap();
        let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
        data.read_exact(&mut buffer).unwrap();
        self.model = String::from_utf8(buffer).unwrap();
    }
}

impl State for BosInfo {
    fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
        let timestamp = get_timestamp();
        Ok(Self::new(
            gb.mode().to_string(Some(true)),
            timestamp,
            Info::name(),
            Info::version(),
        ))
    }

    fn to_gb(&self, _gb: &mut GameBoy) -> Result<(), String> {
        Ok(())
    }
}

impl Default for BosInfo {
    fn default() -> Self {
        Self::new(String::from(""), 0, String::from(""), String::from(""))
    }
}

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])
    }
#[cfg_attr(feature = "wasm", wasm_bindgen)]
João Magalhães's avatar
João Magalhães committed
#[derive(Default)]
pub struct BessState {
    footer: BessFooter,
    name: BessName,
    info: BessInfo,
    core: BessCore,
    mbc: BessMbc,
    end: BessBlock,
impl BessState {
    /// 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(-4)).unwrap();
        let mut buffer = [0x00; 4];
        data.read_exact(&mut buffer).unwrap();
        let magic = u32::from_le_bytes(buffer);
João Magalhães's avatar
João Magalhães committed
        data.rewind().unwrap();
    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> {
João Magalhães's avatar
João Magalhães committed
        self.footer.verify()?;
        self.core.verify()?;
    /// 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();
impl Serialize for BessState {
    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
        // the cursor in the start of the BESS data
        // according to the footer information
        data.seek(SeekFrom::End(-8)).unwrap();
        self.footer.read(data);
        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 block = BessBlockHeader::from_data(data);
            let offset = -((size_of::<u32>() * 2) as i64);
            data.seek(SeekFrom::Current(offset)).unwrap();

            match block.magic.as_str() {
                "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),
João Magalhães's avatar
João Magalhães committed
                _ => {
                    BessBlock::from_data(data);
impl State for BessState {
    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)?;
        self.mbc.to_gb(gb)?;
impl Display for BessState {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.description(9))
pub struct BessBlockHeader {
impl BessBlockHeader {
    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.read(data);
        instance
    }

    pub fn is_end(&self) -> bool {
        self.magic == "END "
    }
impl Serialize for BessBlockHeader {
    fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
João Magalhães's avatar
João Magalhães committed
        buffer.write_all(self.magic.as_bytes()).unwrap();
        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();
        let mut buffer = [0x00; 4];
        data.read_exact(&mut buffer).unwrap();
João Magalhães's avatar
João Magalhães committed
        self.size = u32::from_le_bytes(buffer);
impl Default for BessBlockHeader {
    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.read(data);
        instance
    }

    pub fn magic(&self) -> &String {
        &self.header.magic
    }

    pub fn is_end(&self) -> bool {
        self.header.is_end()
impl Serialize for BessBlock {
    fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
        self.header.write(buffer);
        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();
impl Default for BessBlock {
    fn default() -> Self {
        Self::new(BessBlockHeader::default(), vec![])
pub struct BessBuffer {
impl BessBuffer {
    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
    }
impl Serialize for BessBuffer {
    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();
João Magalhães's avatar
João Magalhães committed
        self.size = u32::from_le_bytes(buffer);
        let mut buffer = [0x00; 4];
        data.read_exact(&mut buffer).unwrap();
João Magalhães's avatar
João Magalhães committed
        self.offset = u32::from_le_bytes(buffer);
        self.buffer = self.load_buffer(data);
    }
}

impl Default for BessBuffer {
    fn default() -> Self {
        Self::new(0, 0, vec![])
pub struct BessFooter {
    start_offset: u32,
    magic: u32,
impl BessFooter {
    pub fn new(start_offset: u32, magic: u32) -> Self {
        Self {
            start_offset,
            magic,
        }
    }

    pub fn verify(&self) -> Result<(), String> {
        if self.magic != BESS_MAGIC {
            return Err(String::from("Invalid magic"));
        }
        Ok(())
    }
impl Serialize for BessFooter {
    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();
João Magalhães's avatar
João Magalhães committed
        self.start_offset = u32::from_le_bytes(buffer);
        let mut buffer = [0x00; 4];
        data.read_exact(&mut buffer).unwrap();
João Magalhães's avatar
João Magalhães committed
        self.magic = u32::from_le_bytes(buffer);
impl Default for BessFooter {
    fn default() -> Self {
        Self::new(0x00, BESS_MAGIC)
pub struct BessName {
    header: BessBlockHeader,
impl BessName {
    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();
        instance.read(data);
impl Serialize for BessName {
    fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
        self.header.write(buffer);
        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];
        data.read_exact(&mut buffer).unwrap();
João Magalhães's avatar
João Magalhães committed
        self.name = String::from_utf8(buffer).unwrap();
impl State for BessName {
    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(())
    }
impl Default for BessName {
    fn default() -> Self {
        Self::new(String::from(""))
    }
}

pub struct BessInfo {
    header: BessBlockHeader,
    title: [u8; 16],
    checksum: [u8; 2],
impl BessInfo {
    pub fn new(title: &[u8], checksum: &[u8]) -> Self {
        Self {
            header: BessBlockHeader::new(
                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();
        instance.read(data);
    pub fn title(&self) -> String {
        let mut final_index = 16;
        for (offset, byte) in self.title.iter().enumerate() {
            if *byte == 0u8 {
                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(),
        )
impl Serialize for BessInfo {
    fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
        self.header.write(buffer);
        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();
impl State for BessInfo {
    fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
        Ok(Self::new(
            &gb.cartridge_i().rom_data()[0x0134..=0x0143],
            &gb.cartridge_i().rom_data()[0x014e..=0x014f],
    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(())
    }
impl Default for BessInfo {
    fn default() -> Self {
        Self::new(&[0_u8; 16], &[0_u8; 2])
    }
}

pub struct BessCore {
    header: BessBlockHeader,
    model: String,
    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,
impl BessCore {
João Magalhães's avatar
João Magalhães committed
    #[allow(clippy::too_many_arguments)]
    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 {
            header: BessBlockHeader::new(
                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,