Skip to content
Snippets Groups Projects
rom.rs 37.1 KiB
Newer Older
//! Cartridge (ROM) related functions and structures.

use core::fmt;
use std::{
    cmp::max,
    fmt::{Display, Formatter},
    cheats::{genie::GameGenie, shark::GameShark},
    debugln,
    gb::GameBoyMode,
    licensee::Licensee,
    util::read_file,
    warnln,
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;

pub const ROM_BANK_SIZE: usize = 16384;
pub const RAM_BANK_SIZE: usize = 8192;
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum MbcType {
    NoMbc = 0x00,
    Mbc1 = 0x01,
    Mbc2 = 0x02,
    Mbc3 = 0x03,
    Mbc5 = 0x04,
    Mbc6 = 0x05,
    Mbc7 = 0x06,
    Unknown = 0x07,
}

impl MbcType {
    pub fn ram_bank_mask(&self) -> u8 {
        match self {
            MbcType::NoMbc => 0x00,
            MbcType::Mbc1 => 0x03,
            MbcType::Mbc2 => unimplemented!("MBC2 is not supported"),
            MbcType::Mbc3 => 0x03,
            MbcType::Mbc5 => 0x0f,
            MbcType::Mbc6 => unimplemented!("MBC6 is not supported"),
            MbcType::Mbc7 => unimplemented!("MBC7 is not supported"),
            MbcType::Unknown => unimplemented!(),
        }
    }
}

#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RomType {
    RomOnly = 0x00,
    Mbc1 = 0x01,
    Mbc1Ram = 0x02,
    Mbc1RamBattery = 0x03,
    Mbc2 = 0x05,
    Mbc2Battery = 0x06,
    RomRam = 0x08,
    RomRamBattery = 0x09,
    Mmm01 = 0x0b,
    Mmm01Ram = 0x0c,
    Mmm01RamBattery = 0x0d,
    Mbc3TimerBattery = 0x0f,
    Mbc3TimerRamBattery = 0x10,
    Mbc3 = 0x11,
    Mbc3Ram = 0x12,
    Mbc3RamBattery = 0x13,
    Mbc5 = 0x19,
    Mbc5Ram = 0x1a,
    Mbc5RamBattery = 0x1b,
    Mbc5Rumble = 0x1c,
    Mbc5RumbleRam = 0x1d,
    Mbc5RumbleRamBattery = 0x1e,
    Mbc6 = 0x20,
    Mbc7SensorRumbleRamBattery = 0x22,
    PocketCamera = 0xfc,
    BandaiTama5 = 0xfd,
    HuC3 = 0xfe,
    HuC1RamBattery = 0xff,
    Unknown = 0xef,
João Magalhães's avatar
João Magalhães committed
impl RomType {
    pub fn description(&self) -> &'static str {
        match self {
            RomType::RomOnly => "ROM Only",
            RomType::Mbc1 => "MBC1",
            RomType::Mbc1Ram => "MBC1 + RAM",
            RomType::Mbc1RamBattery => "MBC1 + RAM + Battery",
            RomType::Mbc2 => "MBC2",
            RomType::Mbc2Battery => "MBC2 + RAM",
            RomType::RomRam => "ROM + RAM",
            RomType::RomRamBattery => "ROM + RAM + BATTERY",
            RomType::Mmm01 => "MMM01",
            RomType::Mmm01Ram => "MMM01 + RAM",
            RomType::Mmm01RamBattery => "MMM01 + RAM + BATTERY",
            RomType::Mbc3TimerBattery => "MBC3 + TIMER + BATTERY",
            RomType::Mbc3TimerRamBattery => "MBC3 + TIMER + RAM + BATTERY",
            RomType::Mbc3 => "MBC3",
            RomType::Mbc3Ram => "MBC3 + RAM",
            RomType::Mbc3RamBattery => "MBC3 + RAM + BATTERY",
            RomType::Mbc5 => "MBC5",
            RomType::Mbc5Ram => "MBC5 + RAM",
            RomType::Mbc5RamBattery => "MBC5 + RAM + BATTERY",
            RomType::Mbc5Rumble => "MBC5 + RUMBLE",
            RomType::Mbc5RumbleRam => "MBC5 + RUMBLE + RAM",
            RomType::Mbc5RumbleRamBattery => "MBC5 + RUMBLE + RAM + BATTERY",
            RomType::Mbc6 => "MBC6",
            RomType::Mbc7SensorRumbleRamBattery => "MBC6 + SENSOR + RUMBLE + RAM + BATTERY",
            RomType::PocketCamera => "POCKET CAMERA",
            RomType::BandaiTama5 => "BANDAI TAMA5",
            RomType::HuC3 => "HuC3",
            RomType::HuC1RamBattery => "HuC1 + RAM + BATTERY",
            RomType::Unknown => "Unknown",

    pub fn mbc_type(&self) -> MbcType {
        match self {
            RomType::RomOnly => MbcType::NoMbc,
            RomType::Mbc1 | RomType::Mbc1Ram | RomType::Mbc1RamBattery => MbcType::Mbc1,
            RomType::Mbc2 | RomType::Mbc2Battery => MbcType::Mbc2,
            RomType::Mbc3
            | RomType::Mbc3Ram
            | RomType::Mbc3RamBattery
            | RomType::Mbc3TimerBattery
            | RomType::Mbc3TimerRamBattery => MbcType::Mbc3,
            RomType::Mbc5
            | RomType::Mbc5Ram
            | RomType::Mbc5RamBattery
            | RomType::Mbc5Rumble
            | RomType::Mbc5RumbleRam
            | RomType::Mbc5RumbleRamBattery => MbcType::Mbc5,
            RomType::Mbc6 => MbcType::Mbc6,
            RomType::Mbc7SensorRumbleRamBattery => MbcType::Mbc7,
            _ => MbcType::Unknown,
        }
    }
João Magalhães's avatar
João Magalhães committed
}

impl Display for RomType {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.description())
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RomSize {
    Size32K,
    Size64K,
    Size128K,
João Magalhães's avatar
João Magalhães committed
    Size256K,
    Size512K,
    Size1M,
    Size2M,
    Size4M,
    Size8M,
impl RomSize {
João Magalhães's avatar
João Magalhães committed
    pub fn description(&self) -> &'static str {
        match self {
            RomSize::Size32K => "32 KB",
            RomSize::Size64K => "64 KB",
            RomSize::Size128K => "128 KB",
            RomSize::Size256K => "256 KB",
            RomSize::Size512K => "512 KB",
            RomSize::Size1M => "1 MB",
            RomSize::Size2M => "2 MB",
            RomSize::Size4M => "4 MB",
            RomSize::Size8M => "8 MB",
            RomSize::SizeUnknown => "Unknown",
        }
    }

    pub fn rom_banks(&self) -> u16 {
        match self {
            RomSize::Size32K => 2,
            RomSize::Size64K => 4,
            RomSize::Size128K => 8,
            RomSize::Size256K => 16,
            RomSize::Size512K => 32,
            RomSize::Size1M => 64,
            RomSize::Size2M => 128,
            RomSize::Size4M => 256,
            RomSize::Size8M => 512,
            RomSize::SizeUnknown => 0,
        }
    }
}

impl Display for RomSize {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
João Magalhães's avatar
João Magalhães committed
        write!(f, "{}", self.description())
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RamSize {
    NoRam,
    Unused,
    Size8K,
    Size32K,
    Size64K,
    Size128K,
    SizeUnknown,
}

impl RamSize {
João Magalhães's avatar
João Magalhães committed
    pub fn description(&self) -> &'static str {
        match self {
            RamSize::NoRam => "No RAM",
            RamSize::Unused => "Unused",
            RamSize::Size8K => "8 KB",
            RamSize::Size16K => "16 KB",
João Magalhães's avatar
João Magalhães committed
            RamSize::Size32K => "32 KB",
            RamSize::Size128K => "128 KB",
            RamSize::Size64K => "64 KB",
            RamSize::SizeUnknown => "Unknown",
        }
    }

    pub fn ram_banks(&self) -> u16 {
        match self {
            RamSize::NoRam => 0,
            RamSize::Unused => 0,
            RamSize::Size8K => 1,
            RamSize::Size16K => 2,
            RamSize::Size32K => 4,
            RamSize::Size64K => 8,
            RamSize::Size128K => 16,
            RamSize::SizeUnknown => 0,
        }
    }
}

impl Display for RamSize {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
João Magalhães's avatar
João Magalhães committed
        write!(f, "{}", self.description())
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Region {
    World,
    Japan,
    USA,
    Europe,
    Spain,
    Italy,
    France,
    Germany,
    Korean,
    Australia,
    Unknown,
}

impl Region {
    pub fn description(&self) -> &'static str {
        match self {
            Region::World => "World",
            Region::Japan => "Japan",
            Region::USA => "USA",
            Region::Europe => "Europe",
            Region::Spain => "Spain",
            Region::Italy => "Italy",
            Region::France => "France",
            Region::Germany => "Germany",
            Region::Korean => "Korea",
            Region::Australia => "Australia",
            Region::Unknown => "Unknown",
        }
    }
}

impl Display for Region {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.description())
    }
}

