some save work

This commit is contained in:
Alex Janka 2023-05-15 13:30:29 +10:00
parent 85f938a45b
commit bf4b8d4caa
10 changed files with 362 additions and 177 deletions

View file

@ -7,18 +7,16 @@ use gb_emu_lib::{
},
EmulatorCore,
};
use nih_plug::midi::MidiResult::Basic;
use nih_plug::prelude::*;
use nih_plug::{midi::MidiResult::Basic, params::persist::PersistentField};
use std::sync::{
mpsc::{self, channel, Receiver, Sender},
Arc, Mutex,
Arc, Mutex, RwLock,
};
use ui::{Emulator, EmulatorRenderer};
#[cfg(feature = "savestate")]
use gb_emu_lib::connect::CpuSaveState;
#[cfg(feature = "savestate")]
use nih_plug::params::persist::PersistentField;
mod ui;
@ -42,11 +40,31 @@ impl PersistentField<'_, Option<CpuSaveState<[u8; 4]>>> for SaveStateParam {
}
}
#[derive(Default)]
struct SramParam {
state: Arc<RwLock<Vec<u8>>>,
}
impl PersistentField<'_, Vec<u8>> for SramParam {
fn set(&self, new_value: Vec<u8>) {
*self.state.write().unwrap() = new_value;
}
fn map<F, R>(&self, f: F) -> R
where
F: Fn(&Vec<u8>) -> R,
{
f(&self.state.read().unwrap())
}
}
#[derive(Params, Default)]
struct EmuParams {
#[cfg(feature = "savestate")]
#[persist = "save_state"]
last_save_state: SaveStateParam,
#[persist = "sram"]
sram_save: SramParam,
// #[cfg(feature = "savestate")]
// #[persist = "save_state"]
// last_save_state: SaveStateParam,
}
struct EmuVars {
@ -70,7 +88,7 @@ type JoypadSender = Mutex<Option<Sender<(JoypadButtons, bool)>>>;
const BUFFERS_PER_FRAME: usize = 1;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::Linear;
const ROM: &[u8; 65536] = include_bytes!("../../test-roms/mGB.gb");
const ROM: &[u8; 65536] = include_bytes!("../../test-roms/mGB-save-patch.gb");
impl Plugin for GameboyEmu {
const NAME: &'static str = "Gameboy";
@ -262,7 +280,7 @@ impl Plugin for GameboyEmu {
let mut emulator_core = {
let options = EmulatorOptions::new(window, rom, output)
.with_serial_target(serial_target)
.force_no_save();
.with_sram_buffer(self.params.sram_save.state.clone());
EmulatorCore::init(receiver, options, NoCamera::default())
};

View file

@ -1,5 +1,5 @@
use std::marker::PhantomData;
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Mutex, RwLock};
pub use crate::processor::memory::mmio::gpu::Colour;
pub use crate::processor::memory::mmio::joypad::{JoypadButtons, JoypadState};
@ -151,6 +151,12 @@ where
}
}
#[derive(Debug)]
pub enum SramType {
File(String),
RawBuffer(Arc<RwLock<Vec<u8>>>),
}
#[non_exhaustive]
pub struct EmulatorOptions<ColourFormat, R>
where
@ -161,7 +167,7 @@ where
pub(crate) tile_window: Option<R>,
pub(crate) rom: RomFile,
pub(crate) output: AudioOutput,
pub(crate) save_path: Option<String>,
pub(crate) save: Option<SramType>,
pub(crate) no_save: bool,
pub(crate) bootrom: Option<RomFile>,
@ -182,7 +188,7 @@ where
tile_window: None,
rom,
output,
save_path: None,
save: None,
no_save: false,
bootrom: None,
show_bootrom: false,
@ -193,7 +199,12 @@ where
}
pub fn with_save_path(mut self, path: Option<String>) -> Self {
self.save_path = path;
self.save = path.map(SramType::File);
self
}
pub fn with_sram_buffer(mut self, buffer: Arc<RwLock<Vec<u8>>>) -> Self {
self.save = Some(SramType::RawBuffer(buffer));
self
}

View file

