use bitfield::bitfield; use crate::bus::BusIo; use crate::emu::SM83_CLOCK_SPEED; use crate::Cycle; const RAM_SIZE_ADDRESS: usize = 0x0149; const ROM_SIZE_ADDRESS: usize = 0x0148; const MBC_KIND_ADDRESS: usize = 0x0147; const ROM_TITLE_START: usize = 0x134; const ROM_TITLE_MAX_SIZE: usize = 16; const ROM_MANUFACTURER_START: usize = 0x13F; #[derive(Debug)] pub(crate) struct Cartridge { mem: Vec, pub(crate) title: Option, mbc: Box, } impl Cartridge { pub(crate) fn new(mem: Vec) -> Self { let title_mem: &[u8; 16] = mem[ROM_TITLE_START..(ROM_TITLE_START + ROM_TITLE_MAX_SIZE)] .try_into() .expect("coerce slice containing cartridge title from ROM to [u8; 16]"); let title = Self::detect_title(title_mem); let mbc = Self::detect_mbc(&mem); tracing::info!("Title: {:?}", title); Self { mem, title, mbc } } pub(crate) fn ext_ram(&self) -> Option<&[u8]> { self.mbc.ext_ram() } pub(crate) fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { self.mbc.ext_ram_mut() } #[inline] pub(crate) fn tick(&mut self) { self.mbc.tick() } fn detect_mbc(mem: &[u8]) -> Box { let ram_size: RamSize = mem[RAM_SIZE_ADDRESS].into(); let rom_size: RomSize = mem[ROM_SIZE_ADDRESS].into(); let mbc_kind = Self::detect_mbc_kind(mem[MBC_KIND_ADDRESS]); let ram_cap = ram_size.capacity(); let rom_cap = rom_size.capacity(); tracing::info!("RAM size: {} bytes", ram_cap); tracing::info!("ROM size: {} bytes", rom_cap); tracing::info!("MBC kind: {:?}", mbc_kind); match mbc_kind { MbcKind::None => Box::new(NoMbc), MbcKind::Mbc1(hw) => Box::new(Mbc1::new(hw, ram_size, rom_size)), MbcKind::Mbc2(hw) => Box::new(Mbc2::new(hw, rom_cap)), MbcKind::Mbc3(hw @ Mbc3Hardware::Rtc) => Box::new(Mbc3::new(hw, ram_cap)), MbcKind::Mbc3(hw @ Mbc3Hardware::RtcBatteryRam) => Box::new(Mbc3::new(hw, ram_cap)), MbcKind::Mbc3(hw @ Mbc3Hardware::BatteryRam) => Box::new(Mbc3::new(hw, ram_cap)), MbcKind::Mbc5(hw @ Mbc5Hardware::None) => Box::new(Mbc5::new(hw, ram_cap, rom_cap)), MbcKind::Mbc5(hw @ Mbc5Hardware::BatteryRam) => { Box::new(Mbc5::new(hw, ram_cap, rom_cap)) } kind => todo!("ROMS with {:?} are currently unsupported", kind), } } fn detect_title(title_mem: &[u8; ROM_TITLE_MAX_SIZE]) -> Option { use std::str::from_utf8; const ALT_TITLE_LEN: usize = ROM_MANUFACTURER_START - ROM_TITLE_START; // ascii byte slie does not have a null-terminator let ascii = match title_mem.iter().position(|b| *b == 0x00) { Some(end) => &title_mem[..end], None => &title_mem[..ROM_TITLE_MAX_SIZE], }; match from_utf8(ascii).map(str::trim).ok() { None => match from_utf8(&title_mem[..ALT_TITLE_LEN]).map(str::trim).ok() { Some("") | None => None, Some(title) => Some(String::from(title)), }, Some("") => None, Some(title) => Some(String::from(title)), } } fn detect_mbc_kind(id: u8) -> MbcKind { use MbcKind::*; match id { 0x00 => None, 0x01 => Mbc1(Mbc1Hardware::None), 0x02 => Mbc1(Mbc1Hardware::Ram), 0x03 => Mbc1(Mbc1Hardware::BatteryRam), 0x05 => Mbc2(Mbc2Hardware::None), 0x06 => Mbc2(Mbc2Hardware::BatteryRam), 0x08 | 0x09 => unimplemented!("NoMBC + RAM and NoMBC + Battery unsupported"), 0x0B | 0x0C | 0x0D => unimplemented!("MM01 unsupported"), 0x0F => Mbc3(Mbc3Hardware::Rtc), 0x10 => Mbc3(Mbc3Hardware::RtcBatteryRam), 0x11 => Mbc3(Mbc3Hardware::None), 0x12 => Mbc3(Mbc3Hardware::Ram), 0x13 => Mbc3(Mbc3Hardware::BatteryRam), 0x19 => Mbc5(Mbc5Hardware::None), 0x1A => Mbc5(Mbc5Hardware::Ram), 0x1B => Mbc5(Mbc5Hardware::BatteryRam), 0x1C => Mbc5(Mbc5Hardware::Rumble), 0x1D => Mbc5(Mbc5Hardware::RumbleRam), 0x1E => Mbc5(Mbc5Hardware::RumbleBatteryRam), id => unimplemented!("MBC with code {:#04X} is unsupported", id), } } } impl BusIo for Cartridge { fn read_byte(&self, addr: u16) -> u8 { use MbcResult::*; match self.mbc.handle_read(addr) { Addr(addr) => self.mem[addr], Byte(byte) => byte, } } fn write_byte(&mut self, addr: u16, byte: u8) { self.mbc.handle_write(addr, byte); } } #[derive(Debug)] struct Mbc1 { /// 5-bit number rom_bank: u8, /// 2-bit number ram_bank: u8, mode: bool, ram_size: RamSize, mem: Vec, rom_size: RomSize, mem_enabled: bool, hw: Mbc1Hardware, } impl Mbc1 { fn new(hw: Mbc1Hardware, ram_size: RamSize, rom_size: RomSize) -> Self { Self { rom_bank: 0x01, mem: vec![0; ram_size.capacity() as usize], ram_size, rom_size, ram_bank: Default::default(), mode: Default::default(), mem_enabled: Default::default(), hw, } } fn zero_bank(&self) -> u8 { use RomSize::*; match self.rom_size { None | Four | Eight | Sixteen | ThirtyTwo => 0x00, SixtyFour => (self.ram_bank & 0x01) << 5, OneTwentyEight => (self.ram_bank & 0x03) << 5, _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), } } fn _mbcm_zero_bank(&self) -> u8 { use RomSize::*; match self.rom_size { None | Four | Eight | Sixteen | ThirtyTwo => 0x00, SixtyFour => (self.ram_bank & 0x03) << 4, OneTwentyEight => (self.ram_bank & 0x03) << 5, _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), } } fn high_bank(&self) -> u8 { use RomSize::*; let base = self.rom_bank & self.rom_size_mask(); match self.rom_size { None | Four | Eight | Sixteen | ThirtyTwo => base, SixtyFour => base & !(0x01 << 5) | ((self.ram_bank & 0x01) << 5), OneTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5), _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), } } fn rom_size_mask(&self) -> u8 { use RomSize::*; match self.rom_size { None => 0b00000001, Four => 0b00000011, Eight => 0b00000111, Sixteen => 0b00001111, ThirtyTwo | SixtyFour | OneTwentyEight => 0b00011111, _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), } } fn ram_addr(&self, addr: u16) -> usize { use RamSize::*; match self.ram_size { Unused | One => (addr as usize - 0xA000) % self.ram_size.capacity(), Four => { if self.mode { 0x2000 * self.ram_bank as usize + (addr as usize - 0xA000) } else { addr as usize - 0xA000 } } _ => unreachable!("RAM size can not be greater than 32KB on MBC1"), } } } impl Savable for Mbc1 { fn ext_ram(&self) -> Option<&[u8]> { match self.hw { Mbc1Hardware::BatteryRam => Some(&self.mem), _ => None, } } fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { match self.hw { Mbc1Hardware::BatteryRam => Some(&mut self.mem), _ => None, } } } impl MbcIo for Mbc1 { fn handle_read(&self, addr: u16) -> MbcResult { use MbcResult::*; match addr { 0x0000..=0x3FFF if self.mode => { Addr(0x4000 * self.zero_bank() as usize + addr as usize) } 0x0000..=0x3FFF => Addr(addr as usize), 0x4000..=0x7FFF => Addr(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)), 0xA000..=0xBFFF if self.mem_enabled => Byte(self.mem[self.ram_addr(addr)]), 0xA000..=0xBFFF => Byte(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.mem_enabled = (byte & 0x0F) == 0x0A, 0x2000..=0x3FFF => { let value = byte & 0x1F; let masked_value = byte & self.rom_size_mask(); self.rom_bank = if value == 0 { 0x01 } else { masked_value }; } 0x4000..=0x5FFF => self.ram_bank = byte & 0x03, 0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01, 0xA000..=0xBFFF if self.mem_enabled => { let ram_addr = self.ram_addr(addr); self.mem[ram_addr] = byte; } 0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write _ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr), } } } impl RtcTick for Mbc1 { fn tick(&mut self) {} } #[derive(Debug, Default, Clone, Copy)] struct RtClock { /// 6-bit unsigned integer sec: u8, /// 6-bit unsigned integer min: u8, /// 6-bit unsigned integer hr: u8, day_low: u8, day_high: DayHigh, cycles: Cycle, } impl RtClock { fn inc_day(&mut self) { // TODO: Figure out order of operations, the brackets are a bit too defensive here let days: u16 = (((self.day_high.ninth() as u16) << 8) | self.day_low as u16) + 1; if days > 0x1FF { self.day_high.set_carry(true); } self.day_high.set_ninth(((days >> 8) & 0x01) == 0x01); self.day_low = days as u8; } } impl RtcTick for RtClock { fn tick(&mut self) { // This is the sort of situation where you'd want to use a scheduler. if self.day_high.halt() { return; } self.cycles += 1; if self.cycles >= SM83_CLOCK_SPEED { self.cycles %= SM83_CLOCK_SPEED; self.sec += 1; if self.sec == 60 { self.sec = 0; self.min += 1; } if self.min == 60 { self.min = 0; self.hr += 1; } if self.hr == 24 { self.hr = 0; self.inc_day(); } } } } trait RtcTick { fn tick(&mut self); } bitfield! { struct DayHigh(u8); impl Debug; _, set_carry: 7; halt, _: 6; ninth, set_ninth: 0; } impl Copy for DayHigh {} impl Clone for DayHigh { fn clone(&self) -> Self { *self } } impl Default for DayHigh { fn default() -> Self { Self(0) } } impl From for DayHigh { fn from(byte: u8) -> Self { Self(byte) } } impl From for u8 { fn from(dh: DayHigh) -> Self { dh.0 } } #[derive(Debug, Clone, Copy)] enum Mbc3Device { Ram, Rtc(RtcRegister), } #[derive(Debug, Clone, Copy)] enum RtcRegister { Sec, Min, Hr, DayLow, DayHigh, } #[derive(Debug)] struct Mbc3 { /// 7-bit Number rom_bank: u8, /// 2-bit Number ram_bank: u8, devs_enabled: bool, mapped: Option, mem: Vec, // RTC Data Latch Previous Write prev_latch_write: Option, hw: Mbc3Hardware, rtc: RtClock, rtc_latch: Option, } impl Mbc3 { fn new(hw: Mbc3Hardware, ram_cap: usize) -> Self { Self { mem: vec![0; ram_cap], rom_bank: Default::default(), ram_bank: Default::default(), devs_enabled: Default::default(), mapped: Default::default(), prev_latch_write: Default::default(), rtc: Default::default(), rtc_latch: Default::default(), hw, } } } impl Savable for Mbc3 { fn ext_ram(&self) -> Option<&[u8]> { match self.hw { Mbc3Hardware::BatteryRam | Mbc3Hardware::RtcBatteryRam => Some(&self.mem), _ => None, } } fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { match self.hw { Mbc3Hardware::BatteryRam | Mbc3Hardware::RtcBatteryRam => Some(&mut self.mem), _ => None, } } } impl MbcIo for Mbc3 { fn handle_read(&self, addr: u16) -> MbcResult { use MbcResult::*; use RtcRegister::*; let res = match addr { 0x0000..=0x3FFF => Addr(addr as usize), 0x4000..=0x7FFF => Addr(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)), 0xA000..=0xBFFF => match self.mapped { Some(Mbc3Device::Ram) if self.devs_enabled => { Byte(self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) } Some(Mbc3Device::Rtc(reg)) if self.devs_enabled => Byte( self.rtc_latch .as_ref() .map(|rtc| match reg { Sec => rtc.sec, Min => rtc.min, Hr => rtc.hr, DayLow => rtc.day_low, DayHigh => rtc.day_high.into(), }) .unwrap_or(0xFF), ), _ => Byte(0xFF), }, _ => unreachable!("A read from {:#06X} should not be handled by MBC3", addr), }; res } fn handle_write(&mut self, addr: u16, byte: u8) { use RtcRegister::*; match addr { 0x000..=0x1FFF => self.devs_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one 0x2000..=0x3FFF => { self.rom_bank = match byte { 0x00 => 0x01, byte => byte & 0x7F, } } 0x4000..=0x5FFF => match byte { 0x00 | 0x01 | 0x02 | 0x03 => { self.ram_bank = byte & 0x03; self.mapped = Some(Mbc3Device::Ram); } 0x08 => self.mapped = Some(Mbc3Device::Rtc(Sec)), 0x09 => self.mapped = Some(Mbc3Device::Rtc(Min)), 0x0A => self.mapped = Some(Mbc3Device::Rtc(Hr)), 0x0B => self.mapped = Some(Mbc3Device::Rtc(DayLow)), 0x0C => self.mapped = Some(Mbc3Device::Rtc(DayHigh)), _ => {} }, 0x6000..=0x7FFF => { if let Some(0x00) = self.prev_latch_write { if byte == 0x01 { self.rtc_latch = Some(self.rtc); } } self.prev_latch_write = Some(byte); } 0xA000..=0xBFFF => match self.mapped { Some(Mbc3Device::Ram) if self.devs_enabled => { self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte } Some(Mbc3Device::Rtc(rtc_reg)) if self.devs_enabled => match rtc_reg { Sec => { self.rtc.sec = byte & 0x3F; // Writing to RTC S resets the internal sub-second counter self.rtc.cycles = 0; } Min => self.rtc.min = byte & 0x3F, Hr => self.rtc.hr = byte & 0x1F, DayLow => self.rtc.day_low = byte, DayHigh => self.rtc.day_high = (byte & 0xC1).into(), }, _ => {} }, _ => unreachable!("A write to {:#06X} should not be handled by MBC3", addr), } } } impl RtcTick for Mbc3 { fn tick(&mut self) { if let Mbc3Hardware::RtcBatteryRam | Mbc3Hardware::Rtc = self.hw { self.rtc.tick(); } } } #[derive(Debug)] struct Mbc5 { /// 9-bit number rom_bank: u16, /// 4-bit number ram_bank: u8, rom_cap: usize, mem: Vec, mem_enabled: bool, hw: Mbc5Hardware, } impl Mbc5 { fn new(hw: Mbc5Hardware, ram_cap: usize, rom_cap: usize) -> Self { Self { rom_bank: 0x01, mem: vec![0; ram_cap], rom_cap, ram_bank: Default::default(), mem_enabled: Default::default(), hw, } } fn bank_addr(&self, addr: u16) -> usize { (0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)) % self.rom_cap } } impl Savable for Mbc5 { fn ext_ram(&self) -> Option<&[u8]> { match self.hw { Mbc5Hardware::RumbleBatteryRam | Mbc5Hardware::BatteryRam => Some(&self.mem), _ => None, } } fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { match self.hw { Mbc5Hardware::RumbleBatteryRam | Mbc5Hardware::BatteryRam => Some(&mut self.mem), _ => None, } } } impl MbcIo for Mbc5 { fn handle_read(&self, addr: u16) -> MbcResult { use MbcResult::*; match addr { 0x0000..=0x3FFF => Addr(addr as usize), 0x4000..=0x7FFF => Addr(self.bank_addr(addr)), 0xA000..=0xBFFF if self.mem_enabled => { Byte(self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) } 0xA000..=0xBFFF => Byte(0xFF), _ => unreachable!("A read from {:#06X} should not be handled by MBC5", addr), } } fn handle_write(&mut self, addr: u16, byte: u8) { match addr { 0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A, 0x2000..=0x2FFF => self.rom_bank = (self.rom_bank & 0x0100) | byte as u16, 0x3000..=0x3FFF => self.rom_bank = (self.rom_bank & 0x00FF) | (byte as u16 & 0x01) << 8, 0x4000..=0x5FFF => self.ram_bank = byte & 0x0F, 0xA000..=0xBFFF if self.mem_enabled => { self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte; } 0xA000..=0xBFFF => {} _ => unreachable!("A write to {:#06X} should not be handled by MBC5", addr), } } } impl RtcTick for Mbc5 { fn tick(&mut self) {} } #[derive(Debug)] struct Mbc2 { /// 4-bit number rom_bank: u8, mem: Box<[u8; Self::RAM_SIZE]>, mem_enabled: bool, rom_cap: usize, hw: Mbc2Hardware, } impl Mbc2 { const RAM_SIZE: usize = 0x0200; fn new(hw: Mbc2Hardware, rom_cap: usize) -> Self { Self { rom_bank: 0x01, mem: Box::new([0; Self::RAM_SIZE]), rom_cap, mem_enabled: Default::default(), hw, } } fn rom_addr(&self, addr: u16) -> usize { (0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)) % self.rom_cap } } impl Savable for Mbc2 { fn ext_ram(&self) -> Option<&[u8]> { match self.hw { Mbc2Hardware::BatteryRam => Some(self.mem.as_ref()), Mbc2Hardware::None => None, } } fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { match self.hw { Mbc2Hardware::BatteryRam => Some(self.mem.as_mut()), Mbc2Hardware::None => None, } } } impl MbcIo for Mbc2 { fn handle_read(&self, addr: u16) -> MbcResult { use MbcResult::*; match addr { 0x0000..=0x3FFF => Addr(addr as usize), 0x4000..=0x7FFF => Addr(self.rom_addr(addr)), 0xA000..=0xBFFF if self.mem_enabled => { let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1); Byte(self.mem[mbc2_addr] | 0xF0) } 0xA000..=0xBFFF => Byte(0xFF), _ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr), } } fn handle_write(&mut self, addr: u16, byte: u8) { let nybble = byte & 0x0F; match addr { 0x0000..=0x3FFF if addr >> 8 & 0x01 == 0x01 => { self.rom_bank = if nybble == 0x00 { 0x01 } else { nybble }; } 0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A, 0xA000..=0xBFFF if self.mem_enabled => { let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1); self.mem[mbc2_addr] = nybble; } 0x4000..=0x7FFF | 0xA000..=0xBFFF => {} _ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr), } } } impl RtcTick for Mbc2 { fn tick(&mut self) {} } #[derive(Debug)] struct NoMbc; impl Savable for NoMbc { fn ext_ram(&self) -> Option<&[u8]> { None } fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { None } } impl MbcIo for NoMbc { fn handle_read(&self, addr: u16) -> MbcResult { MbcResult::Addr(addr as usize) } fn handle_write(&mut self, _: u16, byte: u8) { tracing::warn!("Attempted write of {:#04X} to cartridge w/out MBC", byte); } } impl RtcTick for NoMbc { fn tick(&mut self) {} } trait MbcIo: Savable + RtcTick { fn handle_read(&self, addr: u16) -> MbcResult; fn handle_write(&mut self, addr: u16, byte: u8); } #[derive(Debug, Clone, Copy)] enum MbcResult { Addr(usize), Byte(u8), } #[derive(Debug, Clone, Copy)] enum MbcKind { None, Mbc1(Mbc1Hardware), Mbc2(Mbc2Hardware), Mbc3(Mbc3Hardware), Mbc5(Mbc5Hardware), } #[derive(Debug, Clone, Copy)] enum Mbc1Hardware { None, Ram, BatteryRam, } #[derive(Debug, Clone, Copy)] enum Mbc2Hardware { None, BatteryRam, } #[derive(Debug, Clone, Copy)] enum Mbc3Hardware { Rtc, RtcBatteryRam, None, Ram, BatteryRam, } #[derive(Debug, Clone, Copy)] enum Mbc5Hardware { None, Ram, BatteryRam, Rumble, RumbleRam, RumbleBatteryRam, } #[derive(Debug, Clone, Copy, PartialEq)] enum RamSize { None = 0x00, Unused = 0x01, One = 0x02, Four = 0x03, Sixteen = 0x04, Eight = 0x05, } impl RamSize { fn capacity(&self) -> usize { use RamSize::*; match *self { None => 0, Unused => 0x800, One => 0x2000, Four => 0x8000, Sixteen => 0x20000, Eight => 0x10000, } } } impl From for RamSize { fn from(byte: u8) -> Self { use RamSize::*; match byte { 0x00 => None, 0x01 => Unused, 0x02 => One, 0x03 => Four, 0x04 => Sixteen, 0x05 => Eight, _ => unreachable!("{:#04X} is not a valid value for RAMSize", byte), } } } #[derive(Debug, Clone, Copy)] enum RomSize { None = 0x00, // 32KB (also called Two) Four = 0x01, // 64KB Eight = 0x02, // 128KB Sixteen = 0x03, // 256KB ThirtyTwo = 0x04, // 512KB SixtyFour = 0x05, // 1MB OneTwentyEight = 0x06, // 2MB TwoFiftySix = 0x07, // 4MB FiveHundredTwelve = 0x08, // 8MB SeventyTwo = 0x52, // 1.1MB Eighty = 0x53, // 1.2MB NinetySix = 0x54, // 1.5MB } impl RomSize { // https://hacktix.github.io/GBEDG/mbcs/#rom-size fn capacity(&self) -> usize { use RomSize::*; match self { SeventyTwo => 0x120000, Eighty => 0x140000, NinetySix => 0x180000, _ => 0x8000 << *self as u8, } } } impl From for RomSize { fn from(byte: u8) -> Self { use RomSize::*; match byte { 0x00 => None, 0x01 => Four, 0x02 => Eight, 0x03 => Sixteen, 0x04 => ThirtyTwo, 0x05 => SixtyFour, 0x06 => OneTwentyEight, 0x07 => TwoFiftySix, 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"); } } trait Savable { fn ext_ram(&self) -> Option<&[u8]>; fn ext_ram_mut(&mut self) -> Option<&mut [u8]>; } #[cfg(test)] mod tests { use super::Cartridge; #[test] fn empty_rom_title() { let title = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; assert_eq!(None, Cartridge::detect_title(&title)); } #[test] fn normal_rom_title() { let title = [ 0x50, 0x4F, 0x4B, 0x45, 0x4D, 0x4F, 0x4E, 0x20, 0x42, 0x4C, 0x55, 0x45, 0x00, 0x00, 0x00, 0x00, ]; assert_eq!( Some(String::from("POKEMON BLUE")), Cartridge::detect_title(&title) ); } #[test] fn extra_spaces_title() { let title = [ 0x54, 0x4f, 0x4b, 0x49, 0x4d, 0x45, 0x4b, 0x49, 0x20, 0x43, 0x55, 0x4c, 0x20, 0x20, 0, 0, ]; assert_eq!( Some(String::from("TOKIMEKI CUL")), Cartridge::detect_title(&title) ) } #[test] fn long_title() { let title = [ 0x54, 0x4f, 0x4b, 0x49, 0x4d, 0x45, 0x4b, 0x49, 0x20, 0x43, 0x55, 0x4c, 0x54, 0x55, 0x52, 0x45, ]; assert_eq!( Some(String::from("TOKIMEKI CULTURE")), Cartridge::detect_title(&title), ); } #[test] fn publisher_code_and_title() { let title: [u8; 16] = [ 0x47, 0x52, 0x41, 0x4E, 0x44, 0x20, 0x54, 0x48, 0x45, 0x46, 0x54, 0x41, 0x4F, 0x41, 0x45, 0x80, ]; assert_eq!( Some(String::from("GRAND THEFT")), Cartridge::detect_title(&title) ); } #[test] fn all_whitespace_title() { let title = [ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; assert_eq!(None, Cartridge::detect_title(&title)); } }