From 72df783d8cc35026acddd5940e10efbc81047b7f Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Thu, 19 Mar 2026 14:06:53 +0100 Subject: [PATCH] Add CLI option to load a save state (#681) * Add CLI option to load a save state * more * more * more --- parallel-rdp/interface.cpp | 4 ++-- parallel-rdp/interface.hpp | 2 +- src/device.rs | 4 ++++ src/main.rs | 14 ++++++++++++++ src/savestates.rs | 8 ++++++++ src/ui/gui.rs | 2 ++ src/ui/netplay.rs | 4 ++++ src/ui/usb.rs | 1 + src/ui/video.rs | 1 + 9 files changed, 37 insertions(+), 3 deletions(-) diff --git a/parallel-rdp/interface.cpp b/parallel-rdp/interface.cpp index 7cca8e4..e2e0f9c 100644 --- a/parallel-rdp/interface.cpp +++ b/parallel-rdp/interface.cpp @@ -300,7 +300,7 @@ static ImageHandle create_message_image(Vulkan::Device &device, int width, const return handle; } -void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size) +void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size, uint32_t save_state_slot) { memset(&rdp_device, 0, sizeof(RDP_DEVICE)); @@ -365,7 +365,7 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_s callback.emu_running = true; callback.enable_speedlimiter = true; callback.paused = false; - callback.save_state_slot = 0; + callback.save_state_slot = save_state_slot; crop_letterbox = false; messages = std::queue(); diff --git a/parallel-rdp/interface.hpp b/parallel-rdp/interface.hpp index 7106406..57c0dad 100644 --- a/parallel-rdp/interface.hpp +++ b/parallel-rdp/interface.hpp @@ -38,7 +38,7 @@ extern "C" uint32_t save_state_slot; } CALL_BACK; - void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size); + void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size, uint32_t save_state_slot); void rdp_close(); void rdp_set_vi_register(uint32_t reg, uint32_t value); void rdp_update_screen(); diff --git a/src/device.rs b/src/device.rs index 2409507..f86ed3f 100644 --- a/src/device.rs +++ b/src/device.rs @@ -50,6 +50,10 @@ pub fn run_game(device: &mut Device, rom_contents: Vec, game_settings: ui::g if game_settings.disable_expansion_pak { device.rdram.size = 0x400000; } + if let Some(slot) = game_settings.load_savestate_slot { + device.ui.storage.save_state_slot = slot; + device.load_state = true; + } init_rng(device); diff --git a/src/main.rs b/src/main.rs index 4ae3c1e..ced0156 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,6 +72,13 @@ struct Args { help = "Clear all input profile bindings and controller assignments" )] clear_input_bindings: bool, + #[arg( + short = 's', + long, + value_name = "SLOT", + help = "Load savestate from slot 0-9 when starting the game" + )] + load_state: Option, } fn main() -> std::io::Result<()> { @@ -98,6 +105,12 @@ fn main() -> std::io::Result<()> { ))); }; + if let Some(slot) = args.load_state + && slot > 9 + { + return Err(Error::other("Savestate slot must be between 0 and 9")); + } + let handle = runtime.spawn(async move { let mut device = device::Device::new(); let overclock = device.ui.config.emulation.overclock; @@ -116,6 +129,7 @@ fn main() -> std::io::Result<()> { overclock, disable_expansion_pak, cheats: game_cheats, + load_savestate_slot: args.load_state, }, ); }); diff --git a/src/savestates.rs b/src/savestates.rs index 5a13f2b..41fde77 100644 --- a/src/savestates.rs +++ b/src/savestates.rs @@ -219,6 +219,14 @@ pub fn load_savestate(device: &mut device::Device) { ), ); } + } else { + ui::video::onscreen_message( + &device.ui, + &format!( + "Could not find savestate in slot {}", + device.ui.storage.save_state_slot + ), + ); } } diff --git a/src/ui/gui.rs b/src/ui/gui.rs index e7de83e..3358dce 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -30,6 +30,7 @@ pub struct GameSettings { pub overclock: bool, pub disable_expansion_pak: bool, pub cheats: std::collections::HashMap>, + pub load_savestate_slot: Option, } fn check_latest_version(weak: slint::Weak) { @@ -458,6 +459,7 @@ fn open_rom(app: &AppWindow, usb: ui::Usb) { overclock, disable_expansion_pak, cheats: std::collections::HashMap::new(), // will be filled in later + load_savestate_slot: None, }, None, usb, diff --git a/src/ui/netplay.rs b/src/ui/netplay.rs index f839e45..159f8f8 100644 --- a/src/ui/netplay.rs +++ b/src/ui/netplay.rs @@ -704,6 +704,7 @@ fn create_session( overclock: overclock.parse().unwrap(), disable_expansion_pak: disable_expansion_pak.parse().unwrap(), cheats: serde_json::from_str(cheats).unwrap(), + load_savestate_slot: None, }, handle.get_peer_addr(), weak_app, @@ -821,6 +822,7 @@ fn join_session( overclock: overclock.parse().unwrap(), disable_expansion_pak: disable_expansion_pak.parse().unwrap(), cheats: serde_json::from_str(cheats).unwrap(), + load_savestate_slot: None, }, handle.get_peer_addr(), weak_app, @@ -1083,6 +1085,7 @@ fn setup_wait_window( overclock: game_settings.overclock, disable_expansion_pak: game_settings.disable_expansion_pak, cheats: game_settings.cheats, + load_savestate_slot: None, }, Some(NetplayDevice { peer_addr: socket_addr, @@ -1203,6 +1206,7 @@ pub fn netplay_window(app: &AppWindow, controller_paths: &[Option]) { overclock: handle.get_overclock_n64_cpu(), disable_expansion_pak: handle.get_disable_expansion_pak(), cheats: std::collections::HashMap::new(), // not used here + load_savestate_slot: None, }, handle.get_rom_dir(), weak_app, diff --git a/src/ui/usb.rs b/src/ui/usb.rs index d5c7db1..ebeffae 100644 --- a/src/ui/usb.rs +++ b/src/ui/usb.rs @@ -52,6 +52,7 @@ fn upload_rom( overclock: handle.get_overclock_n64_cpu(), disable_expansion_pak: handle.get_disable_expansion_pak(), cheats: std::collections::HashMap::new(), // will be filled in later + load_savestate_slot: None, }, None, ui::Usb { diff --git a/src/ui/video.rs b/src/ui/video.rs index 28e3660..006700b 100644 --- a/src/ui/video.rs +++ b/src/ui/video.rs @@ -81,6 +81,7 @@ pub fn init(device: &mut device::Device) { gfx_info, font_bytes.as_ptr() as *const std::ffi::c_void, font_bytes.len(), + device.ui.storage.save_state_slot, ) } }