#[allow(clippy::module_inception)]
mod cartridge;
mod mbc0;
mod mbc1;
pub use cartridge::GbCartridge;
use mbc0::Mbc0;
use mbc1::Mbc1;
#[derive(Debug, PartialEq)]
pub enum RomError {
TooShort,
BadHeaderChecksum { expected: u8, actual: u8 },
UnsupportedMbc(u8),
}
fn compute_header_checksum(bytes: &[u8]) -> u8 {
bytes[0x0134..=0x014C]
.iter()
.fold(0u8, |acc, &b| acc.wrapping_sub(b).wrapping_sub(1))
}
fn ram_size_from_byte(byte: u8) -> usize {
match byte {
0x01 => 2 * 1024,
0x02 => 8 * 1024,
0x03 => 32 * 1024,
0x04 => 128 * 1024,
0x05 => 64 * 1024,
_ => 0,
}
}
pub fn load_cartridge(bytes: &[u8]) -> Result<Box<dyn GbCartridge>, RomError> {
if bytes.len() < 0x8000 {
return Err(RomError::TooShort);
}
let expected = compute_header_checksum(bytes);
let actual = bytes[0x014D];
if expected != actual {
return Err(RomError::BadHeaderChecksum { expected, actual });
}
let mbc_type = bytes[0x0147];
match mbc_type {
0x00 => Ok(Box::new(Mbc0::new(bytes.to_vec()))),
0x01..=0x03 => {
let ram_size = ram_size_from_byte(bytes[0x0149]);
Ok(Box::new(Mbc1::new(bytes.to_vec(), vec![0u8; ram_size])))
}
n => Err(RomError::UnsupportedMbc(n)),
}
}
#[cfg(test)]
mod tests {
use super::{RomError, compute_header_checksum, load_cartridge};
fn make_valid_rom(mbc_type: u8, rom_size_byte: u8) -> Vec<u8> {
let bank_count: usize = 2 << (rom_size_byte as usize);
let mut rom = vec![0u8; bank_count * 0x4000];
rom[0x0147] = mbc_type;
rom[0x0148] = rom_size_byte;
rom[0x0149] = 0x00; let checksum = compute_header_checksum(&rom);
rom[0x014D] = checksum;
rom
}
#[test]
fn test_load_returns_error_for_too_short_input() {
let short = vec![0u8; 0x100];
assert!(matches!(load_cartridge(&short), Err(RomError::TooShort)));
}
#[test]
fn test_load_returns_error_for_bad_header_checksum() {
let mut rom = make_valid_rom(0x00, 0x00);
rom[0x014D] = rom[0x014D].wrapping_add(1); let result = load_cartridge(&rom);
assert!(matches!(result, Err(RomError::BadHeaderChecksum { .. })));
}
#[test]
fn test_load_returns_ok_for_valid_mbc0_rom() {
let rom = make_valid_rom(0x00, 0x00);
assert!(load_cartridge(&rom).is_ok());
}
#[test]
fn test_load_returns_ok_for_valid_mbc1_rom() {
let rom = make_valid_rom(0x01, 0x01);
assert!(load_cartridge(&rom).is_ok());
}
#[test]
fn test_load_returns_error_for_unsupported_mbc_type() {
let rom = make_valid_rom(0x05, 0x00);
assert!(matches!(
load_cartridge(&rom),
Err(RomError::UnsupportedMbc(0x05))
));
}
}