From e36bfd182150456d72626980ec266bdae533bd8a Mon Sep 17 00:00:00 2001 From: Theron Date: Mon, 2 Mar 2020 19:01:00 -0600 Subject: [PATCH] fixes save states for games with cartridge RAM. not the most elegant way to serialize mappers but straightforward. was hoping to downcast dyn Mapper trait object but couldn't get it to work. --- src/cartridge/cnrom.rs | 18 ++++++++++- src/cartridge/mmc1.rs | 40 ++++++++++++++++++++++- src/cartridge/mmc3.rs | 42 +++++++++++++++++++++++- src/cartridge/mod.rs | 8 +++-- src/cartridge/nrom.rs | 18 ++++++++++- src/cartridge/serialize.rs | 65 ++++++++++++++++++++++++++++++++++++++ src/cartridge/uxrom.rs | 20 +++++++++++- src/cpu/mod.rs | 64 ++++++++++++++++++------------------- src/main.rs | 11 +++++-- src/state.rs | 7 ++-- 10 files changed, 248 insertions(+), 45 deletions(-) create mode 100644 src/cartridge/serialize.rs diff --git a/src/cartridge/cnrom.rs b/src/cartridge/cnrom.rs index ce399e3..bc6a790 100644 --- a/src/cartridge/cnrom.rs +++ b/src/cartridge/cnrom.rs @@ -1,4 +1,4 @@ -use super::{Cartridge, Mapper, Mirror}; +use super::{Cartridge, Mapper, Mirror, serialize::*}; pub struct Cnrom { cart: Cartridge, @@ -41,4 +41,20 @@ impl Mapper for Cnrom { fn save_battery_backed_ram(&self) {} fn clock(&mut self) {} fn check_irq(&mut self) -> bool {false} + + fn save_state(&self) -> MapperData { + MapperData::Cnrom( + CnromData { + cart: self.cart.clone(), + chr_bank_select: self.chr_bank_select, + } + ) + } + + fn load_state(&mut self, mapper_data: MapperData) { + if let MapperData::Cnrom(cnrom_data) = mapper_data { + self.cart = cnrom_data.cart; + self.chr_bank_select = cnrom_data.chr_bank_select; + } + } } diff --git a/src/cartridge/mmc1.rs b/src/cartridge/mmc1.rs index 24a26a1..08e25ba 100644 --- a/src/cartridge/mmc1.rs +++ b/src/cartridge/mmc1.rs @@ -1,4 +1,4 @@ -use super::{Cartridge, Mapper, Mirror}; +use super::{Cartridge, Mapper, Mirror, serialize::*}; use std::fs::File; use std::io::{Read, Write}; @@ -210,4 +210,42 @@ impl Mapper for Mmc1 { fn clock(&mut self) {} fn check_irq(&mut self) -> bool {false} + + fn save_state(&self) -> MapperData { + MapperData::Mmc1( + Mmc1Data { + cart: self.cart.clone(), + step: self.step, + shift_register: self.shift_register, + mirroring: self.mirroring, + control: self.control, + prg_ram_bank: self.prg_ram_bank.clone(), + prg_ram_enabled: self.prg_ram_enabled, + prg_bank_mode: self.prg_bank_mode, + prg_bank_select: self.prg_bank_select, + chr_ram_bank: self.chr_ram_bank.clone(), + chr_low_bank: self.chr_low_bank, + chr_high_bank: self.chr_high_bank, + chr_bank_mode: self.chr_bank_mode, + } + ) + } + + fn load_state(&mut self, mapper_data: MapperData) { + if let MapperData::Mmc1(mmc1_data) = mapper_data { + self.cart = mmc1_data.cart; + self.step = mmc1_data.step; + self.shift_register = mmc1_data.shift_register; + self.mirroring = mmc1_data.mirroring; + self.control = mmc1_data.control; + self.prg_ram_bank = mmc1_data.prg_ram_bank; + self.prg_ram_enabled = mmc1_data.prg_ram_enabled; + self.prg_bank_mode = mmc1_data.prg_bank_mode; + self.prg_bank_select = mmc1_data.prg_bank_select; + self.chr_ram_bank = mmc1_data.chr_ram_bank; + self.chr_low_bank = mmc1_data.chr_low_bank; + self.chr_high_bank = mmc1_data.chr_high_bank; + self.chr_bank_mode = mmc1_data.chr_bank_mode; + } + } } diff --git a/src/cartridge/mmc3.rs b/src/cartridge/mmc3.rs index a0995ee..218651e 100644 --- a/src/cartridge/mmc3.rs +++ b/src/cartridge/mmc3.rs @@ -1,4 +1,4 @@ -use super::{Cartridge, Mapper, Mirror}; +use super::{Cartridge, Mapper, Mirror, serialize::*}; pub struct Mmc3 { cart: Cartridge, @@ -222,4 +222,44 @@ impl Mapper for Mmc3 { } false } + + fn save_state(&self) -> MapperData { + MapperData::Mmc3( + Mmc3Data { + cart: self.cart.clone(), + mirroring: self.mirroring, + bank_registers: self.bank_registers.clone(), + next_bank: self.next_bank, + irq_latch: self.irq_latch, + irq_counter: self.irq_counter, + irq_enable: self.irq_enable, + trigger_irq: self.trigger_irq, + reload_counter: self.reload_counter, + irq_delay: self.irq_delay, + prg_ram_bank: self.prg_ram_bank.clone(), + prg_rom_bank_mode: self.prg_rom_bank_mode, + chr_rom_bank_mode: self.chr_rom_bank_mode, + chr_ram_bank: self.chr_ram_bank.clone(), + } + ) + } + + fn load_state(&mut self, mapper_data: MapperData) { + if let MapperData::Mmc3(mmc3_data) = mapper_data { + self.cart = mmc3_data.cart; + self.mirroring = mmc3_data.mirroring; + self.bank_registers = mmc3_data.bank_registers; + self.next_bank = mmc3_data.next_bank; + self.irq_latch = mmc3_data.irq_latch; + self.irq_counter = mmc3_data.irq_counter; + self.irq_enable = mmc3_data.irq_enable; + self.trigger_irq = mmc3_data.trigger_irq; + self.reload_counter = mmc3_data.reload_counter; + self.irq_delay = mmc3_data.irq_delay; + self.prg_ram_bank = mmc3_data.prg_ram_bank; + self.prg_rom_bank_mode = mmc3_data.prg_rom_bank_mode; + self.chr_rom_bank_mode = mmc3_data.chr_rom_bank_mode; + self.chr_ram_bank = mmc3_data.chr_ram_bank; + } + } } diff --git a/src/cartridge/mod.rs b/src/cartridge/mod.rs index 1059c0c..c933ef9 100644 --- a/src/cartridge/mod.rs +++ b/src/cartridge/mod.rs @@ -3,6 +3,7 @@ mod mmc1; mod uxrom; mod cnrom; mod mmc3; +pub mod serialize; use nrom::Nrom; use mmc1::Mmc1; @@ -12,8 +13,6 @@ use mmc3::Mmc3; use std::cell::RefCell; use std::fs::File; -use std::io; -use std::io::prelude::*; use std::io::Read; use std::rc::Rc; @@ -25,9 +24,11 @@ pub trait Mapper { fn save_battery_backed_ram(&self); fn clock(&mut self); fn check_irq(&mut self) -> bool; + fn save_state(&self) -> serialize::MapperData; + fn load_state(&mut self, mapper_data: serialize::MapperData); } -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub enum Mirror { LowBank, HighBank, @@ -49,6 +50,7 @@ pub fn get_mapper(filename: String) -> Rc> { } } +#[derive(Clone, serde::Serialize, serde::Deserialize)] pub struct Cartridge { filename: String, prg_rom_size: usize, diff --git a/src/cartridge/nrom.rs b/src/cartridge/nrom.rs index 881cc18..4246e95 100644 --- a/src/cartridge/nrom.rs +++ b/src/cartridge/nrom.rs @@ -1,4 +1,4 @@ -use super::{Cartridge, Mapper, Mirror}; +use super::{Cartridge, Mapper, Mirror, serialize::*}; pub struct Nrom { cart: Cartridge, @@ -57,4 +57,20 @@ impl Mapper for Nrom { fn save_battery_backed_ram(&self) {} fn clock(&mut self) {} fn check_irq(&mut self) -> bool {false} + + fn save_state(&self) -> MapperData { + MapperData::Nrom( + NromData { + cart: self.cart.clone(), + chr_ram: self.chr_ram.clone(), + } + ) + } + + fn load_state(&mut self, mapper_data: MapperData) { + if let MapperData::Nrom(nrom_data) = mapper_data { + self.cart = nrom_data.cart; + self.chr_ram = nrom_data.chr_ram; + } + } } diff --git a/src/cartridge/serialize.rs b/src/cartridge/serialize.rs new file mode 100644 index 0000000..c00e1c8 --- /dev/null +++ b/src/cartridge/serialize.rs @@ -0,0 +1,65 @@ +use super::{Cartridge, Mirror}; + +#[derive(serde::Serialize, serde::Deserialize)] +pub enum MapperData { + Nrom(NromData), + Mmc1(Mmc1Data), + Uxrom(UxromData), + Cnrom(CnromData), + Mmc3(Mmc3Data), +} + + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct NromData { + pub cart: Cartridge, + pub chr_ram: Vec, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Mmc1Data { + pub cart: Cartridge, + pub step: u8, + pub shift_register: u8, + pub mirroring: Mirror, + pub control: u8, + pub prg_ram_bank: Vec, + pub prg_ram_enabled: bool, + pub prg_bank_mode: u8, + pub prg_bank_select: usize, + pub chr_ram_bank: Vec, + pub chr_low_bank: usize, + pub chr_high_bank: usize, + pub chr_bank_mode: bool, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct UxromData { + pub cart: Cartridge, + pub chr_ram: Vec, + pub bank_select: usize, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct CnromData { + pub cart: Cartridge, + pub chr_bank_select: usize, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Mmc3Data { + pub cart: Cartridge, + pub mirroring: Mirror, + pub bank_registers: Vec, + pub next_bank: u8, + pub irq_latch: u8, + pub irq_counter: u8, + pub irq_enable: bool, + pub trigger_irq: bool, + pub reload_counter: bool, + pub irq_delay: u8, + pub prg_ram_bank: Vec, + pub prg_rom_bank_mode: bool, + pub chr_rom_bank_mode: bool, + pub chr_ram_bank: Vec, +} diff --git a/src/cartridge/uxrom.rs b/src/cartridge/uxrom.rs index c660bda..1e38dd7 100644 --- a/src/cartridge/uxrom.rs +++ b/src/cartridge/uxrom.rs @@ -1,4 +1,4 @@ -use super::{Cartridge, Mapper, Mirror}; +use super::{Cartridge, Mapper, Mirror, serialize::*}; pub struct Uxrom { cart: Cartridge, @@ -52,4 +52,22 @@ impl Mapper for Uxrom { fn save_battery_backed_ram(&self) {} fn clock(&mut self) {} fn check_irq(&mut self) -> bool {false} + + fn save_state(&self) -> MapperData { + MapperData::Uxrom( + UxromData { + cart: self.cart.clone(), + chr_ram: self.chr_ram.clone(), + bank_select: self.bank_select, + } + ) + } + + fn load_state(&mut self, mapper_data: MapperData) { + if let MapperData::Uxrom(uxrom_data) = mapper_data { + self.cart = uxrom_data.cart; + self.chr_ram = uxrom_data.chr_ram; + self.bank_select = uxrom_data.bank_select; + } + } } diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 9a3252b..0862895 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -65,7 +65,7 @@ pub struct Cpu { clock: u64, // number of ticks in current cycle delay: usize, // for skipping cycles during OAM DMA - mapper: Rc>, // cartridge data + pub mapper: Rc>, // cartridge data pub ppu: super::Ppu, pub apu: super::Apu, @@ -301,36 +301,36 @@ $4020-$FFFF $BFE0 Cartridge space: PRG ROM, PRG RAM, and mapper registers (See // For debug output const _OPCODE_DISPLAY_NAMES: [&str; 256] = [ - "BRK", "ORA", "BAD", "SLO", "NOP", "ORA", "ASL", "SLO", - "PHP", "ORA", "ASL", "ANC", "NOP", "ORA", "ASL", "SLO", - "BPL", "ORA", "BAD", "SLO", "NOP", "ORA", "ASL", "SLO", - "CLC", "ORA", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO", - "JSR", "AND", "BAD", "RLA", "BIT", "AND", "ROL", "RLA", - "PLP", "AND", "ROL", "ANC", "BIT", "AND", "ROL", "RLA", - "BMI", "AND", "BAD", "RLA", "NOP", "AND", "ROL", "RLA", - "SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA", - "RTI", "EOR", "BAD", "SRE", "NOP", "EOR", "LSR", "SRE", - "PHA", "EOR", "LSR", "ALR", "JMP", "EOR", "LSR", "SRE", - "BVC", "EOR", "BAD", "SRE", "NOP", "EOR", "LSR", "SRE", - "CLI", "EOR", "NOP", "SRE", "NOP", "EOR", "LSR", "SRE", - "RTS", "ADC", "BAD", "RRA", "NOP", "ADC", "ROR", "RRA", - "PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA", - "BVS", "ADC", "BAD", "RRA", "NOP", "ADC", "ROR", "RRA", - "SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA", - "NOP", "STA", "NOP", "SAX", "STY", "STA", "STX", "SAX", - "DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX", "SAX", - "BCC", "STA", "BAD", "AHX", "STY", "STA", "STX", "SAX", - "TYA", "STA", "TXS", "TAS", "SHY", "STA", "SHX", "AHX", - "LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX", - "TAY", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX", - "BCS", "LDA", "BAD", "LAX", "LDY", "LDA", "LDX", "LAX", - "CLV", "LDA", "TSX", "LAS", "LDY", "LDA", "LDX", "LAX", - "CPY", "CMP", "NOP", "DCP", "CPY", "CMP", "DEC", "DCP", - "INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP", - "BNE", "CMP", "BAD", "DCP", "NOP", "CMP", "DEC", "DCP", - "CLD", "CMP", "NOP", "DCP", "NOP", "CMP", "DEC", "DCP", - "CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC", - "INX", "SBC", "NOP", "SBC", "CPX", "SBC", "INC", "ISC", - "BEQ", "SBC", "BAD", "ISC", "NOP", "SBC", "INC", "ISC", + "BRK", "ORA", "BAD", "SLO", "NOP", "ORA", "ASL", "SLO", + "PHP", "ORA", "ASL", "ANC", "NOP", "ORA", "ASL", "SLO", + "BPL", "ORA", "BAD", "SLO", "NOP", "ORA", "ASL", "SLO", + "CLC", "ORA", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO", + "JSR", "AND", "BAD", "RLA", "BIT", "AND", "ROL", "RLA", + "PLP", "AND", "ROL", "ANC", "BIT", "AND", "ROL", "RLA", + "BMI", "AND", "BAD", "RLA", "NOP", "AND", "ROL", "RLA", + "SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA", + "RTI", "EOR", "BAD", "SRE", "NOP", "EOR", "LSR", "SRE", + "PHA", "EOR", "LSR", "ALR", "JMP", "EOR", "LSR", "SRE", + "BVC", "EOR", "BAD", "SRE", "NOP", "EOR", "LSR", "SRE", + "CLI", "EOR", "NOP", "SRE", "NOP", "EOR", "LSR", "SRE", + "RTS", "ADC", "BAD", "RRA", "NOP", "ADC", "ROR", "RRA", + "PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA", + "BVS", "ADC", "BAD", "RRA", "NOP", "ADC", "ROR", "RRA", + "SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA", + "NOP", "STA", "NOP", "SAX", "STY", "STA", "STX", "SAX", + "DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX", "SAX", + "BCC", "STA", "BAD", "AHX", "STY", "STA", "STX", "SAX", + "TYA", "STA", "TXS", "TAS", "SHY", "STA", "SHX", "AHX", + "LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX", + "TAY", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX", + "BCS", "LDA", "BAD", "LAX", "LDY", "LDA", "LDX", "LAX", + "CLV", "LDA", "TSX", "LAS", "LDY", "LDA", "LDX", "LAX", + "CPY", "CMP", "NOP", "DCP", "CPY", "CMP", "DEC", "DCP", + "INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP", + "BNE", "CMP", "BAD", "DCP", "NOP", "CMP", "DEC", "DCP", + "CLD", "CMP", "NOP", "DCP", "NOP", "CMP", "DEC", "DCP", + "CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC", + "INX", "SBC", "NOP", "SBC", "CPX", "SBC", "INC", "ISC", + "BEQ", "SBC", "BAD", "ISC", "NOP", "SBC", "INC", "ISC", "SED", "SBC", "NOP", "ISC", "NOP", "SBC", "INC", "ISC", ]; diff --git a/src/main.rs b/src/main.rs index 9dfd272..aef22ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,8 +63,13 @@ fn main() -> Result<(), String> { Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => return Ok(()), Event::DropFile{ filename: f, .. } => { - name = f; - break 'waiting; + match check_signature(&f) { + Ok(()) => { + name = f; + break 'waiting; + }, + Err(e) => println!("{}", e), + } }, _ => (), // println!("event: {:?}", event), } @@ -210,7 +215,7 @@ fn process_events(event_pump: &mut EventPump, filepath: &PathBuf, cpu: &mut Cpu) res.unwrap(); // } else if f.len() > 4 && &f[f.len()-4..] == ".nes" { } else { - match cartridge::check_signature(&f) { + match check_signature(&f) { Ok(()) => return GameExitMode::NewGame(f), Err(e) => println!("{}", e), } diff --git a/src/state.rs b/src/state.rs index be794ce..5106f13 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,17 +1,18 @@ use super::cpu; use super::ppu; use super::apu; +use super::cartridge; use std::fs::File; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; -use serde::{Serialize, Deserialize}; -#[derive(Serialize, Deserialize)] +#[derive(serde::Serialize, serde::Deserialize)] struct SaveState { cpu: cpu::serialize::CpuData, ppu: ppu::serialize::PpuData, apu: apu::serialize::ApuData, + mapper: cartridge::serialize::MapperData, } pub fn save_state(cpu: &cpu::Cpu, save_file: &PathBuf) -> Result<(), String> { @@ -19,6 +20,7 @@ pub fn save_state(cpu: &cpu::Cpu, save_file: &PathBuf) -> Result<(), String> { cpu: cpu.save_state(), ppu: cpu.ppu.save_state(), apu: cpu.apu.save_state(), + mapper: cpu.mapper.borrow().save_state(), }; let serialized = serde_json::to_string(&data) .map_err(|e| e.to_string())?; @@ -44,6 +46,7 @@ pub fn load_state(cpu: &mut cpu::Cpu, save_file: &PathBuf) -> Result<(), String> cpu.load_state(state.cpu); cpu.ppu.load_state(state.ppu); cpu.apu.load_state(state.apu); + cpu.mapper.borrow_mut().load_state(state.mapper); println!("loading save state from file: {:?}", save_file); Ok(()) } else {