multiple state saving and loading working

This commit is contained in:
Theron 2020-03-01 12:36:34 -06:00
parent 5f54f10b3c
commit 435a1b41fc
3 changed files with 42 additions and 36 deletions

View File

@ -20,10 +20,11 @@ ___________________
| Left | Left |
| Right | Right |
-------------------
Save state: F5
Load state: F9
(Saving state a second time overwrites the first unless you copy/rename the `.dat` file. You can drag and drop any `.dat` file onto the window to load it.)
```
## Save states
To save the state of the game at any time, press F5. To load the most recent save state, press F9. If the game is called `mygame.nes`, the save state files will be called `mygame-X.dat` where `X` is a number. To load any previous save state, drag and drop a `.dat` file onto the window.
The code aims to follow the explanations from the [NES dev wiki](https://wiki.nesdev.com/w/index.php/NES_reference_guide) where possible, especially in the PPU, and the comments quote from it often. Thanks to everyone who contributes to that wiki/forum, and to Michael Fogleman's [NES](https://github.com/fogleman/nes) and Scott Ferguson's [Fergulator](https://github.com/scottferg/Fergulator) for getting me unstuck at several points.
## Compilation and use
@ -48,8 +49,6 @@ The code aims to follow the explanations from the [NES dev wiki](https://wiki.ne
- Better GUI
- Better save state handling
- Player 2 controller?
## Known problem games

View File

@ -13,15 +13,15 @@ use apu::Apu;
use cartridge::get_mapper;
use input::poll_buttons;
use screen::{init_window, draw_pixel, draw_to_window};
use state::{save_state, load_state, change_file_extension, find_next_filename};
use state::{save_state, load_state, find_next_filename, find_last_filename};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::{Instant, Duration};
use sdl2::keyboard::Keycode;
use sdl2::EventPump;
use sdl2::event::Event;
use sdl2::pixels::PixelFormatEnum;
use std::path::Path;
// use cpuprofiler::PROFILER;
@ -47,6 +47,7 @@ fn main() -> Result<(), String> {
// Initialize hardware components
let filename = get_filename();
let filepath = Path::new(&filename).to_path_buf();
let mapper = get_mapper(filename.clone());
let ppu = Ppu::new(mapper.clone());
let apu = Apu::new();
@ -92,7 +93,7 @@ fn main() -> Result<(), String> {
std::thread::sleep(timer + Duration::from_millis(1000/60) - now);
}
timer = Instant::now();
if !process_events(&mut event_pump, &filename, &mut cpu) {
if !process_events(&mut event_pump, &filepath, &mut cpu) {
break 'running;
}
}
@ -121,27 +122,31 @@ fn get_filename() -> String {
argv[1].clone()
}
fn process_events(event_pump: &mut EventPump, filename: &str, cpu: &mut Cpu) -> bool {
fn process_events(event_pump: &mut EventPump, filepath: &PathBuf, cpu: &mut Cpu) -> bool {
for event in event_pump.poll_iter() {
match event {
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. }
=> return false,
Event::KeyDown{ keycode: Some(Keycode::F5), .. } => {
let dat_filename = change_file_extension(&filename, "dat").unwrap();
println!("{:?}", find_next_filename(&dat_filename));
let res: Result<(), String> = save_state(cpu, dat_filename)
let save_file = find_next_filename(filepath, Some("dat"))
.expect("could not generate save state filename");
let res: Result<(), String> = save_state(cpu, &save_file)
.or_else(|e| {println!("{}", e); Ok(())});
res.unwrap();
},
Event::KeyDown{ keycode: Some(Keycode::F9), .. } => {
let dat_filename = change_file_extension(&filename, "dat").unwrap();
let res: Result<(), String> = load_state(cpu, dat_filename)
.or_else(|e| {println!("{}", e); Ok(())});
res.unwrap();
match find_last_filename(filepath, Some("dat")) {
Some(p) => {
let res: Result<(), String> = load_state(cpu, &p)
.or_else(|e| { println!("{}", e); Ok(()) } );
res.unwrap();
},
None => println!("no save state found for {:?}", filepath)
}
},
Event::DropFile{ timestamp: _t, window_id: _w, filename: f } => {
let p = Path::new(&f).to_path_buf();
let res: Result<(), String> = load_state(cpu, p)
let res: Result<(), String> = load_state(cpu, &p)
.or_else(|e| {println!("{}", e); Ok(())});
res.unwrap();
},
@ -158,8 +163,7 @@ TODO:
- DMC audio channel
- untangle CPU and APU/PPU?
- GUI? drag and drop ROMs?
- reset function
- save states: multiple, search/select, and generalized "find file by different extension" functionality
- reset function/button
Timing notes:

View File

@ -14,7 +14,7 @@ struct SaveState {
apu: apu::serialize::ApuData,
}
pub fn save_state(cpu: &cpu::Cpu, save_file: PathBuf) -> Result<(), String> {
pub fn save_state(cpu: &cpu::Cpu, save_file: &PathBuf) -> Result<(), String> {
let data = SaveState{
cpu: cpu.save_state(),
ppu: cpu.ppu.save_state(),
@ -30,7 +30,7 @@ pub fn save_state(cpu: &cpu::Cpu, save_file: PathBuf) -> Result<(), String> {
Ok(())
}
pub fn load_state(cpu: &mut cpu::Cpu, save_file: PathBuf) -> Result<(), String> {
pub fn load_state(cpu: &mut cpu::Cpu, save_file: &PathBuf) -> Result<(), String> {
if Path::new(&save_file).exists() {
let mut f = File::open(save_file.clone())
.map_err(|e| e.to_string())?;
@ -51,18 +51,10 @@ pub fn load_state(cpu: &mut cpu::Cpu, save_file: PathBuf) -> Result<(), String>
}
}
pub fn change_file_extension(filename: &str, extension: &str) -> Option<PathBuf> {
let path = Path::new(filename).parent()?;
let stem = Path::new(&filename).file_stem()?;
let mut save_file = path.join(stem);
save_file.set_extension(extension);
Some(save_file)
}
pub fn find_next_filename(filepath: &PathBuf) -> Option<PathBuf> {
pub fn find_next_filename(filepath: &PathBuf, new_ext: Option<&str>) -> Option<PathBuf> {
let path = filepath.parent()?.to_str()?;
let stem = filepath.file_stem()?.to_str()?;
let ext = filepath.extension()?.to_str()?;
let ext = new_ext.or(Some(filepath.extension()?.to_str()?)).unwrap();
let sep = std::path::MAIN_SEPARATOR.to_string();
let mut i = 0;
loop {
@ -75,13 +67,24 @@ pub fn find_next_filename(filepath: &PathBuf) -> Option<PathBuf> {
}
}
pub fn find_last_filename(filepath: &PathBuf) -> Option<PathBuf> {
pub fn find_last_filename(filepath: &PathBuf, new_ext: Option<&str>) -> Option<PathBuf> {
let path = filepath.parent()?;
let stem = filepath.file_stem()?.to_str()?;
let ext = new_ext.or(Some(filepath.extension()?.to_str()?)).unwrap();
let files = std::fs::read_dir(path).expect("couldn't read directory");
let save_states = files.filter(|f| {
let n = f.unwrap().file_name().to_str().unwrap();
&n[..stem.len()] == stem && &n[n.len()-4..] == ".dat"
}).collect::<Result<Vec<_>, std::io::Error>>().unwrap();
let mut save_states = files
.map(|f| f.unwrap().path() )
.filter(|p| {
let pfs = p.file_name().unwrap().to_str().unwrap();
pfs.len() >= stem.len()
&& pfs.len() >= ext.len()
&& &pfs[..stem.len()] == stem
&& &pfs[pfs.len()-ext.len()..] == ext
})
.collect::<Vec<PathBuf>>();
save_states.sort();
match save_states.len() {
0 => None,
_ => Some(save_states[save_states.len()-1].clone()),
}
}