use std::fs::File; use std::io::{self, Read}; use std::path::Path; const RAM_SIZE_ADDRESS: usize = 0x0149; const ROM_SIZE_ADDRESS: usize = 0x0148; const MBC_TYPE_ADDRESS: usize = 0x0147; #[derive(Debug, Clone, Default)] pub struct Cartridge { memory: Vec, mbc: Box, } impl Cartridge { pub fn new + ?Sized>(path: &P) -> io::Result { let mut memory = vec![]; let mut rom = File::open(path)?; rom.read_to_end(&mut memory)?; Ok(Self { mbc: Self::detect_mbc(&memory), memory, }) } fn detect_mbc(memory: &[u8]) -> Box { let ram_size = Self::find_ram_size(&memory); let bank_count = Self::find_bank_count(&memory); let mbc_kind = Self::find_mbc(&memory); let ram_byte_count = ram_size.to_byte_count(); match mbc_kind { MbcKind::None => Box::new(NoMbc {}), MbcKind::Mbc1 => { let mbc = Mbc1 { ram_size, ram: vec![0; ram_byte_count as usize], bank_count, ..Default::default() }; Box::new(mbc) } MbcKind::Mbc5 => todo!("Implement MBC5"), } } fn find_ram_size(memory: &[u8]) -> RamSize { let id = memory[RAM_SIZE_ADDRESS]; id.into() } fn find_bank_count(memory: &[u8]) -> BankCount { let id = memory[ROM_SIZE_ADDRESS]; id.into() } fn find_mbc(memory: &[u8]) -> MbcKind { let id = memory[MBC_TYPE_ADDRESS]; // TODO: Refactor this to match the other enums in this module match id { 0x00 => MbcKind::None, 0x01 => MbcKind::Mbc1, 0x19 => MbcKind::Mbc5, _ => unimplemented!("{} is the id of an unsupported memory bank controller", id), } } } impl Cartridge { pub fn read_byte(&self, addr: u16) -> u8 { match self.mbc.handle_read(addr) { MbcResult::Address(addr) => self.memory[addr as usize], MbcResult::Value(byte) => byte, } } pub fn write_byte(&mut self, addr: u16, byte: u8) { self.mbc.handle_write(addr, byte); } pub fn read_word(&self, addr: u16) -> u16 { (self.read_byte(addr + 1) as u16) << 8 | self.read_byte(addr) as u16 } pub fn write_word(&mut self, addr: u16, word: u16) { self.write_byte(addr + 1, (word >> 8) as u8); self.write_byte(addr, (word & 0x00FF) as u8); } } #[derive(Debug, Clone, Default)] struct Mbc1 { rom_bank: u8, // 5-bit Number ram_bank: u8, // 2-bit number mode: bool, ram_size: RamSize, ram: Vec, bank_count: BankCount, ram_enabled: bool, } impl Mbc1 { fn calc_zero_bank_number(&self) -> u8 { use BankCount::*; match self.bank_count { ThirtyTwo | Sixteen | Eight | Four => 0, SixtyFour => (self.ram_bank & 0x01) << 5, OneHundredTwentyEight => self.ram_bank << 5, _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count), } } fn calc_high_bank_number(&self) -> u8 { use BankCount::*; let less_than_mb = self.apply_rom_size_bitmask(self.rom_bank); match self.bank_count { None | Four | Eight | Sixteen | ThirtyTwo => less_than_mb, SixtyFour => (less_than_mb & !(0x01 << 5)) | (self.ram_bank & 0x01) << 5, OneHundredTwentyEight => (less_than_mb & !(0b11 << 5)) | (self.ram_bank & 0b11) << 5, _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count), } } fn apply_rom_size_bitmask(&self, byte: u8) -> u8 { use BankCount::*; let err_bc = self.bank_count; // Bank Count, but with a shorter name let result = match self.bank_count { None => byte, // FIXME: I don't think this is the correct behaviour Four => byte & 0b00000011, Eight => byte & 0b00000111, Sixteen => byte & 0b00001111, ThirtyTwo => byte & 0b00011111, SixtyFour => byte & 0b00011111, OneHundredTwentyEight => byte & 0b00011111, _ => unreachable!("{:?} does not have a rom size bitmask in MBC1", err_bc), }; if result == 0 { return 1; } result } fn calc_ram_address(&self, addr: u16) -> u16 { match self.ram_size { RamSize::_2KB | RamSize::_8KB => { let ram_size = self.ram_size.to_byte_count() as u16; (addr - 0xA000) % ram_size } RamSize::_32KB => { if self.mode { 0x2000 * self.ram_bank as u16 + (addr - 0xA000) } else { addr - 0xA000 } } _ => unreachable!("RAM size can not be greater than 32KB on MBC1"), } } } impl MemoryBankController for Mbc1 { fn handle_read(&self, addr: u16) -> MbcResult { use MbcResult::*; match addr { 0x0000..=0x3FFF => { if self.mode { let zero_bank = self.calc_zero_bank_number() as u16; Address(0x4000 * zero_bank + addr) } else { Address(addr) } } 0x4000..=0x7FFF => { let high_bank = self.calc_high_bank_number() as u16; Address(0x4000 * high_bank + (addr - 0x4000)) } 0xA000..=0xBFFF => { if self.ram_enabled { let ram_addr = self.calc_ram_address(addr); Value(self.ram[ram_addr as usize]) } else { Value(0xFF) } } _ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr), } } fn handle_write(&mut self, addr: u16, byte: u8) { match addr { 0x0000..=0x1FFF => self.ram_enabled = (byte & 0x0F) == 0x0A, 0x2000..=0x3FFF => { self.rom_bank = self.apply_rom_size_bitmask(byte); } 0x4000..=0x5FFF => self.ram_bank = byte & 0b11, 0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01, 0xA000..=0xBFFF => { if self.ram_enabled { let ram_addr = self.calc_ram_address(addr); self.ram[ram_addr as usize] = byte; } } _ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr), } } } #[derive(Debug, Clone, Copy)] struct NoMbc {} impl MemoryBankController for NoMbc { fn handle_read(&self, addr: u16) -> MbcResult { MbcResult::Address(addr) } fn handle_write(&mut self, _addr: u16, _byte: u8) { panic!("A MBC-less cartridge is read only") } } trait MemoryBankController: CloneMbc { fn handle_read(&self, addr: u16) -> MbcResult; fn handle_write(&mut self, addr: u16, byte: u8); } trait CloneMbc { fn clone_mbc(&self) -> Box; } impl CloneMbc for T where T: MemoryBankController + Clone + 'static, { fn clone_mbc<'a>(&self) -> Box { Box::new(self.clone()) } } enum MbcResult { Address(u16), Value(u8), } #[derive(Debug, Clone, Copy)] enum MbcKind { None, Mbc1, Mbc5, } impl Default for MbcKind { fn default() -> Self { Self::None } } #[derive(Debug, Clone, Copy)] enum RamSize { None = 0x00, _2KB = 0x01, _8KB = 0x02, _32KB = 0x03, // Split into 4 RAM banks _128KB = 0x04, // Split into 16 RAM banks _64KB = 0x05, // Split into 8 RAm Banks } impl RamSize { pub fn to_byte_count(&self) -> u32 { use RamSize::*; match *self { None => 0, _2KB => 2_048, _8KB => 8_192, _32KB => 32_768, _128KB => 131_072, _64KB => 65_536, } } } impl Default for RamSize { fn default() -> Self { Self::None } } impl From for RamSize { fn from(byte: u8) -> Self { use RamSize::*; match byte { 0x00 => None, 0x01 => _2KB, 0x02 => _8KB, 0x03 => _32KB, 0x04 => _128KB, 0x05 => _64KB, _ => unreachable!("{:#04X} is not a valid value for RAMSize", byte), } } } #[derive(Debug, Clone, Copy)] enum BankCount { None = 0x00, // 32KB Four = 0x01, // 64KB Eight = 0x02, // 128KB Sixteen = 0x03, // 256KB ThirtyTwo = 0x04, // 512KB SixtyFour = 0x05, // 1MB OneHundredTwentyEight = 0x06, // 2MB TwoHundredFiftySix = 0x07, // 4MB FiveHundredTwelve = 0x08, // 8MB SeventyTwo = 0x52, // 1.1MB Eighty = 0x53, // 1.2MB NinetySix = 0x54, // 1.5MB } impl Default for BankCount { fn default() -> Self { Self::None } } impl BankCount { // https://hacktix.github.io/GBEDG/mbcs/#rom-size pub fn to_byte_count(self) -> u32 { use BankCount::*; match self { None => 32_768, Four => 65_536, Eight => 131_072, Sixteen => 262_144, ThirtyTwo => 524_288, SixtyFour => 1_048_576, OneHundredTwentyEight => 2_097_152, TwoHundredFiftySix => 4_194_304, FiveHundredTwelve => 8_388_608, SeventyTwo => 1_179_648, Eighty => 1_310_720, NinetySix => 1_572_864, } } } impl From for BankCount { fn from(byte: u8) -> Self { use BankCount::*; match byte { 0x00 => None, 0x01 => Four, 0x02 => Eight, 0x03 => Sixteen, 0x04 => ThirtyTwo, 0x05 => SixtyFour, 0x06 => OneHundredTwentyEight, 0x07 => TwoHundredFiftySix, 0x08 => FiveHundredTwelve, 0x52 => SeventyTwo, 0x53 => Eighty, 0x54 => NinetySix, _ => unreachable!("{:#04X} is not a valid value for BankCount", byte), } } } impl std::fmt::Debug for Box { fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { todo!("Implement Debug for Box Trait Object"); } } impl std::clone::Clone for Box { fn clone(&self) -> Self { self.clone_mbc() } } impl std::default::Default for Box { fn default() -> Self { Box::new(Mbc1::default()) } }