diff --git a/src/bus.rs b/src/bus.rs index 03dc939..389f125 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -57,10 +57,10 @@ pub struct Bus { impl Bus { pub fn new() -> Self { - let game_rom = match ROM::load_file("ignore/tetris.gb".to_string()) { + // let game_rom = match ROM::load_file("ignore/m3_scy_change.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/01-special.gb".to_string()) { - // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/02-interrupts.gb".to_string()) { + let game_rom = match ROM::load_file("roms/cpu_instrs_individual/02-interrupts.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/03-op sp,hl.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/04-op r,imm.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/05-op rp.gb".to_string()) { @@ -107,9 +107,8 @@ impl Bus { } pub fn read(&self, address: u16) -> u8 { - if BANK_ZERO.in_range(address) || BANK_SWITCHABLE.in_range(address) { + if BANK_ZERO.in_range(address) || BANK_SWITCHABLE.in_range(address) || EXTERNAL_RAM.in_range(address) { return self.game_rom.read(address); - } else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS { return 0b11100000 | self.data[address as usize]; } else if address == JOYPAD_ADDRESS { @@ -127,7 +126,8 @@ impl Bus { // print!("{}", data as char); } - if BANK_ZERO.in_range(address) || BANK_SWITCHABLE.in_range(address) { + if BANK_ZERO.in_range(address) || BANK_SWITCHABLE.in_range(address) || EXTERNAL_RAM.in_range(address) { + self.game_rom.write(address, data); // println!("WRITING TO ROM"); } else if WORK_RAM_1.in_range(address) || WORK_RAM_2.in_range(address) { self.data[address as usize] = data; diff --git a/src/ppu.rs b/src/ppu.rs index c6731aa..c62c647 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -266,7 +266,7 @@ impl PPU { // The gameboy only supports 10 sprites per line, // but since we are on an emulator we can avoud that limitation if self.sprite_buffer.len() >= 10 { - todo!("Make a setting for the 10 sprites per scanline"); + // todo!("Make a setting for the 10 sprites per scanline"); // break; } let y = bus.read(addr); diff --git a/src/rom.rs b/src/rom.rs index 3d05989..2ebcab2 100644 --- a/src/rom.rs +++ b/src/rom.rs @@ -1,24 +1,215 @@ use std::fs::File; use std::io::Read; +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, +} + +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 + } +} + pub struct ROM { - bytes: Vec, + data: Vec, + info: ROMInfo, + ram: Vec, + rom_bank: u16, + ram_bank: u8, + ram_enable: bool, + banking_mode: BankingMode, } impl ROM { pub fn load_file(filename: String) -> std::io::Result { let mut file = File::open(filename)?; - let mut bytes = vec![]; - file.read_to_end(&mut bytes)?; + let mut data = vec![]; + file.read_to_end(&mut data)?; + + let info = ROMInfo::from_bytes(&data); + let ram = Vec::with_capacity(info.ram_size() as usize); + Ok(Self { - bytes, + data, + info, + ram, + rom_bank: 1, + ram_bank: 0, + ram_enable: false, + banking_mode: BankingMode::Simple, }) } pub fn read(&self, address: u16) -> u8 { - match self.bytes.get(address as usize) { - Some(val) => *val, - None => 0xFF, + match self.info.mbc { + MBC::MBC1 => { + if BANK_SWITCHABLE.in_range(address) { + return self.data[(address + (BANK_SWITCHABLE.begin() * (self.rom_bank - 1))) as usize] + } else if EXTERNAL_RAM.in_range(address) { + return self.ram[(address - EXTERNAL_RAM.begin() + (EXTERNAL_RAM.begin() * self.ram_bank as u16)) as usize] + } + }, + _ => {}, + } + 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 + self.rom_bank = data as u16 & 0b00011111; + if self.rom_bank > self.info.rom_banks.saturating_sub(1) { + self.rom_bank = self.info.rom_banks.saturating_sub(1); + } + } else if address >= 0x4000 || address <= 0x5FFF { // ROM and RAM bank number register + self.ram_bank = data & 0b11; + } else if address >= 0x6000 || address <= 0x7FFF { // Banking mode select + } else if EXTERNAL_RAM.in_range(address) { + if !self.ram_enable { + return; + } + } + }, + _ => {}, } } }