Compare commits

..

4 Commits

Author SHA1 Message Date
52ad064ab5 Saving RAM when the emulator is closed 2021-11-19 17:20:37 -05:00
2eb5d55bcc Fix DMA bug 2021-11-19 13:26:16 -05:00
186d6fa23b fix ram banking mbc1 2021-11-19 12:46:14 -05:00
841cc96a67 Fix sprites bug 2021-11-19 10:19:01 -05:00
8 changed files with 225 additions and 31 deletions

View File

@ -17,11 +17,11 @@ Any help or suggestion is welcome!
- [x] MBC1
- [x] MBC2
- [ ] MBC3 (partially implemented)
- [X] MBC5
- [x] MBC5
- [ ] MBC6
- [ ] MBC7
- [ ] HuC1
- [ ] Save files
- [x] Save files
- [ ] Web Assembly support (because this is a Rust project and it has to support Web Assembly)
- [ ] Gameboy boot ROM (Not important for now)
- [ ] Gameboy Color compatibility

View File

@ -27,8 +27,8 @@ pub const INTERRUPT_ENABLE_ADDRESS: u16 = 0xFFFF;
pub const INTERRUPT_FLAG_ADDRESS: u16 = 0xFF0F;
pub struct Bus {
game_rom: Box<dyn ROM>,
data: [u8; 0x10000],
pub rom: Box<dyn ROM>,
pub ppu: PPU,
pub joypad: Joypad,
pub timer: Timer,
@ -39,19 +39,19 @@ impl Bus {
let args: Vec<String> = std::env::args().collect();
#[cfg(not(test))]
if args.len() < 2 {
println!("Please, specify a ROM file");
eprintln!("Please, specify a ROM file");
std::process::exit(1);
}
let game_rom = match load_rom(&args.get(1).unwrap_or(&"".to_string())) {
let rom = match load_rom(&args.get(1).unwrap_or(&"".to_string())) {
Ok(rom) => rom,
Err(err) => {
println!("Could not read ROM: {}", err);
eprintln!("Could not read ROM: {}", err);
std::process::exit(1);
},
};
let mut bus = Self {
data: [0x00; 0x10000],
game_rom,
rom,
ppu: PPU::new(),
joypad: Joypad::new(),
timer: Timer::new(),
@ -85,7 +85,7 @@ impl Bus {
pub fn read(&self, address: u16) -> u8 {
if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) {
return self.game_rom.read(address);
return self.rom.read(address);
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
return 0b11100000 | self.data[address as usize];
} else if VIDEO_RAM.contains(&address) {
@ -112,7 +112,7 @@ impl Bus {
}
if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) {
self.game_rom.write(address, data);
self.rom.write(address, data);
} else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) {
self.data[address as usize] = data;
// Copy to the ECHO RAM
@ -120,7 +120,7 @@ impl Bus {
self.data[(ECHO_RAM.min().unwrap() + (address - WORK_RAM_1.min().unwrap())) as usize] = data;
}
} else if EXTERNAL_RAM.contains(&address) {
self.game_rom.write(address, data);
self.rom.write(address, data);
} else if ECHO_RAM.contains(&address) {
self.data[address as usize] = data;
self.data[(WORK_RAM_1.min().unwrap() + (address - ECHO_RAM.min().unwrap())) as usize] = data; // Copy to the working RAM
@ -139,7 +139,7 @@ impl Bus {
let mut count: u16 = 0;
let oam_addr = SPRITE_ATTRIBUTE_TABLE.min().unwrap();
while count < 160 {
self.ppu.write_oam(oam_addr + count, self.data[(source + count) as usize]);
self.ppu.write_oam(oam_addr + count, self.read(source + count));
count += 1;
}
} else if PPU::is_io_register(address) {

View File

@ -5,6 +5,8 @@ use winit::event::{VirtualKeyCode};
use crate::cpu::{CPU, Cycles, Interrupt};
use crate::bus::Bus;
use crate::joypad::{Button};
#[cfg(not(test))]
use crate::rom::{save_file};
pub struct Emulator {
bus: Bus,
@ -19,6 +21,16 @@ impl Emulator {
}
}
pub fn close(&self) {
println!("closing emulator");
#[cfg(not(test))]
match save_file(self.bus.rom.ram(), self.bus.rom.info()) {
Err(err) => eprintln!("Could not save file: {}", err),
_ => {},
};
}
pub fn handle_input(&mut self, input: &WinitInputHelper) {
let mut change = false;
if input.key_pressed(VirtualKeyCode::K) {

View File

@ -2,6 +2,7 @@ pub mod utils;
pub mod cpu;
pub mod ppu;
pub mod timer;
pub mod sound;
pub mod rom;
pub mod bus;
pub mod joypad;

View File

@ -156,8 +156,8 @@ impl Sprite {
true => 16,
false => 8,
};
let x = lcd_x.saturating_sub(self.x.saturating_sub(8));
let y = lcd_y.saturating_sub(self.y .saturating_sub(16));
let x = (lcd_x + 8) - self.x;
let y = (lcd_y + 16) - self.y;
let x = match self.x_flip {
true => 7 - x,
false => x,
@ -430,16 +430,28 @@ impl PPU {
let y = self.read_oam(addr);
let x = self.read_oam(addr + 1);
if x == 0 {
addr += 4;
continue;
}
let sprite_height: u8 = match long_sprites {
true => 16,
false => 8,
};
if x == 0 {
addr += 4;
continue;
} else if x >= 160 + 8 {
addr += 4;
continue;
} else if y == 0 {
addr += 4;
continue;
} else if y >= 144 + 16 {
addr += 4;
continue;
} else if !long_sprites && y <= 8 {
addr += 4;
continue;
}
let lcd_y = self.lcd_y.saturating_add(16);
if lcd_y < y || lcd_y > (y + sprite_height - 1) {

View File

@ -52,12 +52,13 @@ pub fn start_eventloop() {
let mut pixels = create_pixels(WIDTH, HEIGHT, &window);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
// *control_flow = ControlFlow::Wait;
// Handle input events
if input.update(&event) {
// Close events
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
emulator.close();
*control_flow = ControlFlow::Exit;
return;
}
@ -76,6 +77,7 @@ pub fn start_eventloop() {
..
} => {
println!("The close button was pressed; stopping");
emulator.close();
*control_flow = ControlFlow::Exit
},
Event::MainEventsCleared => {

View File

@ -2,6 +2,8 @@
use std::fs::File;
#[cfg(not(test))]
use std::io::Read;
#[cfg(not(test))]
use std::io::Write;
use crate::bus::{
BANK_ZERO,
@ -36,6 +38,7 @@ fn header_checksum(data: &Vec<u8>) -> bool {
pub fn load_rom(_filename: &str) -> std::io::Result<Box<dyn ROM>> {
Ok(Box::new(NoMBC::new(Vec::new(), ROMInfo {
mbc: MBC::NoMBC,
filename: "".to_string(),
publisher: "".to_string(),
title: "".to_string(),
cgb_only: false,
@ -58,16 +61,59 @@ pub fn load_rom(filename: &str) -> std::io::Result<Box<dyn ROM>> {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Header checksum failed. Is this a Gameboy ROM?"));
}
let info = ROMInfo::from_bytes(&data);
let mut info = ROMInfo::from_bytes(&data);
info.set_filename(filename.to_string());
let info_copy = info.clone();
Ok(match info.mbc {
let mut rom: Box<dyn ROM> = match info.mbc {
MBC::NoMBC => Box::new(NoMBC::new(data, info)),
MBC::MBC1 => Box::new(MBC1::new(data, info)),
MBC::MBC2 => Box::new(MBC2::new(data, info)),
MBC::MBC3 => Box::new(MBC3::new(data, info)),
MBC::MBC5 => Box::new(MBC5::new(data, info)),
_ => unimplemented!(),
})
};
#[cfg(not(test))]
match load_save(rom.ram_mut(), &info_copy) {
Err(err) => eprintln!("Could not load save file: {}", err),
_ => {},
};
Ok(rom)
}
#[cfg(not(test))]
pub fn save_file(ram: &Vec<u8>, info: &ROMInfo) -> std::io::Result<()> {
if !info.has_ram || !info.has_battery {
return Ok(());
}
let mut file = File::create(format!("{}.sav", info.filename))?;
file.write_all(ram)?;
Ok(())
}
#[cfg(not(test))]
pub fn load_save(ram: &mut Vec<u8>, info: &ROMInfo) -> std::io::Result<()> {
if !info.has_ram || !info.has_battery {
return Ok(());
}
let mut file = File::open(format!("{}.sav", info.filename))?;
let mut data = vec![];
file.read_to_end(&mut data)?;
let mut index = 0;
let size = match ram.len() < data.len() {
true => ram.len(),
false => data.len(),
};
while index < size {
ram[index] = data[index];
index += 1;
}
Ok(())
}
#[derive(Debug, Copy, Clone)]
@ -86,21 +132,22 @@ enum MBC {
BandaiTIMA5,
}
#[derive(Debug)]
#[derive(Copy, Clone, Debug)]
enum Region {
Japanese,
NonJapanese,
}
#[derive(Debug, PartialEq)]
#[derive(Copy, Clone, PartialEq, Debug)]
enum BankingMode {
Simple,
Advanced,
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct ROMInfo {
mbc: MBC,
filename: String,
publisher: String,
title: String,
cgb_only: bool,
@ -114,6 +161,10 @@ pub struct ROMInfo {
}
impl ROMInfo {
pub fn set_filename(&mut self, filename: String) {
self.filename = filename;
}
pub fn from_bytes(bytes: &[u8]) -> Self {
let rom_type = bytes[CARTRIDGE_TYPE_ADDRESS as usize];
Self {
@ -147,6 +198,7 @@ impl ROMInfo {
0xFF => MBC::HuC1,
_ => unreachable!(),
},
filename: "".to_string(),
region: match bytes[DESTINATION_CODE_ADDRESS as usize] {
0x00 => Region::Japanese,
_ => Region::NonJapanese,
@ -207,11 +259,15 @@ impl ROMInfo {
pub trait ROM {
fn read(&self, address: u16) -> u8;
fn write(&mut self, address: u16, data: u8);
fn ram_mut(&mut self) -> &mut Vec<u8>;
fn ram(&self) -> &Vec<u8>;
fn info(&self) -> &ROMInfo;
}
pub struct NoMBC {
data: Vec<u8>,
info: ROMInfo,
ram: Vec<u8>,
}
impl NoMBC {
@ -219,6 +275,7 @@ impl NoMBC {
let rom = Self {
data,
info,
ram: Vec::new(),
};
println!("MBC {:?}", rom.info.mbc);
println!("Region {:?}", rom.info.region);
@ -236,6 +293,18 @@ impl ROM for NoMBC {
}
fn write(&mut self, _address: u16, _data: u8) {}
fn ram_mut(&mut self) -> &mut Vec<u8> {
&mut self.ram
}
fn ram(&self) -> &Vec<u8> {
&self.ram
}
fn info(&self) -> &ROMInfo {
&self.info
}
}
pub struct MBC1 {
@ -254,10 +323,11 @@ impl MBC1 {
println!("MBC {:?}", info.mbc);
println!("Region {:?}", info.region);
println!("Has RAM {}", info.has_ram);
println!("Has battery {}", info.has_battery);
println!("ROM banks {}", info.rom_banks);
println!("RAM banks {}", info.ram_banks);
let ram = vec![0; info.ram_size() as usize];
let is_large_rom = info.rom_size() >= 1048576;
let is_large_rom = info.rom_banks > 32;
Self {
data,
info,
@ -278,12 +348,13 @@ impl MBC1 {
if self.rom_bank > self.info.rom_banks.saturating_sub(1) {
self.rom_bank = self.info.rom_banks.saturating_sub(1);
}
//println!("switched to ROM bank {}", self.rom_bank);
}
fn switch_ram_bank(&mut self, bank: u8) {
self.ram_bank = bank & 0b11;
// println!("switched to RAM bank {}", self.ram_bank);
if self.ram_bank > self.info.ram_banks.saturating_sub(1) {
self.ram_bank = self.info.ram_banks.saturating_sub(1);
}
}
fn get_bank_zero_address(&self, address: u16) -> usize {
@ -292,9 +363,7 @@ impl MBC1 {
}
match self.banking_mode {
BankingMode::Simple => address as usize,
BankingMode::Advanced => {
((self.ram_bank as usize) << 5) * ((address & 0x3FFF) as usize)
},
BankingMode::Advanced => ((self.ram_bank as usize) << 5) * ((address & 0x3FFF) as usize),
}
}
@ -368,6 +437,18 @@ impl ROM for MBC1 {
}
}
}
fn ram_mut(&mut self) -> &mut Vec<u8> {
&mut self.ram
}
fn ram(&self) -> &Vec<u8> {
&self.ram
}
fn info(&self) -> &ROMInfo {
&self.info
}
}
pub struct MBC2 {
@ -383,6 +464,7 @@ impl MBC2 {
println!("MBC {:?}", info.mbc);
println!("Region {:?}", info.region);
println!("Has RAM {}", info.has_ram);
println!("Has battery {}", info.has_battery);
println!("ROM banks {}", info.rom_banks);
println!("RAM banks {}", info.ram_banks);
let ram = vec![0; 0x200];
@ -444,6 +526,18 @@ impl ROM for MBC2 {
}
}
}
fn ram_mut(&mut self) -> &mut Vec<u8> {
&mut self.ram
}
fn ram(&self) -> &Vec<u8> {
&self.ram
}
fn info(&self) -> &ROMInfo {
&self.info
}
}
pub struct MBC3 {
@ -460,6 +554,7 @@ impl MBC3 {
println!("MBC {:?}", info.mbc);
println!("Region {:?}", info.region);
println!("Has RAM {}", info.has_ram);
println!("Has battery {}", info.has_battery);
println!("ROM banks {}", info.rom_banks);
println!("RAM banks {}", info.ram_banks);
let ram = vec![0; info.ram_size() as usize];
@ -520,6 +615,18 @@ impl ROM for MBC3 {
}
}
fn ram_mut(&mut self) -> &mut Vec<u8> {
&mut self.ram
}
fn ram(&self) -> &Vec<u8> {
&self.ram
}
fn info(&self) -> &ROMInfo {
&self.info
}
}
pub struct MBC5 {
@ -536,6 +643,7 @@ impl MBC5 {
println!("MBC {:?}", info.mbc);
println!("Region {:?}", info.region);
println!("Has RAM {}", info.has_ram);
println!("Has battery {}", info.has_battery);
println!("ROM banks {}", info.rom_banks);
println!("RAM banks {}", info.ram_banks);
let ram = vec![0; info.ram_size() as usize];
@ -592,4 +700,16 @@ impl ROM for MBC5 {
}
}
}
fn ram_mut(&mut self) -> &mut Vec<u8> {
&mut self.ram
}
fn ram(&self) -> &Vec<u8> {
&self.ram
}
fn info(&self) -> &ROMInfo {
&self.info
}
}

47
src/sound.rs Normal file
View File

@ -0,0 +1,47 @@
use std::ops::RangeInclusive;
pub const NR10_ADDRESS: u16 = 0xFF10;
pub const NR11_ADDRESS: u16 = 0xFF11;
pub const NR12_ADDRESS: u16 = 0xFF12;
pub const NR13_ADDRESS: u16 = 0xFF13;
pub const NR14_ADDRESS: u16 = 0xFF14;
pub const NR21_ADDRESS: u16 = 0xFF16;
pub const NR22_ADDRESS: u16 = 0xFF17;
pub const NR23_ADDRESS: u16 = 0xFF18;
pub const NR24_ADDRESS: u16 = 0xFF19;
pub const NR30_ADDRESS: u16 = 0xFF1A;
pub const NR31_ADDRESS: u16 = 0xFF1B;
pub const NR32_ADDRESS: u16 = 0xFF1C;
pub const NR33_ADDRESS: u16 = 0xFF1D;
pub const NR34_ADDRESS: u16 = 0xFF1E;
pub const NR41_ADDRESS: u16 = 0xFF20;
pub const NR42_ADDRESS: u16 = 0xFF21;
pub const NR43_ADDRESS: u16 = 0xFF22;
pub const NR44_ADDRESS: u16 = 0xFF23;
pub const NR50_ADDRESS: u16 = 0xFF24;
pub const NR51_ADDRESS: u16 = 0xFF25;
pub const NR52_ADDRESS: u16 = 0xFF26;
pub const WAVE_PATTERN_RAM: RangeInclusive<u16> = 0xFF30..=0xFF3F;
pub struct Sound {
io_registers: [u8; 48],
}
impl Sound {
pub fn is_io_register(address: u16) -> bool {
address >= 0xFF10 && address <= 0xFF3F
}
pub fn get_register(&self, address: u16) -> u8 {
self.io_registers[(address - 0xFF10) as usize]
}
pub fn set_register(&mut self, address: u16, data: u8) {
self.io_registers[(address - 0xFF10) as usize] = data;
}
}