mirror of
https://github.com/FranLMSP/rmg-001.git
synced 2024-11-23 02:01:32 +00:00
Saving RAM when the emulator is closed
This commit is contained in:
parent
2eb5d55bcc
commit
52ad064ab5
@ -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
|
||||
|
16
src/bus.rs
16
src/bus.rs
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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 => {
|
||||
|
133
src/rom.rs
133
src/rom.rs
@ -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,6 +323,7 @@ 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];
|
||||
@ -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 {
|
||||
@ -382,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];
|
||||
@ -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 {
|
||||
@ -459,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];
|
||||
@ -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 {
|
||||
@ -535,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];
|
||||
@ -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
47
src/sound.rs
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user