mirror of
https://github.com/gopher64/gopher64.git
synced 2026-11-12 09:09:01 +01:00
1 parent
4d7387537a
commit
fb4b6de375
9 files changed
+340
-243
No files matched your search
+5
-8
@@ -289,11 +289,11 @@ impl Device {
|
||||
self.rng.clone_from(&device.rng);
|
||||
self.cpu.clone_from(&device.cpu);
|
||||
self.pif.clone_from(&device.pif);
|
||||
self.cart.clone_from(&device.cart);
|
||||
self.cart.clone_state_from(&device.cart);
|
||||
self.memory.clone_from(&device.memory);
|
||||
self.rsp.clone_from(&device.rsp);
|
||||
self.rdp.clone_from(&device.rdp);
|
||||
self.rdram.clone_from(&device.rdram);
|
||||
self.rdram.clone_state_from(&device.rdram);
|
||||
self.mi.clone_from(&device.mi);
|
||||
self.pi.clone_from(&device.pi);
|
||||
self.vi.clone_from(&device.vi);
|
||||
@@ -301,13 +301,10 @@ impl Device {
|
||||
self.si.clone_from(&device.si);
|
||||
self.ri.clone_from(&device.ri);
|
||||
self.vru.clone_from(&device.vru);
|
||||
self.transferpaks.clone_from(&device.transferpaks);
|
||||
self.cheats.clone_from(&device.cheats);
|
||||
|
||||
self.cart.rom = Vec::new();
|
||||
for transferpak in self.transferpaks.iter_mut() {
|
||||
transferpak.cart.rom = Vec::new();
|
||||
for (dst, src) in self.transferpaks.iter_mut().zip(device.transferpaks.iter()) {
|
||||
dst.clone_state_from(src);
|
||||
}
|
||||
self.cheats.clone_from(&device.cheats);
|
||||
}
|
||||
|
||||
pub fn new(with_ui: bool) -> Box<Device> {
|
||||
|
||||
@@ -45,6 +45,33 @@ pub struct Cart {
|
||||
pub rtc_timestamp: i64,
|
||||
}
|
||||
|
||||
impl Cart {
|
||||
/// Clone everything except the ROM, which is large and not part of the savestate.
|
||||
/// `self.rom` is left untouched.
|
||||
pub fn clone_state_from(&mut self, other: &Cart) {
|
||||
// Destructure so adding a field to Cart forces this to be updated.
|
||||
let Cart {
|
||||
rom: _,
|
||||
is_viewer_buffer,
|
||||
pal,
|
||||
latch,
|
||||
cic_seed,
|
||||
rtc,
|
||||
sc64,
|
||||
flashram,
|
||||
rtc_timestamp,
|
||||
} = other;
|
||||
self.is_viewer_buffer.clone_from(is_viewer_buffer);
|
||||
self.pal = *pal;
|
||||
self.latch = *latch;
|
||||
self.cic_seed = *cic_seed;
|
||||
self.rtc.clone_from(rtc);
|
||||
self.sc64.clone_from(sc64);
|
||||
self.flashram.clone_from(flashram);
|
||||
self.rtc_timestamp = *rtc_timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(device: &mut device::Device, channel: usize) {
|
||||
let cmd = device.pif.ram[device.pif.channels[channel].tx_buf.unwrap()];
|
||||
|
||||
|
||||
@@ -27,6 +27,38 @@ pub struct GbCart {
|
||||
pub rtc_base_time: i64,
|
||||
}
|
||||
|
||||
impl GbCart {
|
||||
/// Clone everything except the ROM; `self.rom` is left untouched.
|
||||
pub fn clone_state_from(&mut self, other: &GbCart) {
|
||||
// Destructure so adding a field to GbCart forces this to be updated.
|
||||
let GbCart {
|
||||
enabled,
|
||||
rom: _,
|
||||
ram,
|
||||
cart_type,
|
||||
ram_enabled,
|
||||
mbc1_mode,
|
||||
ram_bank,
|
||||
rom_bank,
|
||||
rtc_regs,
|
||||
rtc_regs_latch,
|
||||
last_elapsed_time,
|
||||
rtc_base_time,
|
||||
} = other;
|
||||
self.enabled = *enabled;
|
||||
self.ram.clone_from(ram);
|
||||
self.cart_type = *cart_type;
|
||||
self.ram_enabled = *ram_enabled;
|
||||
self.mbc1_mode = *mbc1_mode;
|
||||
self.ram_bank = *ram_bank;
|
||||
self.rom_bank = *rom_bank;
|
||||
self.rtc_regs = *rtc_regs;
|
||||
self.rtc_regs_latch = *rtc_regs_latch;
|
||||
self.last_elapsed_time = *last_elapsed_time;
|
||||
self.rtc_base_time = *rtc_base_time;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum CartType {
|
||||
#[default]
|
||||
|
||||
@@ -8,6 +8,22 @@ pub struct TransferPak {
|
||||
pub cart: device::controller::gbcart::GbCart,
|
||||
}
|
||||
|
||||
impl TransferPak {
|
||||
/// Clone everything except the GB cart ROM; `self.cart.rom` is left untouched.
|
||||
pub fn clone_state_from(&mut self, other: &TransferPak) {
|
||||
let TransferPak {
|
||||
enabled,
|
||||
reset_state,
|
||||
bank,
|
||||
cart,
|
||||
} = other;
|
||||
self.enabled = *enabled;
|
||||
self.reset_state = *reset_state;
|
||||
self.bank = *bank;
|
||||
self.cart.clone_state_from(cart);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
|
||||
let pak = &mut device.transferpaks[channel];
|
||||
|
||||
|
||||
@@ -24,6 +24,18 @@ pub struct Rdram {
|
||||
pub regs: [[u32; RDRAM_REGS_COUNT]; 4],
|
||||
}
|
||||
|
||||
impl Rdram {
|
||||
/// Clone `other` into `self`, reusing `self.mem`'s allocation so it is not reallocated
|
||||
/// (and keeps its address, which the RDP holds) when the sizes match.
|
||||
pub fn clone_state_from(&mut self, other: &Rdram) {
|
||||
// Destructure so adding a field to Rdram forces this to be updated.
|
||||
let Rdram { mem, size, regs } = other;
|
||||
self.mem.clone_from(mem);
|
||||
self.size = *size;
|
||||
self.regs = *regs;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
device: &device::Device,
|
||||
address: u64,
|
||||
|
||||
@@ -255,6 +255,7 @@ pub fn run(args: Args, arg_count: usize) -> std::io::Result<()> {
|
||||
number_of_players,
|
||||
input_delay,
|
||||
ice_config_path: dirs.cache_dir.join("ice_config.json"),
|
||||
rollback: false,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
||||
+24
-13
@@ -1,7 +1,10 @@
|
||||
use crate::device;
|
||||
use crate::savestates;
|
||||
use crate::ui;
|
||||
use sha2::digest::Digest;
|
||||
|
||||
const MAX_PREDICTION_WINDOW: usize = 16;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct RtcIceServerConfig {
|
||||
pub urls: Vec<String>,
|
||||
@@ -45,6 +48,7 @@ pub struct NetplayConfig {
|
||||
pub number_of_players: usize,
|
||||
pub input_delay: usize,
|
||||
pub ice_config_path: std::path::PathBuf,
|
||||
pub rollback: bool,
|
||||
}
|
||||
|
||||
pub struct Netplay {
|
||||
@@ -61,6 +65,7 @@ pub struct Netplay {
|
||||
pub requests: std::collections::VecDeque<ggrs::GgrsRequest<GgrsConfig>>,
|
||||
pub incoming_message: Vec<u8>,
|
||||
pub ice_config_path: std::path::PathBuf,
|
||||
pub rollback: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
@@ -201,7 +206,7 @@ pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<
|
||||
}
|
||||
|
||||
pub fn send_input_delay(netplay: &mut Netplay, input_delay: usize) {
|
||||
if input_delay < 1 {
|
||||
if !(1..=MAX_PREDICTION_WINDOW).contains(&input_delay) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -269,7 +274,9 @@ pub fn process_requests(
|
||||
if let Some(request) = device.netplay.as_mut().unwrap().requests.pop_front() {
|
||||
match request {
|
||||
ggrs::GgrsRequest::SaveGameState { cell, frame } => {
|
||||
//savestates::create_savestate(device, true, Some(frame));
|
||||
if device.netplay.as_ref().unwrap().rollback {
|
||||
savestates::create_savestate(device, true, Some(frame));
|
||||
}
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
for reg in device.cpu.cop0.regs.as_ref() {
|
||||
@@ -279,9 +286,8 @@ pub fn process_requests(
|
||||
cell.save(frame, Some(frame), Some(hash));
|
||||
}
|
||||
ggrs::GgrsRequest::LoadGameState { cell, frame: _ } => {
|
||||
eprintln!("attempting to load game state");
|
||||
if let Some(_frame) = cell.load() {
|
||||
// savestates::load_savestate(device, true, Some(frame));
|
||||
if let Some(frame) = cell.load() {
|
||||
savestates::load_savestate(device, true, Some(frame));
|
||||
}
|
||||
}
|
||||
ggrs::GgrsRequest::AdvanceFrame { inputs } => {
|
||||
@@ -339,7 +345,9 @@ fn process_netplay(device: &mut device::Device) {
|
||||
|
||||
fn advance_frame(device: &mut device::Device) {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
let local_input = if netplay.session.current_frame() > netplay.session.max_prediction() as i32 {
|
||||
let local_input = if netplay.session.current_frame() > netplay.session.max_prediction() as i32
|
||||
|| netplay.rollback
|
||||
{
|
||||
ui::input::get(&mut device.ui, 0)
|
||||
} else {
|
||||
//workaround for disabled rollback
|
||||
@@ -351,12 +359,14 @@ fn advance_frame(device: &mut device::Device) {
|
||||
.add_local_input(local_handle, local_input)
|
||||
.unwrap();
|
||||
|
||||
// avoid rollback
|
||||
while !netplay.disconnected
|
||||
&& netplay.session.current_frame() > netplay.session.confirmed_frame()
|
||||
&& netplay.session.confirmed_frame() != ggrs::NULL_FRAME
|
||||
{
|
||||
poll_clients(netplay);
|
||||
if !netplay.rollback {
|
||||
// avoid rollback
|
||||
while !netplay.disconnected
|
||||
&& netplay.session.current_frame() > netplay.session.confirmed_frame()
|
||||
&& netplay.session.confirmed_frame() != ggrs::NULL_FRAME
|
||||
{
|
||||
poll_clients(netplay);
|
||||
}
|
||||
}
|
||||
|
||||
if netplay.disconnected {
|
||||
@@ -504,7 +514,7 @@ pub fn init(
|
||||
.with_fps(if pal { 50 } else { 60 })
|
||||
.unwrap()
|
||||
.with_desync_detection_mode(ggrs::DesyncDetection::On { interval: 60 })
|
||||
.with_max_prediction_window(16)
|
||||
.with_max_prediction_window(MAX_PREDICTION_WINDOW)
|
||||
.with_disconnect_timeout(std::time::Duration::from_secs(if cfg!(debug_assertions) {
|
||||
10
|
||||
} else {
|
||||
@@ -569,6 +579,7 @@ pub fn init(
|
||||
received_data: std::collections::VecDeque::new(),
|
||||
messages: std::collections::HashMap::new(),
|
||||
ice_config_path: netplay_config.ice_config_path.clone(),
|
||||
rollback: netplay_config.rollback,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+176
-221
@@ -5,7 +5,6 @@ use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
|
||||
use serde::ser::{Serialize, SerializeSeq, Serializer};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::arch::x86_64::*;
|
||||
use std::ops::Deref;
|
||||
|
||||
struct M128iArrayVisitor<const N: usize>;
|
||||
|
||||
@@ -99,10 +98,11 @@ pub struct Savestate {
|
||||
pub load_state: bool,
|
||||
pub load_rewind: bool,
|
||||
pub last_rewind_saved: f64,
|
||||
pub rewind_pool:
|
||||
std::sync::Arc<tokio::sync::Mutex<std::collections::BTreeMap<i32, SavestateData>>>,
|
||||
pub rewind_pool: std::collections::BTreeMap<i32, SavestateData>,
|
||||
}
|
||||
|
||||
const REWIND_POOL_SIZE: usize = 30;
|
||||
|
||||
pub struct SavestateData {
|
||||
device: Box<device::Device>,
|
||||
saves: ui::storage::Saves,
|
||||
@@ -110,11 +110,16 @@ pub struct SavestateData {
|
||||
ra_state: Vec<u8>,
|
||||
}
|
||||
|
||||
static DEVICE_CLONE: std::sync::LazyLock<tokio::sync::Mutex<Box<device::Device>>> =
|
||||
std::sync::LazyLock::new(|| tokio::sync::Mutex::new(device::Device::new(false)));
|
||||
|
||||
static SAVES_CLONE: std::sync::LazyLock<tokio::sync::Mutex<ui::storage::Saves>> =
|
||||
std::sync::LazyLock::new(|| tokio::sync::Mutex::new(ui::storage::Saves::default()));
|
||||
impl SavestateData {
|
||||
fn new() -> SavestateData {
|
||||
SavestateData {
|
||||
device: device::Device::new(false),
|
||||
saves: ui::storage::Saves::default(),
|
||||
rdp_state: Vec::new(),
|
||||
ra_state: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_savestates(device: &mut device::Device) {
|
||||
let save_rewind = if device.netplay.is_none()
|
||||
@@ -138,82 +143,76 @@ pub fn process_savestates(device: &mut device::Device) {
|
||||
}
|
||||
|
||||
pub fn create_savestate(device: &mut device::Device, rewind: bool, rewind_frame: Option<i32>) {
|
||||
if !rewind {
|
||||
ui::video::idle();
|
||||
ui::video::idle();
|
||||
|
||||
// Rewind snapshots reuse the buffers of the pool entry they replace or evict.
|
||||
let rewind_key = rewind.then(|| {
|
||||
let pool = &device.ui.savestate.rewind_pool;
|
||||
rewind_frame.unwrap_or_else(|| pool.keys().last().map_or(0, |key| key + 1))
|
||||
});
|
||||
let mut state = if let Some(key) = rewind_key {
|
||||
let pool = &mut device.ui.savestate.rewind_pool;
|
||||
pool.remove(&key)
|
||||
.or_else(|| {
|
||||
if pool.len() >= REWIND_POOL_SIZE {
|
||||
pool.pop_first().map(|(_, state)| state)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(SavestateData::new)
|
||||
} else {
|
||||
SavestateData::new()
|
||||
};
|
||||
|
||||
state.rdp_state.resize(ui::video::state_size(), 0);
|
||||
ui::video::save_state(state.rdp_state.as_mut_ptr());
|
||||
|
||||
state.ra_state.resize(retroachievements::state_size(), 0);
|
||||
retroachievements::save_state(state.ra_state.as_mut_ptr(), state.ra_state.len());
|
||||
|
||||
state.device.clone_state(device);
|
||||
state.saves.copy_from(&device.ui.storage.saves);
|
||||
|
||||
if let Some(key) = rewind_key {
|
||||
device.ui.savestate.rewind_pool.insert(key, state);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut rdp_state: Vec<u8> = vec![0; ui::video::state_size()];
|
||||
ui::video::save_state(rdp_state.as_mut_ptr());
|
||||
|
||||
let mut ra_state: Vec<u8> = vec![0; retroachievements::state_size()];
|
||||
retroachievements::save_state(ra_state.as_mut_ptr(), ra_state.len());
|
||||
|
||||
DEVICE_CLONE.blocking_lock().clone_state(device);
|
||||
SAVES_CLONE
|
||||
.blocking_lock()
|
||||
.clone_from(&device.ui.storage.saves);
|
||||
|
||||
let save_path = device.ui.storage.paths.savestate_file_path.clone();
|
||||
let save_state_slot = device.ui.storage.save_state_slot;
|
||||
let rewind_pool = device.ui.savestate.rewind_pool.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut error = false;
|
||||
|
||||
if rewind {
|
||||
let mut pool = rewind_pool.lock().await;
|
||||
let mut state = device::Device::new(false);
|
||||
state.clone_state(DEVICE_CLONE.lock().await.deref());
|
||||
let key = if let Some(key) = rewind_frame {
|
||||
key
|
||||
} else if let Some(key) = pool.keys().last() {
|
||||
key + 1
|
||||
let compressed_file = if let Ok(device_data) = postcard::to_stdvec(&state.device)
|
||||
&& let Ok(saves_data) = postcard::to_stdvec(&state.saves)
|
||||
&& let Ok(compressed_file) = ui::storage::compress_file(&[
|
||||
(&device_data, "device"),
|
||||
(&saves_data, "saves"),
|
||||
(&state.rdp_state, "rdp_state"),
|
||||
(&state.ra_state, "ra_state"),
|
||||
]) {
|
||||
Some(compressed_file)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let error = if let Some(compressed_file) = compressed_file {
|
||||
if let Err(e) = tokio::fs::write(save_path, compressed_file).await {
|
||||
eprintln!("Error writing savestate: {}", e);
|
||||
true
|
||||
} else {
|
||||
0
|
||||
};
|
||||
pool.insert(
|
||||
key,
|
||||
SavestateData {
|
||||
device: state,
|
||||
saves: SAVES_CLONE.lock().await.clone(),
|
||||
rdp_state,
|
||||
ra_state,
|
||||
},
|
||||
);
|
||||
if pool.len() > 30 {
|
||||
pool.pop_first();
|
||||
false
|
||||
}
|
||||
} else {
|
||||
let compressed_file = if let Ok(device_data) =
|
||||
postcard::to_stdvec(DEVICE_CLONE.lock().await.deref())
|
||||
&& let Ok(saves_data) = postcard::to_stdvec(SAVES_CLONE.lock().await.deref())
|
||||
&& let Ok(compressed_file) = ui::storage::compress_file(&[
|
||||
(&device_data, "device"),
|
||||
(&saves_data, "saves"),
|
||||
(&rdp_state, "rdp_state"),
|
||||
(&ra_state, "ra_state"),
|
||||
]) {
|
||||
Some(compressed_file)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(compressed_file) = compressed_file {
|
||||
if let Err(e) = tokio::fs::write(save_path, compressed_file).await {
|
||||
eprintln!("Error writing savestate: {}", e);
|
||||
error = true;
|
||||
}
|
||||
} else {
|
||||
error = true;
|
||||
eprintln!("Error compressing savestate");
|
||||
}
|
||||
}
|
||||
eprintln!("Error compressing savestate");
|
||||
true
|
||||
};
|
||||
|
||||
if error {
|
||||
ui::video::onscreen_message(
|
||||
&format!("Failed to create savestate in slot {}", save_state_slot),
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
|
||||
);
|
||||
} else if !rewind {
|
||||
} else {
|
||||
ui::video::onscreen_message(
|
||||
&format!("Savestate created in slot {}", save_state_slot),
|
||||
ui::video::MESSAGE_LENGTH_MESSAGE_VERY_SHORT,
|
||||
@@ -231,163 +230,45 @@ pub fn load_savestate(device: &mut device::Device, rewind: bool, rewind_frame: O
|
||||
return;
|
||||
}
|
||||
|
||||
let state_data = if rewind {
|
||||
if let Some(rewind_frame) = rewind_frame {
|
||||
let timeout = std::time::Duration::from_secs(1);
|
||||
let now = std::time::Instant::now();
|
||||
loop {
|
||||
if let Some(state) = device
|
||||
.ui
|
||||
.savestate
|
||||
.rewind_pool
|
||||
.blocking_lock()
|
||||
.remove(&rewind_frame)
|
||||
{
|
||||
break Some(state);
|
||||
}
|
||||
if now.elapsed() > timeout {
|
||||
break None;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
} else if let Some((_key, state)) =
|
||||
device.ui.savestate.rewind_pool.blocking_lock().pop_last()
|
||||
let loaded = if rewind && let Some(rewind_frame) = rewind_frame {
|
||||
// Keep the entry: GGRS does not re-save the frame it rolls back to, and may
|
||||
// roll back to the same frame again (e.g. late inputs from another player).
|
||||
if let Some(state) = device.ui.savestate.rewind_pool.remove(&rewind_frame) {
|
||||
let loaded = apply_state(device, &state);
|
||||
device.ui.savestate.rewind_pool.insert(rewind_frame, state);
|
||||
loaded
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
let state_data = if rewind {
|
||||
device
|
||||
.ui
|
||||
.savestate
|
||||
.rewind_pool
|
||||
.pop_last()
|
||||
.map(|(_key, state)| state)
|
||||
} else if let Ok(savestate) = std::fs::read(&device.ui.storage.paths.savestate_file_path)
|
||||
&& let Ok(device_data) = ui::storage::decompress_file(&savestate, "device")
|
||||
&& let Ok(saves_data) = ui::storage::decompress_file(&savestate, "saves")
|
||||
&& let Ok(rdp_state) = ui::storage::decompress_file(&savestate, "rdp_state")
|
||||
&& let Ok(ra_state) = ui::storage::decompress_file(&savestate, "ra_state")
|
||||
&& let Ok(state) = postcard::from_bytes::<Box<device::Device>>(&device_data)
|
||||
&& let Ok(saves) = postcard::from_bytes(&saves_data)
|
||||
{
|
||||
Some(state)
|
||||
Some(SavestateData {
|
||||
device: state,
|
||||
saves,
|
||||
rdp_state,
|
||||
ra_state,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Ok(savestate) = std::fs::read(&device.ui.storage.paths.savestate_file_path)
|
||||
&& let Ok(device_data) = ui::storage::decompress_file(&savestate, "device")
|
||||
&& let Ok(saves_data) = ui::storage::decompress_file(&savestate, "saves")
|
||||
&& let Ok(rdp_state) = ui::storage::decompress_file(&savestate, "rdp_state")
|
||||
&& let Ok(ra_state) = ui::storage::decompress_file(&savestate, "ra_state")
|
||||
&& let Ok(state) = postcard::from_bytes::<Box<device::Device>>(&device_data)
|
||||
&& let Ok(saves) = postcard::from_bytes(&saves_data)
|
||||
{
|
||||
Some(SavestateData {
|
||||
device: state,
|
||||
saves,
|
||||
rdp_state,
|
||||
ra_state,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state_data.is_some_and(|state| apply_state(device, &state))
|
||||
};
|
||||
|
||||
if let Some(mut state) = state_data
|
||||
&& device.rdram.size == state.device.rdram.size
|
||||
{
|
||||
if device.netplay.is_none() {
|
||||
ui::video::idle();
|
||||
}
|
||||
|
||||
device.ui.savestate.last_rewind_saved = state.device.vi.elapsed_time;
|
||||
|
||||
std::mem::swap(&mut device.rng, &mut state.device.rng);
|
||||
|
||||
std::mem::swap(&mut device.ui.storage.saves, &mut state.saves);
|
||||
|
||||
std::mem::swap(&mut device.cpu, &mut state.device.cpu);
|
||||
std::mem::swap(&mut device.pif, &mut state.device.pif);
|
||||
|
||||
std::mem::swap(&mut device.cart, &mut state.device.cart);
|
||||
std::mem::swap(&mut device.cart.rom, &mut state.device.cart.rom); // ROM is not included in the savestate
|
||||
|
||||
std::mem::swap(&mut device.memory, &mut state.device.memory);
|
||||
std::mem::swap(&mut device.rsp, &mut state.device.rsp);
|
||||
std::mem::swap(&mut device.rdp, &mut state.device.rdp);
|
||||
|
||||
device.rdram.mem.copy_from_slice(&state.device.rdram.mem); // RDRAM address should not change
|
||||
std::mem::swap(&mut device.rdram.regs, &mut state.device.rdram.regs);
|
||||
|
||||
std::mem::swap(&mut device.mi, &mut state.device.mi);
|
||||
std::mem::swap(&mut device.pi, &mut state.device.pi);
|
||||
std::mem::swap(&mut device.vi, &mut state.device.vi);
|
||||
std::mem::swap(&mut device.ai, &mut state.device.ai);
|
||||
std::mem::swap(&mut device.si, &mut state.device.si);
|
||||
std::mem::swap(&mut device.ri, &mut state.device.ri);
|
||||
std::mem::swap(&mut device.vru, &mut state.device.vru);
|
||||
std::mem::swap(&mut device.cheats, &mut state.device.cheats);
|
||||
|
||||
std::mem::swap(&mut device.transferpaks, &mut state.device.transferpaks);
|
||||
for (i, item) in device.transferpaks.iter_mut().enumerate() {
|
||||
std::mem::swap(
|
||||
&mut item.cart.rom,
|
||||
&mut state.device.transferpaks[i].cart.rom,
|
||||
); // ROM is not included in the savestate
|
||||
}
|
||||
|
||||
device::memory::init(device);
|
||||
if device.mi.regs[device::mi::MI_INIT_MODE_REG] & device::mi::MI_INIT_MODE != 0 {
|
||||
device::rdram::set_write_handler(device, device::rdram::write_mem_repeat);
|
||||
}
|
||||
device::vi::set_expected_refresh_rate(device);
|
||||
device::cpu::map_instructions(device);
|
||||
device::cop0::map_instructions(device);
|
||||
device::cop1::map_instructions(device);
|
||||
device::cop1::set_host_flush_mode(device);
|
||||
device::cop2::map_instructions(device);
|
||||
device::rsp_cpu::map_instructions(device);
|
||||
|
||||
let mut mem_addr = 0x1000;
|
||||
while mem_addr < 0x2000 {
|
||||
let data =
|
||||
u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap());
|
||||
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func =
|
||||
device::rsp_cpu::decode_opcode(device, data);
|
||||
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data;
|
||||
mem_addr += 4;
|
||||
}
|
||||
|
||||
for line_index in 0..512 {
|
||||
for i in 0..8 {
|
||||
device.memory.icache[line_index].instruction[i] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[i]);
|
||||
}
|
||||
}
|
||||
|
||||
device::pif::connect_pif_channels(device);
|
||||
for item in device.pif.channels.iter_mut().take(4) {
|
||||
if let Some(handler) = item.pak_handler {
|
||||
if handler.pak_type == device::controller::PakType::RumblePak {
|
||||
let rumblepak_handler = device::controller::PakHandler {
|
||||
read: device::controller::rumble::read,
|
||||
write: device::controller::rumble::write,
|
||||
pak_type: device::controller::PakType::RumblePak,
|
||||
};
|
||||
item.pak_handler = Some(rumblepak_handler);
|
||||
} else if handler.pak_type == device::controller::PakType::MemPak {
|
||||
let mempak_handler = device::controller::PakHandler {
|
||||
read: device::controller::mempak::read,
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: device::controller::PakType::MemPak,
|
||||
};
|
||||
item.pak_handler = Some(mempak_handler);
|
||||
} else if handler.pak_type == device::controller::PakType::TransferPak {
|
||||
let tpak_handler = device::controller::PakHandler {
|
||||
read: device::controller::transferpak::read,
|
||||
write: device::controller::transferpak::write,
|
||||
pak_type: device::controller::PakType::TransferPak,
|
||||
};
|
||||
item.pak_handler = Some(tpak_handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui::audio::update_freq(device);
|
||||
ui::video::load_state(device, state.rdp_state.as_ptr(), state.rdp_state.len());
|
||||
|
||||
if !state.ra_state.is_empty() {
|
||||
retroachievements::load_state(state.ra_state.as_ptr(), state.ra_state.len());
|
||||
} else {
|
||||
retroachievements::load_state(std::ptr::null(), 0);
|
||||
}
|
||||
|
||||
if device.cheats.enabled {
|
||||
cheats::execute_cheats(device, device.cheats.cheats.clone());
|
||||
}
|
||||
|
||||
if loaded {
|
||||
if !rewind {
|
||||
ui::video::onscreen_message(
|
||||
&format!(
|
||||
@@ -421,6 +302,80 @@ pub fn load_savestate(device: &mut device::Device, rewind: bool, rewind_frame: O
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore emulator state from `state`. The ROMs are not part of the savestate and are kept.
|
||||
fn apply_state(device: &mut device::Device, state: &SavestateData) -> bool {
|
||||
if device.rdram.size != state.device.rdram.size {
|
||||
return false;
|
||||
}
|
||||
|
||||
ui::video::idle();
|
||||
|
||||
device.ui.savestate.last_rewind_saved = state.device.vi.elapsed_time;
|
||||
|
||||
// clone_from keeps the RDRAM allocation (same size), so its address does not change.
|
||||
device.clone_state(&state.device);
|
||||
device.ui.storage.saves.copy_from(&state.saves);
|
||||
|
||||
device::memory::init(device);
|
||||
if device.mi.regs[device::mi::MI_INIT_MODE_REG] & device::mi::MI_INIT_MODE != 0 {
|
||||
device::rdram::set_write_handler(device, device::rdram::write_mem_repeat);
|
||||
}
|
||||
device::vi::set_expected_refresh_rate(device);
|
||||
device::cpu::map_instructions(device);
|
||||
device::cop0::map_instructions(device);
|
||||
device::cop1::map_instructions(device);
|
||||
device::cop1::set_host_flush_mode(device);
|
||||
device::cop2::map_instructions(device);
|
||||
device::rsp_cpu::map_instructions(device);
|
||||
|
||||
let mut mem_addr = 0x1000;
|
||||
while mem_addr < 0x2000 {
|
||||
let data = u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap());
|
||||
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func =
|
||||
device::rsp_cpu::decode_opcode(device, data);
|
||||
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data;
|
||||
mem_addr += 4;
|
||||
}
|
||||
|
||||
for line_index in 0..512 {
|
||||
for i in 0..8 {
|
||||
device.memory.icache[line_index].instruction[i] =
|
||||
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[i]);
|
||||
}
|
||||
}
|
||||
|
||||
device::pif::connect_pif_channels(device);
|
||||
for item in device.pif.channels.iter_mut().take(4) {
|
||||
if let Some(handler) = item.pak_handler.as_mut() {
|
||||
use device::controller::{PakType, mempak, rumble, transferpak};
|
||||
type Handler = fn(&mut device::Device, usize, u16, usize, usize);
|
||||
let (read, write): (Handler, Handler) = match handler.pak_type {
|
||||
PakType::RumblePak => (rumble::read, rumble::write),
|
||||
PakType::MemPak => (mempak::read, mempak::write),
|
||||
PakType::TransferPak => (transferpak::read, transferpak::write),
|
||||
PakType::None => continue,
|
||||
};
|
||||
handler.read = read;
|
||||
handler.write = write;
|
||||
}
|
||||
}
|
||||
|
||||
ui::audio::update_freq(device);
|
||||
ui::video::load_state(device, state.rdp_state.as_ptr(), state.rdp_state.len());
|
||||
|
||||
if !state.ra_state.is_empty() {
|
||||
retroachievements::load_state(state.ra_state.as_ptr(), state.ra_state.len());
|
||||
} else {
|
||||
retroachievements::load_state(std::ptr::null(), 0);
|
||||
}
|
||||
|
||||
if device.cheats.enabled {
|
||||
cheats::execute_cheats(device, device.cheats.cheats.clone());
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn default_pak_handler() -> fn(&mut device::Device, usize, u16, usize, usize) {
|
||||
device::controller::mempak::read
|
||||
}
|
||||
|
||||
+47
-1
@@ -41,7 +41,7 @@ pub struct RomSave {
|
||||
pub write_pending: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Saves {
|
||||
pub eeprom: Save,
|
||||
pub sram: Save,
|
||||
@@ -52,6 +52,52 @@ pub struct Saves {
|
||||
pub write_to_disk: bool,
|
||||
}
|
||||
|
||||
impl Save {
|
||||
fn copy_from(&mut self, other: &Save) {
|
||||
let Save {
|
||||
data,
|
||||
write_pending,
|
||||
} = other;
|
||||
self.data.clone_from(data);
|
||||
self.write_pending = *write_pending;
|
||||
}
|
||||
}
|
||||
|
||||
impl RomSave {
|
||||
fn copy_from(&mut self, other: &RomSave) {
|
||||
let RomSave {
|
||||
data,
|
||||
write_pending,
|
||||
} = other;
|
||||
self.data.clone_from(data);
|
||||
self.write_pending = *write_pending;
|
||||
}
|
||||
}
|
||||
|
||||
impl Saves {
|
||||
/// Copy `other` into `self`, reusing `self`'s buffers. A derived `clone_from` would
|
||||
/// allocate new ones on every savestate snapshot/load.
|
||||
pub fn copy_from(&mut self, other: &Saves) {
|
||||
// Destructure so adding a field forces this to be updated.
|
||||
let Saves {
|
||||
eeprom,
|
||||
sram,
|
||||
flash,
|
||||
mempak,
|
||||
sdcard,
|
||||
romsave,
|
||||
write_to_disk,
|
||||
} = other;
|
||||
self.eeprom.copy_from(eeprom);
|
||||
self.sram.copy_from(sram);
|
||||
self.flash.copy_from(flash);
|
||||
self.mempak.copy_from(mempak);
|
||||
self.sdcard.copy_from(sdcard);
|
||||
self.romsave.copy_from(romsave);
|
||||
self.write_to_disk = *write_to_disk;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_save_type(rom: &[u8], game_id: &str) -> Vec<SaveTypes> {
|
||||
if let Ok(header_type) = std::str::from_utf8(rom[0x3C..0x3E].try_into().unwrap())
|
||||
&& header_type == "ED"
|
||||
|
||||
Reference in new issue
Block a user