mirror of
https://github.com/FranLMSP/rmg-001.git
synced 2024-11-23 18:21:31 +00:00
Compare commits
5 Commits
5d488d4fa6
...
2bd74eddb4
Author | SHA1 | Date | |
---|---|---|---|
2bd74eddb4 | |||
6da1b022d8 | |||
62071d7310 | |||
e490228c39 | |||
488f36272c |
@ -1,9 +1,6 @@
|
|||||||
use rmg_001::render::start_eventloop;
|
use rmg_001::render::start_eventloop;
|
||||||
use rmg_001::emulator::Emulator;
|
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
start_eventloop();
|
start_eventloop();
|
||||||
/* let mut emulator = Emulator::new();
|
|
||||||
emulator.cpu_loop(); */
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
16
src/bus.rs
16
src/bus.rs
@ -1,14 +1,10 @@
|
|||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
get_bit,
|
get_bit,
|
||||||
set_bit,
|
|
||||||
BitIndex,
|
BitIndex,
|
||||||
join_bytes
|
join_bytes
|
||||||
};
|
};
|
||||||
use crate::rom::ROM;
|
use crate::rom::ROM;
|
||||||
use crate::ppu::{
|
use crate::ppu::{
|
||||||
PPU,
|
|
||||||
LCDStatus,
|
|
||||||
LCDStatusModeFlag,
|
|
||||||
LCD_STATUS_ADDRESS,
|
LCD_STATUS_ADDRESS,
|
||||||
LCD_CONTROL_ADDRESS,
|
LCD_CONTROL_ADDRESS,
|
||||||
LCD_Y_ADDRESS,
|
LCD_Y_ADDRESS,
|
||||||
@ -61,10 +57,10 @@ pub struct Bus {
|
|||||||
|
|
||||||
impl Bus {
|
impl Bus {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let game_rom = match ROM::load_file("ignore/tetris.gb".to_string()) {
|
// let game_rom = match ROM::load_file("ignore/mooneye/acceptance/if_ie_registers.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.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/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/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/04-op r,imm.gb".to_string()) {
|
||||||
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/05-op rp.gb".to_string()) {
|
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/05-op rp.gb".to_string()) {
|
||||||
@ -111,9 +107,8 @@ impl Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(&self, address: u16) -> u8 {
|
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);
|
return self.game_rom.read(address);
|
||||||
|
|
||||||
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
|
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
|
||||||
return 0b11100000 | self.data[address as usize];
|
return 0b11100000 | self.data[address as usize];
|
||||||
} else if address == JOYPAD_ADDRESS {
|
} else if address == JOYPAD_ADDRESS {
|
||||||
@ -131,7 +126,8 @@ impl Bus {
|
|||||||
// print!("{}", data as char);
|
// 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");
|
// println!("WRITING TO ROM");
|
||||||
} else if WORK_RAM_1.in_range(address) || WORK_RAM_2.in_range(address) {
|
} else if WORK_RAM_1.in_range(address) || WORK_RAM_2.in_range(address) {
|
||||||
self.data[address as usize] = data;
|
self.data[address as usize] = data;
|
||||||
@ -139,6 +135,8 @@ impl Bus {
|
|||||||
if address <= 0xDDFF {
|
if address <= 0xDDFF {
|
||||||
self.data[(ECHO_RAM.begin() + (address - WORK_RAM_1.begin())) as usize] = data;
|
self.data[(ECHO_RAM.begin() + (address - WORK_RAM_1.begin())) as usize] = data;
|
||||||
}
|
}
|
||||||
|
} else if EXTERNAL_RAM.in_range(address) {
|
||||||
|
// self.game_rom.write(address, data);
|
||||||
} else if ECHO_RAM.in_range(address) {
|
} else if ECHO_RAM.in_range(address) {
|
||||||
self.data[address as usize] = data;
|
self.data[address as usize] = data;
|
||||||
self.data[(WORK_RAM_1.begin() + (address - ECHO_RAM.begin())) as usize] = data; // Copy to the working RAM
|
self.data[(WORK_RAM_1.begin() + (address - ECHO_RAM.begin())) as usize] = data; // Copy to the working RAM
|
||||||
|
17
src/cpu.rs
17
src/cpu.rs
@ -871,10 +871,6 @@ impl CPU {
|
|||||||
self.cycles.0 += cycles.0;
|
self.cycles.0 += cycles.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrement_cycles(&mut self, cycles: Cycles) {
|
|
||||||
self.cycles.0 -= cycles.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_cycles(&mut self) {
|
pub fn reset_cycles(&mut self) {
|
||||||
self.cycles = Cycles(0);
|
self.cycles = Cycles(0);
|
||||||
}
|
}
|
||||||
@ -919,7 +915,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_interrupts(&mut self, bus: &mut Bus) -> Option<Interrupt> {
|
pub fn check_interrupts(&mut self, bus: &mut Bus) -> Option<Interrupt> {
|
||||||
if bus.read(INTERRUPT_ENABLE_ADDRESS) & bus.read(INTERRUPT_FLAG_ADDRESS) != 0 {
|
if (bus.read(INTERRUPT_ENABLE_ADDRESS) & 0b00011111) & (bus.read(INTERRUPT_FLAG_ADDRESS) & 0b00011111) != 0 {
|
||||||
self.is_halted = false;
|
self.is_halted = false;
|
||||||
}
|
}
|
||||||
if !self.ime {
|
if !self.ime {
|
||||||
@ -2132,7 +2128,16 @@ mod tests {
|
|||||||
let mut cpu = CPU::new();
|
let mut cpu = CPU::new();
|
||||||
let mut bus = Bus::new();
|
let mut bus = Bus::new();
|
||||||
cpu.exec(Opcode::DI, &mut bus);
|
cpu.exec(Opcode::DI, &mut bus);
|
||||||
assert_eq!(bus.read(0xFFFF), 0x00);
|
assert_eq!(cpu.ime, false);
|
||||||
|
assert_eq!(cpu.registers.get(Register::PC), 0x101);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ei_instructions() {
|
||||||
|
let mut cpu = CPU::new();
|
||||||
|
let mut bus = Bus::new();
|
||||||
|
cpu.exec(Opcode::EI, &mut bus);
|
||||||
|
assert_eq!(cpu.ime, true);
|
||||||
assert_eq!(cpu.registers.get(Register::PC), 0x101);
|
assert_eq!(cpu.registers.get(Register::PC), 0x101);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{thread, time};
|
// use std::{thread, time};
|
||||||
use winit_input_helper::WinitInputHelper;
|
use winit_input_helper::WinitInputHelper;
|
||||||
use winit::event::{VirtualKeyCode};
|
use winit::event::{VirtualKeyCode};
|
||||||
|
|
||||||
@ -6,7 +6,7 @@ use crate::cpu::{CPU, Cycles, Interrupt};
|
|||||||
use crate::ppu::PPU;
|
use crate::ppu::PPU;
|
||||||
use crate::bus::Bus;
|
use crate::bus::Bus;
|
||||||
use crate::timer::Timer;
|
use crate::timer::Timer;
|
||||||
use crate::joypad::{Joypad, Button, JOYPAD_ADDRESS};
|
use crate::joypad::{Button};
|
||||||
|
|
||||||
pub struct Emulator {
|
pub struct Emulator {
|
||||||
cpu: CPU,
|
cpu: CPU,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use crate::bus::{Bus};
|
|
||||||
use crate::utils::{BitIndex, get_bit};
|
use crate::utils::{BitIndex, get_bit};
|
||||||
|
|
||||||
pub const JOYPAD_ADDRESS: u16 = 0xFF00;
|
pub const JOYPAD_ADDRESS: u16 = 0xFF00;
|
||||||
|
46
src/ppu.rs
46
src/ppu.rs
@ -2,9 +2,8 @@ use crate::utils::{
|
|||||||
BitIndex,
|
BitIndex,
|
||||||
get_bit,
|
get_bit,
|
||||||
set_bit,
|
set_bit,
|
||||||
to_bit_index,
|
|
||||||
};
|
};
|
||||||
use crate::bus::{Bus, AddressRange, BANK_ZERO, VIDEO_RAM, SPRITE_ATTRIBUTE_TABLE};
|
use crate::bus::{Bus, SPRITE_ATTRIBUTE_TABLE};
|
||||||
use crate::cpu::{Cycles, Interrupt};
|
use crate::cpu::{Cycles, Interrupt};
|
||||||
|
|
||||||
pub const LCD_WIDTH: u32 = 160;
|
pub const LCD_WIDTH: u32 = 160;
|
||||||
@ -93,7 +92,6 @@ pub enum LCDStatus {
|
|||||||
pub struct PPU {
|
pub struct PPU {
|
||||||
prev_state: bool,
|
prev_state: bool,
|
||||||
cycles: Cycles,
|
cycles: Cycles,
|
||||||
rgba_frame: [[u8; 4]; FRAME_BUFFER_LENGTH as usize],
|
|
||||||
sprite_buffer: Vec<Sprite>,
|
sprite_buffer: Vec<Sprite>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,11 +112,14 @@ impl Sprite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pixel(&self, lcd_x: u8, lcd_y: u8, bus: &Bus) -> Option<Pixel> {
|
pub fn get_pixel(&self, lcd_x: u8, lcd_y: u8, bus: &Bus) -> Option<Pixel> {
|
||||||
todo!("Implement sprite flipping");
|
|
||||||
if lcd_x < self.x.saturating_sub(8) || lcd_x >= self.x {
|
if lcd_x < self.x.saturating_sub(8) || lcd_x >= self.x {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.over_bg {
|
||||||
|
// todo!("Implement over_bg sprite property");
|
||||||
|
}
|
||||||
|
|
||||||
let height: u8 = match self.is_long {
|
let height: u8 = match self.is_long {
|
||||||
true => 16,
|
true => 16,
|
||||||
false => 8,
|
false => 8,
|
||||||
@ -127,8 +128,25 @@ impl Sprite {
|
|||||||
let x = lcd_x.saturating_sub(self.x.saturating_sub(8));
|
let x = lcd_x.saturating_sub(self.x.saturating_sub(8));
|
||||||
let y = lcd_y.saturating_sub(self.y .saturating_sub(16));
|
let y = lcd_y.saturating_sub(self.y .saturating_sub(16));
|
||||||
|
|
||||||
|
let x = match self.x_flip {
|
||||||
|
true => 7 - x,
|
||||||
|
false => x,
|
||||||
|
};
|
||||||
|
let y = match self.y_flip {
|
||||||
|
true => height - 1 - y,
|
||||||
|
false => y,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tile_number = self.tile_number;
|
||||||
|
|
||||||
|
if self.is_long && x <= 7 {
|
||||||
|
tile_number = tile_number & 0xFE;
|
||||||
|
} else if self.is_long && x > 7 {
|
||||||
|
tile_number = tile_number | 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
let tile_line = y.rem_euclid(height) * 2;
|
let tile_line = y.rem_euclid(height) * 2;
|
||||||
let addr = 0x8000 + (self.tile_number as u16 * 16) + tile_line as u16;
|
let addr = 0x8000 + (tile_number as u16 * 16) + tile_line as u16;
|
||||||
|
|
||||||
let tile_byte_1 = bus.read(addr);
|
let tile_byte_1 = bus.read(addr);
|
||||||
let tile_byte_2 = bus.read(addr + 1);
|
let tile_byte_2 = bus.read(addr + 1);
|
||||||
@ -155,7 +173,6 @@ impl PPU {
|
|||||||
Self {
|
Self {
|
||||||
prev_state: false,
|
prev_state: false,
|
||||||
cycles: Cycles(0),
|
cycles: Cycles(0),
|
||||||
rgba_frame: [[0xFF, 0xFF, 0xFF, 0]; FRAME_BUFFER_LENGTH as usize],
|
|
||||||
sprite_buffer: Vec::new(),
|
sprite_buffer: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -221,11 +238,9 @@ impl PPU {
|
|||||||
|
|
||||||
fn stat_interrupt(&mut self, bus: &mut Bus) {
|
fn stat_interrupt(&mut self, bus: &mut Bus) {
|
||||||
let state = self.prev_state;
|
let state = self.prev_state;
|
||||||
self.prev_state = (
|
self.prev_state = (PPU::get_lcd_status(bus, LCDStatus::Mode2OAMInterrupt) && PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM))) ||
|
||||||
(PPU::get_lcd_status(bus, LCDStatus::Mode2OAMInterrupt) && PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM))) ||
|
|
||||||
(PPU::get_lcd_status(bus, LCDStatus::Mode0HBlankInterrupt) && PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank))) ||
|
(PPU::get_lcd_status(bus, LCDStatus::Mode0HBlankInterrupt) && PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank))) ||
|
||||||
(PPU::get_lcd_status(bus, LCDStatus::Mode1VBlankInterrupt) && PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank)))
|
(PPU::get_lcd_status(bus, LCDStatus::Mode1VBlankInterrupt) && PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank)));
|
||||||
);
|
|
||||||
if self.prev_state && !state {
|
if self.prev_state && !state {
|
||||||
bus.set_interrupt_flag(Interrupt::LCDSTAT, true);
|
bus.set_interrupt_flag(Interrupt::LCDSTAT, true);
|
||||||
}
|
}
|
||||||
@ -251,7 +266,7 @@ impl PPU {
|
|||||||
// The gameboy only supports 10 sprites per line,
|
// The gameboy only supports 10 sprites per line,
|
||||||
// but since we are on an emulator we can avoud that limitation
|
// but since we are on an emulator we can avoud that limitation
|
||||||
if self.sprite_buffer.len() >= 10 {
|
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;
|
// break;
|
||||||
}
|
}
|
||||||
let y = bus.read(addr);
|
let y = bus.read(addr);
|
||||||
@ -269,7 +284,7 @@ impl PPU {
|
|||||||
|
|
||||||
let lcd_y = PPU::get_lcd_y(bus).saturating_add(16);
|
let lcd_y = PPU::get_lcd_y(bus).saturating_add(16);
|
||||||
|
|
||||||
if lcd_y < y || lcd_y > (y + sprite_height) {
|
if lcd_y < y || lcd_y > (y + sprite_height - 1) {
|
||||||
addr += 4;
|
addr += 4;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -334,11 +349,6 @@ impl PPU {
|
|||||||
control.get(byte)
|
control.get(byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_lcd_control(bus: &mut Bus, control: LCDControl, val: bool) {
|
|
||||||
let mut byte = bus.read(LCD_CONTROL_ADDRESS);
|
|
||||||
bus.force_write(LCD_CONTROL_ADDRESS, control.set(byte, val));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_lcd_status(bus: &Bus, status: LCDStatus) -> bool {
|
pub fn get_lcd_status(bus: &Bus, status: LCDStatus) -> bool {
|
||||||
let byte = bus.read(LCD_STATUS_ADDRESS);
|
let byte = bus.read(LCD_STATUS_ADDRESS);
|
||||||
match status {
|
match status {
|
||||||
@ -444,7 +454,7 @@ impl PPU {
|
|||||||
frame_buffer[idx + 1] = rgba[1];
|
frame_buffer[idx + 1] = rgba[1];
|
||||||
frame_buffer[idx + 2] = rgba[2];
|
frame_buffer[idx + 2] = rgba[2];
|
||||||
if let Some(window_pixel) = PPU::get_window_pixel(lcd_x, bus) {
|
if let Some(window_pixel) = PPU::get_window_pixel(lcd_x, bus) {
|
||||||
let rgba = PPU::get_rgba(pixel);
|
let rgba = PPU::get_rgba(window_pixel);
|
||||||
frame_buffer[idx] = rgba[0];
|
frame_buffer[idx] = rgba[0];
|
||||||
frame_buffer[idx + 1] = rgba[1];
|
frame_buffer[idx + 1] = rgba[1];
|
||||||
frame_buffer[idx + 2] = rgba[2];
|
frame_buffer[idx + 2] = rgba[2];
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use crate::emulator::Emulator;
|
use crate::emulator::Emulator;
|
||||||
use crate::cpu::{CPU, Cycles};
|
use crate::cpu::{Cycles};
|
||||||
use crate::ppu::{WIDTH, HEIGHT};
|
use crate::ppu::{WIDTH, HEIGHT};
|
||||||
|
|
||||||
use std::{thread, time};
|
use std::{thread, time};
|
||||||
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use pixels::{Error, Pixels, SurfaceTexture};
|
use pixels::{Pixels, SurfaceTexture};
|
||||||
use winit::dpi::LogicalSize;
|
use winit::dpi::LogicalSize;
|
||||||
use winit::event::{Event, VirtualKeyCode, WindowEvent};
|
use winit::event::{Event, VirtualKeyCode, WindowEvent};
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
@ -67,7 +67,7 @@ pub fn start_eventloop() {
|
|||||||
emulator.run(Cycles(70224), pixels.get_frame());
|
emulator.run(Cycles(70224), pixels.get_frame());
|
||||||
// emulator.draw(pixels.get_frame());
|
// emulator.draw(pixels.get_frame());
|
||||||
|
|
||||||
thread::sleep(time::Duration::from_millis(10));
|
thread::sleep(time::Duration::from_millis(1));
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
},
|
},
|
||||||
Event::RedrawRequested(_) => {
|
Event::RedrawRequested(_) => {
|
||||||
|
206
src/rom.rs
206
src/rom.rs
@ -1,43 +1,215 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
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 {
|
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 {
|
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 {
|
pub struct ROM {
|
||||||
bytes: Vec<u8>,
|
data: Vec<u8>,
|
||||||
|
info: ROMInfo,
|
||||||
|
ram: Vec<u8>,
|
||||||
|
rom_bank: u16,
|
||||||
|
ram_bank: u8,
|
||||||
|
ram_enable: bool,
|
||||||
|
banking_mode: BankingMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ROM {
|
impl ROM {
|
||||||
pub fn load_file(filename: String) -> std::io::Result<Self> {
|
pub fn load_file(filename: String) -> std::io::Result<Self> {
|
||||||
let mut file = File::open(filename)?;
|
let mut file = File::open(filename)?;
|
||||||
let mut bytes = vec![];
|
let mut data = vec![];
|
||||||
file.read_to_end(&mut bytes)?;
|
file.read_to_end(&mut data)?;
|
||||||
|
|
||||||
|
let info = ROMInfo::from_bytes(&data);
|
||||||
|
let ram = Vec::with_capacity(info.ram_size() as usize);
|
||||||
|
|
||||||
Ok(Self {
|
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 {
|
pub fn read(&self, address: u16) -> u8 {
|
||||||
match self.bytes.get(address as usize) {
|
match self.info.mbc {
|
||||||
Some(val) => *val,
|
MBC::MBC1 => {
|
||||||
None => 0xFF,
|
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 from_bytes(bytes: &[u8]) -> Self {
|
pub fn write(&mut self, address: u16, data: u8) {
|
||||||
Self {
|
match self.info.mbc {
|
||||||
bytes: bytes.to_vec(),
|
MBC::MBC1 => {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_content(&self, address: Option<u16>) {
|
if address >= 0x0000 || address <= 0x1FFF { // RAM enable register
|
||||||
match address {
|
self.ram_enable = match data & 0x0F {
|
||||||
Some(address) => println!("{:02X?}", self.read(address)),
|
0x0A => true,
|
||||||
None => println!("{:02X?}", self.bytes),
|
_ => 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user