João Magalhães's avatar
João Magalhães committed
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
João Magalhães's avatar
João Magalhães committed
pub enum CgbMode {
    NoCgb = 0x00,
    CgbCompatible = 0x80,
    CgbOnly = 0xc0,
}

#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SgbMode {
    NoSgb = 0x00,
    SgbFunctions = 0x03,
}

João Magalhães's avatar
João Magalhães committed
impl CgbMode {
    pub fn description(&self) -> &'static str {
        match self {
            CgbMode::NoCgb => "No CGB support",
            CgbMode::CgbCompatible => "CGB backwards compatible",
            CgbMode::CgbOnly => "CGB only",
        }
    }
}

impl Display for CgbMode {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.description())
    }
}

João Magalhães's avatar
João Magalhães committed
/// Structure that defines the ROM and ROM contents
/// of a Game Boy cartridge. Should correctly address
/// the specifics of all the major MBCs (Memory Bank
/// Controllers).
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone)]
pub struct Cartridge {
    /// The complete data of the ROM cartridge, should
    /// include the complete set o ROM banks.
    rom_data: Vec<u8>,
    /// The base RAM that is going to be used to store
    /// temporary data for basic cartridges.
    ram_data: Vec<u8>,

    /// The MBC (Memory Bank Controller) to be used for
    /// RAM and ROM access on the current cartridge.
    mbc: &'static Mbc,
    /// The current memory handler in charge of handling the
    /// memory access for the current cartridge.
    /// Typically this is the same as the MBC, but to allow
    /// memory patching (ex: Game Genie) we may need another
    /// level of indirection.
    handler: &'static Mbc,

