some save work
This commit is contained in:
parent
85f938a45b
commit
bf4b8d4caa
10 changed files with 362 additions and 177 deletions
|
@ -7,18 +7,16 @@ use gb_emu_lib::{
|
||||||
},
|
},
|
||||||
EmulatorCore,
|
EmulatorCore,
|
||||||
};
|
};
|
||||||
use nih_plug::midi::MidiResult::Basic;
|
|
||||||
use nih_plug::prelude::*;
|
use nih_plug::prelude::*;
|
||||||
|
use nih_plug::{midi::MidiResult::Basic, params::persist::PersistentField};
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
mpsc::{self, channel, Receiver, Sender},
|
mpsc::{self, channel, Receiver, Sender},
|
||||||
Arc, Mutex,
|
Arc, Mutex, RwLock,
|
||||||
};
|
};
|
||||||
use ui::{Emulator, EmulatorRenderer};
|
use ui::{Emulator, EmulatorRenderer};
|
||||||
|
|
||||||
#[cfg(feature = "savestate")]
|
#[cfg(feature = "savestate")]
|
||||||
use gb_emu_lib::connect::CpuSaveState;
|
use gb_emu_lib::connect::CpuSaveState;
|
||||||
#[cfg(feature = "savestate")]
|
|
||||||
use nih_plug::params::persist::PersistentField;
|
|
||||||
|
|
||||||
mod ui;
|
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)]
|
#[derive(Params, Default)]
|
||||||
struct EmuParams {
|
struct EmuParams {
|
||||||
#[cfg(feature = "savestate")]
|
#[persist = "sram"]
|
||||||
#[persist = "save_state"]
|
sram_save: SramParam,
|
||||||
last_save_state: SaveStateParam,
|
// #[cfg(feature = "savestate")]
|
||||||
|
// #[persist = "save_state"]
|
||||||
|
// last_save_state: SaveStateParam,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EmuVars {
|
struct EmuVars {
|
||||||
|
@ -70,7 +88,7 @@ type JoypadSender = Mutex<Option<Sender<(JoypadButtons, bool)>>>;
|
||||||
const BUFFERS_PER_FRAME: usize = 1;
|
const BUFFERS_PER_FRAME: usize = 1;
|
||||||
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::Linear;
|
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 {
|
impl Plugin for GameboyEmu {
|
||||||
const NAME: &'static str = "Gameboy";
|
const NAME: &'static str = "Gameboy";
|
||||||
|
@ -262,7 +280,7 @@ impl Plugin for GameboyEmu {
|
||||||
let mut emulator_core = {
|
let mut emulator_core = {
|
||||||
let options = EmulatorOptions::new(window, rom, output)
|
let options = EmulatorOptions::new(window, rom, output)
|
||||||
.with_serial_target(serial_target)
|
.with_serial_target(serial_target)
|
||||||
.force_no_save();
|
.with_sram_buffer(self.params.sram_save.state.clone());
|
||||||
|
|
||||||
EmulatorCore::init(receiver, options, NoCamera::default())
|
EmulatorCore::init(receiver, options, NoCamera::default())
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::marker::PhantomData;
|
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::gpu::Colour;
|
||||||
pub use crate::processor::memory::mmio::joypad::{JoypadButtons, JoypadState};
|
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]
|
#[non_exhaustive]
|
||||||
pub struct EmulatorOptions<ColourFormat, R>
|
pub struct EmulatorOptions<ColourFormat, R>
|
||||||
where
|
where
|
||||||
|
@ -161,7 +167,7 @@ where
|
||||||
pub(crate) tile_window: Option<R>,
|
pub(crate) tile_window: Option<R>,
|
||||||
pub(crate) rom: RomFile,
|
pub(crate) rom: RomFile,
|
||||||
pub(crate) output: AudioOutput,
|
pub(crate) output: AudioOutput,
|
||||||
pub(crate) save_path: Option<String>,
|
pub(crate) save: Option<SramType>,
|
||||||
|
|
||||||
pub(crate) no_save: bool,
|
pub(crate) no_save: bool,
|
||||||
pub(crate) bootrom: Option<RomFile>,
|
pub(crate) bootrom: Option<RomFile>,
|
||||||
|
@ -182,7 +188,7 @@ where
|
||||||
tile_window: None,
|
tile_window: None,
|
||||||
rom,
|
rom,
|
||||||
output,
|
output,
|
||||||
save_path: None,
|
save: None,
|
||||||
no_save: false,
|
no_save: false,
|
||||||
bootrom: None,
|
bootrom: None,
|
||||||
show_bootrom: false,
|
show_bootrom: false,
|
||||||
|
@ -193,7 +199,12 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_save_path(mut self, path: Option<String>) -> Self {
|
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,14 @@ use crate::{
|
||||||
};
|
};
|
||||||
use connect::{
|
use connect::{
|
||||||
AudioOutput, CameraWrapper, CameraWrapperRef, EmulatorCoreTrait, EmulatorMessage,
|
AudioOutput, CameraWrapper, CameraWrapperRef, EmulatorCoreTrait, EmulatorMessage,
|
||||||
EmulatorOptions, NoCamera, PocketCamera, Renderer, RomFile, SerialTarget,
|
EmulatorOptions, NoCamera, PocketCamera, Renderer, RomFile, SerialTarget, SramType,
|
||||||
};
|
};
|
||||||
use processor::{
|
use processor::{
|
||||||
memory::{mmio::gpu::Colour, rom::CgbRomType, OutputTargets, Rom},
|
memory::{
|
||||||
|
mmio::gpu::Colour,
|
||||||
|
rom::{sram_save::SaveDataLocation, CgbRomType},
|
||||||
|
OutputTargets, Rom,
|
||||||
|
},
|
||||||
Cpu, CpuSaveState,
|
Cpu, CpuSaveState,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -54,16 +58,21 @@ where
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let camera: CameraWrapperRef<C> = Arc::new(Mutex::new(CameraWrapper::new(camera)));
|
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 {
|
let rom = match options.rom {
|
||||||
RomFile::Path(path) => {
|
RomFile::Path(path) => {
|
||||||
let maybe_save = if options.no_save {
|
let maybe_save = if options.no_save {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(if let Some(path) = options.save_path {
|
Some(maybe_save.unwrap_or(SaveDataLocation::File(
|
||||||
PathBuf::from_str(&path).unwrap()
|
PathBuf::from_str(&path).unwrap().with_extension("sav"),
|
||||||
} else {
|
)))
|
||||||
PathBuf::from_str(&path).unwrap().with_extension("sav")
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match fs::read(path) {
|
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 {
|
let (bootrom, cgb) = if let Some(b) = options.bootrom {
|
||||||
|
|
|
@ -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 crate::connect::{CameraWrapperRef, PocketCamera as PocketCameraTrait};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{marker::PhantomData, str::from_utf8};
|
||||||
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 super::addresses::{CartRamAddress, RomAddress};
|
use super::addresses::{CartRamAddress, RomAddress};
|
||||||
|
|
||||||
mod mbcs;
|
mod mbcs;
|
||||||
|
pub mod sram_save;
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub(crate) enum CgbRomType {
|
pub(crate) enum CgbRomType {
|
||||||
|
@ -190,7 +81,7 @@ where
|
||||||
{
|
{
|
||||||
pub(crate) fn load(
|
pub(crate) fn load(
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
save_path: Option<PathBuf>,
|
sram_location: Option<SaveDataLocation>,
|
||||||
camera: CameraWrapperRef<C>,
|
camera: CameraWrapperRef<C>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut title_length = 0x143;
|
let mut title_length = 0x143;
|
||||||
|
@ -213,22 +104,26 @@ where
|
||||||
0x00 => Box::new(None::init(data)),
|
0x00 => Box::new(None::init(data)),
|
||||||
0x01 => Box::new(Mbc1::init(data, rom_size, 0, None)),
|
0x01 => Box::new(Mbc1::init(data, rom_size, 0, None)),
|
||||||
0x02 => Box::new(Mbc1::init(data, rom_size, ram_size, 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)),
|
0x05 => Box::new(Mbc2::init(data, rom_size, None)),
|
||||||
0x06 => Box::new(Mbc2::init(data, rom_size, save_path)),
|
0x06 => Box::new(Mbc2::init(data, rom_size, sram_location)),
|
||||||
0x0F => Box::new(Mbc3::init(data, rom_size, 0, true, save_path)),
|
0x0F => Box::new(Mbc3::init(data, rom_size, 0, true, sram_location)),
|
||||||
0x10 => Box::new(Mbc3::init(data, rom_size, ram_size, true, save_path)),
|
0x10 => Box::new(Mbc3::init(data, rom_size, ram_size, true, sram_location)),
|
||||||
0x11 => Box::new(Mbc3::init(data, rom_size, 0, false, None)),
|
0x11 => Box::new(Mbc3::init(data, rom_size, 0, false, None)),
|
||||||
0x12 => Box::new(Mbc3::init(data, rom_size, ram_size, 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)),
|
0x19 => Box::new(Mbc5::init(data, rom_size, 0, false, None)),
|
||||||
0x1A => Box::new(Mbc5::init(data, rom_size, ram_size, 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)),
|
0x1C => Box::new(Mbc5::init(data, rom_size, 0, true, None)),
|
||||||
0x1D => Box::new(Mbc5::init(data, rom_size, ram_size, 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(
|
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]),
|
_ => panic!("unimplemented mbc: {:#X}", data[0x147]),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
|
||||||
use crate::processor::memory::{
|
use crate::processor::memory::{
|
||||||
addresses::{AddressMarker, CartRamAddress, RomAddress},
|
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)]
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
enum BankingMode {
|
enum BankingMode {
|
||||||
|
@ -37,7 +37,12 @@ pub struct Mbc1SaveState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mbc1 {
|
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;
|
let rom_len = rom_banks(rom_size) * ROM_BANK_SIZE;
|
||||||
// in kb
|
// in kb
|
||||||
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
|
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::processor::memory::{
|
use crate::processor::memory::{
|
||||||
addresses::{AddressMarker, CartRamAddress, RomAddress},
|
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};
|
use super::{rom_banks, Mbc, ROM_BANK_SIZE};
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ pub struct Mbc2SaveState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mbc2 {
|
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 rom_len = rom_banks(rom_size) * ROM_BANK_SIZE;
|
||||||
let ram = MaybeBufferedSram::new(save_file, 512);
|
let ram = MaybeBufferedSram::new(save_file, 512);
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
|
use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
|
||||||
use crate::{
|
use crate::{
|
||||||
processor::memory::{
|
processor::memory::{
|
||||||
addresses::{AddressMarker, CartRamAddress, RomAddress},
|
addresses::{AddressMarker, CartRamAddress, RomAddress},
|
||||||
rom::{MaybeBufferedSram, MbcSaveState, SramSaveState},
|
rom::{
|
||||||
|
sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation, SramSaveState},
|
||||||
|
MbcSaveState,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
util::set_or_clear_bit,
|
util::set_or_clear_bit,
|
||||||
};
|
};
|
||||||
use std::{
|
use serde::{Deserialize, Serialize};
|
||||||
path::PathBuf,
|
use std::time::{Duration, Instant};
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||||
enum RtcRegister {
|
enum RtcRegister {
|
||||||
|
@ -112,7 +111,7 @@ impl Mbc3 {
|
||||||
rom_size: u8,
|
rom_size: u8,
|
||||||
ram_size: u8,
|
ram_size: u8,
|
||||||
rtc: bool,
|
rtc: bool,
|
||||||
save_file: Option<PathBuf>,
|
save_file: Option<SaveDataLocation>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
|
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
processor::memory::{
|
processor::memory::{
|
||||||
addresses::{AddressMarker, CartRamAddress, RomAddress},
|
addresses::{AddressMarker, CartRamAddress, RomAddress},
|
||||||
rom::{MaybeBufferedSram, MbcSaveState, SramSaveState},
|
rom::{
|
||||||
|
sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation, SramSaveState},
|
||||||
|
MbcSaveState,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
util::get_bit,
|
util::get_bit,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
|
use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ impl Mbc5 {
|
||||||
rom_size: u8,
|
rom_size: u8,
|
||||||
ram_size: u8,
|
ram_size: u8,
|
||||||
rumble: bool,
|
rumble: bool,
|
||||||
save_file: Option<PathBuf>,
|
save_file: Option<SaveDataLocation>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
|
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -3,11 +3,13 @@ use crate::{
|
||||||
connect::{CameraWrapperRef, PocketCamera as PocketCameraTrait},
|
connect::{CameraWrapperRef, PocketCamera as PocketCameraTrait},
|
||||||
processor::memory::{
|
processor::memory::{
|
||||||
addresses::{AddressMarker, CartRamAddress, RomAddress},
|
addresses::{AddressMarker, CartRamAddress, RomAddress},
|
||||||
rom::{MaybeBufferedSram, MbcSaveState},
|
rom::{
|
||||||
|
sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation},
|
||||||
|
MbcSaveState,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
enum RamBank {
|
enum RamBank {
|
||||||
Ram(u8),
|
Ram(u8),
|
||||||
|
@ -41,7 +43,7 @@ where
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
rom_size: u8,
|
rom_size: u8,
|
||||||
ram_size: u8,
|
ram_size: u8,
|
||||||
save_file: Option<PathBuf>,
|
save_file: Option<SaveDataLocation>,
|
||||||
camera: CameraWrapperRef<C>,
|
camera: CameraWrapperRef<C>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
|
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
|
||||||
|
|
246
lib/src/processor/memory/rom/sram_save.rs
Normal file
246
lib/src/processor/memory/rom/sram_save.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue