rmg-001/src/rom.rs

236 lines
7.0 KiB
Rust
Raw Normal View History

2021-10-13 00:11:52 +00:00
use std::fs::File;
use std::io::Read;
2021-11-05 19:20:10 +00:00
use crate::bus::{
BANK_SWITCHABLE,
EXTERNAL_RAM,
};
pub const CARTRIDGE_TYPE_ADDRESS: u16 = 0x0147;
pub const CGB_FLAG_ADDRESS: u16 = 0x0143;
pub const SGB_FLAG_ADDRESS: u16 = 0x0146;
pub const RAM_SIZE_ADDRESS: u16 = 0x0149;
pub const ROM_SIZE_ADDRESS: u16 = 0x0148;
pub const DESTINATION_CODE_ADDRESS: u16 = 0x014A;
enum Region {
Japanese,
NonJapanese,
}
2021-11-07 02:36:16 +00:00
#[derive(Debug, Copy, Clone)]
2021-11-05 19:20:10 +00:00
enum MBC {
NoMBC,
MBC1,
MBC2,
MBC3,
MBC4,
MBC5,
MBC6,
MBC7,
HuC1,
HuC3,
MMM01,
MBC1M,
PocketCamera,
BandaiTIMA5,
}
enum BankingMode {
Simple,
Advanced,
}
pub struct ROMInfo {
mbc: MBC,
publisher: String,
title: String,
cgb_only: bool,
sgb_features: bool,
has_ram: bool,
has_battery: bool,
has_timer: bool,
ram_banks: u8,
rom_banks: u16,
region: Region,
}
impl ROMInfo {
pub fn from_bytes(bytes: &[u8]) -> Self {
let rom_type = bytes[CARTRIDGE_TYPE_ADDRESS as usize];
Self {
mbc: match rom_type {
0x00 => MBC::NoMBC,
0x01 => MBC::MBC1,
0x02 => MBC::MBC1,
0x03 => MBC::MBC1,
0x05 => MBC::MBC2,
0x06 => MBC::MBC2,
0x08 => MBC::NoMBC,
0x09 => MBC::NoMBC,
0x0B => MBC::MMM01,
0x0C => MBC::MMM01,
0x0F => MBC::MBC3,
0x10 => MBC::MBC3,
0x11 => MBC::MBC3,
0x12 => MBC::MBC3,
0x13 => MBC::MBC3,
0x19 => MBC::MBC5,
0x1A => MBC::MBC5,
0x1B => MBC::MBC5,
0x1C => MBC::MBC5,
0x1D => MBC::MBC5,
0x1E => MBC::MBC5,
0x20 => MBC::MBC6,
0x22 => MBC::MBC7,
0xFC => MBC::PocketCamera,
0xFD => MBC::BandaiTIMA5,
0xFE => MBC::HuC3,
0xFF => MBC::HuC1,
_ => unreachable!(),
},
region: match bytes[DESTINATION_CODE_ADDRESS as usize] {
0x00 => Region::Japanese,
_ => Region::NonJapanese,
},
publisher: "".to_string(), // TODO: Extract publisher
title: "".to_string(), // TODO: Extract the game title
cgb_only: bytes[CGB_FLAG_ADDRESS as usize] == 0xC0,
sgb_features: bytes[SGB_FLAG_ADDRESS as usize] == 0x03,
has_ram: match rom_type {
0x02 | 0x03 | 0x08 | 0x09 | 0x0C | 0x0D | 0x10 | 0x12 |
0x13 | 0x1A | 0x1B | 0x1D | 0x1E | 0x22 | 0xFF => true,
_ => false,
},
has_battery: match rom_type {
0x03 | 0x06 | 0x09 | 0x0D | 0x0F | 0x10 |
0x13 | 0x1B | 0x1E | 0x22 | 0xFF => true,
_ => false,
},
has_timer: match rom_type {
0x0F | 0x10 => true,
_ => false,
},
ram_banks: match bytes[RAM_SIZE_ADDRESS as usize] {
0x00 | 0x01 => 0,
0x02 => 1,
0x03 => 4,
0x04 => 16,
0x05 => 8,
_ => unreachable!(),
},
rom_banks: match bytes[ROM_SIZE_ADDRESS as usize] {
0x00 => 2,
0x01 => 4,
0x02 => 8,
0x03 => 16,
0x04 => 32,
0x05 => 64,
0x06 => 128,
0x07 => 256,
0x08 => 512,
0x52 => 72,
0x53 => 80,
0x54 => 96,
_ => unreachable!(),
},
}
}
pub fn ram_size(&self) -> u16 {
0x4000 * self.ram_banks as u16
}
}
2021-10-13 00:11:52 +00:00
pub struct ROM {
2021-11-05 19:20:10 +00:00
data: Vec<u8>,
info: ROMInfo,
ram: Vec<u8>,
rom_bank: u16,
ram_bank: u8,
ram_enable: bool,
banking_mode: BankingMode,
2021-10-13 00:11:52 +00:00
}
impl ROM {
pub fn load_file(filename: String) -> std::io::Result<Self> {
let mut file = File::open(filename)?;
2021-11-05 19:20:10 +00:00
let mut data = vec![];
file.read_to_end(&mut data)?;
let info = ROMInfo::from_bytes(&data);
2021-11-06 00:08:51 +00:00
println!("has ram {}", info.has_ram);
2021-11-07 02:36:16 +00:00
println!("mbc {:?}", info.mbc);
2021-11-05 19:20:10 +00:00
let ram = Vec::with_capacity(info.ram_size() as usize);
2021-10-13 00:11:52 +00:00
Ok(Self {
2021-11-05 19:20:10 +00:00
data,
info,
ram,
rom_bank: 1,
ram_bank: 0,
ram_enable: false,
banking_mode: BankingMode::Simple,
2021-10-13 00:11:52 +00:00
})
}
pub fn read(&self, address: u16) -> u8 {
2021-11-05 19:20:10 +00:00
match self.info.mbc {
MBC::MBC1 => {
if BANK_SWITCHABLE.in_range(address) {
2021-11-06 00:08:51 +00:00
return self.data[(address + (BANK_SWITCHABLE.begin() * (self.rom_bank - 1))) as usize];
2021-11-05 19:20:10 +00:00
} else if EXTERNAL_RAM.in_range(address) {
2021-11-06 00:08:51 +00:00
println!("RAM read");
if !self.info.has_ram {
return 0xFF;
}
return self.ram[(address - EXTERNAL_RAM.begin() + (EXTERNAL_RAM.begin() * self.ram_bank as u16)) as usize];
2021-11-05 19:20:10 +00:00
}
},
_ => {},
}
self.data[address as usize]
}
pub fn write(&mut self, address: u16, data: u8) {
match self.info.mbc {
MBC::MBC1 => {
if address >= 0x0000 || address <= 0x1FFF { // RAM enable register
self.ram_enable = match data & 0x0F {
0x0A => true,
_ => false,
};
return;
} else if address >= 0x2000 || address <= 0x3FFF { // ROM bank number register
2021-11-06 00:08:51 +00:00
println!("Switch bank to {:02X}", data);
self.switch_rom_bank(data as u16 & 0b00011111);
2021-11-05 19:20:10 +00:00
} else if address >= 0x4000 || address <= 0x5FFF { // ROM and RAM bank number register
2021-11-06 00:08:51 +00:00
println!("RAM bank {:02X}", data);
2021-11-05 19:20:10 +00:00
self.ram_bank = data & 0b11;
} else if address >= 0x6000 || address <= 0x7FFF { // Banking mode select
2021-11-06 00:08:51 +00:00
println!("Change banking mode");
2021-11-05 19:20:10 +00:00
} else if EXTERNAL_RAM.in_range(address) {
2021-11-06 00:08:51 +00:00
if !self.ram_enable || !self.info.has_ram {
2021-11-05 19:20:10 +00:00
return;
}
2021-11-06 00:08:51 +00:00
let address = (address - EXTERNAL_RAM.begin() + (EXTERNAL_RAM.begin() * self.ram_bank as u16)) as usize;
self.ram[address] = data;
self.switch_rom_bank(self.rom_bank + (data as u16 >> 5));
2021-11-05 19:20:10 +00:00
}
},
_ => {},
}
}
2021-11-06 00:08:51 +00:00
pub fn switch_rom_bank(&mut self, bank: u16) {
self.rom_bank = bank;
if self.rom_bank > self.info.rom_banks.saturating_sub(1) {
self.rom_bank = self.info.rom_banks.saturating_sub(1);
}
if self.rom_bank == 0 {
self.rom_bank = 1;
}
}
2021-10-13 00:11:52 +00:00
}