Saving RAM when the emulator is closed

This commit is contained in:
Franco Colmenarez 2021-11-19 17:11:27 -05:00
parent 2eb5d55bcc
commit 52ad064ab5
7 changed files with 200 additions and 17 deletions

View File

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

View File

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

View File

@ -5,6 +5,8 @@ use winit::event::{VirtualKeyCode};
use crate::cpu::{CPU, Cycles, Interrupt}; use crate::cpu::{CPU, Cycles, Interrupt};
use crate::bus::Bus; use crate::bus::Bus;
use crate::joypad::{Button}; use crate::joypad::{Button};
#[cfg(not(test))]
use crate::rom::{save_file};
pub struct Emulator { pub struct Emulator {
bus: Bus, 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) { pub fn handle_input(&mut self, input: &WinitInputHelper) {
let mut change = false; let mut change = false;
if input.key_pressed(VirtualKeyCode::K) { if input.key_pressed(VirtualKeyCode::K) {

View File

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

View File

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

View File

@ -2,6 +2,8 @@
use std::fs::File; use std::fs::File;
#[cfg(not(test))] #[cfg(not(test))]
use std::io::Read; use std::io::Read;
#[cfg(not(test))]
use std::io::Write;
use crate::bus::{ use crate::bus::{
BANK_ZERO, 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>> { pub fn load_rom(_filename: &str) -> std::io::Result<Box<dyn ROM>> {
Ok(Box::new(NoMBC::new(Vec::new(), ROMInfo { Ok(Box::new(NoMBC::new(Vec::new(), ROMInfo {
mbc: MBC::NoMBC, mbc: MBC::NoMBC,
filename: "".to_string(),
publisher: "".to_string(), publisher: "".to_string(),
title: "".to_string(), title: "".to_string(),
cgb_only: false, 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?")); 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::NoMBC => Box::new(NoMBC::new(data, info)),
MBC::MBC1 => Box::new(MBC1::new(data, info)), MBC::MBC1 => Box::new(MBC1::new(data, info)),
MBC::MBC2 => Box::new(MBC2::new(data, info)), MBC::MBC2 => Box::new(MBC2::new(data, info)),
MBC::MBC3 => Box::new(MBC3::new(data, info)), MBC::MBC3 => Box::new(MBC3::new(data, info)),
MBC::MBC5 => Box::new(MBC5::new(data, info)), MBC::MBC5 => Box::new(MBC5::new(data, info)),
_ => unimplemented!(), _ => 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)] #[derive(Debug, Copy, Clone)]
@ -86,21 +132,22 @@ enum MBC {
BandaiTIMA5, BandaiTIMA5,
} }
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
enum Region { enum Region {
Japanese, Japanese,
NonJapanese, NonJapanese,
} }
#[derive(Debug, PartialEq)] #[derive(Copy, Clone, PartialEq, Debug)]
enum BankingMode { enum BankingMode {
Simple, Simple,
Advanced, Advanced,
} }
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct ROMInfo { pub struct ROMInfo {
mbc: MBC, mbc: MBC,
filename: String,
publisher: String, publisher: String,
title: String, title: String,
cgb_only: bool, cgb_only: bool,
@ -114,6 +161,10 @@ pub struct ROMInfo {
} }
impl ROMInfo { impl ROMInfo {
pub fn set_filename(&mut self, filename: String) {
self.filename = filename;
}
pub fn from_bytes(bytes: &[u8]) -> Self { pub fn from_bytes(bytes: &[u8]) -> Self {
let rom_type = bytes[CARTRIDGE_TYPE_ADDRESS as usize]; let rom_type = bytes[CARTRIDGE_TYPE_ADDRESS as usize];
Self { Self {
@ -147,6 +198,7 @@ impl ROMInfo {
0xFF => MBC::HuC1, 0xFF => MBC::HuC1,
_ => unreachable!(), _ => unreachable!(),
}, },
filename: "".to_string(),
region: match bytes[DESTINATION_CODE_ADDRESS as usize] { region: match bytes[DESTINATION_CODE_ADDRESS as usize] {
0x00 => Region::Japanese, 0x00 => Region::Japanese,
_ => Region::NonJapanese, _ => Region::NonJapanese,
@ -207,11 +259,15 @@ impl ROMInfo {
pub trait ROM { pub trait ROM {
fn read(&self, address: u16) -> u8; fn read(&self, address: u16) -> u8;
fn write(&mut self, address: u16, data: 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 { pub struct NoMBC {
data: Vec<u8>, data: Vec<u8>,
info: ROMInfo, info: ROMInfo,
ram: Vec<u8>,
} }
impl NoMBC { impl NoMBC {
@ -219,6 +275,7 @@ impl NoMBC {
let rom = Self { let rom = Self {
data, data,
info, info,
ram: Vec::new(),
}; };
println!("MBC {:?}", rom.info.mbc); println!("MBC {:?}", rom.info.mbc);
println!("Region {:?}", rom.info.region); println!("Region {:?}", rom.info.region);
@ -236,6 +293,18 @@ impl ROM for NoMBC {
} }
fn write(&mut self, _address: u16, _data: u8) {} 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 { pub struct MBC1 {
@ -254,6 +323,7 @@ impl MBC1 {
println!("MBC {:?}", info.mbc); println!("MBC {:?}", info.mbc);
println!("Region {:?}", info.region); println!("Region {:?}", info.region);
println!("Has RAM {}", info.has_ram); println!("Has RAM {}", info.has_ram);
println!("Has battery {}", info.has_battery);
println!("ROM banks {}", info.rom_banks); println!("ROM banks {}", info.rom_banks);
println!("RAM banks {}", info.ram_banks); println!("RAM banks {}", info.ram_banks);
let ram = vec![0; info.ram_size() as usize]; let ram = vec![0; info.ram_size() as usize];
@ -367,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 { pub struct MBC2 {
@ -382,6 +464,7 @@ impl MBC2 {
println!("MBC {:?}", info.mbc); println!("MBC {:?}", info.mbc);
println!("Region {:?}", info.region); println!("Region {:?}", info.region);
println!("Has RAM {}", info.has_ram); println!("Has RAM {}", info.has_ram);
println!("Has battery {}", info.has_battery);
println!("ROM banks {}", info.rom_banks); println!("ROM banks {}", info.rom_banks);
println!("RAM banks {}", info.ram_banks); println!("RAM banks {}", info.ram_banks);
let ram = vec![0; 0x200]; let ram = vec![0; 0x200];
@ -443,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 { pub struct MBC3 {
@ -459,6 +554,7 @@ impl MBC3 {
println!("MBC {:?}", info.mbc); println!("MBC {:?}", info.mbc);
println!("Region {:?}", info.region); println!("Region {:?}", info.region);
println!("Has RAM {}", info.has_ram); println!("Has RAM {}", info.has_ram);
println!("Has battery {}", info.has_battery);
println!("ROM banks {}", info.rom_banks); println!("ROM banks {}", info.rom_banks);
println!("RAM banks {}", info.ram_banks); println!("RAM banks {}", info.ram_banks);
let ram = vec![0; info.ram_size() as usize]; let ram = vec![0; info.ram_size() as usize];
@ -519,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 { pub struct MBC5 {
@ -535,6 +643,7 @@ impl MBC5 {
println!("MBC {:?}", info.mbc); println!("MBC {:?}", info.mbc);
println!("Region {:?}", info.region); println!("Region {:?}", info.region);
println!("Has RAM {}", info.has_ram); println!("Has RAM {}", info.has_ram);
println!("Has battery {}", info.has_battery);
println!("ROM banks {}", info.rom_banks); println!("ROM banks {}", info.rom_banks);
println!("RAM banks {}", info.ram_banks); println!("RAM banks {}", info.ram_banks);
let ram = vec![0; info.ram_size() as usize]; let ram = vec![0; info.ram_size() as usize];
@ -591,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;
}
}