From 74e86f1ab736ec8076a446f01effc39be521c20c Mon Sep 17 00:00:00 2001 From: Ajurna Date: Tue, 6 May 2025 12:00:52 +0100 Subject: [PATCH] Add GameRom struct with MBC1 support and integrate with MemoryBus Introduced `GameRom` struct and implemented support for MBC1 cartridges, including ROM and RAM banking. Modified `MemoryBus` and `CPU` to use `GameRom` for cartridge interactions, replacing raw game ROM handling. Added logic to load, read, and write cartridge memory based on type and address. --- src/main.rs | 131 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1565d86..a2b85bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,118 @@ mod registers; + +use std::ops::Index; use crate::registers::FlagsRegister; use crate::registers::Registers; +#[derive(Debug)] +struct GameRom { + data: Vec, + title: String, + cgb_flag: u8, + licensee: [u8; 2], + sgb_flag: u8, + cartridge_type: CartridgeType, + rom_size: u16, + rom_bank_low: u8, + rom_bank_high: u8, + ram_size: u8, + ram_enable: bool, + banking_mode: bool, +} +#[derive(Debug)] +enum CartridgeType{ + RomOnly, + MBC1, + MBC2, + MMM01, + MBC3, + MBC5, + MBC6, + MBC7, + PocketCamera, + BandaiTama5, + HuC3, + HuC1RamBattery, +} +impl GameRom { + fn load(filename: &str) -> GameRom { + + let data = std::fs::read(filename).unwrap(); + let title_end = data[0x134..0x143].iter().position(|b| *b == 0x0).unwrap(); + let title = data[0x134..0x134 + title_end].to_vec().escape_ascii().to_string(); + let cgb_flag = data[0x143]; + if cgb_flag == 0xC0 { + panic!("CGB not supported"); + } + let licensee = [data[0x144], data[0x145]]; + let sgb_flag = data[0x146]; + println!("{:x}", data[0x147]); + + let cartridge_type = match data[0x147] { + 0x00 => CartridgeType::RomOnly, + 0x01 => CartridgeType::MBC1, + _ => {panic!("not a supported cartridge")} + }; + let rom_size = match data[0x148] { + 0x00 => 2, // 0b1 + 0x01 => 4, // 0b11 + 0x02 => 8, // 0b111 + 0x03 => 16, // 0b1111 + 0x04 => 32, // 0b1111 1 + 0x05 => 64, // 0b1111 11 + 0x06 => 128,// 0b1111 111 + 0x07 => 256,// 0b1111 1111 + 0x08 => 512,// 0b1111 1111 1 + _ => { panic!("not a supported rom size"); } + }; + let ram_size = data[0x149]; + println!("{rom_size}"); + GameRom{data, title, cgb_flag, licensee, sgb_flag, cartridge_type, rom_size, ram_size, rom_bank_low: 0, rom_bank_high: 0, ram_enable: false, banking_mode: false, } + } + fn read_byte(&self, addr: usize) -> u8 { + match self.cartridge_type { + CartridgeType::RomOnly => {self.data[addr]} + CartridgeType::MBC1 => { + match addr { + 0x0000..=0x3FFF => self.data[addr], + 0x4000..=0x7FFF => { + let bank: u8; + if self.rom_size > 32 { + bank = self.rom_bank_low & 0b11111 | self.rom_bank_high << 5 + } else { + bank = self.rom_bank_low & 0b11111; + } + let offset = (0x4000 * bank as usize) + (addr - 0x4000); + self.data[offset] + }, + _ => {panic!("unsupported memory access")} + } + } + _ => {panic!("not a supported cartridge")} + } + } + fn write_byte(&mut self, addr: usize, data: u8) { + match self.cartridge_type { + CartridgeType::RomOnly => {} + CartridgeType::MBC1 => { + match addr { + 0x0000..=0x1FFF => { self.ram_enable = data & 0xF == 0x0A;} + 0x2000..=0x3FFF => { self.rom_bank_low = data } + 0x4000..=0x5FFF => { self.rom_bank_high = data } + 0x6000..=0x7FFF => { self.banking_mode = data &0x1 == 0x1 } + 0xA000..=0xBFFF => { if self.ram_enable { + self.data[addr] = data; + } } + _ => {panic!("unsupported Cartridge access")} + } + } + _ => {panic!("unsupported Cartridge type")} + } + + + } +} struct CPU { registers: Registers, @@ -10,10 +120,11 @@ struct CPU { bus: MemoryBus, sp: u16, boot_rom: Vec, - game_rom: Vec, } const VRAM_BEGIN: usize = 0x8000; const VRAM_END: usize = 0x9FFF; +const CART_BEGIN: usize = 0x0000; +const CART_END: usize = 0x7FFF; struct GPU { vram: Vec, tile_set: [[[TilePixelValue; 8]; 8]; 384] } #[derive(Copy, Clone)] enum TilePixelValue { Three, Two, One, Zero } @@ -87,6 +198,7 @@ impl GPU { struct MemoryBus { memory: [u8; 0xFFFF], gpu: GPU, + rom: GameRom, } impl MemoryBus { fn read_byte(&self, address: u16) -> u8 { @@ -95,6 +207,7 @@ impl MemoryBus { VRAM_BEGIN ..= VRAM_END => { self.gpu.read_vram(address - VRAM_BEGIN) } + CART_BEGIN ..= CART_END => {self.rom.read_byte(address)} _ => self.memory[address as usize] } } @@ -226,11 +339,6 @@ impl CPU { self.bus.write_byte(address as u16, *byte); } // println!("Game ROM: {:02X?}", self.game_rom); - for (address, byte) in self.game_rom.iter().enumerate() { - if address < 0x100 { continue; } - if address >= 0x4000 { break; } - self.bus.write_byte(address as u16, *byte); - } } fn check_condition(&self, condition: Condition) -> bool { @@ -1285,15 +1393,20 @@ impl CPU { fn main() { let boot_rom = std::fs::read("boot/dmg.bin").unwrap(); - let game_rom = std::fs::read("cpu_instrs/cpu_instrs.gb").unwrap(); + let game_rom= GameRom::load("cpu_instrs/cpu_instrs.gb"); + + // let game_rom = std::fs::read("cpu_instrs/cpu_instrs.gb").unwrap(); let mut gameboy = CPU{ registers:Registers{a:0,b:0,c:0,d:0,e:0,f:FlagsRegister::from(0),h:0,l:0}, pc:0, - bus:MemoryBus{ memory: [0xFFu8; 0xFFFF], gpu:GPU{ vram: Vec::with_capacity(VRAM_END-VRAM_BEGIN), tile_set: [[[TilePixelValue::Zero; 8]; 8]; 384] }}, + bus:MemoryBus{ + memory: [0xFFu8; 0xFFFF], + gpu:GPU{ vram: Vec::with_capacity(VRAM_END-VRAM_BEGIN), tile_set: [[[TilePixelValue::Zero; 8]; 8]; 384] }, + rom: game_rom, + }, sp: 0, boot_rom, - game_rom, }; gameboy.init(); let sp = gameboy.sp;