Newer
Older
use std::{
cmp::max,
fmt::{Display, Formatter},
cheats::{genie::GameGenie, shark::GameShark},
debugln,
#[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,
impl RomType {
pub fn description(&self) -> &'static str {
match self {
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",
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,
}
}
}
impl Display for RomType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
Size32K,
Size64K,
Size128K,
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 {
pub enum RamSize {
NoRam,
Unused,
Size8K,
Size32K,
Size64K,
Size128K,
SizeUnknown,
}
pub fn description(&self) -> &'static str {
match self {
RamSize::NoRam => "No RAM",
RamSize::Unused => "Unused",
RamSize::Size8K => "8 KB",
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::Size32K => 4,
RamSize::Size64K => 8,
RamSize::Size128K => 16,
RamSize::SizeUnknown => 0,
}
}
}
impl Display for RamSize {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub enum CgbMode {
NoCgb = 0x00,
CgbCompatible = 0x80,
CgbOnly = 0xc0,
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub enum SgbMode {
NoSgb = 0x00,
SgbFunctions = 0x03,
}
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())
}
}
/// 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.
/// The base RAM that is going to be used to store
/// temporary data for basic cartridges.
/// 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.
/// 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 {
pub fn from_data(data: &[u8]) -> Result<Self, Error> {
cartridge.set_data(data)?;
Ok(cartridge)
pub fn from_file(path: &str) -> Result<Self, Error> {
Self::from_data(&data)
}
pub fn read(&mut self, addr: u16) -> u8 {
match addr & 0xf000 {
0x0000 | 0x1000 | 0x2000 | 0x3000 | 0x4000 | 0x5000 | 0x6000 | 0x7000 => {
(self.handler.read_rom)(self, addr)
0xa000 | 0xb000 => (self.handler.read_ram)(self, addr),
_ => {
debugln!("Reading from unknown Cartridge control 0x{:04x}", addr);
0x00
}
}
pub fn write(&mut self, addr: u16, value: u8) {
match addr & 0xf000 {
0x0000 | 0x1000 | 0x2000 | 0x3000 | 0x4000 | 0x5000 | 0x6000 | 0x7000 => {
(self.handler.write_rom)(self, addr, value)
0xa000 | 0xb000 => (self.handler.write_ram)(self, addr, value),
_ => debugln!("Writing to unknown Cartridge address 0x{:04x}", addr),
}
pub fn reset(&mut self) {
self.rom_data = vec![];
self.ram_data = vec![];
self.mbc = &NO_MBC;
self.rom_bank_count = 0;
self.ram_bank_count = 0;
self.rom_offset = 0x4000;
self.ram_offset = 0x0000;
self.ram_enabled = false;
self.title_offset = 0x0143;
self.rumble_active = false;
self.rumble_cb = |_| {};
}
pub fn vblank(&mut self) -> Option<Vec<(u16, u16, u8)>> {
if let Some(game_shark) = &mut self.game_shark {
pub fn get_bank(&self, index: u8) -> &[u8] {
let start = index as usize * ROM_BANK_SIZE;
let end = (index + 1) as usize * ROM_BANK_SIZE;
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
pub fn set_rom_type(&mut self, rom_type: RomType) -> Result<(), Error> {
self.rom_data[0x0147] = match rom_type {
RomType::RomOnly => 0x00,
RomType::Mbc1 => 0x01,
RomType::Mbc1Ram => 0x02,
RomType::Mbc1RamBattery => 0x03,
RomType::Mbc2 => 0x05,
RomType::Mbc2Battery => 0x06,
RomType::RomRam => 0x08,
RomType::RomRamBattery => 0x09,
RomType::Mmm01 => 0x0b,
RomType::Mmm01Ram => 0x0c,
RomType::Mmm01RamBattery => 0x0d,
RomType::Mbc3TimerBattery => 0x0f,
RomType::Mbc3TimerRamBattery => 0x10,
RomType::Mbc3 => 0x11,
RomType::Mbc3Ram => 0x12,
RomType::Mbc3RamBattery => 0x13,
RomType::Mbc5 => 0x19,
RomType::Mbc5Ram => 0x1a,
RomType::Mbc5RamBattery => 0x1b,
RomType::Mbc5Rumble => 0x1c,
RomType::Mbc5RumbleRam => 0x1d,
RomType::Mbc5RumbleRamBattery => 0x1e,
RomType::Mbc6 => 0x20,
RomType::Mbc7SensorRumbleRamBattery => 0x22,
RomType::PocketCamera => 0xfc,
RomType::BandaiTama5 => 0xfd,
RomType::HuC3 => 0xfe,
RomType::HuC1RamBattery => 0xff,
RomType::Unknown => return Err(Error::CustomError(String::from("Unknown ROM type"))),
};
Ok(())
}
pub fn mbc(&self) -> Result<&'static Mbc, Error> {
RomType::RomOnly => &NO_MBC,
RomType::Mbc1 => &MBC1,
RomType::Mbc1Ram => &MBC1,
RomType::Mbc1RamBattery => &MBC1,
RomType::Mbc3TimerBattery => &MBC3,
RomType::Mbc3TimerRamBattery => &MBC3,
RomType::Mbc3 => &MBC3,
RomType::Mbc3Ram => &MBC3,
RomType::Mbc3RamBattery => &MBC3,
RomType::Mbc5 => &MBC5,
RomType::Mbc5Ram => &MBC5,
RomType::Mbc5RamBattery => &MBC5,
RomType::Mbc5Rumble => &MBC5,
RomType::Mbc5RumbleRam => &MBC5,
RomType::Mbc5RumbleRamBattery => &MBC5,
rom_type => {
return Err(Error::CustomError(format!(
"No MBC controller available for {}",
rom_type
)))
}
})
pub fn has_rumble(&mut self) -> bool {
matches!(
self.rom_type(),
RomType::Mbc5Rumble | RomType::Mbc5RumbleRam | RomType::Mbc5RumbleRamBattery
)
}
pub fn ram_enabled(&self) -> bool {
self.ram_enabled
}
pub fn set_ram_enabled(&mut self, ram_enabled: bool) {
self.ram_enabled = ram_enabled
}
pub fn ram_bank(&self) -> u8 {
(self.ram_offset / RAM_BANK_SIZE) as u8
}
pub fn set_ram_bank(&mut self, ram_bank: u8) {
self.ram_offset = ram_bank as usize * RAM_BANK_SIZE;
}
pub fn rom_bank(&self) -> u16 {
(self.rom_offset / ROM_BANK_SIZE) as u16
pub fn set_rom_bank(&mut self, rom_bank: u16) {
self.rom_offset = rom_bank as usize * ROM_BANK_SIZE;
}
pub fn set_rumble_cb(&mut self, rumble_cb: fn(active: bool)) {
self.rumble_cb = rumble_cb;
}
pub fn trigger_rumble(&self) {
(self.rumble_cb)(self.rumble_active);
}
fn set_data(&mut self, data: &[u8]) -> Result<(), Error> {
self.ensure_data(data)?;
self.rom_data = data.to_vec();
self.rom_offset = 0x4000;
self.ram_offset = 0x0000;
self.allocate_ram();
self.set_rom_bank(1);
self.set_ram_bank(0);
fn set_mbc(&mut self) -> Result<(), Error> {
self.mbc = self.mbc()?;
fn set_computed(&mut self) {
self.rom_bank_count = self.rom_size().rom_banks();
self.ram_bank_count = self.ram_size().ram_banks();
if self.rom_data.len() < 0x0143 {
return;
}
for byte in &self.rom_data[0x0134..=0x0143] {
if *byte == 0u8 {
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)
{
break;
}
}
self.title_offset = 0x0134 + offset;
}
pub fn game_genie(&self) -> &Option<GameGenie> {
&self.game_genie
}
pub fn game_genie_mut(&mut self) -> &mut Option<GameGenie> {
&mut self.game_genie
}
pub fn set_game_genie(&mut self, game_genie: Option<GameGenie>) {
self.game_genie = game_genie;
}
pub fn game_shark(&self) -> &Option<GameShark> {
&self.game_shark
}
pub fn game_shark_mut(&mut self) -> &mut Option<GameShark> {
&mut self.game_shark
}
pub fn set_game_shark(&mut self, game_shark: Option<GameShark>) {
self.game_shark = game_shark;
}
fn allocate_ram(&mut self) {
let ram_banks = max(self.ram_size().ram_banks(), 1);
self.ram_data = vec![0u8; ram_banks as usize * RAM_BANK_SIZE];
}
/// Ensures that the data provided is of a valid Game Boy ROM
/// and that it's size is within the expected range.
fn ensure_data(&self, data: &[u8]) -> Result<(), Error> {
if data.len() < 0x7fff {
return Err(Error::RomSize);
}
if data.len() % (16 * 1024) != 0 {
return Err(Error::RomSize);
}
Ok(())
}
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl Cartridge {
pub fn title(&self) -> String {
String::from(
std::str::from_utf8(&self.rom_data[0x0134..self.title_offset])
.unwrap()
.trim(),
)
pub fn cgb_flag(&self) -> CgbMode {
match self.rom_data[0x0143] {
0x80 => CgbMode::CgbCompatible,
0xc0 => CgbMode::CgbOnly,
_ => CgbMode::NoCgb,
}
}
pub fn sgb_flag(&self) -> SgbMode {
match self.rom_data[0x0146] {
0x03 => SgbMode::SgbFunctions,
_ => SgbMode::NoSgb,
}
}
pub fn gb_mode(&self) -> GameBoyMode {
match self.cgb_flag() {
CgbMode::CgbCompatible | CgbMode::CgbOnly => GameBoyMode::Cgb,
_ => GameBoyMode::Dmg,
}
}
/// A cartridge is considered legacy if it does
/// not have a CGB flag bit (bit 7 of 0x0143) set.
/// These are the monochromatic only Cartridges built
/// for the original DMG Game Boy.
pub fn is_legacy(&self) -> bool {
self.rom_data[0x0143] & 0x80 == 0x00
}
pub fn rom_type(&self) -> RomType {
if self.rom_data.len() < 0x0147 {
return RomType::Unknown;
}
0x00 => RomType::RomOnly,
0x01 => RomType::Mbc1,
0x02 => RomType::Mbc1Ram,
0x03 => RomType::Mbc1RamBattery,
0x05 => RomType::Mbc2,
0x06 => RomType::Mbc2Battery,
0x08 => RomType::RomRam,
0x09 => RomType::RomRamBattery,
0x0b => RomType::Mmm01,
0x0c => RomType::Mmm01Ram,
0x0d => RomType::Mmm01RamBattery,
0x0f => RomType::Mbc3TimerBattery,
0x10 => RomType::Mbc3TimerRamBattery,
0x11 => RomType::Mbc3,
0x12 => RomType::Mbc3Ram,
0x13 => RomType::Mbc3RamBattery,
0x19 => RomType::Mbc5,
0x1a => RomType::Mbc5Ram,
0x1b => RomType::Mbc5RamBattery,
0x1c => RomType::Mbc5Rumble,
0x1d => RomType::Mbc5RumbleRam,
0x1e => RomType::Mbc5RumbleRamBattery,
0x20 => RomType::Mbc6,
0x22 => RomType::Mbc7SensorRumbleRamBattery,
0xfc => RomType::PocketCamera,
0xfd => RomType::BandaiTama5,
0xfe => RomType::HuC3,
0xff => RomType::HuC1RamBattery,
_ => RomType::Unknown,
}
}
pub fn rom_size(&self) -> RomSize {
if self.rom_data.len() < 0x0148 {
return RomSize::SizeUnknown;
}
0x00 => RomSize::Size32K,
0x01 => RomSize::Size64K,
0x02 => RomSize::Size128K,
0x03 => RomSize::Size256K,
0x04 => RomSize::Size512K,
0x05 => RomSize::Size1M,
0x06 => RomSize::Size2M,
0x07 => RomSize::Size4M,
0x08 => RomSize::Size8M,
_ => RomSize::SizeUnknown,
}
}
pub fn ram_size(&self) -> RamSize {
if self.rom_data.len() < 0x0149 {
return RamSize::SizeUnknown;
}
0x00 => RamSize::NoRam,
0x01 => RamSize::Unused,
0x02 => RamSize::Size8K,
0x03 => RamSize::Size32K,
0x04 => RamSize::Size128K,
0x05 => RamSize::Size64K,
_ => RamSize::SizeUnknown,
}
}
pub fn rom_type_s(&self) -> String {
String::from(self.rom_type().description())
}
pub fn rom_size_s(&self) -> String {
String::from(self.rom_size().description())
}
pub fn ram_size_s(&self) -> String {
String::from(self.ram_size().description())
}
| RomType::Mbc2Battery
| RomType::RomRamBattery
| RomType::Mmm01RamBattery
| RomType::Mbc3TimerBattery
| RomType::Mbc3TimerRamBattery
| RomType::Mbc3RamBattery
| RomType::Mbc5RamBattery
| RomType::Mbc5RumbleRamBattery
| RomType::Mbc7SensorRumbleRamBattery
| RomType::HuC1RamBattery
pub fn rom_data_eager(&self) -> Vec<u8> {
self.rom_data.clone()
}
pub fn ram_data_eager(&self) -> Vec<u8> {
self.ram_data.clone()
}
pub fn set_ram_data(&mut self, data: &[u8]) {
self.ram_data = data.to_vec();
pub fn clear_ram_data(&mut self) {
self.ram_data = vec![0u8; self.ram_data.len()];
pub fn attach_genie(&mut self, game_genie: GameGenie) {
self.game_genie = Some(game_genie);
self.handler = &GAME_GENIE;
}
pub fn detach_genie(&mut self) {
self.game_genie = None;
self.handler = self.mbc;
}
pub fn attach_shark(&mut self, game_shark: GameShark) {
self.game_shark = Some(game_shark);
self.game_shark.as_mut().unwrap().set_rom_type(rom_type);
}
pub fn detach_shark(&mut self) {
self.game_shark = None;
}
pub fn description(&self, column_length: usize) -> String {
let name_l = format!("{:width$}", "Name", width = column_length);
let type_l = format!("{:width$}", "Type", width = column_length);
let rom_size_l = format!("{:width$}", "ROM Size", width = column_length);
let ram_size_l = format!("{:width$}", "RAM Size", width = column_length);
let cgb_l = format!("{:width$}", "CGB Mode", width = column_length);
format!(
"{} {}\n{} {}\n{} {}\n{} {}\n{} {}",
impl Cartridge {
pub fn rom_data(&self) -> &Vec<u8> {
&self.rom_data
}
pub fn rom_data_mut(&mut self) -> &mut Vec<u8> {
&mut self.rom_data
}
pub fn ram_data(&self) -> &Vec<u8> {
&self.ram_data
}
pub fn ram_data_mut(&mut self) -> &mut Vec<u8> {
&mut self.ram_data
}
impl BusComponent for Cartridge {
fn read(&mut self, addr: u16) -> u8 {
self.read(addr)
}
fn write(&mut self, addr: u16, value: u8) {
self.write(addr, value);
}
}
impl Default for Cartridge {
fn default() -> Self {
Self::new()
}
}
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description(9))
pub struct Mbc {
pub name: &'static str,
pub read_rom: fn(rom: &Cartridge, addr: u16) -> u8,
pub write_rom: fn(rom: &mut Cartridge, addr: u16, value: u8),
pub read_ram: fn(rom: &Cartridge, addr: u16) -> u8,
pub write_ram: fn(rom: &mut Cartridge, addr: u16, value: u8),
}
pub static NO_MBC: Mbc = Mbc {
name: "No MBC",
read_rom: |rom: &Cartridge, addr: u16| -> u8 { rom.rom_data[addr as usize] },
write_rom: |_rom: &mut Cartridge, addr: u16, _value: u8| {
match addr {
// ignores this address as Tetris and some other games write
// to this address for some reason (probably related to
_ => panic!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
read_ram: |rom: &Cartridge, addr: u16| -> u8 { rom.ram_data[(addr - 0xa000) as usize] },
write_ram: |rom: &mut Cartridge, addr: u16, value: u8| {
rom.ram_data[(addr - 0xa000) as usize] = value;
},
};
pub static MBC1: Mbc = Mbc {
name: "MBC1",
read_rom: |rom: &Cartridge, addr: u16| -> u8 {
match addr & 0xf000 {
0x0000 | 0x1000 | 0x2000 | 0x3000 => rom.rom_data[addr as usize],
0x4000 | 0x5000 | 0x6000 | 0x7000 => *rom
.rom_data
.get(rom.rom_offset + (addr - 0x4000) as usize)
.unwrap_or(&0x0),
_ => {
warnln!("Reading from unknown Cartridge ROM location 0x{:04x}", addr);
0xff
}
}
},
write_rom: |rom: &mut Cartridge, addr: u16, value: u8| {
match addr & 0xf000 {
let mut rom_bank = value as u16 & 0x1f;
rom_bank &= rom.rom_bank_count * 2 - 1;
if rom_bank == 0 {
rom_bank = 1;
}
rom.set_rom_bank(rom_bank);
}
// RAM bank selection and ROM bank selection upper bits
let ram_bank = value & 0x03;
if ram_bank as u16 >= rom.ram_bank_count {
return;
}
rom.set_ram_bank(ram_bank);
if value == 0x1 && rom.rom_bank_count > 32 {
unimplemented!("Advanced ROM banking mode for MBC1 is not implemented");
}
_ => warnln!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
}
},
read_ram: |rom: &Cartridge, addr: u16| -> u8 {
if !rom.ram_enabled {
return 0xff;
}
rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize]
},
write_ram: |rom: &mut Cartridge, addr: u16, value: u8| {
if !rom.ram_enabled {
warnln!("Attempt to write to ERAM while write protect is active");
return;
}
rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize] = value;
},
};
pub static MBC3: Mbc = Mbc {
name: "MBC3",
read_rom: |rom: &Cartridge, addr: u16| -> u8 {
match addr & 0xf000 {
0x0000 | 0x1000 | 0x2000 | 0x3000 => rom.rom_data[addr as usize],
0x4000 | 0x5000 | 0x6000 | 0x7000 => *rom
.rom_data
.get(rom.rom_offset + (addr - 0x4000) as usize)
.unwrap_or(&0x0),
_ => {
warnln!("Reading from unknown Cartridge ROM location 0x{:04x}", addr);
0xff
}
}
},
write_rom: |rom: &mut Cartridge, addr: u16, value: u8| {
match addr & 0xf000 {
// RAM enabled flag
0x0000 | 0x1000 => {
rom.ram_enabled = (value & 0x0f) == 0x0a;
}
// ROM bank selection
0x2000 | 0x3000 => {
let mut rom_bank = value as u16 & 0x7f;
rom_bank &= rom.rom_bank_count * 2 - 1;
if rom_bank == 0 {
rom_bank = 1;
}
rom.set_rom_bank(rom_bank);
}
// RAM bank selection
0x4000 | 0x5000 => {
let ram_bank = value & 0x03;
if ram_bank as u16 >= rom.ram_bank_count {
return;
}
_ => warnln!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
}
},
read_ram: |rom: &Cartridge, addr: u16| -> u8 {
if !rom.ram_enabled {
return 0xff;
}
rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize]
},
write_ram: |rom: &mut Cartridge, addr: u16, value: u8| {
if !rom.ram_enabled {
warnln!("Attempt to write to ERAM while write protect is active");