    /// The number of ROM banks (of 8KB) that are available
    /// to the current cartridge, this is a computed value
    /// to allow improved performance.
    rom_bank_count: u16,

    /// The number of RAM banks (of 8KB) that are available
    /// to the current cartridge, this is a computed value
    /// to allow improved performance.
    ram_bank_count: u16,

    /// The offset address to the ROM bank (#1) that is
    /// currently in use by the ROM cartridge.
    rom_offset: usize,

    /// The offset address to the ERAM bank that is
    /// currently in use by the ROM cartridge.
    ram_offset: usize,

    /// If the RAM access ia enabled, this flag allows
    /// control of memory access to avoid corruption.
    ram_enabled: bool,
    /// The final offset of the last character of the title
    /// that is considered to be non zero (0x0) so that a
    /// proper safe conversion to UTF-8 string can be done.
    title_offset: usize,
    /// The current rumble state of the cartridge, this
    /// boolean value controls if vibration is currently active.
    rumble_active: bool,

    /// Callback function to be called whenever there's a new
    /// rumble vibration triggered or when it's disabled.
    rumble_cb: fn(active: bool),
    /// Optional reference to the Game Genie instance that
    /// would be used for the "cheating" by patching the
    /// current ROM's cartridge data.
    game_genie: Option<GameGenie>,
    /// Optional reference to the GameShark instance that
    /// would be used for the "cheating" by patching the
    /// current ROM's cartridge data.
    game_shark: Option<GameShark>,
}

impl Cartridge {
    pub fn new() -> Self {
            rom_data: vec![],
            ram_data: vec![],
            mbc: &NO_MBC,
            handler: &NO_MBC,
            rom_bank_count: 0,
            ram_bank_count: 0,
            rom_offset: 0x4000,
            ram_offset: 0x0000,
            ram_enabled: false,
            title_offset: 0x0143,
            rumble_active: false,
            rumble_cb: |_| {},
            game_genie: None,
            game_shark: None,
    pub fn from_data(data: &[u8]) -> Result<Self, Error> {
        let mut cartridge = Cartridge::new();
        cartridge.set_data(data)?;
        Ok(cartridge)
    pub fn from_file(path: &str) -> Result<Self, Error> {
Loading
Loading full blame...