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.
This commit is contained in:
2025-05-06 12:00:52 +01:00
parent 918c9020b5
commit 74e86f1ab7

View File

@@ -1,8 +1,118 @@
mod registers;
use std::ops::Index;
use crate::registers::FlagsRegister;
use crate::registers::Registers;
#[derive(Debug)]
struct GameRom {
data: Vec<u8>,
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<u8>,
game_rom: Vec<u8>,
}
const VRAM_BEGIN: usize = 0x8000;
const VRAM_END: usize = 0x9FFF;
const CART_BEGIN: usize = 0x0000;
const CART_END: usize = 0x7FFF;
struct GPU { vram: Vec<u8>, 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;