diff --git a/src/device.rs b/src/device.rs index c304393c..858d7e93 100644 --- a/src/device.rs +++ b/src/device.rs @@ -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 { diff --git a/src/device/cart.rs b/src/device/cart.rs index 56166e30..69b06cd1 100644 --- a/src/device/cart.rs +++ b/src/device/cart.rs @@ -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()]; diff --git a/src/device/controller/gbcart.rs b/src/device/controller/gbcart.rs index 42f94813..870c8a95 100644 --- a/src/device/controller/gbcart.rs +++ b/src/device/controller/gbcart.rs @@ -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] diff --git a/src/device/controller/transferpak.rs b/src/device/controller/transferpak.rs index 7002c42e..eb4e76e3 100644 --- a/src/device/controller/transferpak.rs +++ b/src/device/controller/transferpak.rs @@ -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]; diff --git a/src/device/rdram.rs b/src/device/rdram.rs index 6b9ebf7d..e8f67408 100644 --- a/src/device/rdram.rs +++ b/src/device/rdram.rs @@ -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, diff --git a/src/lib.rs b/src/lib.rs index bac31783..5fc8cd12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 diff --git a/src/netplay.rs b/src/netplay.rs index dc56b206..2ff878ba 100644 --- a/src/netplay.rs +++ b/src/netplay.rs @@ -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, @@ -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>, pub incoming_message: Vec, 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, }) } diff --git a/src/savestates.rs b/src/savestates.rs index 3dd49d0e..97e35af9 100644 --- a/src/savestates.rs +++ b/src/savestates.rs @@ -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; @@ -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>>, + pub rewind_pool: std::collections::BTreeMap, } +const REWIND_POOL_SIZE: usize = 30; + pub struct SavestateData { device: Box, saves: ui::storage::Saves, @@ -110,11 +110,16 @@ pub struct SavestateData { ra_state: Vec, } -static DEVICE_CLONE: std::sync::LazyLock>> = - std::sync::LazyLock::new(|| tokio::sync::Mutex::new(device::Device::new(false))); - -static SAVES_CLONE: std::sync::LazyLock> = - 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) { - 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 = vec![0; ui::video::state_size()]; - ui::video::save_state(rdp_state.as_mut_ptr()); - - let mut ra_state: Vec = 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::>(&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::>(&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 } diff --git a/src/ui/storage.rs b/src/ui/storage.rs index 6dfddaa6..0fe2469d 100644 --- a/src/ui/storage.rs +++ b/src/ui/storage.rs @@ -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 { if let Ok(header_type) = std::str::from_utf8(rom[0x3C..0x3E].try_into().unwrap()) && header_type == "ED"