@ -6,10 +6,14 @@ use crate::{
};
use connect::{
AudioOutput, CameraWrapper, CameraWrapperRef, EmulatorCoreTrait, EmulatorMessage,
EmulatorOptions, NoCamera, PocketCamera, Renderer, RomFile, SerialTarget,
EmulatorOptions, NoCamera, PocketCamera, Renderer, RomFile, SerialTarget, SramType,
};
use processor::{
memory::{mmio::gpu::Colour, rom::CgbRomType, OutputTargets, Rom},
memory::{
mmio::gpu::Colour,
rom::{sram_save::SaveDataLocation, CgbRomType},
OutputTargets, Rom,
},
Cpu, CpuSaveState,
};
use std::{
@ -54,16 +58,21 @@ where
) -> Self {
let camera: CameraWrapperRef<C> = Arc::new(Mutex::new(CameraWrapper::new(camera)));
let maybe_save = options.save.map(|i| match i {
SramType::File(sram_path) => {
SaveDataLocation::File(PathBuf::from_str(&sram_path).unwrap())
}
SramType::RawBuffer(buffer) => SaveDataLocation::Raw(buffer),
});
let rom = match options.rom {
RomFile::Path(path) => {
let maybe_save = if options.no_save {
None
} else {
Some(if let Some(path) = options.save_path {
PathBuf::from_str(&path).unwrap()
} else {
PathBuf::from_str(&path).unwrap().with_extension("sav")
})
Some(maybe_save.unwrap_or(SaveDataLocation::File(
PathBuf::from_str(&path).unwrap().with_extension("sav"),
)))
};
match fs::read(path) {
@ -74,7 +83,7 @@ where
}
}
}
RomFile::Raw(data) => Rom::load(data, None, camera.clone()),
RomFile::Raw(data) => Rom::load(data, maybe_save, camera.clone()),
};
let (bootrom, cgb) = if let Some(b) = options.bootrom {

View file

@ -1,127 +1,18 @@
use self::{
mbcs::{
Mbc, Mbc1, Mbc1SaveState, Mbc2, Mbc2SaveState, Mbc3, Mbc3SaveState, Mbc5, Mbc5SaveState,
None, PocketCamera, PocketCameraSaveState,
},
sram_save::SaveDataLocation,
};
use crate::connect::{CameraWrapperRef, PocketCamera as PocketCameraTrait};
use serde::{Deserialize, Serialize};
use std::{
fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
marker::PhantomData,
path::PathBuf,
str::from_utf8,
};
use self::mbcs::{
Mbc, Mbc1, Mbc1SaveState, Mbc2, Mbc2SaveState, Mbc3, Mbc3SaveState, Mbc5, Mbc5SaveState, None,
PocketCamera, PocketCameraSaveState,
};
use std::{marker::PhantomData, str::from_utf8};
use super::addresses::{CartRamAddress, RomAddress};
mod mbcs;
struct MaybeBufferedSram {
buf: Vec<u8>,
length: usize,
inner: Option<File>,
unbuffered_writes: usize,
}
#[derive(Serialize, Deserialize)]
struct SramSaveState {
buf: Vec<u8>,
length: usize,
}
impl SramSaveState {
pub fn create(sram: &MaybeBufferedSram) -> Self {
Self {
buf: sram.buf.clone(),
length: sram.length,
}
}
}
const NUM_WRITES_TO_FLUSH: usize = 256;
impl MaybeBufferedSram {
fn new(path: Option<PathBuf>, length: usize) -> Self {
let mut buf = Vec::new();
let inner = if let Some(path) = path {
if path.exists() {
let mut writer = OpenOptions::new()
.write(true)
.read(true)
.open(path)
.unwrap();
writer.read_to_end(&mut buf).unwrap();
Some(writer)
} else {
buf.resize(8 * mbcs::KB, 0);
let writer = OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.unwrap();
Some(writer)
}
} else {
None
};
Self {
buf,
length,
inner,
unbuffered_writes: 0,
}
}
fn from_save_state(state: SramSaveState) -> Self {
// TODO - restore file path
Self {
buf: state.buf,
length: state.length,
inner: None,
unbuffered_writes: 0,
}
}
fn len(&self) -> usize {
self.length
}
fn get(&self, addr: usize) -> u8 {
if addr >= self.buf.len() {
0
} else {
self.buf[addr]
}
}
fn set(&mut self, addr: usize, data: u8) {
self.unbuffered_writes += 1;
while addr >= self.buf.len() {
self.buf.resize(self.buf.len() + (8 * mbcs::KB), 0);
}
self.buf[addr] = data;
if self.unbuffered_writes >= NUM_WRITES_TO_FLUSH {
self.flush();
}
}
fn flush(&mut self) {
if let Some(ref mut writer) = self.inner {
writer.seek(SeekFrom::Start(0)).unwrap();
writer.set_len(self.buf.len() as u64).unwrap();
writer.write_all(&self.buf).unwrap();
self.unbuffered_writes = 0;
}
}
}
impl Drop for MaybeBufferedSram {
fn drop(&mut self) {
self.flush();
}
}
pub mod sram_save;
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub(crate) enum CgbRomType {
@ -190,7 +81,7 @@ where
{
pub(crate) fn load(
data: Vec<u8>,
save_path: Option<PathBuf>,
sram_location: Option<SaveDataLocation>,
camera: CameraWrapperRef<C>,
) -> Self {
let mut title_length = 0x143;
@ -213,22 +104,26 @@ where
0x00 => Box::new(None::init(data)),
0x01 => Box::new(Mbc1::init(data, rom_size, 0, None)),
0x02 => Box::new(Mbc1::init(data, rom_size, ram_size, None)),
0x03 => Box::new(Mbc1::init(data, rom_size, ram_size, save_path)),
0x03 => Box::new(Mbc1::init(data, rom_size, ram_size, sram_location)),
0x05 => Box::new(Mbc2::init(data, rom_size, None)),
0x06 => Box::new(Mbc2::init(data, rom_size, save_path)),
0x0F => Box::new(Mbc3::init(data, rom_size, 0, true, save_path)),
0x10 => Box::new(Mbc3::init(data, rom_size, ram_size, true, save_path)),
0x06 => Box::new(Mbc2::init(data, rom_size, sram_location)),
0x0F => Box::new(Mbc3::init(data, rom_size, 0, true, sram_location)),
0x10 => Box::new(Mbc3::init(data, rom_size, ram_size, true, sram_location)),
0x11 => Box::new(Mbc3::init(data, rom_size, 0, false, None)),
0x12 => Box::new(Mbc3::init(data, rom_size, ram_size, false, None)),
0x13 => Box::new(Mbc3::init(data, rom_size, ram_size, false, save_path)),
0x13 => Box::new(Mbc3::init(data, rom_size, ram_size, false, sram_location)),
0x19 => Box::new(Mbc5::init(data, rom_size, 0, false, None)),
0x1A => Box::new(Mbc5::init(data, rom_size, ram_size, false, None)),
0x1B => Box::new(Mbc5::init(data, rom_size, ram_size, false, save_path)),
0x1B => Box::new(Mbc5::init(data, rom_size, ram_size, false, sram_location)),
0x1C => Box::new(Mbc5::init(data, rom_size, 0, true, None)),
0x1D => Box::new(Mbc5::init(data, rom_size, ram_size, true, None)),
0x1E => Box::new(Mbc5::init(data, rom_size, ram_size, true, save_path)),
0x1E => Box::new(Mbc5::init(data, rom_size, ram_size, true, sram_location)),
0xFC => Box::new(PocketCamera::init(
data, rom_size, ram_size, save_path, camera,
data,
rom_size,
ram_size,
sram_location,
camera,
)),
_ => panic!("unimplemented mbc: {:#X}", data[0x147]),
};

View file

@ -1,12 +1,12 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
use crate::processor::memory::{
addresses::{AddressMarker, CartRamAddress, RomAddress},
rom::{MaybeBufferedSram, MbcSaveState, SramSaveState},
rom::{
sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation, SramSaveState},
MbcSaveState,
},
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Serialize, Deserialize)]
enum BankingMode {
@ -37,7 +37,12 @@ pub struct Mbc1SaveState {
}
impl Mbc1 {
pub fn init(data: Vec<u8>, rom_size: u8, ram_size: u8, save_file: Option<PathBuf>) -> Self {
pub fn init(
data: Vec<u8>,
rom_size: u8,
ram_size: u8,
save_file: Option<SaveDataLocation>,
) -> Self {
let rom_len = rom_banks(rom_size) * ROM_BANK_SIZE;
// in kb
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));

View file

@ -1,11 +1,11 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::processor::memory::{
addresses::{AddressMarker, CartRamAddress, RomAddress},
rom::{MaybeBufferedSram, MbcSaveState, SramSaveState},
rom::{
sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation, SramSaveState},
MbcSaveState,
},
};
use serde::{Deserialize, Serialize};
use super::{rom_banks, Mbc, ROM_BANK_SIZE};
@ -26,7 +26,7 @@ pub struct Mbc2SaveState {
}
impl Mbc2 {
pub fn init(data: Vec<u8>, rom_size: u8, save_file: Option<PathBuf>) -> Self {
pub fn init(data: Vec<u8>, rom_size: u8, save_file: Option<SaveDataLocation>) -> Self {
let rom_len = rom_banks(rom_size) * ROM_BANK_SIZE;
let ram = MaybeBufferedSram::new(save_file, 512);

View file

@ -1,17 +1,16 @@
use serde::{Deserialize, Serialize};
use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
use crate::{
processor::memory::{
addresses::{AddressMarker, CartRamAddress, RomAddress},
rom::{MaybeBufferedSram, MbcSaveState, SramSaveState},
rom::{
sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation, SramSaveState},
MbcSaveState,
},
},
util::set_or_clear_bit,
};
use std::{
path::PathBuf,
time::{Duration, Instant},
};
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
enum RtcRegister {
@ -112,7 +111,7 @@ impl Mbc3 {
rom_size: u8,
ram_size: u8,
rtc: bool,
save_file: Option<PathBuf>,
save_file: Option<SaveDataLocation>,
) -> Self {
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
Self {

View file

@ -1,14 +1,14 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::{
processor::memory::{
addresses::{AddressMarker, CartRamAddress, RomAddress},
rom::{MaybeBufferedSram, MbcSaveState, SramSaveState},
rom::{
sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation, SramSaveState},
MbcSaveState,
},
},
util::get_bit,
};
use serde::{Deserialize, Serialize};
use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
@ -42,7 +42,7 @@ impl Mbc5 {
rom_size: u8,
ram_size: u8,
rumble: bool,
save_file: Option<PathBuf>,
save_file: Option<SaveDataLocation>,
) -> Self {
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
Self {

View file

@ -3,11 +3,13 @@ use crate::{
connect::{CameraWrapperRef, PocketCamera as PocketCameraTrait},
processor::memory::{
addresses::{AddressMarker, CartRamAddress, RomAddress},
rom::{MaybeBufferedSram, MbcSaveState},
rom::{
sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation},
MbcSaveState,
},
},
};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
enum RamBank {
Ram(u8),
@ -41,7 +43,7 @@ where
data: Vec<u8>,
rom_size: u8,
ram_size: u8,
save_file: Option<PathBuf>,
save_file: Option<SaveDataLocation>,
camera: CameraWrapperRef<C>,
) -> Self {
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));

View file

@ -0,0 +1,246 @@
use serde::{Deserialize, Serialize};
use std::{
fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
path::PathBuf,
sync::{Arc, RwLock},
};
use super::mbcs;
#[derive(Debug)]
pub enum SaveDataLocation {
File(PathBuf),
Raw(Arc<RwLock<Vec<u8>>>),
}
pub(crate) enum MaybeBufferedSram {
File(FileBufferedSram),
Raw(RawBufferedSram),
Unbuffered(UnbufferedSram),
}
impl MaybeBufferedSram {
pub(crate) fn new(save: Option<SaveDataLocation>, length: usize) -> Self {
match save {
Some(SaveDataLocation::File(path)) => Self::File(FileBufferedSram::new(path, length)),
Some(SaveDataLocation::Raw(buf)) => Self::Raw(RawBufferedSram::new(buf, length)),
None => Self::Unbuffered(UnbufferedSram::new(length)),
}
}
pub(crate) fn from_save_state(state: SramSaveState) -> Self {
Self::Unbuffered(UnbufferedSram::new(state.length))
}
}
pub(crate) struct UnbufferedSram {
buf: Vec<u8>,
}
impl UnbufferedSram {
fn new(length: usize) -> Self {
let mut buf = Vec::new();
buf.resize(length, 0);
Self { buf }
}
}
impl BufferedSramTrait for UnbufferedSram {
fn len(&self) -> usize {
self.buf.len()
}
fn get(&self, addr: usize) -> u8 {
if addr >= self.buf.len() {
0
} else {
self.buf[addr]
}
}
fn set(&mut self, addr: usize, data: u8) {
if addr < self.buf.len() {
self.buf[addr] = data;
}
}
fn flush(&mut self) {}
}
pub(crate) struct RawBufferedSram {
buf: Arc<RwLock<Vec<u8>>>,
}
impl RawBufferedSram {
fn new(buf: Arc<RwLock<Vec<u8>>>, length: usize) -> Self {
if let Ok(mut buf) = buf.write() {
buf.resize(length, 0);
}
Self { buf }
}
}
impl BufferedSramTrait for RawBufferedSram {
fn len(&self) -> usize {
match self.buf.read() {
Ok(buf) => buf.len(),
Err(e) => panic!("failed to lock sram buffer: {e}"),
}
}
fn get(&self, addr: usize) -> u8 {
match self.buf.read() {
Ok(buf) => {
if addr >= buf.len() {
0
} else {
buf[addr]
}
}
Err(e) => panic!("failed to lock sram buffer: {e}"),
}
}
fn set(&mut self, addr: usize, data: u8) {
match self.buf.write() {
Ok(mut buf) => {
if addr < buf.len() {
buf[addr] = data;
}
}
Err(e) => panic!("failed to lock sram buffer: {e}"),
}
}
fn flush(&mut self) {}
}
pub(crate) struct FileBufferedSram {
buf: Vec<u8>,
length: usize,
inner: File,
unbuffered_writes: usize,
}
const NUM_WRITES_TO_FLUSH: usize = 256;
impl FileBufferedSram {
fn new(path: PathBuf, length: usize) -> Self {
let mut buf = Vec::new();
let inner = {
if path.exists() {
let mut writer = OpenOptions::new()
.write(true)
.read(true)
.open(path)
.unwrap();
writer.read_to_end(&mut buf).unwrap();
writer
} else {
buf.resize(8 * mbcs::KB, 0);
let writer = OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.unwrap();
writer
}
};
Self {
buf,
length,
inner,
unbuffered_writes: 0,
}
}
}
impl BufferedSramTrait for FileBufferedSram {
fn len(&self) -> usize {
self.length
}
fn get(&self, addr: usize) -> u8 {
if addr >= self.buf.len() {
0
} else {
self.buf[addr]
}
}
fn set(&mut self, addr: usize, data: u8) {
self.unbuffered_writes += 1;
while addr >= self.buf.len() {
self.buf.resize(self.buf.len() + (8 * mbcs::KB), 0);
}
self.buf[addr] = data;
if self.unbuffered_writes >= NUM_WRITES_TO_FLUSH {
self.flush();
}
}
fn flush(&mut self) {
self.inner.seek(SeekFrom::Start(0)).unwrap();
self.inner.set_len(self.buf.len() as u64).unwrap();
self.inner.write_all(&self.buf).unwrap();
self.unbuffered_writes = 0;
}
}
pub(crate) trait BufferedSramTrait {
fn len(&self) -> usize;
fn get(&self, addr: usize) -> u8;
fn set(&mut self, addr: usize, data: u8);
fn flush(&mut self);
}
impl BufferedSramTrait for MaybeBufferedSram {
fn len(&self) -> usize {
match self {
MaybeBufferedSram::File(f) => f.len(),
MaybeBufferedSram::Raw(r) => r.len(),
MaybeBufferedSram::Unbuffered(u) => u.len(),
}
}
fn get(&self, addr: usize) -> u8 {
match self {
MaybeBufferedSram::File(f) => f.get(addr),
MaybeBufferedSram::Raw(r) => r.get(addr),
MaybeBufferedSram::Unbuffered(u) => u.get(addr),
}
}
fn set(&mut self, addr: usize, data: u8) {
match self {
MaybeBufferedSram::File(f) => f.set(addr, data),
MaybeBufferedSram::Raw(r) => r.set(addr, data),
MaybeBufferedSram::Unbuffered(u) => u.set(addr, data),
}
}
fn flush(&mut self) {
if let Self::File(ref mut f) = self {
f.flush();
}
}
}
#[derive(Serialize, Deserialize)]
pub(crate) struct SramSaveState {
length: usize,
}
impl SramSaveState {
pub fn create(sram: &MaybeBufferedSram) -> Self {
Self { length: sram.len() }
}
}
impl Drop for MaybeBufferedSram {
fn drop(&mut self) {
self.flush();
}
}