save states are working on the first try! crude, only one at a time, and probably needing some cleanup, but working.
This commit is contained in:
parent
531240e94e
commit
7f5a71cf07
|
@ -7,6 +7,7 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sdl2 = { version = "0.33", features = ["bundled", "static-link"] }
|
sdl2 = { version = "0.33", features = ["bundled", "static-link"] }
|
||||||
serde = { version = "1.0.104", features = ["derive"] }
|
serde = { version = "1.0.104", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
cpuprofiler = "0.0.3"
|
cpuprofiler = "0.0.3"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
@ -21,6 +21,9 @@ ___________________
|
||||||
| Left | Left |
|
| Left | Left |
|
||||||
| Right | Right |
|
| Right | Right |
|
||||||
-------------------
|
-------------------
|
||||||
|
Save state: F5
|
||||||
|
Load state: F9
|
||||||
|
(Only one save state at a time supported currently: saving a second time overwrites the first.)
|
||||||
```
|
```
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
pub struct DMC {
|
pub struct DMC {
|
||||||
pub sample: u16,
|
pub sample: u16,
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
pub struct Envelope {
|
pub struct Envelope {
|
||||||
pub period: u16, // constant volume/envelope period
|
pub period: u16, // constant volume/envelope period
|
||||||
divider: u16,
|
divider: u16,
|
||||||
|
|
|
@ -3,6 +3,7 @@ mod square;
|
||||||
mod triangle;
|
mod triangle;
|
||||||
mod dmc;
|
mod dmc;
|
||||||
mod envelope;
|
mod envelope;
|
||||||
|
pub mod serialize;
|
||||||
|
|
||||||
use noise::Noise;
|
use noise::Noise;
|
||||||
use square::Square;
|
use square::Square;
|
||||||
|
@ -19,6 +20,7 @@ const LENGTH_COUNTER_TABLE: [u8; 32] = [
|
||||||
12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30,
|
12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
pub struct Apu {
|
pub struct Apu {
|
||||||
square1: Square,
|
square1: Square,
|
||||||
square2: Square,
|
square2: Square,
|
||||||
|
|
|
@ -4,6 +4,7 @@ const NOISE_TABLE: [u16; 16] = [4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 5
|
||||||
|
|
||||||
// $400E M---.PPPP Mode and period (write)
|
// $400E M---.PPPP Mode and period (write)
|
||||||
// bit 7 M--- ---- Mode flag
|
// bit 7 M--- ---- Mode flag
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
pub struct Noise {
|
pub struct Noise {
|
||||||
pub sample: u16, // output value that gets sent to the mixer
|
pub sample: u16, // output value that gets sent to the mixer
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
pub type ApuData = super::Apu;
|
||||||
|
|
||||||
|
impl super::Apu{
|
||||||
|
pub fn save_state(&self) -> ApuData {
|
||||||
|
let x: ApuData = self.clone();
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_state(&mut self, data: ApuData) {
|
||||||
|
self.square1 = data.square1;
|
||||||
|
self.square2 = data.square2;
|
||||||
|
self.triangle = data.triangle;
|
||||||
|
self.noise = data.noise;
|
||||||
|
self.dmc = data.dmc;
|
||||||
|
self.square_table = data.square_table;
|
||||||
|
self.tnd_table = data.tnd_table;
|
||||||
|
self.frame_sequence = data.frame_sequence;
|
||||||
|
self.frame_counter = data.frame_counter;
|
||||||
|
self.interrupt_inhibit = data.interrupt_inhibit;
|
||||||
|
self.frame_interrupt = data.frame_interrupt;
|
||||||
|
self.cycle = data.cycle;
|
||||||
|
self.trigger_irq = data.trigger_irq;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ const DUTY_CYCLE_SEQUENCES: [[u8; 8]; 4] = [
|
||||||
[1, 0, 0, 1, 1, 1, 1, 1],
|
[1, 0, 0, 1, 1, 1, 1, 1],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
pub struct Square {
|
pub struct Square {
|
||||||
pub sample: u16, // output value that gets sent to the mixer
|
pub sample: u16, // output value that gets sent to the mixer
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
|
|
@ -3,6 +3,7 @@ const WAVEFORM: [u16; 32] = [
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
pub struct Triangle {
|
pub struct Triangle {
|
||||||
pub sample: u16,
|
pub sample: u16,
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
|
|
@ -33,8 +33,8 @@ pub enum Mirror {
|
||||||
FourScreen,
|
FourScreen,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mapper() -> Rc<RefCell<dyn Mapper>> {
|
pub fn get_mapper(filename: String) -> Rc<RefCell<dyn Mapper>> {
|
||||||
let cart = Cartridge::new();
|
let cart = Cartridge::new(filename);
|
||||||
let num = cart.mapper_num;
|
let num = cart.mapper_num;
|
||||||
match num {
|
match num {
|
||||||
0 => Rc::new(RefCell::new(Nrom::new(cart))),
|
0 => Rc::new(RefCell::new(Nrom::new(cart))),
|
||||||
|
@ -64,11 +64,8 @@ pub struct Cartridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cartridge {
|
impl Cartridge {
|
||||||
pub fn new() -> Self {
|
pub fn new(filename: String) -> Self {
|
||||||
let argv: Vec<String> = std::env::args().collect();
|
let mut f = std::fs::File::open(&filename).expect("could not open {}");
|
||||||
assert!(argv.len() > 1, "must include .nes ROM as argument");
|
|
||||||
let filename = &argv[1];
|
|
||||||
let mut f = std::fs::File::open(filename).expect("could not open {}");
|
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
f.read_to_end(&mut data).unwrap();
|
f.read_to_end(&mut data).unwrap();
|
||||||
assert!(data[0..4] == [0x4E, 0x45, 0x53, 0x1A], "signature mismatch, not an iNES file");
|
assert!(data[0..4] == [0x4E, 0x45, 0x53, 0x1A], "signature mismatch, not an iNES file");
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
mod addressing_modes;
|
mod addressing_modes;
|
||||||
mod opcodes;
|
mod opcodes;
|
||||||
mod utility;
|
mod utility;
|
||||||
|
pub mod serialize;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use serde::ser::{Serializer, SerializeStruct};
|
|
||||||
use crate::cartridge::Mapper;
|
use crate::cartridge::Mapper;
|
||||||
|
|
||||||
// RAM locations
|
// RAM locations
|
||||||
|
@ -53,7 +53,6 @@ impl Mode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Cpu {
|
pub struct Cpu {
|
||||||
mem: Vec<u8>, // CPU's RAM, $0000-$1FFF
|
mem: Vec<u8>, // CPU's RAM, $0000-$1FFF
|
||||||
A: u8, // accumulator
|
A: u8, // accumulator
|
||||||
|
@ -66,7 +65,6 @@ pub struct Cpu {
|
||||||
clock: u64, // number of ticks in current cycle
|
clock: u64, // number of ticks in current cycle
|
||||||
delay: usize, // for skipping cycles during OAM DMA
|
delay: usize, // for skipping cycles during OAM DMA
|
||||||
|
|
||||||
// #[serde(skip_serializing)]
|
|
||||||
mapper: Rc<RefCell<dyn Mapper>>, // cartridge data
|
mapper: Rc<RefCell<dyn Mapper>>, // cartridge data
|
||||||
pub ppu: super::Ppu,
|
pub ppu: super::Ppu,
|
||||||
pub apu: super::Apu,
|
pub apu: super::Apu,
|
||||||
|
@ -288,30 +286,6 @@ impl Cpu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Serialize for Cpu {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let mut state = serializer.serialize_struct("Cpu", 13)?;
|
|
||||||
state.serialize_field("mem", &self.mem)?;
|
|
||||||
state.serialize_field("A", &self.A)?;
|
|
||||||
state.serialize_field("X", &self.X)?;
|
|
||||||
state.serialize_field("Y", &self.Y)?;
|
|
||||||
state.serialize_field("PC", &self.PC)?;
|
|
||||||
state.serialize_field("S", &self.S)?;
|
|
||||||
state.serialize_field("P", &self.P)?;
|
|
||||||
state.serialize_field("clock", &self.clock)?;
|
|
||||||
state.serialize_field("delay", &self.delay)?;
|
|
||||||
state.serialize_field("strobe", &self.strobe)?;
|
|
||||||
state.serialize_field("button_states", &self.button_states)?;
|
|
||||||
state.serialize_field("button_number", &self.button_number)?;
|
|
||||||
state.serialize_field("mode_table", &self.mode_table)?;
|
|
||||||
state.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Address range Size Device
|
Address range Size Device
|
||||||
$0000-$07FF $0800 2KB internal RAM
|
$0000-$07FF $0800 2KB internal RAM
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
use super::Mode;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct CpuData {
|
||||||
|
mem: Vec<u8>,
|
||||||
|
A: u8,
|
||||||
|
X: u8,
|
||||||
|
Y: u8,
|
||||||
|
PC: usize,
|
||||||
|
S: u8,
|
||||||
|
P: u8,
|
||||||
|
clock: u64,
|
||||||
|
delay: usize,
|
||||||
|
strobe: u8,
|
||||||
|
button_states: u8,
|
||||||
|
button_number: u8,
|
||||||
|
mode_table: Vec<Mode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Cpu {
|
||||||
|
pub fn save_state(&self) -> CpuData {
|
||||||
|
CpuData{
|
||||||
|
mem: self.mem.clone(),
|
||||||
|
A: self.A,
|
||||||
|
X: self.X,
|
||||||
|
Y: self.Y,
|
||||||
|
PC: self.PC,
|
||||||
|
S: self.S,
|
||||||
|
P: self.P,
|
||||||
|
clock: self.clock,
|
||||||
|
delay: self.delay,
|
||||||
|
strobe: self.strobe,
|
||||||
|
button_states: self.button_states,
|
||||||
|
button_number: self.button_number,
|
||||||
|
mode_table: self.mode_table.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_state(&mut self, data: CpuData) {
|
||||||
|
self.mem = data.mem;
|
||||||
|
self.A = data.A;
|
||||||
|
self.X = data.X;
|
||||||
|
self.Y = data.Y;
|
||||||
|
self.PC = data.PC;
|
||||||
|
self.S = data.S;
|
||||||
|
self.P = data.P;
|
||||||
|
self.clock = data.clock;
|
||||||
|
self.delay = data.delay;
|
||||||
|
self.strobe = data.strobe;
|
||||||
|
self.button_states = data.button_states;
|
||||||
|
self.button_number = data.button_number;
|
||||||
|
self.mode_table = data.mode_table;
|
||||||
|
}
|
||||||
|
}
|
23
src/main.rs
23
src/main.rs
|
@ -5,6 +5,7 @@ mod cartridge;
|
||||||
mod input;
|
mod input;
|
||||||
mod screen;
|
mod screen;
|
||||||
mod audio;
|
mod audio;
|
||||||
|
mod state;
|
||||||
|
|
||||||
use cpu::Cpu;
|
use cpu::Cpu;
|
||||||
use ppu::Ppu;
|
use ppu::Ppu;
|
||||||
|
@ -12,6 +13,7 @@ use apu::Apu;
|
||||||
use cartridge::get_mapper;
|
use cartridge::get_mapper;
|
||||||
use input::poll_buttons;
|
use input::poll_buttons;
|
||||||
use screen::{init_window, draw_pixel, draw_to_window};
|
use screen::{init_window, draw_pixel, draw_to_window};
|
||||||
|
use state::{save_state, load_state};
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::{Instant, Duration};
|
use std::time::{Instant, Duration};
|
||||||
|
@ -42,7 +44,8 @@ fn main() -> Result<(), String> {
|
||||||
audio_device.resume();
|
audio_device.resume();
|
||||||
|
|
||||||
// Initialize hardware components
|
// Initialize hardware components
|
||||||
let mapper = get_mapper();
|
let filename = get_filename();
|
||||||
|
let mapper = get_mapper(filename.clone());
|
||||||
let ppu = Ppu::new(mapper.clone());
|
let ppu = Ppu::new(mapper.clone());
|
||||||
let apu = Apu::new();
|
let apu = Apu::new();
|
||||||
let mut cpu = Cpu::new(mapper.clone(), ppu, apu);
|
let mut cpu = Cpu::new(mapper.clone(), ppu, apu);
|
||||||
|
@ -92,6 +95,16 @@ fn main() -> Result<(), String> {
|
||||||
match event {
|
match event {
|
||||||
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. }
|
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. }
|
||||||
=> break 'running,
|
=> break 'running,
|
||||||
|
Event::KeyDown{ keycode: Some(Keycode::F5), .. } => {
|
||||||
|
let res: Result<(), String> = save_state(&cpu, &filename)
|
||||||
|
.or_else(|e| {println!("{}", e); Ok(())});
|
||||||
|
res.unwrap();
|
||||||
|
},
|
||||||
|
Event::KeyDown{ keycode: Some(Keycode::F9), .. } => {
|
||||||
|
let res: Result<(), String> = load_state(&mut cpu, &filename)
|
||||||
|
.or_else(|e| {println!("{}", e); Ok(())});
|
||||||
|
res.unwrap();
|
||||||
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,6 +128,12 @@ fn main() -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_filename() -> String {
|
||||||
|
let argv: Vec<String> = std::env::args().collect();
|
||||||
|
assert!(argv.len() > 1, "must include .nes ROM as argument");
|
||||||
|
argv[1].clone()
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
|
@ -123,7 +142,7 @@ TODO:
|
||||||
- untangle CPU and APU/PPU?
|
- untangle CPU and APU/PPU?
|
||||||
- GUI? drag and drop ROMs?
|
- GUI? drag and drop ROMs?
|
||||||
- reset function
|
- reset function
|
||||||
- save/load/pause functionality
|
- save states: multiple, search/select, and generalized "find file by different extension" functionality
|
||||||
|
|
||||||
|
|
||||||
Timing notes:
|
Timing notes:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
mod cpu_registers;
|
mod cpu_registers;
|
||||||
mod rendering;
|
mod rendering;
|
||||||
mod memory;
|
mod memory;
|
||||||
|
pub mod serialize;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct PpuData {
|
||||||
|
line_cycle: usize,
|
||||||
|
scanline: usize,
|
||||||
|
frame: usize,
|
||||||
|
v: u16,
|
||||||
|
t: u16,
|
||||||
|
x: u8,
|
||||||
|
w: u8,
|
||||||
|
nametable_A: Vec<u8>,
|
||||||
|
nametable_B: Vec<u8>,
|
||||||
|
nametable_C: Vec<u8>,
|
||||||
|
nametable_D: Vec<u8>,
|
||||||
|
palette_ram: Vec<u8>,
|
||||||
|
background_pattern_sr_low: u16,
|
||||||
|
background_pattern_sr_high: u16,
|
||||||
|
nametable_byte: u8,
|
||||||
|
attribute_table_byte: u8,
|
||||||
|
low_pattern_table_byte: u8,
|
||||||
|
high_pattern_table_byte: u8,
|
||||||
|
background_palette_sr_low: u8,
|
||||||
|
background_palette_sr_high: u8,
|
||||||
|
background_palette_latch: u8,
|
||||||
|
primary_oam: Vec<u8>,
|
||||||
|
secondary_oam: Vec<u8>,
|
||||||
|
sprite_attribute_latches: Vec<u8>,
|
||||||
|
sprite_counters: Vec<u8>,
|
||||||
|
sprite_indexes: Vec<u8>,
|
||||||
|
sprite_pattern_table_srs: Vec<(u8, u8)>,
|
||||||
|
num_sprites: usize,
|
||||||
|
address_increment: u16,
|
||||||
|
sprite_pattern_table_base: usize,
|
||||||
|
background_pattern_table_base:usize,
|
||||||
|
oam_address: usize,
|
||||||
|
sprite_size: u8,
|
||||||
|
grayscale: bool,
|
||||||
|
show_background_left: bool,
|
||||||
|
show_sprites_left: bool,
|
||||||
|
show_background: bool,
|
||||||
|
show_sprites: bool,
|
||||||
|
emphasize_red: bool,
|
||||||
|
emphasize_green: bool,
|
||||||
|
emphasize_blue: bool,
|
||||||
|
sprite_overflow: bool,
|
||||||
|
sprite_zero_hit: bool,
|
||||||
|
should_generate_nmi: bool,
|
||||||
|
vertical_blank: bool,
|
||||||
|
trigger_nmi: bool,
|
||||||
|
previous_nmi: bool,
|
||||||
|
nmi_delay: usize,
|
||||||
|
read_buffer: u8,
|
||||||
|
recent_bits: u8,
|
||||||
|
previous_a12: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Ppu {
|
||||||
|
pub fn save_state(&self) -> PpuData {
|
||||||
|
PpuData{
|
||||||
|
line_cycle: self.line_cycle,
|
||||||
|
scanline: self.scanline,
|
||||||
|
frame: self.frame,
|
||||||
|
v: self.v,
|
||||||
|
t: self.t,
|
||||||
|
x: self.x,
|
||||||
|
w: self.w,
|
||||||
|
nametable_A: self.nametable_A.clone(),
|
||||||
|
nametable_B: self.nametable_B.clone(),
|
||||||
|
nametable_C: self.nametable_C.clone(),
|
||||||
|
nametable_D: self.nametable_D.clone(),
|
||||||
|
palette_ram: self.palette_ram.clone(),
|
||||||
|
background_pattern_sr_low: self.background_pattern_sr_low,
|
||||||
|
background_pattern_sr_high: self.background_pattern_sr_high,
|
||||||
|
nametable_byte: self.nametable_byte,
|
||||||
|
attribute_table_byte: self.attribute_table_byte,
|
||||||
|
low_pattern_table_byte: self.low_pattern_table_byte,
|
||||||
|
high_pattern_table_byte: self.high_pattern_table_byte,
|
||||||
|
background_palette_sr_low: self.background_palette_sr_low,
|
||||||
|
background_palette_sr_high: self.background_palette_sr_high,
|
||||||
|
background_palette_latch: self.background_palette_latch,
|
||||||
|
primary_oam: self.primary_oam.clone(),
|
||||||
|
secondary_oam: self.secondary_oam.clone(),
|
||||||
|
sprite_attribute_latches: self.sprite_attribute_latches.clone(),
|
||||||
|
sprite_counters: self.sprite_counters.clone(),
|
||||||
|
sprite_indexes: self.sprite_indexes.clone(),
|
||||||
|
sprite_pattern_table_srs: self.sprite_pattern_table_srs.clone(),
|
||||||
|
num_sprites: self.num_sprites,
|
||||||
|
address_increment: self.address_increment,
|
||||||
|
sprite_pattern_table_base: self.sprite_pattern_table_base,
|
||||||
|
background_pattern_table_base: self.background_pattern_table_base,
|
||||||
|
oam_address: self.oam_address,
|
||||||
|
sprite_size: self.sprite_size,
|
||||||
|
grayscale: self.grayscale,
|
||||||
|
show_background_left: self.show_background_left,
|
||||||
|
show_sprites_left: self.show_sprites_left,
|
||||||
|
show_background: self.show_background,
|
||||||
|
show_sprites: self.show_sprites,
|
||||||
|
emphasize_red: self.emphasize_red,
|
||||||
|
emphasize_green: self.emphasize_green,
|
||||||
|
emphasize_blue: self.emphasize_blue,
|
||||||
|
sprite_overflow: self.sprite_overflow,
|
||||||
|
sprite_zero_hit: self.sprite_zero_hit,
|
||||||
|
should_generate_nmi: self.should_generate_nmi,
|
||||||
|
vertical_blank: self.vertical_blank,
|
||||||
|
trigger_nmi: self.trigger_nmi,
|
||||||
|
previous_nmi: self.previous_nmi,
|
||||||
|
nmi_delay: self.nmi_delay,
|
||||||
|
read_buffer: self.read_buffer,
|
||||||
|
recent_bits: self.recent_bits,
|
||||||
|
previous_a12: self.previous_a12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_state(&mut self, data: PpuData) {
|
||||||
|
self.line_cycle = data.line_cycle;
|
||||||
|
self.scanline = data.scanline;
|
||||||
|
self.frame = data.frame;
|
||||||
|
self.v = data.v;
|
||||||
|
self.t = data.t;
|
||||||
|
self.x = data.x;
|
||||||
|
self.w = data.w;
|
||||||
|
self.nametable_A = data.nametable_A;
|
||||||
|
self.nametable_B = data.nametable_B;
|
||||||
|
self.nametable_C = data.nametable_C;
|
||||||
|
self.nametable_D = data.nametable_D;
|
||||||
|
self.palette_ram = data.palette_ram;
|
||||||
|
self.background_pattern_sr_low = data.background_pattern_sr_low;
|
||||||
|
self.background_pattern_sr_high = data.background_pattern_sr_high;
|
||||||
|
self.nametable_byte = data.nametable_byte;
|
||||||
|
self.attribute_table_byte = data.attribute_table_byte;
|
||||||
|
self.low_pattern_table_byte = data.low_pattern_table_byte;
|
||||||
|
self.high_pattern_table_byte = data.high_pattern_table_byte;
|
||||||
|
self.background_palette_sr_low = data.background_palette_sr_low;
|
||||||
|
self.background_palette_sr_high = data.background_palette_sr_high;
|
||||||
|
self.background_palette_latch = data.background_palette_latch;
|
||||||
|
self.primary_oam = data.primary_oam;
|
||||||
|
self.secondary_oam = data.secondary_oam;
|
||||||
|
self.sprite_attribute_latches = data.sprite_attribute_latches;
|
||||||
|
self.sprite_counters = data.sprite_counters;
|
||||||
|
self.sprite_indexes = data.sprite_indexes;
|
||||||
|
self.sprite_pattern_table_srs = data.sprite_pattern_table_srs;
|
||||||
|
self.num_sprites = data.num_sprites;
|
||||||
|
self.address_increment = data.address_increment;
|
||||||
|
self.sprite_pattern_table_base = data.sprite_pattern_table_base;
|
||||||
|
self.background_pattern_table_base = data.background_pattern_table_base;
|
||||||
|
self.oam_address = data.oam_address;
|
||||||
|
self.sprite_size = data.sprite_size;
|
||||||
|
self.grayscale = data.grayscale;
|
||||||
|
self.show_background_left = data.show_background_left;
|
||||||
|
self.show_sprites_left = data.show_sprites_left;
|
||||||
|
self.show_background = data.show_background;
|
||||||
|
self.show_sprites = data.show_sprites;
|
||||||
|
self.emphasize_red = data.emphasize_red;
|
||||||
|
self.emphasize_green = data.emphasize_green;
|
||||||
|
self.emphasize_blue = data.emphasize_blue;
|
||||||
|
self.sprite_overflow = data.sprite_overflow;
|
||||||
|
self.sprite_zero_hit = data.sprite_zero_hit;
|
||||||
|
self.should_generate_nmi = data.should_generate_nmi;
|
||||||
|
self.vertical_blank = data.vertical_blank;
|
||||||
|
self.trigger_nmi = data.trigger_nmi;
|
||||||
|
self.previous_nmi = data.previous_nmi;
|
||||||
|
self.nmi_delay = data.nmi_delay;
|
||||||
|
self.read_buffer = data.read_buffer;
|
||||||
|
self.recent_bits = data.recent_bits;
|
||||||
|
self.previous_a12 = data.previous_a12;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
use super::cpu;
|
||||||
|
use super::ppu;
|
||||||
|
use super::apu;
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct SaveState {
|
||||||
|
cpu: cpu::serialize::CpuData,
|
||||||
|
ppu: ppu::serialize::PpuData,
|
||||||
|
apu: apu::serialize::ApuData,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_state(cpu: &cpu::Cpu, filename: &str) -> Result<(), String> {
|
||||||
|
let data = SaveState{
|
||||||
|
cpu: cpu.save_state(),
|
||||||
|
ppu: cpu.ppu.save_state(),
|
||||||
|
apu: cpu.apu.save_state(),
|
||||||
|
};
|
||||||
|
let serialized = serde_json::to_string(&data)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let path = match Path::new(&filename).parent() {
|
||||||
|
Some(p) => p,
|
||||||
|
None => return Err("couldn't convert filename to path".to_string()),
|
||||||
|
};
|
||||||
|
let stem = match Path::new(&filename).file_stem() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Err("couldn't get file stem".to_string()),
|
||||||
|
};
|
||||||
|
let mut save_file = path.join(stem);
|
||||||
|
save_file.set_extension("dat");
|
||||||
|
println!("state saved to file: {:?}", save_file);
|
||||||
|
let mut f = File::create(&save_file)
|
||||||
|
.expect("could not create output file for save state");
|
||||||
|
f.write_all(serialized.as_bytes())
|
||||||
|
.map_err(|_| "couldn't write serialized data to file".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_state(cpu: &mut cpu::Cpu, filename: &str) -> Result<(), String> {
|
||||||
|
// load file, deserialize to cpudata, set cpu fields to data fields
|
||||||
|
let path = match Path::new(&filename).parent() {
|
||||||
|
Some(p) => p,
|
||||||
|
None => return Err("couldn't convert filename to path".to_string()),
|
||||||
|
};
|
||||||
|
let stem = match Path::new(&filename).file_stem() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Err("couldn't get file stem".to_string()),
|
||||||
|
};
|
||||||
|
let mut save_file = path.join(stem);
|
||||||
|
save_file.set_extension("dat");
|
||||||
|
|
||||||
|
if Path::new(&save_file).exists() {
|
||||||
|
let mut f = File::open(save_file.clone())
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let mut serialized_data = vec![];
|
||||||
|
f.read_to_end(&mut serialized_data)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let serialized_string = std::str::from_utf8(&serialized_data)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let state: SaveState = serde_json::from_str(serialized_string)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
cpu.load_state(state.cpu);
|
||||||
|
cpu.ppu.load_state(state.ppu);
|
||||||
|
cpu.apu.load_state(state.apu);
|
||||||
|
println!("loading save state from file: {:?}", save_file);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("no save state file at {:?}", save_file))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue