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:
131
src/main.rs
131
src/main.rs
@@ -1,8 +1,118 @@
|
|||||||
mod registers;
|
mod registers;
|
||||||
|
|
||||||
|
use std::ops::Index;
|
||||||
use crate::registers::FlagsRegister;
|
use crate::registers::FlagsRegister;
|
||||||
use crate::registers::Registers;
|
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 {
|
struct CPU {
|
||||||
registers: Registers,
|
registers: Registers,
|
||||||
@@ -10,10 +120,11 @@ struct CPU {
|
|||||||
bus: MemoryBus,
|
bus: MemoryBus,
|
||||||
sp: u16,
|
sp: u16,
|
||||||
boot_rom: Vec<u8>,
|
boot_rom: Vec<u8>,
|
||||||
game_rom: Vec<u8>,
|
|
||||||
}
|
}
|
||||||
const VRAM_BEGIN: usize = 0x8000;
|
const VRAM_BEGIN: usize = 0x8000;
|
||||||
const VRAM_END: usize = 0x9FFF;
|
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] }
|
struct GPU { vram: Vec<u8>, tile_set: [[[TilePixelValue; 8]; 8]; 384] }
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
enum TilePixelValue { Three, Two, One, Zero }
|
enum TilePixelValue { Three, Two, One, Zero }
|
||||||
@@ -87,6 +198,7 @@ impl GPU {
|
|||||||
struct MemoryBus {
|
struct MemoryBus {
|
||||||
memory: [u8; 0xFFFF],
|
memory: [u8; 0xFFFF],
|
||||||
gpu: GPU,
|
gpu: GPU,
|
||||||
|
rom: GameRom,
|
||||||
}
|
}
|
||||||
impl MemoryBus {
|
impl MemoryBus {
|
||||||
fn read_byte(&self, address: u16) -> u8 {
|
fn read_byte(&self, address: u16) -> u8 {
|
||||||
@@ -95,6 +207,7 @@ impl MemoryBus {
|
|||||||
VRAM_BEGIN ..= VRAM_END => {
|
VRAM_BEGIN ..= VRAM_END => {
|
||||||
self.gpu.read_vram(address - VRAM_BEGIN)
|
self.gpu.read_vram(address - VRAM_BEGIN)
|
||||||
}
|
}
|
||||||
|
CART_BEGIN ..= CART_END => {self.rom.read_byte(address)}
|
||||||
_ => self.memory[address as usize]
|
_ => self.memory[address as usize]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,11 +339,6 @@ impl CPU {
|
|||||||
self.bus.write_byte(address as u16, *byte);
|
self.bus.write_byte(address as u16, *byte);
|
||||||
}
|
}
|
||||||
// println!("Game ROM: {:02X?}", self.game_rom);
|
// 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 {
|
fn check_condition(&self, condition: Condition) -> bool {
|
||||||
@@ -1285,15 +1393,20 @@ impl CPU {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let boot_rom = std::fs::read("boot/dmg.bin").unwrap();
|
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{
|
let mut gameboy = CPU{
|
||||||
registers:Registers{a:0,b:0,c:0,d:0,e:0,f:FlagsRegister::from(0),h:0,l:0},
|
registers:Registers{a:0,b:0,c:0,d:0,e:0,f:FlagsRegister::from(0),h:0,l:0},
|
||||||
pc: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,
|
sp: 0,
|
||||||
boot_rom,
|
boot_rom,
|
||||||
game_rom,
|
|
||||||
};
|
};
|
||||||
gameboy.init();
|
gameboy.init();
|
||||||
let sp = gameboy.sp;
|
let sp = gameboy.sp;
|
||||||
|
|||||||
Reference in New Issue
Block a user