Split MBC logic
This commit is contained in:
parent
411f0876af
commit
3458d4b7d9
180
src/cartridge.rs
180
src/cartridge.rs
@ -1,3 +1,5 @@
|
||||
use super::mbc::mbc::MBC;
|
||||
|
||||
#[derive(Debug,PartialEq)]
|
||||
enum MemoryBankControllerType {
|
||||
None,
|
||||
@ -16,32 +18,22 @@ enum RamSize {
|
||||
}
|
||||
|
||||
pub struct Cartridge {
|
||||
rom: Box<[u8]>,
|
||||
ram: Box<[u8]>,
|
||||
bank_no: u8,
|
||||
mbc_type: MemoryBankControllerType,
|
||||
ram_rtc_enabled: bool,
|
||||
ram_bank_no: u8,
|
||||
|
||||
ram_size: RamSize,
|
||||
rom_banks: u16,
|
||||
|
||||
bank_mode: u8,
|
||||
bank_no_high: u8,
|
||||
mbc: Box<super::mbc::mbc::MBC>,
|
||||
}
|
||||
|
||||
impl Cartridge {
|
||||
pub fn new(rom: Box<[u8]>) -> Cartridge {
|
||||
let mbc: MemoryBankControllerType = match rom[0x0147] {
|
||||
let mbc_type: MemoryBankControllerType = match rom[0x0147] {
|
||||
0x00 | 0x08 ... 0x09 => MemoryBankControllerType::None,
|
||||
0x01 ... 0x03 => MemoryBankControllerType::MBC1,
|
||||
0x05 ... 0x06 => MemoryBankControllerType::MBC2,
|
||||
// 0x01 ... 0x03 => MemoryBankControllerType::MBC1,
|
||||
// 0x05 ... 0x06 => MemoryBankControllerType::MBC2,
|
||||
0x0F ... 0x13 => MemoryBankControllerType::MBC3,
|
||||
0xFF => MemoryBankControllerType::HuC1,
|
||||
// 0xFF => MemoryBankControllerType::HuC1,
|
||||
_ => panic!("Unsupported MBC type: {:02X}", rom[0x0147]),
|
||||
};
|
||||
|
||||
println!("MBC Type: {:?}", &mbc);
|
||||
println!("MBC Type: {:?}", &mbc_type);
|
||||
|
||||
let rom_banks: u16 = match rom[0x0148] {
|
||||
0x00 => 0,
|
||||
@ -70,157 +62,23 @@ impl Cartridge {
|
||||
RamSize::Ram32KB => vec![0u8; 16 * 2048].into_boxed_slice(),
|
||||
};
|
||||
|
||||
let mbc: Box<super::mbc::mbc::MBC> = match mbc_type {
|
||||
MemoryBankControllerType::None => Box::new(super::mbc::mbc::NoMBC::new(rom, ram)),
|
||||
MemoryBankControllerType::MBC3 => Box::new(super::mbc::mbc3::MBC3::new(rom, ram)),
|
||||
_ => panic!("{:?} not implemented", mbc_type),
|
||||
};
|
||||
|
||||
Cartridge {
|
||||
rom: rom,
|
||||
mbc_type: mbc,
|
||||
bank_no: 1,
|
||||
|
||||
ram_rtc_enabled: false,
|
||||
ram_bank_no: 0,
|
||||
ram_size: ram_size,
|
||||
rom_banks: rom_banks,
|
||||
|
||||
ram: ram,
|
||||
bank_mode: 0,
|
||||
bank_no_high: 0,
|
||||
mbc: mbc,
|
||||
mbc_type: mbc_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_byte_mbc3(&self, addr: u16) -> u8 {
|
||||
match addr {
|
||||
0x0000 ... 0x3FFF => self.rom[addr as usize],
|
||||
0x4000 ... 0x7FFF => {
|
||||
let addr = addr - 0x4000;
|
||||
// println!("BankNo: {:02X}", self.bank_no);
|
||||
let abs_addr: usize = addr as usize + self.bank_no as usize * 0x4000;
|
||||
let val: u8 = self.rom[abs_addr];
|
||||
val
|
||||
},
|
||||
0xA000 ... 0xBFFF => {
|
||||
// TODO: Safty checks? Or let rust handle this?
|
||||
let addr = addr - 0xA000;
|
||||
if self.ram_bank_no < 4 {
|
||||
self.ram[self.ram_bank_no as usize * 0x2000 + addr as usize]
|
||||
} else {
|
||||
println!("Ignoring RTC read");
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("Cartride: Unable to read from {:04X}", addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_byte_mbc1(&self, addr: u16) -> u8 {
|
||||
match addr {
|
||||
0x0000 ... 0x3FFF => self.rom[addr as usize],
|
||||
0x4000 ... 0x7FFF => {
|
||||
let addr = addr - 0x4000;
|
||||
let mut bank: u8 = self.bank_no;
|
||||
if self.bank_mode == 0 {
|
||||
bank |= self.bank_no_high << 5;
|
||||
}
|
||||
// println!("BankNo: {:02X}", bank);
|
||||
let abs_addr: usize = addr as usize + bank as usize * 0x4000;
|
||||
let val: u8 = self.rom[abs_addr];
|
||||
val
|
||||
},
|
||||
0xA000 ... 0xBFFF => {
|
||||
// TODO: Safty checks? Or let rust handle this?
|
||||
let addr = addr - 0xA000;
|
||||
let mut bank: u8 = 0;
|
||||
if self.bank_mode == 1 {
|
||||
bank = self.bank_no_high
|
||||
}
|
||||
|
||||
self.ram[bank as usize * 0x2000 + addr as usize]
|
||||
}
|
||||
_ => {
|
||||
panic!("Cartride: Unable to read from {:04X}", addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn read_byte(&self, addr: u16) -> u8{
|
||||
match self.mbc_type {
|
||||
MemoryBankControllerType::MBC1 => self.read_byte_mbc1(addr),
|
||||
MemoryBankControllerType::MBC3 | MemoryBankControllerType::None => self.read_byte_mbc3(addr),
|
||||
_ => panic!("MBC not supported.")
|
||||
}
|
||||
}
|
||||
|
||||
fn write_byte_none(&mut self, addr: u16, val: u8) {
|
||||
match addr {
|
||||
0xA000 ... 0xBFFF => {
|
||||
// self.ram[addr as usize - 0xA000] = val;
|
||||
println!("No MBC, ignoring RAM write {:02X} to {:04X}", val, addr);
|
||||
}
|
||||
_ => println!("No MBC, unable to write {:02X} to {:04X}", val, addr),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_byte_mbc1(&mut self, addr: u16, val: u8) {
|
||||
match addr {
|
||||
0x2000 ... 0x3FFF => {
|
||||
// Write low bits of addr.
|
||||
self.bank_no &= 0xE0;
|
||||
self.bank_no |= val & 0x1F;
|
||||
|
||||
// Can't select 0x00, 0x20, 0x40, 0x60. Adapt it to 0x01, 0x21, 0x41, 0x61
|
||||
if self.bank_no & 0x1F == 0 {
|
||||
self.bank_no |= 1;
|
||||
}
|
||||
}
|
||||
0x4000 ... 0x5FFF => {
|
||||
self.bank_no_high = val & 3;
|
||||
}
|
||||
0x6000 ... 0x7FFF => {
|
||||
self.bank_mode = val & 1;
|
||||
}
|
||||
_ => panic!("MBC1 Write {:02X} to {:04X} unsupported", val, addr),
|
||||
}
|
||||
}
|
||||
fn write_byte_mbc3(&mut self, addr: u16, val: u8) {
|
||||
match addr {
|
||||
0x0000 ... 0x1FFF => {
|
||||
match val {
|
||||
0x0A => self.ram_rtc_enabled = true,
|
||||
0x00 => self.ram_rtc_enabled = false,
|
||||
_ => println!("Unknown MBC value {:02X} for {:04X}", val, addr)
|
||||
}
|
||||
},
|
||||
0x2000 ... 0x3FFF => self.bank_no = val & 0x7F,
|
||||
0x4000 ... 0x5FFF => {
|
||||
// RAM bank select
|
||||
match val {
|
||||
0x00 ... 0x03 => self.ram_bank_no = val,
|
||||
0x08 ... 0x0C => self.ram_bank_no = val, // RTC clock values, TODO
|
||||
_ => panic!("Unknown MBC3 RAM BANK NO: {:02X}", val)
|
||||
}
|
||||
|
||||
},
|
||||
0x6000 ... 0x7FFF => {
|
||||
// Latch clock data, ignore.
|
||||
},
|
||||
0xA000 ... 0xBFFF => {
|
||||
// TODO: Safty checks? Or let rust handle this?
|
||||
let addr = addr - 0xA000;
|
||||
if self.ram_bank_no < 4 {
|
||||
self.ram[self.ram_bank_no as usize * 0x2000 + addr as usize] = val;
|
||||
} else {
|
||||
println!("Ignoring RTC write");
|
||||
}
|
||||
}
|
||||
_ => panic!("MBC3: Writing {:02X} to {:04X} not supported", val, addr),
|
||||
}
|
||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
||||
self.mbc.read_byte(addr)
|
||||
}
|
||||
|
||||
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
||||
match self.mbc_type {
|
||||
MemoryBankControllerType::None => self.write_byte_none(addr, val),
|
||||
MemoryBankControllerType::MBC1 => self.write_byte_mbc1(addr, val),
|
||||
MemoryBankControllerType::MBC3 => self.write_byte_mbc3(addr, val),
|
||||
_ => panic!("MBC not supported.")
|
||||
}
|
||||
self.mbc.write_byte(addr, val);
|
||||
}
|
||||
}
|
||||
|
||||
13
src/cpu.rs
13
src/cpu.rs
@ -157,7 +157,7 @@ impl CPU {
|
||||
self.set_flag(FLAG_N);
|
||||
self.set_clear_flag(FLAG_Z, new == 0);
|
||||
self.set_clear_flag(FLAG_C, new > old || (new == old && c == 1));
|
||||
self.set_clear_flag(FLAG_H, ((old & 0x0F) - (val & 0x0F) - c) > 0x0F);
|
||||
self.set_clear_flag(FLAG_H, ((old & 0x0F).wrapping_sub((val & 0x0F)).wrapping_sub(c)) > 0x0F);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -658,7 +658,7 @@ impl CPU {
|
||||
#[inline]
|
||||
fn rst(&mut self, val: u8) -> u8 {
|
||||
// Make sure this is correct.
|
||||
if self.debug || true {
|
||||
if self.debug {
|
||||
println!("RST {:02X}", val);
|
||||
}
|
||||
self.call(val as u16);
|
||||
@ -887,7 +887,7 @@ impl CPU {
|
||||
// println!("Handling vblank interrupt");
|
||||
self.handle_interrupt(0x40, interconnect::INTERRUPT_DISPLAY_VBLANK);
|
||||
} else if e_pending & interconnect::INTERRUPT_DISPLAY_STAT > 0 {
|
||||
println!("Handling display stat interrupt");
|
||||
// println!("Handling display stat interrupt");
|
||||
self.handle_interrupt(0x48, interconnect::INTERRUPT_DISPLAY_STAT);
|
||||
} else if e_pending & interconnect::INTERRUPT_TIMER_OVERFLOW > 0{
|
||||
println!("Handling timer interrupt");
|
||||
@ -985,7 +985,10 @@ impl CPU {
|
||||
4
|
||||
}
|
||||
|
||||
0x10 => panic!("STOP 0 {:02X} not implemented.", self.load_args(1)[0]),
|
||||
0x10 => {
|
||||
println!("STOP 0 {:02X} not implemented.", self.load_args(1)[0]);
|
||||
4
|
||||
},
|
||||
0x11 => self.ld_rr_vv(REG_N_D, REG_N_E),
|
||||
0x12 => self.ld_dref_rr_a(REG_N_D, REG_N_E),
|
||||
0x13 => self.inc_rr(REG_N_D, REG_N_E),
|
||||
@ -1192,7 +1195,7 @@ impl CPU {
|
||||
if self.debug {
|
||||
println!("LD A, (HL-)");
|
||||
}
|
||||
let mut addr = self.get_pair_value(REG_N_H, REG_N_L);
|
||||
let addr = self.get_pair_value(REG_N_H, REG_N_L);
|
||||
self.regs[REG_A] = self.interconnect.read_byte(addr);
|
||||
self.set_pair_value(REG_N_H, REG_N_L, addr.wrapping_sub(1));
|
||||
8
|
||||
|
||||
@ -72,7 +72,6 @@ pub struct Display {
|
||||
|
||||
pub event_pump: sdl2::EventPump,
|
||||
|
||||
vblank_fired: bool,
|
||||
vblank_interrupt: bool,
|
||||
stat_interrupt: bool,
|
||||
}
|
||||
@ -83,7 +82,7 @@ impl Display {
|
||||
let video_ctx = sdl_ctx.video().unwrap();
|
||||
let wnd = video_ctx.window("RustBoy", (GB_PIXELS_X * SCALE) as u32, (GB_PIXELS_Y * SCALE) as u32).position_centered().build().expect("Failed to create window :<");
|
||||
let renderer = wnd.renderer().build().expect("Could not build renderer");
|
||||
let mut event_pump = sdl_ctx.event_pump().expect("Getting event pump failed");
|
||||
let event_pump = sdl_ctx.event_pump().expect("Getting event pump failed");
|
||||
|
||||
Display {
|
||||
control: 0,
|
||||
@ -106,7 +105,6 @@ impl Display {
|
||||
|
||||
event_pump: event_pump,
|
||||
|
||||
vblank_fired: false,
|
||||
vblank_interrupt: false,
|
||||
stat_interrupt: false,
|
||||
}
|
||||
@ -401,8 +399,8 @@ impl Display {
|
||||
false => 8
|
||||
};
|
||||
for x_o in 0 .. limit {
|
||||
let mut b1: bool;
|
||||
let mut b2: bool;
|
||||
let b1: bool;
|
||||
let b2: bool;
|
||||
let mut factor = 0;
|
||||
if wide_mode && x_o > 7 {
|
||||
b1 = (tile_line_3 & 1 << (14 - x_o)) > 0;
|
||||
|
||||
@ -193,6 +193,9 @@ impl Interconnect {
|
||||
},
|
||||
0xD000 ... 0xDFFF => {
|
||||
self.ram[(addr - 0xD000) as usize + self.wram_bank as usize * 0x1000]
|
||||
},
|
||||
0xE000 ... 0xEFFF => {
|
||||
self.ram[(addr - 0xE000) as usize]
|
||||
}
|
||||
0xFF00 => {
|
||||
if self.joy_switch & KEY_REGULAR == 0 {
|
||||
@ -201,7 +204,7 @@ impl Interconnect {
|
||||
self.joy_special_keys
|
||||
} else {
|
||||
println!("Reading none from joystick?");
|
||||
0xFF
|
||||
0x3F
|
||||
}
|
||||
}
|
||||
0xFF01 ... 0xFF02 => self.serial.read_byte(addr),
|
||||
@ -238,7 +241,8 @@ impl Interconnect {
|
||||
self.interrupt
|
||||
}
|
||||
_ => {
|
||||
panic!("Read from {:04X} not supported.", addr);
|
||||
println!("Read from {:04X} not supported.", addr);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ mod interconnect;
|
||||
mod sound;
|
||||
mod timer;
|
||||
mod serial;
|
||||
mod mbc;
|
||||
|
||||
|
||||
fn main() {
|
||||
let bios_path = env::args().nth(1).unwrap();
|
||||
|
||||
40
src/mbc/mbc.rs
Normal file
40
src/mbc/mbc.rs
Normal file
@ -0,0 +1,40 @@
|
||||
pub trait MBC {
|
||||
fn read_byte(&self, addr: u16) -> u8;
|
||||
fn write_byte(&mut self, addr: u16, val: u8);
|
||||
}
|
||||
|
||||
pub struct NoMBC {
|
||||
rom: Box<[u8]>,
|
||||
ram: Box<[u8]>,
|
||||
}
|
||||
|
||||
impl NoMBC {
|
||||
pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> NoMBC {
|
||||
|
||||
NoMBC {
|
||||
rom: rom,
|
||||
ram: ram,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MBC for NoMBC {
|
||||
fn write_byte(&mut self, addr: u16, val: u8) {
|
||||
panic!("Writing not supported for cartridges without MBC.");
|
||||
}
|
||||
|
||||
fn read_byte(&self, addr: u16) -> u8 {
|
||||
match addr {
|
||||
0x0000 ... 0x7FFF => self.rom[addr as usize],
|
||||
0xA000 ... 0xBFFF => {
|
||||
// TODO: Check for ram
|
||||
let addr = (addr as usize) - 0xA000;
|
||||
|
||||
self.ram[addr]
|
||||
}
|
||||
_ => {
|
||||
panic!("Cartride: Unable to read from {:04X}", addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
src/mbc/mbc3.rs
Normal file
93
src/mbc/mbc3.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use super::mbc::MBC;
|
||||
|
||||
pub struct MBC3 {
|
||||
rom: Box<[u8]>,
|
||||
ram: Box<[u8]>,
|
||||
bank_no: u8,
|
||||
ram_rtc_enabled: bool,
|
||||
ram_bank_no: u8,
|
||||
|
||||
rom_banks: u16,
|
||||
|
||||
bank_mode: u8,
|
||||
bank_no_high: u8,
|
||||
}
|
||||
|
||||
impl MBC3 {
|
||||
pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> MBC3 {
|
||||
MBC3 {
|
||||
rom: rom,
|
||||
ram: ram,
|
||||
bank_no: 0,
|
||||
ram_rtc_enabled: false,
|
||||
ram_bank_no: 0,
|
||||
rom_banks: 0,
|
||||
bank_mode: 0,
|
||||
bank_no_high: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MBC for MBC3 {
|
||||
fn read_byte(&self, addr: u16) -> u8 {
|
||||
match addr {
|
||||
0x0000 ... 0x3FFF => self.rom[addr as usize],
|
||||
0x4000 ... 0x7FFF => {
|
||||
let addr = addr - 0x4000;
|
||||
// println!("BankNo: {:02X}", self.bank_no);
|
||||
let abs_addr: usize = addr as usize + self.bank_no as usize * 0x4000;
|
||||
let val: u8 = self.rom[abs_addr];
|
||||
val
|
||||
},
|
||||
0xA000 ... 0xBFFF => {
|
||||
// TODO: Safty checks? Or let rust handle this?
|
||||
let addr = addr - 0xA000;
|
||||
if self.ram_bank_no < 4 {
|
||||
println!("Access [{:02X}] {:04X}", self.ram_bank_no, addr);
|
||||
self.ram[self.ram_bank_no as usize * 0x2000 + addr as usize]
|
||||
} else {
|
||||
println!("Ignoring RTC read");
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("Cartride: Unable to read from {:04X}", addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_byte(&mut self, addr: u16, val: u8) {
|
||||
match addr {
|
||||
0x0000 ... 0x1FFF => {
|
||||
match val {
|
||||
0x0A => self.ram_rtc_enabled = true,
|
||||
0x00 => self.ram_rtc_enabled = false,
|
||||
_ => println!("Unknown MBC value {:02X} for {:04X}", val, addr)
|
||||
}
|
||||
},
|
||||
0x2000 ... 0x3FFF => self.bank_no = val & 0x7F,
|
||||
0x4000 ... 0x5FFF => {
|
||||
// RAM bank select
|
||||
match val {
|
||||
0x00 ... 0x03 => self.ram_bank_no = 0, //val,
|
||||
0x08 ... 0x0C => self.ram_bank_no = val, // RTC clock values, TODO
|
||||
_ => panic!("Unknown MBC3 RAM BANK NO: {:02X}", val)
|
||||
}
|
||||
|
||||
},
|
||||
0x6000 ... 0x7FFF => {
|
||||
// Latch clock data, ignore.
|
||||
},
|
||||
0xA000 ... 0xBFFF => {
|
||||
// TODO: Safty checks? Or let rust handle this?
|
||||
let addr = addr - 0xA000;
|
||||
if self.ram_bank_no < 4 {
|
||||
self.ram[self.ram_bank_no as usize * 0x2000 + addr as usize] = val;
|
||||
} else {
|
||||
println!("Ignoring RTC write");
|
||||
}
|
||||
}
|
||||
_ => panic!("MBC3: Writing {:02X} to {:04X} not supported", val, addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/mbc/mod.rs
Normal file
2
src/mbc/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod mbc;
|
||||
pub mod mbc3;
|
||||
Loading…
Reference in New Issue
Block a user