From 970b3e38114d3b869bb4157ebe0d6df1a8df283c Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 23 Jun 2025 02:38:13 -0400 Subject: [PATCH 01/61] added ignore vscode --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0592392..40b79c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target .DS_Store +.vscode/ From f533585a7d67ddcadfda93c4578293e3befe638c Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 23 Jun 2025 03:01:03 -0400 Subject: [PATCH 02/61] merged dev and upstream --- Cargo.lock | 71 +++++----- src/config/app_config.rs | 102 +++++++++++--- src/hotkey.rs | 134 +++++++++++++++++- src/livesplit_renderer.rs | 279 +++++++++++++++++++------------------- src/main.rs | 44 +++--- 5 files changed, 408 insertions(+), 222 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b058bf..5dd5bef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,9 +543,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64-simd" @@ -1279,12 +1279,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1547,9 +1547,9 @@ dependencies = [ [[package]] name = "gif" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ "color_quant", "weezl", @@ -1868,9 +1868,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d75c7014ddab93c232bc6bb9f64790d3dfd1d605199acd4b40b6d69e691e9f" +checksum = "f6970fe7a5300b4b42e62c52efa0187540a5bef546c60edaf554ef595d2e6f0b" dependencies = [ "byteorder-lite", "quick-error", @@ -1941,9 +1941,9 @@ dependencies = [ [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" @@ -1980,9 +1980,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.173" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" @@ -2317,18 +2317,19 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2855,9 +2856,9 @@ dependencies = [ [[package]] name = "profiling" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" [[package]] name = "promising-future" @@ -2904,9 +2905,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -3460,9 +3461,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.103" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -3559,9 +3560,9 @@ dependencies = [ [[package]] name = "thread-priority" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e61777c6a097a7cd2247705eebd815ffde638f46e5cd9b776da609e41ac5228" +checksum = "cd4ef372b29fbcc6cb0cef97bcbf3ffa9da90e02eb23dbf14db48ce30d11d2d5" dependencies = [ "bitflags 2.9.1", "cfg-if", @@ -3717,9 +3718,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -4857,9 +4858,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" +checksum = "635887f4315a33cb714eb059bdbd7c1c92bfa71bc5b9d5115460502f788c2ab5" [[package]] name = "xdg-home" @@ -5093,18 +5094,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", @@ -5173,9 +5174,9 @@ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-jpeg" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6fe2e33d02a98ee64423802e16df3de99c43e5cf5ff983767e1128b394c8ac" +checksum = "7384255a918371b5af158218d131530f694de9ad3815ebdd0453a940485cb0fa" dependencies = [ "zune-core", ] diff --git a/src/config/app_config.rs b/src/config/app_config.rs index 39d2a67..5be00ca 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -1,9 +1,10 @@ use clap::Parser; use serde_derive::{Deserialize, Serialize}; +use crate::utils::*; use crate::hotkey::*; -#[derive(Deserialize, Serialize, Parser, Debug, Clone)] +#[derive(Default, Deserialize, Serialize, Parser, Debug, Clone)] #[clap(author, version, about, long_about = None)] pub struct AppConfig { #[clap(name = "load-splits", short = 's', long, value_parser)] @@ -13,17 +14,17 @@ pub struct AppConfig { #[clap(name = "load-autosplitter", short = 'a', long, value_parser)] pub recent_autosplitter: Option, #[clap(name = "use-autosplitter", long, action)] - pub use_autosplitter: Option, + pub use_autosplitter: Option, #[clap(name = "polling-rate", long, short = 'p', value_parser)] pub polling_rate: Option, #[clap(name = "frame-rate", long, short = 'f', value_parser)] pub frame_rate: Option, #[clap(name = "reset-timer-on-game-reset", long, value_parser)] - pub reset_timer_on_game_reset: Option, + pub reset_timer_on_game_reset: Option, #[clap(name = "reset-game-on-timer-reset", long, value_parser)] - pub reset_game_on_timer_reset: Option, + pub reset_game_on_timer_reset: Option, #[clap(name = "global-hotkeys", long, short = 'g', value_parser)] - pub global_hotkeys: Option, + pub global_hotkeys: Option, #[clap(skip)] pub hot_key_start: Option, #[clap(skip)] @@ -40,18 +41,18 @@ pub struct AppConfig { pub hot_key_comparison_prev: Option, } -#[derive(clap::ValueEnum, Clone, Copy, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] -pub enum YesOrNo { - #[default] - Yes, - No, -} +// #[derive(clap::ValueEnum, Clone, Copy, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] +// pub enum YesOrNo { +// #[default] +// Yes, +// No, +// } pub const DEFAULT_FRAME_RATE: f32 = 30.0; pub const DEFAULT_POLLING_RATE: f32 = 20.0; impl AppConfig { - fn new() -> Self { + pub fn new() -> Self { let modifiers = ::egui::Modifiers::default(); AppConfig { recent_splits: None, @@ -85,18 +86,79 @@ impl AppConfig { key: egui::Key::Num4, modifiers, }), - use_autosplitter: Some(YesOrNo::Yes), + use_autosplitter: Some(false), frame_rate: Some(DEFAULT_FRAME_RATE), polling_rate: Some(DEFAULT_POLLING_RATE), - reset_timer_on_game_reset: Some(YesOrNo::No), - reset_game_on_timer_reset: Some(YesOrNo::No), - global_hotkeys: Some(YesOrNo::Yes), + reset_timer_on_game_reset: Some(false), + reset_game_on_timer_reset: Some(false), + global_hotkeys: Some(true), } } -} -impl Default for AppConfig { - fn default() -> Self { - AppConfig::new() + pub fn save_app_config(&self) { + use std::io::Write; + let project_dirs = directories::ProjectDirs::from("", "", "annelid") // get directories + .ok_or("Unable to load computer configuration directory"); + println!("project_dirs = {:#?}", project_dirs); + + let config_dir = + project_dirs.unwrap(); // get preferences directory + println!("project_dirs = {:#?}", config_dir.preference_dir()); + + messagebox_on_error(|| { + std::fs::create_dir_all(config_dir.preference_dir()).expect("Created config dir"); // create preferences directory + + let mut config_path = config_dir.preference_dir().to_path_buf(); + config_path.push("settings.toml"); + + println!("Saving to {:#?}", config_path); + let f = std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(config_path)?; + let mut writer = std::io::BufWriter::new(f); + let toml = toml::to_string_pretty(&self)?; + writer.write_all(toml.as_bytes())?; + writer.flush()?; + Ok(()) + }); + } + + pub fn load_app_config(mut self) -> Self { + let project_dirs = directories::ProjectDirs::from("", "", "annelid") // get directories + .ok_or("Unable to load computer configuration directory"); + println!("project_dirs = {:#?}", project_dirs); + + let config_dir = + project_dirs.unwrap(); // get preferences directory + println!("project_dirs = {:#?}", config_dir.preference_dir()); + + messagebox_on_error(|| { + use std::io::Read; + let mut config_path = config_dir.preference_dir().to_path_buf(); + config_path.push("settings.toml"); + println!("Loading from {:#?}", config_path); + let saved_config: AppConfig = std::fs::File::open(config_path) + .and_then(|mut f| { + let mut buffer = String::new(); + f.read_to_string(&mut buffer)?; + match toml::from_str(&buffer) { + Ok(app_config) => Ok(app_config), + Err(e) => Err(from_de_error(e)), + } + + // }).unwrap; + }).unwrap_or(Self::new()); + self = saved_config; + Ok(()) + }); + self } } + +// impl Default for AppConfig { +// fn default() -> Self { +// AppConfig::new() +// } +// } \ No newline at end of file diff --git a/src/hotkey.rs b/src/hotkey.rs index 5a40953..2aa3e56 100644 --- a/src/hotkey.rs +++ b/src/hotkey.rs @@ -1,3 +1,5 @@ +use std::ops::BitOr; + use serde_derive::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Debug, Copy, Clone)] @@ -12,6 +14,118 @@ impl HotKey { } } +pub fn to_egui_keycode(key: livesplit_hotkey::KeyCode) -> ::egui::Key { + use livesplit_hotkey::KeyCode::*; + + match key { + ArrowDown => egui::Key::ArrowDown, + ArrowLeft => egui::Key::ArrowLeft, + ArrowRight => egui::Key::ArrowRight, + ArrowUp => egui::Key::ArrowUp, + Escape => egui::Key::Escape, + Tab => egui::Key::Tab, + Backspace => egui::Key::Backspace, + Enter => egui::Key::Enter, + Space => egui::Key::Space, + Insert => egui::Key::Insert, + Delete => egui::Key::Delete, + Home => egui::Key::Home, + End => egui::Key::End, + PageUp => egui::Key::PageUp, + PageDown => egui::Key::PageDown, + Numpad0 => egui::Key::Num0, + Numpad1 => egui::Key::Num1, + Numpad2 => egui::Key::Num2, + Numpad3 => egui::Key::Num3, + Numpad4 => egui::Key::Num4, + Numpad5 => egui::Key::Num5, + Numpad6 => egui::Key::Num6, + Numpad7 => egui::Key::Num7, + Numpad8 => egui::Key::Num8, + Numpad9 => egui::Key::Num9, + KeyA => egui::Key::A, + KeyB => egui::Key::B, + KeyC => egui::Key::C, + KeyD => egui::Key::D, + KeyE => egui::Key::E, + KeyF => egui::Key::F, + KeyG => egui::Key::G, + KeyH => egui::Key::H, + KeyI => egui::Key::I, + KeyJ => egui::Key::J, + KeyK => egui::Key::K, + KeyL => egui::Key::L, + KeyM => egui::Key::M, + KeyN => egui::Key::N, + KeyO => egui::Key::O, + KeyP => egui::Key::P, + KeyQ => egui::Key::Q, + KeyR => egui::Key::R, + KeyS => egui::Key::S, + KeyT => egui::Key::T, + KeyU => egui::Key::U, + KeyV => egui::Key::V, + KeyW => egui::Key::W, + KeyX => egui::Key::X, + KeyY => egui::Key::Y, + KeyZ => egui::Key::Z, + F1 => egui::Key::F1, + F2 => egui::Key::F2, + F3 => egui::Key::F3, + F4 => egui::Key::F4, + F5 => egui::Key::F5, + F6 => egui::Key::F6, + F7 => egui::Key::F7, + F8 => egui::Key::F8, + F9 => egui::Key::F9, + F10 => egui::Key::F10, + F11 => egui::Key::F11, + F12 => egui::Key::F12, + F13 => egui::Key::F13, + F14 => egui::Key::F14, + F15 => egui::Key::F15, + F16 => egui::Key::F16, + F17 => egui::Key::F17, + F18 => egui::Key::F18, + F19 => egui::Key::F19, + F20 => egui::Key::F20, + F21 => egui::Key::F21, + F22 => egui::Key::F22, + F23 => egui::Key::F23, + F24 => egui::Key::F24, + // F24 => egui::Key::F25, // TODO: hotkey lib doesn't support this yet + // F24 => egui::Key::F26, // TODO: hotkey lib doesn't support this yet + // F24 => egui::Key::F27, // TODO: hotkey lib doesn't support this yet + // F24 => egui::Key::F28, // TODO: hotkey lib doesn't support this yet + // F24 => egui::Key::F29, // TODO: hotkey lib doesn't support this yet + // F24 => egui::Key::F30, // TODO: hotkey lib doesn't support this yet + // F24 => egui::Key::F31, // TODO: hotkey lib doesn't support this yet + // F24 => egui::Key::F32, // TODO: hotkey lib doesn't support this yet + // F24 => egui::Key::F33, // TODO: hotkey lib doesn't support this yet + // F24 => egui::Key::F34, // TODO: hotkey lib doesn't support this yet + // F24 => egui::Key::F35, // TODO: hotkey lib doesn't support this yet + Minus => egui::Key::Minus, + // Equal => egui::Key::Plus, + Equal => egui::Key::Equals, + Copy => egui::Key::Copy, + Cut => egui::Key::Cut, + Paste => egui::Key::Paste, + Comma => egui::Key::Comma, + Backslash => egui::Key::Backslash, + Slash => egui::Key::Slash, + // Slash => egui::Key::Questionmark, + IntlBackslash => egui::Key::Pipe, + BracketLeft => egui::Key::OpenBracket, + BracketRight => egui::Key::CloseBracket, + Backquote => egui::Key::Backtick, + Period => egui::Key::Period, + Semicolon => egui::Key::Semicolon, + // Semicolon => egui::Key::Colon, + Quote => egui::Key::Quote, + _ => egui::Key::Comma, + } +} + pub fn to_livesplit_keycode(key: &::egui::Key) -> livesplit_hotkey::KeyCode { use livesplit_hotkey::KeyCode::*; @@ -144,6 +258,24 @@ pub fn to_livesplit_keycode_alternative(key: &::egui::Key) -> Option ::egui::Modifiers { + // use livesplit_hotkey::Modifiers; + let mut mods = ::egui::Modifiers::NONE; + if modifiers.contains(livesplit_hotkey::Modifiers::SHIFT) { + mods = ::egui::Modifiers::bitor(mods, ::egui::Modifiers::SHIFT); + }; + if modifiers.contains(livesplit_hotkey::Modifiers::CONTROL) { + mods = ::egui::Modifiers::bitor(mods, ::egui::Modifiers::CTRL); + }; + if modifiers.contains(livesplit_hotkey::Modifiers::ALT) { + mods = ::egui::Modifiers::bitor(mods, ::egui::Modifiers::ALT); + }; + if modifiers.contains(livesplit_hotkey::Modifiers::META) { + mods = ::egui::Modifiers::bitor(mods, ::egui::Modifiers::COMMAND); + }; + mods +} + pub fn to_livesplit_modifiers(modifiers: &::egui::Modifiers) -> livesplit_hotkey::Modifiers { use livesplit_hotkey::Modifiers; let mut mods = Modifiers::empty(); @@ -160,4 +292,4 @@ pub fn to_livesplit_modifiers(modifiers: &::egui::Modifiers) -> livesplit_hotkey mods.insert(Modifiers::META) }; mods -} +} \ No newline at end of file diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 2830db6..fe9932f 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -29,7 +29,7 @@ pub struct LiveSplitCoreRenderer { can_exit: bool, is_exiting: bool, thread_chan: std::sync::mpsc::SyncSender, - project_dirs: directories::ProjectDirs, + // project_dirs: directories::ProjectDirs, pub app_config: std::sync::Arc>, app_config_processed: bool, glow_canvas: GlowCanvas, @@ -76,8 +76,8 @@ impl LiveSplitCoreRenderer { layout: Layout, settings: Arc>, chan: std::sync::mpsc::SyncSender, - project_dirs: directories::ProjectDirs, - cli_config: AppConfig, + // project_dirs: directories::ProjectDirs, + config: AppConfig, ) -> Self { LiveSplitCoreRenderer { timer, @@ -90,8 +90,8 @@ impl LiveSplitCoreRenderer { can_exit: false, is_exiting: false, thread_chan: chan, - project_dirs, - app_config: std::sync::Arc::new(std::sync::RwLock::new(cli_config)), + // project_dirs, + app_config: std::sync::Arc::new(std::sync::RwLock::new(config)), app_config_processed: false, glow_canvas: GlowCanvas::new(), global_hotkey_hook: None, @@ -144,86 +144,79 @@ impl LiveSplitCoreRenderer { Ok(()) } - pub fn save_app_config(&self) { - messagebox_on_error(|| { - use std::io::Write; - let mut config_path = self.project_dirs.preference_dir().to_path_buf(); - config_path.push("settings.toml"); - println!("Saving to {:#?}", config_path); - let f = std::fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(config_path)?; - let mut writer = std::io::BufWriter::new(f); - let toml = toml::to_string_pretty(&self.app_config)?; - writer.write_all(toml.as_bytes())?; - writer.flush()?; - Ok(()) - }); - } + // pub fn save_app_config(&self) { + // messagebox_on_error(|| { + // use std::io::Write; + // let mut config_path = self.project_dirs.preference_dir().to_path_buf(); + // config_path.push("settings.toml"); + // println!("Saving to {:#?}", config_path); + // let f = std::fs::OpenOptions::new() + // .create(true) + // .write(true) + // .truncate(true) + // .open(config_path)?; + // let mut writer = std::io::BufWriter::new(f); + // let toml = toml::to_string_pretty(&self.app_config)?; + // writer.write_all(toml.as_bytes())?; + // writer.flush()?; + // Ok(()) + // }); + // } - pub fn load_app_config(&mut self) { - messagebox_on_error(|| { - use std::io::Read; - let mut config_path = self.project_dirs.preference_dir().to_path_buf(); - config_path.push("settings.toml"); - println!("Loading from {:#?}", config_path); - let saved_config: AppConfig = std::fs::File::open(config_path) - .and_then(|mut f| { - let mut buffer = String::new(); - f.read_to_string(&mut buffer)?; - match toml::from_str(&buffer) { - Ok(app_config) => Ok(app_config), - Err(e) => Err(from_de_error(e)), - } - }) - .unwrap_or_default(); - // Let the CLI options take precedent if any provided - // TODO: this logic is bad, I really need to know if the CLI - // stuff was present and whether the stuff was present in the config - // but instead I just see two different states that need to be merged. - let cli_config = self - .app_config - .read() - .map_err(|e| anyhow!("failed to acquire read lock on config: {e}"))? - .clone(); - let mut new_app_config = saved_config; - if cli_config.recent_layout.is_some() { - new_app_config.recent_layout = cli_config.recent_layout; - } - if cli_config.recent_splits.is_some() { - new_app_config.recent_splits = cli_config.recent_splits; - } - if cli_config.recent_autosplitter.is_some() { - new_app_config.recent_autosplitter = cli_config.recent_autosplitter; - } - if cli_config.use_autosplitter.is_some() { - new_app_config.use_autosplitter = cli_config.use_autosplitter; - } - if cli_config.frame_rate.is_some() { - new_app_config.frame_rate = cli_config.frame_rate; - } - if cli_config.polling_rate.is_some() { - new_app_config.polling_rate = cli_config.polling_rate; - } - if cli_config.reset_timer_on_game_reset.is_some() { - new_app_config.reset_timer_on_game_reset = cli_config.reset_timer_on_game_reset; - } - if cli_config.reset_game_on_timer_reset.is_some() { - new_app_config.reset_game_on_timer_reset = cli_config.reset_game_on_timer_reset; - } - if cli_config.global_hotkeys.is_some() { - new_app_config.global_hotkeys = cli_config.global_hotkeys; - } - *self - .app_config - .write() - .map_err(|e| anyhow!("failed to acquire write lock on config: {e}"))? = - new_app_config; - Ok(()) - }); - } + // pub fn load_app_config(&mut self) { + // messagebox_on_error(|| { + // use std::io::Read; + // // let cli_count = std::env::args_os().count(); + // let mut config_path = self.project_dirs.preference_dir().to_path_buf(); + // config_path.push("settings.toml"); + // println!("Loading from {:#?}", config_path); + // let saved_config: AppConfig = std::fs::File::open(config_path) + // .and_then(|mut f| { + // let mut buffer = String::new(); + // f.read_to_string(&mut buffer)?; + // match toml::from_str(&buffer) { + // Ok(app_config) => Ok(app_config), + // Err(e) => Err(from_de_error(e)), + // } + // }) + // .unwrap_or_default(); + // // Let the CLI options take precedent if any provided + // // TODO: this logic is bad, I really need to know if the CLI + // // stuff was present and whether the stuff was present in the config + // // but instead I just see two different states that need to be merged. + // let cli_config = self.app_config.read().unwrap().clone(); + // let mut new_app_config = saved_config; + // if cli_config.recent_layout.is_some() { + // new_app_config.recent_layout = cli_config.recent_layout; + // } + // if cli_config.recent_splits.is_some() { + // new_app_config.recent_splits = cli_config.recent_splits; + // } + // if cli_config.recent_autosplitter.is_some() { + // new_app_config.recent_autosplitter = cli_config.recent_autosplitter; + // } + // if cli_config.use_autosplitter.is_some() { + // new_app_config.use_autosplitter = cli_config.use_autosplitter; + // } + // if cli_config.frame_rate.is_some() { + // new_app_config.frame_rate = cli_config.frame_rate; + // } + // if cli_config.polling_rate.is_some() { + // new_app_config.polling_rate = cli_config.polling_rate; + // } + // if cli_config.reset_timer_on_game_reset.is_some() { + // new_app_config.reset_timer_on_game_reset = cli_config.reset_timer_on_game_reset; + // } + // if cli_config.reset_game_on_timer_reset.is_some() { + // new_app_config.reset_game_on_timer_reset = cli_config.reset_game_on_timer_reset; + // } + // if cli_config.global_hotkeys.is_some() { + // new_app_config.global_hotkeys = cli_config.global_hotkeys; + // } + // *self.app_config.write().unwrap() = new_app_config; + // Ok(()) + // }); + // } pub fn process_app_config(&mut self, ctx: &egui::Context) { use anyhow::Context; @@ -628,14 +621,14 @@ impl LiveSplitCoreRenderer { .map_err(|e| println!("reset lock failed: {e}")); if app_cfg .read() - .map(|g| g.use_autosplitter == Some(YesOrNo::Yes)) + .map(|g| g.use_autosplitter == Some(true)) .unwrap_or(false) { tc.try_send(ThreadEvent::TimerReset).unwrap_or(()); } - } - })?; - } + } + })?; + } if let Some(hk) = cfg.hot_key_undo { reg(hook, &hk, { let timer = timer.clone(); @@ -645,8 +638,8 @@ impl LiveSplitCoreRenderer { .map(|mut g| g.undo_split().ok()) .map_err(|e| println!("undo lock failed: {e}")); } - })?; - } + })?; + } if let Some(hk) = cfg.hot_key_skip { reg(hook, &hk, { let timer = timer.clone(); @@ -656,8 +649,8 @@ impl LiveSplitCoreRenderer { .map(|mut g| g.skip_split().ok()) .map_err(|e| println!("skip split lock failed: {e}")); } - })?; - } + })?; + } if let Some(hk) = cfg.hot_key_pause { reg(hook, &hk, { let timer = timer.clone(); @@ -667,8 +660,8 @@ impl LiveSplitCoreRenderer { .map(|mut g| g.toggle_pause().ok()) .map_err(|e| println!("toggle pause lock failed: {e}")); } - })?; - } + })?; + } if let Some(hk) = cfg.hot_key_comparison_next { reg(hook, &hk, { let timer = timer.clone(); @@ -678,8 +671,8 @@ impl LiveSplitCoreRenderer { .map(|mut g| g.switch_to_next_comparison()) .map_err(|e| println!("next comparison lock failed: {e}")); } - })?; - } + })?; + } if let Some(hk) = cfg.hot_key_comparison_prev { reg(hook, &hk, { let timer = timer.clone(); @@ -689,8 +682,8 @@ impl LiveSplitCoreRenderer { .map(|mut g| g.switch_to_previous_comparison()) .map_err(|e| println!("prev comparison lock failed: {e}")); } - })?; - } + })?; + } println!("registered"); Ok(()) @@ -721,7 +714,7 @@ impl eframe::App for LiveSplitCoreRenderer { self.is_exiting = true; self.confirm_save(frame.gl().expect("No GL context")) .unwrap(); - self.save_app_config(); + self.app_config.read().unwrap().save_app_config(); // aquire read lock then save app config } }); if self.can_exit { @@ -838,7 +831,7 @@ impl eframe::App for LiveSplitCoreRenderer { if ui.button("Reset").clicked() { // TODO: fix this unwrap self.timer.write().unwrap().reset(true).ok(); - if self.app_config.read().unwrap().use_autosplitter == Some(YesOrNo::Yes) { + if self.app_config.read().unwrap().use_autosplitter == Some(true) { self.thread_chan .try_send(ThreadEvent::TimerReset) .unwrap_or(()); @@ -892,7 +885,7 @@ impl eframe::App for LiveSplitCoreRenderer { }); { let config = self.app_config.read().unwrap(); - if config.global_hotkeys != Some(YesOrNo::Yes) { + if config.global_hotkeys != Some(true) { ctx.input_mut(|input| { if let Some(hot_key) = config.hot_key_start { if input.consume_key(hot_key.modifiers, hot_key.key) { @@ -904,7 +897,7 @@ impl eframe::App for LiveSplitCoreRenderer { if input.consume_key(hot_key.modifiers, hot_key.key) { // TODO: fix this unwrap self.timer.write().unwrap().reset(true).ok(); - if config.use_autosplitter == Some(YesOrNo::Yes) { + if config.use_autosplitter == Some(true) { self.thread_chan .try_send(ThreadEvent::TimerReset) .unwrap_or(()); @@ -956,8 +949,8 @@ pub fn app_init( ) { let context = cc.egui_ctx.clone(); context.set_visuals(egui::Visuals::dark()); - app.load_app_config(); - if app.app_config.read().unwrap().global_hotkeys == Some(YesOrNo::Yes) { + // app.load_app_config(); + if app.app_config.read().unwrap().global_hotkeys == Some(true) { messagebox_on_error(|| app.enable_global_hotkeys()); } let frame_rate = app @@ -994,7 +987,7 @@ pub fn app_init( let settings = app.settings.clone(); let app_config = app.app_config.clone(); // This thread deals with polling the SNES at a fixed rate. - if app_config.read().unwrap().use_autosplitter == Some(YesOrNo::Yes) { + if app_config.read().unwrap().use_autosplitter == Some(true) { let _snes_polling_thread = ThreadBuilder::default() .name("SNES Polling Thread".to_owned()) // We could change this thread priority, but we probably @@ -1002,30 +995,30 @@ pub fn app_init( // polling of SNES state .spawn(move |_| { loop { - let latency = Arc::new(RwLock::new((0.0, 0.0))); + let latency = Arc::new(RwLock::new((0.0, 0.0))); print_on_error(|| -> anyhow::Result<()> { let mut client = crate::usb2snes::SyncClient::connect() .context("creating usb2snes connection")?; - client.set_name("annelid")?; - println!("Server version is {:?}", client.app_version()?); - let mut devices = client.list_device()?.to_vec(); - if devices.len() != 1 { - if devices.is_empty() { + client.set_name("annelid")?; + println!("Server version is {:?}", client.app_version()?); + let mut devices = client.list_device()?.to_vec(); + if devices.len() != 1 { + if devices.is_empty() { Err(anyhow!("No devices present"))?; - } else { + } else { Err(anyhow!("You need to select a device: {:#?}", devices))?; - } } + } let device = devices.pop().ok_or(anyhow!("Device list was empty"))?; - println!("Using device: {}", device); - client.attach(&device)?; - println!("Connected."); - println!("{:#?}", client.info()?); + println!("Using device: {}", device); + client.attach(&device)?; + println!("Connected."); + println!("{:#?}", client.info()?); let mut autosplitter: Box = Box::new(SuperMetroidAutoSplitter::new(settings.clone())); - loop { + loop { let summary = autosplitter.update(&mut client)?; - if summary.start { + if summary.start { timer .write() .map_err(|e| { @@ -1033,16 +1026,16 @@ pub fn app_init( })? .start() .ok(); - } - if summary.reset + } + if summary.reset && app_config .read() .map_err(|e| { anyhow!("failed to acquire read lock on config: {e}") })? .reset_timer_on_game_reset - == Some(YesOrNo::Yes) - { + == Some(true) + { timer .write() .map_err(|e| { @@ -1050,16 +1043,16 @@ pub fn app_init( })? .reset(true) .ok(); - } - if summary.split { + } + if summary.split { if let Some(t) = autosplitter.gametime_to_seconds() { - timer - .write() + timer + .write() .map_err(|e| { anyhow!("failed to acquire write lock on timer: {e}") })? .set_game_time(t) - .ok(); + .ok(); } timer .write() @@ -1068,32 +1061,32 @@ pub fn app_init( })? .split() .ok(); - } - { + } + { *latency.write() = (summary.latency_average, summary.latency_stddev); - } - // If the timer gets reset, we need to make a fresh snes state - if let Ok(ThreadEvent::TimerReset) = sync_receiver.try_recv() { + } + // If the timer gets reset, we need to make a fresh snes state + if let Ok(ThreadEvent::TimerReset) = sync_receiver.try_recv() { autosplitter.reset_game_tracking(); - //Reset the snes + //Reset the snes if app_config .read() .map_err(|e| { anyhow!("failed to acquire read lock on config: {e}") })? .reset_game_on_timer_reset - == Some(YesOrNo::Yes) - { - client.reset()?; - } + == Some(true) + { + client.reset()?; } - std::thread::sleep(std::time::Duration::from_millis( - (1000.0 / polling_rate) as u64, - )); } - }); - std::thread::sleep(std::time::Duration::from_millis(1000)); + std::thread::sleep(std::time::Duration::from_millis( + (1000.0 / polling_rate) as u64, + )); + } + }); + std::thread::sleep(std::time::Duration::from_millis(1000)); } }) //TODO: fix this unwrap diff --git a/src/main.rs b/src/main.rs index 8e56819..e141411 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ use eframe::egui; use livesplit_core::layout::{ComponentSettings, LayoutSettings}; use livesplit_core::{Layout, Run, Segment, Timer}; use parking_lot::RwLock; +use std::env; use std::error::Error; use std::sync::Arc; @@ -54,44 +55,41 @@ fn customize_timer(timer: &mut livesplit_core::component::timer::Settings) { } fn main() -> std::result::Result<(), Box> { - let cli_config = AppConfig::parse(); - let settings = Settings::new(); - let settings = Arc::new(RwLock::new(settings)); - let mut run = Run::default(); - run.push_segment(Segment::new("")); + let mut config = AppConfig::load_app_config(AppConfig::new()); // load saved config into default object + AppConfig::update_from(&mut config, env::args()); // reads command line options into app config object + + let settings = Settings::new(); // setttings for SM autosplitter + let settings = Arc::new(RwLock::new(settings)); // creates a RW pointer to the autosplitter settings + + let mut run = Run::default(); // creates a default livesplit run object + run.push_segment(Segment::new("")); // push blank segment to run - let timer = Timer::new(run) - .expect("Run with at least one segment provided") - .into_shared(); + let timer = Timer::new(run) // create timer object + .expect("Run with at least one segment provided") // error message + .into_shared(); // makes timer sharable across threads + let options = eframe::NativeOptions { renderer: eframe::Renderer::Glow, viewport: egui::viewport::ViewportBuilder { ..Default::default() }, ..eframe::NativeOptions::default() - }; - let layout_settings = Layout::default_layout().settings(); + }; // create egui display options + + let layout_settings = Layout::default_layout().settings(); // create default layout settings //customize_layout(&mut layout_settings); - let layout = Layout::from_settings(layout_settings); + let layout = Layout::from_settings(layout_settings); // create default layout use std::sync::mpsc::sync_channel; - let (sync_sender, sync_receiver) = sync_channel(1); - - let project_dirs = directories::ProjectDirs::from("", "", "annelid") - .ok_or("Unable to computer configuration directory")?; - println!("project_dirs = {:#?}", project_dirs); - - let preference_dir = project_dirs.preference_dir(); - std::fs::create_dir_all(preference_dir)?; + let (sync_sender, sync_receiver) = sync_channel(1); // create thread let mut app = LiveSplitCoreRenderer::new( timer, layout, settings, sync_sender, - project_dirs, - cli_config, - ); + config, + ); // create livesplit-core renderer object eframe::run_native( "Annelid", @@ -99,7 +97,7 @@ fn main() -> std::result::Result<(), Box> { Box::new(move |cc| { livesplit_renderer::app_init(&mut app, sync_receiver, cc); Ok(Box::new(app)) - }), + }), // initialize livesplitrender and load into egui render box )?; Ok(()) } From 5d13330b752afd3e518180c8f2e81b45de447047 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 23 Jun 2025 03:13:49 -0400 Subject: [PATCH 03/61] cargo fmt --- src/config/app_config.rs | 15 +++++++-------- src/hotkey.rs | 2 +- src/main.rs | 18 ++++++------------ 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/config/app_config.rs b/src/config/app_config.rs index 5be00ca..214c277 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -1,8 +1,8 @@ use clap::Parser; use serde_derive::{Deserialize, Serialize}; -use crate::utils::*; use crate::hotkey::*; +use crate::utils::*; #[derive(Default, Deserialize, Serialize, Parser, Debug, Clone)] #[clap(author, version, about, long_about = None)] @@ -101,8 +101,7 @@ impl AppConfig { .ok_or("Unable to load computer configuration directory"); println!("project_dirs = {:#?}", project_dirs); - let config_dir = - project_dirs.unwrap(); // get preferences directory + let config_dir = project_dirs.unwrap(); // get preferences directory println!("project_dirs = {:#?}", config_dir.preference_dir()); messagebox_on_error(|| { @@ -130,8 +129,7 @@ impl AppConfig { .ok_or("Unable to load computer configuration directory"); println!("project_dirs = {:#?}", project_dirs); - let config_dir = - project_dirs.unwrap(); // get preferences directory + let config_dir = project_dirs.unwrap(); // get preferences directory println!("project_dirs = {:#?}", config_dir.preference_dir()); messagebox_on_error(|| { @@ -148,8 +146,9 @@ impl AppConfig { Err(e) => Err(from_de_error(e)), } - // }).unwrap; - }).unwrap_or(Self::new()); + // }).unwrap; + }) + .unwrap_or(Self::new()); self = saved_config; Ok(()) }); @@ -161,4 +160,4 @@ impl AppConfig { // fn default() -> Self { // AppConfig::new() // } -// } \ No newline at end of file +// } diff --git a/src/hotkey.rs b/src/hotkey.rs index 2aa3e56..55ce377 100644 --- a/src/hotkey.rs +++ b/src/hotkey.rs @@ -292,4 +292,4 @@ pub fn to_livesplit_modifiers(modifiers: &::egui::Modifiers) -> livesplit_hotkey mods.insert(Modifiers::META) }; mods -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index e141411..18b783b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,17 +57,17 @@ fn customize_timer(timer: &mut livesplit_core::component::timer::Settings) { fn main() -> std::result::Result<(), Box> { let mut config = AppConfig::load_app_config(AppConfig::new()); // load saved config into default object AppConfig::update_from(&mut config, env::args()); // reads command line options into app config object - + let settings = Settings::new(); // setttings for SM autosplitter let settings = Arc::new(RwLock::new(settings)); // creates a RW pointer to the autosplitter settings - + let mut run = Run::default(); // creates a default livesplit run object run.push_segment(Segment::new("")); // push blank segment to run let timer = Timer::new(run) // create timer object .expect("Run with at least one segment provided") // error message .into_shared(); // makes timer sharable across threads - + let options = eframe::NativeOptions { renderer: eframe::Renderer::Glow, viewport: egui::viewport::ViewportBuilder { @@ -75,21 +75,15 @@ fn main() -> std::result::Result<(), Box> { }, ..eframe::NativeOptions::default() }; // create egui display options - + let layout_settings = Layout::default_layout().settings(); // create default layout settings - //customize_layout(&mut layout_settings); + //customize_layout(&mut layout_settings); let layout = Layout::from_settings(layout_settings); // create default layout use std::sync::mpsc::sync_channel; let (sync_sender, sync_receiver) = sync_channel(1); // create thread - let mut app = LiveSplitCoreRenderer::new( - timer, - layout, - settings, - sync_sender, - config, - ); // create livesplit-core renderer object + let mut app = LiveSplitCoreRenderer::new(timer, layout, settings, sync_sender, config); // create livesplit-core renderer object eframe::run_native( "Annelid", From 33a13fd88b96dbb187bf088e978ff5911aaebc75 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 23 Jun 2025 03:15:00 -0400 Subject: [PATCH 04/61] added nwa autosplitter --- src/autosplitters.rs | 1 + src/autosplitters/nwa.rs | 187 ++++++++++++++++++++++++++++++++++++++ src/livesplit_renderer.rs | 102 ++++++++++----------- 3 files changed, 238 insertions(+), 52 deletions(-) create mode 100644 src/autosplitters/nwa.rs diff --git a/src/autosplitters.rs b/src/autosplitters.rs index fdd6d1a..b44a3ba 100644 --- a/src/autosplitters.rs +++ b/src/autosplitters.rs @@ -1,5 +1,6 @@ pub mod json; pub mod supermetroid; +pub mod nwa; use anyhow::Result; use livesplit_core::TimeSpan; diff --git a/src/autosplitters/nwa.rs b/src/autosplitters/nwa.rs new file mode 100644 index 0000000..9bb3abd --- /dev/null +++ b/src/autosplitters/nwa.rs @@ -0,0 +1,187 @@ +// pub mod nwa { + +use std::collections::HashMap; +use std::fmt::Debug; +use std::net::{SocketAddr, TcpStream, ToSocketAddrs, Shutdown}; +use std::io::{Write, Read, BufReader, BufRead}; +//use std::ptr::read; +use std::time::Duration; + +#[derive(Debug)] +#[derive(PartialEq)] +pub enum ErrorKind { + InvalidError, + InvalidCommand, + InvalidArgument, + NotAllowed, + ProtocolError +} +#[derive(Debug)] +pub struct NWAError { + pub kind : ErrorKind, + pub reason : String +} + +#[derive(Debug)] +pub enum AsciiReply { + Ok, + Hash(HashMap), + ListHash(Vec>) +} +#[derive(Debug)] +pub enum EmulatorReply { + Ascii(AsciiReply), + Error(NWAError), + Binary(Vec) +} + +pub struct NWASyncClient { + connection : TcpStream, + port : u32, + addr : SocketAddr +} + +impl NWASyncClient { + pub fn connect(ip : &str, port : u32) -> Result { + let addr: Vec<_> = format!("{}:{}", ip, port).to_socket_addrs().expect("Can't resolve address").collect(); + //println!("{:?}", addr); + let co = TcpStream::connect_timeout(&addr[0], Duration::from_millis(1000))?; + Ok(NWASyncClient { + connection : co, + port : port, + addr : addr[0] + }) + } + + pub fn get_reply(&mut self) -> Result { + let mut read_stream = BufReader::new(self.connection.try_clone().unwrap()); + let mut first_byte =[0 as u8; 1]; + if read_stream.read(&mut first_byte)? == 0 { + return Err(std::io::Error::new(std::io::ErrorKind::ConnectionAborted, "Read 0 byte")) + } + let first_byte = first_byte[0]; + + // Ascii + if first_byte == b'\n' { + let mut map : HashMap = HashMap::new(); + let mut line : Vec = vec![]; + loop { + line.clear(); + + let rep = read_stream.read_until(b'\n',&mut line)?; + //println!("{:?}", String::from_utf8(line.clone())); + if line[0] == b'\n' && map.len() == 0 { + return Ok(EmulatorReply::Ascii(AsciiReply::Ok)); + } + if line[0] == b'\n' { + break; + } + if rep == 0 { + break; + } + let mut key = [0 as u8; 100]; + let mut value = [0 as u8; 1024]; + let mut cpt = 0; + while line[cpt] != b':' && line[cpt] != b'\n'{ + key[cpt] = line[cpt]; + cpt += 1; + } + let end_key = cpt; + // Should have stopped on : + if line[cpt] == b'\n' { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "Mal formed reply")) + } + cpt += 1; + let offset = cpt; + while line[cpt] != b'\n' { + value[cpt - offset] = line[cpt]; + cpt += 1; + } + let end_value = cpt - offset; + map.insert(String::from_utf8_lossy(&key[0..end_key]).to_string(), String::from_utf8_lossy(&value[0..end_value]).to_string()); + } + if map.contains_key("error") { + if let Some(reason) = map.get("reason") { + let mut mkind = ErrorKind::InvalidError; + match map.get("error").unwrap().as_str() { + "protocol_error" => {mkind = ErrorKind::ProtocolError}, + "invalid_command" => {mkind = ErrorKind::InvalidCommand}, + "invalid_argument" => {mkind = ErrorKind::InvalidArgument}, + "not_allowed" => {mkind = ErrorKind::NotAllowed} + _ => {mkind = ErrorKind::InvalidError} + } + return Ok(EmulatorReply::Error(NWAError {kind : mkind, reason : reason.to_string()})) + } else { + return Ok(EmulatorReply::Error(NWAError {kind : ErrorKind::InvalidError, reason : String::from("Invalid reason")})) + } + } + return Ok(EmulatorReply::Ascii(AsciiReply::Hash(map))); + } + if first_byte == 0 { + let mut header = vec![0;4]; + let r_size = read_stream.read(&mut header)?; + println!(""); + //println!("Reading {:}", r_size); + //println!("Header : {:?}", header); + let header = header; + let mut size : u32 = 0; + size = (header[0] as u32) << 24; + size += (header[1] as u32) << 16; + size += (header[2] as u32) << 8; + size += header[3] as u32; + //println!("Size : {:}", size); + let msize = size as usize; + let mut data : Vec = vec![0; msize]; + //println!("Size : {:}", size); + read_stream.read(&mut data)?; + //println!("Size : {:}", size); + return Ok(EmulatorReply::Binary(data)); + } + Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid reply")) + } + + pub fn execute_command(&mut self, cmd : &str, argString : Option<&str>) -> Result { + if argString == None { + self.connection.write(format!("{}\n", cmd).as_bytes())?; + } else { + self.connection.write(format!("{} {}\n", cmd, argString.unwrap()).as_bytes())?; + } + self.get_reply() + } + pub fn execute_raw_command(&mut self, cmd : &str, argString : Option<&str>) { + if argString == None { + self.connection.write(format!("{}\n", cmd).as_bytes()); + } else { + self.connection.write(format!("{} {}\n", cmd, argString.unwrap()).as_bytes()); + } + } + + pub fn send_data(&mut self, data : Vec) { + let mut buf : Vec = vec![0;5]; + let size = data.len(); + buf[0] = 0; + buf[1] = ((size >> 24) & 0xFF) as u8; + buf[2] = ((size >> 16) & 0xFF) as u8; + buf[3] = ((size >> 8) & 0xFF) as u8; + buf[4] = (size & 0xFF) as u8; + self.connection.write(&buf); + self.connection.write(&data); + } + pub fn is_connected(&mut self) -> bool { + let mut buf = vec![0;0]; + if let Ok(usize) = self.connection.peek(&mut buf) { + return true + } + return false + } + + pub fn close(&mut self) { + self.connection.shutdown(Shutdown::Both); + } + pub fn reconnected(&mut self) -> Result { + self.connection = TcpStream::connect_timeout(&self.addr, Duration::from_millis(1000))?; + return Ok(true); + } +} + +// } \ No newline at end of file diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index fe9932f..9b303e4 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,6 +1,7 @@ use crate::autosplitters::supermetroid::Settings; use crate::autosplitters::supermetroid::SuperMetroidAutoSplitter; use crate::autosplitters::AutoSplitter; +use crate::autosplitters::nwa; use anyhow::{anyhow, Context, Result}; use eframe::egui; use livesplit_core::{Layout, SharedTimer, Timer}; @@ -29,7 +30,6 @@ pub struct LiveSplitCoreRenderer { can_exit: bool, is_exiting: bool, thread_chan: std::sync::mpsc::SyncSender, - // project_dirs: directories::ProjectDirs, pub app_config: std::sync::Arc>, app_config_processed: bool, glow_canvas: GlowCanvas, @@ -76,7 +76,6 @@ impl LiveSplitCoreRenderer { layout: Layout, settings: Arc>, chan: std::sync::mpsc::SyncSender, - // project_dirs: directories::ProjectDirs, config: AppConfig, ) -> Self { LiveSplitCoreRenderer { @@ -90,7 +89,6 @@ impl LiveSplitCoreRenderer { can_exit: false, is_exiting: false, thread_chan: chan, - // project_dirs, app_config: std::sync::Arc::new(std::sync::RwLock::new(config)), app_config_processed: false, glow_canvas: GlowCanvas::new(), @@ -626,9 +624,9 @@ impl LiveSplitCoreRenderer { { tc.try_send(ThreadEvent::TimerReset).unwrap_or(()); } - } - })?; - } + } + })?; + } if let Some(hk) = cfg.hot_key_undo { reg(hook, &hk, { let timer = timer.clone(); @@ -638,8 +636,8 @@ impl LiveSplitCoreRenderer { .map(|mut g| g.undo_split().ok()) .map_err(|e| println!("undo lock failed: {e}")); } - })?; - } + })?; + } if let Some(hk) = cfg.hot_key_skip { reg(hook, &hk, { let timer = timer.clone(); @@ -649,8 +647,8 @@ impl LiveSplitCoreRenderer { .map(|mut g| g.skip_split().ok()) .map_err(|e| println!("skip split lock failed: {e}")); } - })?; - } + })?; + } if let Some(hk) = cfg.hot_key_pause { reg(hook, &hk, { let timer = timer.clone(); @@ -660,8 +658,8 @@ impl LiveSplitCoreRenderer { .map(|mut g| g.toggle_pause().ok()) .map_err(|e| println!("toggle pause lock failed: {e}")); } - })?; - } + })?; + } if let Some(hk) = cfg.hot_key_comparison_next { reg(hook, &hk, { let timer = timer.clone(); @@ -671,8 +669,8 @@ impl LiveSplitCoreRenderer { .map(|mut g| g.switch_to_next_comparison()) .map_err(|e| println!("next comparison lock failed: {e}")); } - })?; - } + })?; + } if let Some(hk) = cfg.hot_key_comparison_prev { reg(hook, &hk, { let timer = timer.clone(); @@ -682,8 +680,8 @@ impl LiveSplitCoreRenderer { .map(|mut g| g.switch_to_previous_comparison()) .map_err(|e| println!("prev comparison lock failed: {e}")); } - })?; - } + })?; + } println!("registered"); Ok(()) @@ -995,30 +993,30 @@ pub fn app_init( // polling of SNES state .spawn(move |_| { loop { - let latency = Arc::new(RwLock::new((0.0, 0.0))); + let latency = Arc::new(RwLock::new((0.0, 0.0))); print_on_error(|| -> anyhow::Result<()> { let mut client = crate::usb2snes::SyncClient::connect() .context("creating usb2snes connection")?; - client.set_name("annelid")?; - println!("Server version is {:?}", client.app_version()?); - let mut devices = client.list_device()?.to_vec(); - if devices.len() != 1 { - if devices.is_empty() { + client.set_name("annelid")?; + println!("Server version is {:?}", client.app_version()?); + let mut devices = client.list_device()?.to_vec(); + if devices.len() != 1 { + if devices.is_empty() { Err(anyhow!("No devices present"))?; - } else { + } else { Err(anyhow!("You need to select a device: {:#?}", devices))?; + } } - } let device = devices.pop().ok_or(anyhow!("Device list was empty"))?; - println!("Using device: {}", device); - client.attach(&device)?; - println!("Connected."); - println!("{:#?}", client.info()?); + println!("Using device: {}", device); + client.attach(&device)?; + println!("Connected."); + println!("{:#?}", client.info()?); let mut autosplitter: Box = Box::new(SuperMetroidAutoSplitter::new(settings.clone())); - loop { + loop { let summary = autosplitter.update(&mut client)?; - if summary.start { + if summary.start { timer .write() .map_err(|e| { @@ -1026,8 +1024,8 @@ pub fn app_init( })? .start() .ok(); - } - if summary.reset + } + if summary.reset && app_config .read() .map_err(|e| { @@ -1035,7 +1033,7 @@ pub fn app_init( })? .reset_timer_on_game_reset == Some(true) - { + { timer .write() .map_err(|e| { @@ -1043,16 +1041,16 @@ pub fn app_init( })? .reset(true) .ok(); - } - if summary.split { + } + if summary.split { if let Some(t) = autosplitter.gametime_to_seconds() { - timer - .write() + timer + .write() .map_err(|e| { anyhow!("failed to acquire write lock on timer: {e}") })? .set_game_time(t) - .ok(); + .ok(); } timer .write() @@ -1061,15 +1059,15 @@ pub fn app_init( })? .split() .ok(); - } - { + } + { *latency.write() = (summary.latency_average, summary.latency_stddev); - } - // If the timer gets reset, we need to make a fresh snes state - if let Ok(ThreadEvent::TimerReset) = sync_receiver.try_recv() { + } + // If the timer gets reset, we need to make a fresh snes state + if let Ok(ThreadEvent::TimerReset) = sync_receiver.try_recv() { autosplitter.reset_game_tracking(); - //Reset the snes + //Reset the snes if app_config .read() .map_err(|e| { @@ -1077,16 +1075,16 @@ pub fn app_init( })? .reset_game_on_timer_reset == Some(true) - { - client.reset()?; + { + client.reset()?; + } } + std::thread::sleep(std::time::Duration::from_millis( + (1000.0 / polling_rate) as u64, + )); } - std::thread::sleep(std::time::Duration::from_millis( - (1000.0 / polling_rate) as u64, - )); - } - }); - std::thread::sleep(std::time::Duration::from_millis(1000)); + }); + std::thread::sleep(std::time::Duration::from_millis(1000)); } }) //TODO: fix this unwrap From bfb7bb6c1ad9194a954452d51b5b72b7cd62d9f3 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 24 Jun 2025 02:57:36 -0400 Subject: [PATCH 05/61] moved autosplitter settings object into livesplit renderer --- src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 18b783b..0030caf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,8 +58,8 @@ fn main() -> std::result::Result<(), Box> { let mut config = AppConfig::load_app_config(AppConfig::new()); // load saved config into default object AppConfig::update_from(&mut config, env::args()); // reads command line options into app config object - let settings = Settings::new(); // setttings for SM autosplitter - let settings = Arc::new(RwLock::new(settings)); // creates a RW pointer to the autosplitter settings + // let settings = Settings::new(); // setttings for SM autosplitter + // let settings = Arc::new(RwLock::new(settings)); // creates a RW pointer to the autosplitter settings let mut run = Run::default(); // creates a default livesplit run object run.push_segment(Segment::new("")); // push blank segment to run @@ -83,7 +83,7 @@ fn main() -> std::result::Result<(), Box> { use std::sync::mpsc::sync_channel; let (sync_sender, sync_receiver) = sync_channel(1); // create thread - let mut app = LiveSplitCoreRenderer::new(timer, layout, settings, sync_sender, config); // create livesplit-core renderer object + let mut app = LiveSplitCoreRenderer::new(timer, layout, sync_sender, config); // create livesplit-core renderer object eframe::run_native( "Annelid", From 794ccb79fc1135b93a786ed19534e70426eca311 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 24 Jun 2025 02:58:49 -0400 Subject: [PATCH 06/61] moved autosplitter settings here; started moving autosplitter settings updater to own window --- src/livesplit_renderer.rs | 159 +++++++++++++------------------------- 1 file changed, 52 insertions(+), 107 deletions(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 9b303e4..9257e34 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,3 +1,4 @@ +use crate::autosplitters; use crate::autosplitters::supermetroid::Settings; use crate::autosplitters::supermetroid::SuperMetroidAutoSplitter; use crate::autosplitters::AutoSplitter; @@ -26,7 +27,7 @@ pub struct LiveSplitCoreRenderer { image_cache: livesplit_core::settings::ImageCache, timer: SharedTimer, show_settings_editor: bool, - settings: Arc>, + settings: std::sync::Arc>, can_exit: bool, is_exiting: bool, thread_chan: std::sync::mpsc::SyncSender, @@ -74,7 +75,7 @@ impl LiveSplitCoreRenderer { pub fn new( timer: SharedTimer, layout: Layout, - settings: Arc>, + // settings: Arc>, chan: std::sync::mpsc::SyncSender, config: AppConfig, ) -> Self { @@ -85,7 +86,7 @@ impl LiveSplitCoreRenderer { image_cache: livesplit_core::settings::ImageCache::new(), layout_state: None, show_settings_editor: false, - settings, + settings: std::sync::Arc::new(std::sync::RwLock::new(autosplitters::supermetroid::Settings::new())), can_exit: false, is_exiting: false, thread_chan: chan, @@ -124,98 +125,24 @@ impl LiveSplitCoreRenderer { self.save_splits_dialog(&document_dir)?; } } - if self.settings.read().has_been_modified() { - let save_requested = MessageDialog::new() - .set_level(MessageLevel::Warning) - .set_title("Save Autosplitter Config") - .set_description( - "Autosplit config may have been modified. Save autosplitter config?", - ) - .set_buttons(MessageButtons::YesNo) - .show(); - if save_requested == MessageDialogResult::Yes { - self.save_autosplitter_dialog(&document_dir)?; - } - } + // if self.settings.read().has_been_modified() { + // let save_requested = MessageDialog::new() + // .set_level(MessageLevel::Warning) + // .set_title("Save Autosplitter Config") + // .set_description( + // "Autosplit config may have been modified. Save autosplitter config?", + // ) + // .set_buttons(MessageButtons::YesNo) + // .show(); + // if save_requested == MessageDialogResult::Yes { + // self.save_autosplitter_dialog(&document_dir)?; + // } + // } self.can_exit = true; self.glow_canvas.destroy(gl); Ok(()) } - // pub fn save_app_config(&self) { - // messagebox_on_error(|| { - // use std::io::Write; - // let mut config_path = self.project_dirs.preference_dir().to_path_buf(); - // config_path.push("settings.toml"); - // println!("Saving to {:#?}", config_path); - // let f = std::fs::OpenOptions::new() - // .create(true) - // .write(true) - // .truncate(true) - // .open(config_path)?; - // let mut writer = std::io::BufWriter::new(f); - // let toml = toml::to_string_pretty(&self.app_config)?; - // writer.write_all(toml.as_bytes())?; - // writer.flush()?; - // Ok(()) - // }); - // } - - // pub fn load_app_config(&mut self) { - // messagebox_on_error(|| { - // use std::io::Read; - // // let cli_count = std::env::args_os().count(); - // let mut config_path = self.project_dirs.preference_dir().to_path_buf(); - // config_path.push("settings.toml"); - // println!("Loading from {:#?}", config_path); - // let saved_config: AppConfig = std::fs::File::open(config_path) - // .and_then(|mut f| { - // let mut buffer = String::new(); - // f.read_to_string(&mut buffer)?; - // match toml::from_str(&buffer) { - // Ok(app_config) => Ok(app_config), - // Err(e) => Err(from_de_error(e)), - // } - // }) - // .unwrap_or_default(); - // // Let the CLI options take precedent if any provided - // // TODO: this logic is bad, I really need to know if the CLI - // // stuff was present and whether the stuff was present in the config - // // but instead I just see two different states that need to be merged. - // let cli_config = self.app_config.read().unwrap().clone(); - // let mut new_app_config = saved_config; - // if cli_config.recent_layout.is_some() { - // new_app_config.recent_layout = cli_config.recent_layout; - // } - // if cli_config.recent_splits.is_some() { - // new_app_config.recent_splits = cli_config.recent_splits; - // } - // if cli_config.recent_autosplitter.is_some() { - // new_app_config.recent_autosplitter = cli_config.recent_autosplitter; - // } - // if cli_config.use_autosplitter.is_some() { - // new_app_config.use_autosplitter = cli_config.use_autosplitter; - // } - // if cli_config.frame_rate.is_some() { - // new_app_config.frame_rate = cli_config.frame_rate; - // } - // if cli_config.polling_rate.is_some() { - // new_app_config.polling_rate = cli_config.polling_rate; - // } - // if cli_config.reset_timer_on_game_reset.is_some() { - // new_app_config.reset_timer_on_game_reset = cli_config.reset_timer_on_game_reset; - // } - // if cli_config.reset_game_on_timer_reset.is_some() { - // new_app_config.reset_game_on_timer_reset = cli_config.reset_game_on_timer_reset; - // } - // if cli_config.global_hotkeys.is_some() { - // new_app_config.global_hotkeys = cli_config.global_hotkeys; - // } - // *self.app_config.write().unwrap() = new_app_config; - // Ok(()) - // }); - // } - pub fn process_app_config(&mut self, ctx: &egui::Context) { use anyhow::Context; let mut queue = vec![]; @@ -315,7 +242,7 @@ impl LiveSplitCoreRenderer { } pub fn load_autosplitter(&mut self, f: &std::fs::File) -> Result<()> { - *self.settings.write() = serde_json::from_reader(std::io::BufReader::new(f))?; + *self.settings.write().unwrap() = serde_json::from_reader(std::io::BufReader::new(f))?; Ok(()) } @@ -413,7 +340,7 @@ impl LiveSplitCoreRenderer { &autosplitter.clone(), ("Autosplitter Configuration", "asc"), |me, f| { - serde_json::to_writer(&f, &*me.settings.read())?; + serde_json::to_writer(&f, &*me.settings.read().unwrap())?; Ok(()) }, ); @@ -686,6 +613,21 @@ impl LiveSplitCoreRenderer { println!("registered"); Ok(()) } + pub fn AutoSplitterSettingsEditor(&mut self, ctx: &egui::Context){ + // let settings_editor = egui::containers::Window::new("Settings Editor"); + // settings_editor + // .open(&mut self.show_settings_editor) + // .resizable(true) + // .collapsible(false) + // .hscroll(true) + // .vscroll(true) + // .show(ctx, |ui| { + // ctx.move_to_top(ui.layer_id()); + // let mut settings = self.settings.write(); + // let mut roots = settings.roots(); + // show_children(&mut settings, ui, ctx, &mut roots); + // }); + } } impl eframe::App for LiveSplitCoreRenderer { @@ -758,10 +700,10 @@ impl eframe::App for LiveSplitCoreRenderer { ); self.glow_canvas .paint_layer(ctx, egui::LayerId::background(), viewport); - //self.glow_canvas.paint_immediate(frame.gl().unwrap(), viewport); - let settings_editor = egui::containers::Window::new("Settings Editor"); + // //self.glow_canvas.paint_immediate(frame.gl().unwrap(), viewport); + // let settings_editor = egui::containers::Window::new("Settings Editor"); egui::Area::new("livesplit".into()) - .enabled(!self.show_settings_editor) + // .enabled(!self.show_settings_editor) .movable(false) .show(ctx, |ui| { ui.set_width(ctx.input(|i| i.screen_rect.width())); @@ -861,18 +803,21 @@ impl eframe::App for LiveSplitCoreRenderer { ctx.send_viewport_cmd(egui::viewport::ViewportCommand::Close) } }); - settings_editor - .open(&mut self.show_settings_editor) - .resizable(true) - .collapsible(false) - .hscroll(true) - .vscroll(true) - .show(ctx, |ui| { - ctx.move_to_top(ui.layer_id()); - let mut settings = self.settings.write(); - let mut roots = settings.roots(); - show_children(&mut settings, ui, ctx, &mut roots); - }); + + self.AutoSplitterSettingsEditor(ctx); + + // settings_editor + // .open(&mut self.show_settings_editor) + // .resizable(true) + // .collapsible(false) + // .hscroll(true) + // .vscroll(true) + // .show(ctx, |ui| { + // ctx.move_to_top(ui.layer_id()); + // let mut settings = self.settings.write(); + // let mut roots = settings.roots(); + // show_children(&mut settings, ui, ctx, &mut roots); + // }); ctx.input(|i| { let scroll_delta = i.raw_scroll_delta; if scroll_delta.y > 0.0 { From 6cf557428d10f19f4797fc95c118db78310d383c Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 24 Jun 2025 15:30:21 -0400 Subject: [PATCH 07/61] cargo fmt; quickly moved the autosplitter settings to it's own window --- src/autosplitters.rs | 2 +- src/autosplitters/nwa.rs | 124 +++++++++++++++---------- src/livesplit_renderer.rs | 189 ++++++++++++++++++++++++++++---------- 3 files changed, 217 insertions(+), 98 deletions(-) diff --git a/src/autosplitters.rs b/src/autosplitters.rs index b44a3ba..cce0ca4 100644 --- a/src/autosplitters.rs +++ b/src/autosplitters.rs @@ -1,6 +1,6 @@ pub mod json; -pub mod supermetroid; pub mod nwa; +pub mod supermetroid; use anyhow::Result; use livesplit_core::TimeSpan; diff --git a/src/autosplitters/nwa.rs b/src/autosplitters/nwa.rs index 9bb3abd..58d0547 100644 --- a/src/autosplitters/nwa.rs +++ b/src/autosplitters/nwa.rs @@ -2,73 +2,78 @@ use std::collections::HashMap; use std::fmt::Debug; -use std::net::{SocketAddr, TcpStream, ToSocketAddrs, Shutdown}; -use std::io::{Write, Read, BufReader, BufRead}; +use std::io::{BufRead, BufReader, Read, Write}; +use std::net::{Shutdown, SocketAddr, TcpStream, ToSocketAddrs}; //use std::ptr::read; use std::time::Duration; -#[derive(Debug)] -#[derive(PartialEq)] +#[derive(Debug, PartialEq)] pub enum ErrorKind { InvalidError, InvalidCommand, InvalidArgument, NotAllowed, - ProtocolError + ProtocolError, } #[derive(Debug)] pub struct NWAError { - pub kind : ErrorKind, - pub reason : String + pub kind: ErrorKind, + pub reason: String, } #[derive(Debug)] pub enum AsciiReply { Ok, Hash(HashMap), - ListHash(Vec>) + ListHash(Vec>), } #[derive(Debug)] pub enum EmulatorReply { Ascii(AsciiReply), Error(NWAError), - Binary(Vec) + Binary(Vec), } pub struct NWASyncClient { - connection : TcpStream, - port : u32, - addr : SocketAddr + connection: TcpStream, + port: u32, + addr: SocketAddr, } impl NWASyncClient { - pub fn connect(ip : &str, port : u32) -> Result { - let addr: Vec<_> = format!("{}:{}", ip, port).to_socket_addrs().expect("Can't resolve address").collect(); + pub fn connect(ip: &str, port: u32) -> Result { + let addr: Vec<_> = format!("{}:{}", ip, port) + .to_socket_addrs() + .expect("Can't resolve address") + .collect(); //println!("{:?}", addr); let co = TcpStream::connect_timeout(&addr[0], Duration::from_millis(1000))?; Ok(NWASyncClient { - connection : co, - port : port, - addr : addr[0] + connection: co, + port: port, + addr: addr[0], }) } pub fn get_reply(&mut self) -> Result { let mut read_stream = BufReader::new(self.connection.try_clone().unwrap()); - let mut first_byte =[0 as u8; 1]; + let mut first_byte = [0 as u8; 1]; if read_stream.read(&mut first_byte)? == 0 { - return Err(std::io::Error::new(std::io::ErrorKind::ConnectionAborted, "Read 0 byte")) + return Err(std::io::Error::new( + std::io::ErrorKind::ConnectionAborted, + "Read 0 byte", + )); } let first_byte = first_byte[0]; - + // Ascii if first_byte == b'\n' { - let mut map : HashMap = HashMap::new(); - let mut line : Vec = vec![]; + let mut map: HashMap = HashMap::new(); + let mut line: Vec = vec![]; loop { line.clear(); - - let rep = read_stream.read_until(b'\n',&mut line)?; + + let rep = read_stream.read_until(b'\n', &mut line)?; //println!("{:?}", String::from_utf8(line.clone())); if line[0] == b'\n' && map.len() == 0 { return Ok(EmulatorReply::Ascii(AsciiReply::Ok)); @@ -82,14 +87,17 @@ impl NWASyncClient { let mut key = [0 as u8; 100]; let mut value = [0 as u8; 1024]; let mut cpt = 0; - while line[cpt] != b':' && line[cpt] != b'\n'{ + while line[cpt] != b':' && line[cpt] != b'\n' { key[cpt] = line[cpt]; cpt += 1; } let end_key = cpt; // Should have stopped on : if line[cpt] == b'\n' { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "Mal formed reply")) + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Mal formed reply", + )); } cpt += 1; let offset = cpt; @@ -98,67 +106,85 @@ impl NWASyncClient { cpt += 1; } let end_value = cpt - offset; - map.insert(String::from_utf8_lossy(&key[0..end_key]).to_string(), String::from_utf8_lossy(&value[0..end_value]).to_string()); + map.insert( + String::from_utf8_lossy(&key[0..end_key]).to_string(), + String::from_utf8_lossy(&value[0..end_value]).to_string(), + ); } if map.contains_key("error") { if let Some(reason) = map.get("reason") { let mut mkind = ErrorKind::InvalidError; match map.get("error").unwrap().as_str() { - "protocol_error" => {mkind = ErrorKind::ProtocolError}, - "invalid_command" => {mkind = ErrorKind::InvalidCommand}, - "invalid_argument" => {mkind = ErrorKind::InvalidArgument}, - "not_allowed" => {mkind = ErrorKind::NotAllowed} - _ => {mkind = ErrorKind::InvalidError} + "protocol_error" => mkind = ErrorKind::ProtocolError, + "invalid_command" => mkind = ErrorKind::InvalidCommand, + "invalid_argument" => mkind = ErrorKind::InvalidArgument, + "not_allowed" => mkind = ErrorKind::NotAllowed, + _ => mkind = ErrorKind::InvalidError, } - return Ok(EmulatorReply::Error(NWAError {kind : mkind, reason : reason.to_string()})) + return Ok(EmulatorReply::Error(NWAError { + kind: mkind, + reason: reason.to_string(), + })); } else { - return Ok(EmulatorReply::Error(NWAError {kind : ErrorKind::InvalidError, reason : String::from("Invalid reason")})) + return Ok(EmulatorReply::Error(NWAError { + kind: ErrorKind::InvalidError, + reason: String::from("Invalid reason"), + })); } } return Ok(EmulatorReply::Ascii(AsciiReply::Hash(map))); } if first_byte == 0 { - let mut header = vec![0;4]; + let mut header = vec![0; 4]; let r_size = read_stream.read(&mut header)?; println!(""); //println!("Reading {:}", r_size); //println!("Header : {:?}", header); let header = header; - let mut size : u32 = 0; + let mut size: u32 = 0; size = (header[0] as u32) << 24; size += (header[1] as u32) << 16; size += (header[2] as u32) << 8; size += header[3] as u32; //println!("Size : {:}", size); let msize = size as usize; - let mut data : Vec = vec![0; msize]; + let mut data: Vec = vec![0; msize]; //println!("Size : {:}", size); read_stream.read(&mut data)?; //println!("Size : {:}", size); return Ok(EmulatorReply::Binary(data)); } - Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid reply")) + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Invalid reply", + )) } - pub fn execute_command(&mut self, cmd : &str, argString : Option<&str>) -> Result { + pub fn execute_command( + &mut self, + cmd: &str, + argString: Option<&str>, + ) -> Result { if argString == None { self.connection.write(format!("{}\n", cmd).as_bytes())?; } else { - self.connection.write(format!("{} {}\n", cmd, argString.unwrap()).as_bytes())?; + self.connection + .write(format!("{} {}\n", cmd, argString.unwrap()).as_bytes())?; } self.get_reply() } - pub fn execute_raw_command(&mut self, cmd : &str, argString : Option<&str>) { + pub fn execute_raw_command(&mut self, cmd: &str, argString: Option<&str>) { if argString == None { self.connection.write(format!("{}\n", cmd).as_bytes()); } else { - self.connection.write(format!("{} {}\n", cmd, argString.unwrap()).as_bytes()); + self.connection + .write(format!("{} {}\n", cmd, argString.unwrap()).as_bytes()); } } - pub fn send_data(&mut self, data : Vec) { - let mut buf : Vec = vec![0;5]; - let size = data.len(); + pub fn send_data(&mut self, data: Vec) { + let mut buf: Vec = vec![0; 5]; + let size = data.len(); buf[0] = 0; buf[1] = ((size >> 24) & 0xFF) as u8; buf[2] = ((size >> 16) & 0xFF) as u8; @@ -168,11 +194,11 @@ impl NWASyncClient { self.connection.write(&data); } pub fn is_connected(&mut self) -> bool { - let mut buf = vec![0;0]; + let mut buf = vec![0; 0]; if let Ok(usize) = self.connection.peek(&mut buf) { - return true + return true; } - return false + return false; } pub fn close(&mut self) { @@ -184,4 +210,4 @@ impl NWASyncClient { } } -// } \ No newline at end of file +// } diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 9257e34..6799906 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,13 +1,15 @@ use crate::autosplitters; +use crate::autosplitters::nwa; use crate::autosplitters::supermetroid::Settings; use crate::autosplitters::supermetroid::SuperMetroidAutoSplitter; use crate::autosplitters::AutoSplitter; -use crate::autosplitters::nwa; use anyhow::{anyhow, Context, Result}; use eframe::egui; use livesplit_core::{Layout, SharedTimer, Timer}; use livesplit_hotkey::Hook; use parking_lot::RwLock; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; use std::sync::Arc; use thread_priority::{set_current_thread_priority, ThreadBuilder, ThreadPriority}; @@ -26,8 +28,8 @@ pub struct LiveSplitCoreRenderer { layout_state: Option, image_cache: livesplit_core::settings::ImageCache, timer: SharedTimer, - show_settings_editor: bool, - settings: std::sync::Arc>, + // show_settings_editor: bool, + settings: std::sync::Arc>, can_exit: bool, is_exiting: bool, thread_chan: std::sync::mpsc::SyncSender, @@ -36,6 +38,7 @@ pub struct LiveSplitCoreRenderer { glow_canvas: GlowCanvas, global_hotkey_hook: Option, load_errors: Vec, + show_edit_autosplitter_settings_dialog: std::sync::Arc, } fn show_children( @@ -85,8 +88,10 @@ impl LiveSplitCoreRenderer { renderer: livesplit_core::rendering::software::BorrowedRenderer::new(), image_cache: livesplit_core::settings::ImageCache::new(), layout_state: None, - show_settings_editor: false, - settings: std::sync::Arc::new(std::sync::RwLock::new(autosplitters::supermetroid::Settings::new())), + // show_settings_editor: false, + settings: std::sync::Arc::new( + RwLock::new(autosplitters::supermetroid::Settings::new()), + ), can_exit: false, is_exiting: false, thread_chan: chan, @@ -95,6 +100,7 @@ impl LiveSplitCoreRenderer { glow_canvas: GlowCanvas::new(), global_hotkey_hook: None, load_errors: vec![], + show_edit_autosplitter_settings_dialog: std::sync::Arc::new(AtomicBool::new(false)), } } @@ -125,19 +131,19 @@ impl LiveSplitCoreRenderer { self.save_splits_dialog(&document_dir)?; } } - // if self.settings.read().has_been_modified() { - // let save_requested = MessageDialog::new() - // .set_level(MessageLevel::Warning) - // .set_title("Save Autosplitter Config") - // .set_description( - // "Autosplit config may have been modified. Save autosplitter config?", - // ) - // .set_buttons(MessageButtons::YesNo) - // .show(); - // if save_requested == MessageDialogResult::Yes { - // self.save_autosplitter_dialog(&document_dir)?; - // } - // } + if self.settings.read().has_been_modified() { + let save_requested = MessageDialog::new() + .set_level(MessageLevel::Warning) + .set_title("Save Autosplitter Config") + .set_description( + "Autosplit config may have been modified. Save autosplitter config?", + ) + .set_buttons(MessageButtons::YesNo) + .show(); + if save_requested == MessageDialogResult::Yes { + self.save_autosplitter_dialog(&document_dir)?; + } + } self.can_exit = true; self.glow_canvas.destroy(gl); Ok(()) @@ -242,7 +248,7 @@ impl LiveSplitCoreRenderer { } pub fn load_autosplitter(&mut self, f: &std::fs::File) -> Result<()> { - *self.settings.write().unwrap() = serde_json::from_reader(std::io::BufReader::new(f))?; + *self.settings.write() = serde_json::from_reader(std::io::BufReader::new(f))?; Ok(()) } @@ -340,7 +346,7 @@ impl LiveSplitCoreRenderer { &autosplitter.clone(), ("Autosplitter Configuration", "asc"), |me, f| { - serde_json::to_writer(&f, &*me.settings.read().unwrap())?; + serde_json::to_writer(&f, &*me.settings.read())?; Ok(()) }, ); @@ -613,20 +619,115 @@ impl LiveSplitCoreRenderer { println!("registered"); Ok(()) } - pub fn AutoSplitterSettingsEditor(&mut self, ctx: &egui::Context){ - // let settings_editor = egui::containers::Window::new("Settings Editor"); - // settings_editor - // .open(&mut self.show_settings_editor) - // .resizable(true) - // .collapsible(false) - // .hscroll(true) - // .vscroll(true) - // .show(ctx, |ui| { - // ctx.move_to_top(ui.layer_id()); - // let mut settings = self.settings.write(); - // let mut roots = settings.roots(); - // show_children(&mut settings, ui, ctx, &mut roots); - // }); + pub fn AutoSplitterSettingsEditor(&mut self, ctx: &egui::Context) { + if self + .show_edit_autosplitter_settings_dialog + .load(Ordering::Relaxed) + { + let show_deferred_viewport = self.show_edit_autosplitter_settings_dialog.clone(); + let aSettings = self.settings.clone(); + // let local_change_binding= self.change_binding.clone(); + // let hStart = self.app_config.read().unwrap().hot_key_start.unwrap(); + // let hReset = self.app_config.read().unwrap().hot_key_reset.unwrap(); + // let hUndo = self.app_config.read().unwrap().hot_key_undo.unwrap(); + // let hSkip = self.app_config.read().unwrap().hot_key_skip.unwrap(); + // let hPause = self.app_config.read().unwrap().hot_key_pause.unwrap(); + // let hSwitchP = self.app_config.read().unwrap().hot_key_comparison_prev.unwrap(); + // let hSwitchN = self.app_config.read().unwrap().hot_key_comparison_next.unwrap(); + // let hToggleG = self.app_config.read().unwrap().hot_key_toggle_global_hotkeys.unwrap(); + // let mut hSelector = self.hotkey_selector.clone(); + // let mut globalHotkeys = self.app_config.clone(); + + ctx.show_viewport_deferred( + egui::ViewportId::from_hash_of("deferred_viewport"), + egui::ViewportBuilder::default() + .with_title("AutoSplitter Settings Editor") + .with_inner_size([200.0, 500.0]), + move |ctx, class| { + assert!( + class == egui::ViewportClass::Deferred, + "This egui backend doesn't support multiple viewports" + ); + egui::CentralPanel::default().show(ctx, |ui| { + let settings_editor = egui::containers::Window::new("Settings Editor"); + settings_editor + .open(&mut show_deferred_viewport.load(Ordering::Relaxed)) + .resizable(true) + .collapsible(false) + .hscroll(true) + .vscroll(true) + .show(ctx, |ui| { + ctx.move_to_top(ui.layer_id()); + let settings = aSettings.clone(); + let mut roots = settings.write().roots(); + show_children(&mut settings.write(), ui, ctx, &mut roots); + }); + // ui.label("Hotkeys"); + // ui.label("Start / Split"); + // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hStart)); + // if response.clicked() { + // local_change_binding.store(true, Ordering::Relaxed); + // hSelector.write().unwrap().replace(1); + // } + // ui.label("Reset"); + // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hReset)); + // if response.clicked() { + // local_change_binding.store(true, Ordering::Relaxed); + // hSelector.write().unwrap().replace(2); + // } + // ui.label("Undo Split"); + // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hUndo)); + // if response.clicked() { + // local_change_binding.store(true, Ordering::Relaxed); + // hSelector.write().unwrap().replace(3); + // } + // ui.label("Skip Split"); + // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hSkip)); + // if response.clicked() { + // local_change_binding.store(true, Ordering::Relaxed); + // hSelector.write().unwrap().replace(4); + // } + // ui.label("Pause"); + // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hPause)); + // if response.clicked() { + // local_change_binding.store(true, Ordering::Relaxed); + // hSelector.write().unwrap().replace(5); + // } + // ui.label("Switch Comparison (Previous)"); + // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hSwitchP)); + // if response.clicked() { + // local_change_binding.store(true, Ordering::Relaxed); + // hSelector.write().unwrap().replace(6); + // } + // ui.label("Switch Comparison (Next)"); + // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hSwitchN)); + // if response.clicked() { + // local_change_binding.store(true, Ordering::Relaxed); + // hSelector.write().unwrap().replace(7); + // } + // ui.label("Toggle Global Hotkeys"); + // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hToggleG)); + // if response.clicked() { + // local_change_binding.store(true, Ordering::Relaxed); + // hSelector.write().unwrap().replace(8); + // } + // let mut value = globalHotkeys.read().unwrap().global_hotkeys.unwrap(); + // let response = ui.checkbox(&mut value, "Global Hotkeys"); + // if response.clicked() { + // globalHotkeys.write().unwrap().global_hotkeys.replace(value); + // } + }); + + if ctx.input(|i| i.viewport().close_requested()) { + // Tell parent to close us. + show_deferred_viewport.store(false, Ordering::Relaxed); + } + }, + ); + // if self.change_binding.load(Ordering::Relaxed) { + // self.update_keybinding(ctx); + // } + } } } @@ -781,7 +882,10 @@ impl eframe::App for LiveSplitCoreRenderer { }); ui.menu_button("Autosplitter", |ui| { if ui.button("Configure").clicked() { - self.show_settings_editor = true; + // self.show_settings_editor = true; + let show_deferred_viewport = true; + self.show_edit_autosplitter_settings_dialog + .store(show_deferred_viewport, Ordering::Relaxed); ui.close_menu(); } if ui.button("Load Configuration").clicked() { @@ -804,20 +908,8 @@ impl eframe::App for LiveSplitCoreRenderer { } }); - self.AutoSplitterSettingsEditor(ctx); + self.AutoSplitterSettingsEditor(ctx); - // settings_editor - // .open(&mut self.show_settings_editor) - // .resizable(true) - // .collapsible(false) - // .hscroll(true) - // .vscroll(true) - // .show(ctx, |ui| { - // ctx.move_to_top(ui.layer_id()); - // let mut settings = self.settings.write(); - // let mut roots = settings.roots(); - // show_children(&mut settings, ui, ctx, &mut roots); - // }); ctx.input(|i| { let scroll_delta = i.raw_scroll_delta; if scroll_delta.y > 0.0 { @@ -957,6 +1049,7 @@ pub fn app_init( client.attach(&device)?; println!("Connected."); println!("{:#?}", client.info()?); + let mut autosplitter: Box = Box::new(SuperMetroidAutoSplitter::new(settings.clone())); loop { From d5cf78f01ac09e73453e5c2559aecfa721835b15 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 28 Jun 2025 03:47:00 -0400 Subject: [PATCH 08/61] added autosplitter type variable --- src/config/app_config.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config/app_config.rs b/src/config/app_config.rs index 214c277..850dcef 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -1,5 +1,6 @@ use clap::Parser; use serde_derive::{Deserialize, Serialize}; +use crate::autosplitters; use crate::hotkey::*; use crate::utils::*; @@ -39,6 +40,8 @@ pub struct AppConfig { pub hot_key_comparison_next: Option, #[clap(skip)] pub hot_key_comparison_prev: Option, + #[clap(skip)] + pub autosplitterType: Option, } // #[derive(clap::ValueEnum, Clone, Copy, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] @@ -92,6 +95,7 @@ impl AppConfig { reset_timer_on_game_reset: Some(false), reset_game_on_timer_reset: Some(false), global_hotkeys: Some(true), + autosplitterType: Some(autosplitters::AType::QUSB2SNES), } } From e6faeb805002d7c67134306917377ac3fe63649d Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 28 Jun 2025 03:47:27 -0400 Subject: [PATCH 09/61] added debug derive --- src/autosplitters/nwa.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/autosplitters/nwa.rs b/src/autosplitters/nwa.rs index 58d0547..a37f28f 100644 --- a/src/autosplitters/nwa.rs +++ b/src/autosplitters/nwa.rs @@ -34,10 +34,11 @@ pub enum EmulatorReply { Binary(Vec), } +#[derive(Debug)] pub struct NWASyncClient { - connection: TcpStream, - port: u32, - addr: SocketAddr, + pub connection: TcpStream, + pub port: u32, + pub addr: SocketAddr, } impl NWASyncClient { From 196d086bd3b0ad08913dc9c53826dd2d73391066 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 28 Jun 2025 03:47:59 -0400 Subject: [PATCH 10/61] removed unused crates --- src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0030caf..63d7cf6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,15 +10,15 @@ pub mod usb2snes; pub mod utils; pub mod widget; -use autosplitters::supermetroid::Settings; +// use autosplitters::supermetroid::Settings; use clap::Parser; use eframe::egui; use livesplit_core::layout::{ComponentSettings, LayoutSettings}; use livesplit_core::{Layout, Run, Segment, Timer}; -use parking_lot::RwLock; +// use parking_lot::RwLock; use std::env; use std::error::Error; -use std::sync::Arc; +// use std::sync::Arc; use config::app_config::*; use livesplit_renderer::*; From cada063af3e8b4798664698e355a723f5cbd98cc Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 28 Jun 2025 03:49:39 -0400 Subject: [PATCH 11/61] added crates for retroarch access work --- Cargo.lock | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ 2 files changed, 57 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 5dd5bef..63b6689 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,6 +194,7 @@ dependencies = [ "livesplit-hotkey", "memoffset 0.9.1", "parking_lot", + "process-memory", "rfd", "roxmltree", "serde", @@ -201,6 +202,7 @@ dependencies = [ "serde_json", "strum 0.27.1", "strum_macros 0.27.1", + "sysinfo", "thread-priority", "time", "toml", @@ -2106,6 +2108,15 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2290,6 +2301,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2524,6 +2544,16 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "objc2-io-surface" version = "0.3.1" @@ -2854,6 +2884,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "process-memory" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9599c34fcc8067c3105dc746c0ce85e3ea61784568b8234179fad490b1dcc1" +dependencies = [ + "libc", + "mach", + "winapi", +] + [[package]] name = "profiling" version = "1.0.17" @@ -3490,6 +3531,20 @@ dependencies = [ "libc", ] +[[package]] +name = "sysinfo" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.61.3", +] + [[package]] name = "tap" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 24d5705..a212b9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,8 @@ bytemuck = { version = "*", features = ["derive"] } memoffset = "*" thread-priority = "2" anyhow = "1" +sysinfo = "0.35.2" +process-memory = "0.5.0" # Remember to test with --release [profile.dev] From eed626c7aff61329b21ecc037c27c26441f64e5b Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 28 Jun 2025 03:50:03 -0400 Subject: [PATCH 12/61] added enum for autotracker type control --- src/autosplitters.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/autosplitters.rs b/src/autosplitters.rs index cce0ca4..f983061 100644 --- a/src/autosplitters.rs +++ b/src/autosplitters.rs @@ -5,6 +5,12 @@ pub mod supermetroid; use anyhow::Result; use livesplit_core::TimeSpan; +#[derive(Debug, Copy, Clone, serde::Deserialize, serde::Serialize, PartialEq)] +pub enum AType { + QUSB2SNES, + NWA, +} + #[derive(Debug, Copy, Clone)] pub struct SNESSummary { pub latency_average: f32, From 7ebfb8cd28845e752913a2bb648d2e1ce75546ef Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 28 Jun 2025 03:51:29 -0400 Subject: [PATCH 13/61] added NWA usage demo for BattleToads --- src/livesplit_renderer.rs | 157 +++++++++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 2 deletions(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 6799906..95a4686 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,5 +1,7 @@ use crate::autosplitters; use crate::autosplitters::nwa; +use crate::autosplitters::nwa::EmulatorReply; +use crate::autosplitters::nwa::NWASyncClient; use crate::autosplitters::supermetroid::Settings; use crate::autosplitters::supermetroid::SuperMetroidAutoSplitter; use crate::autosplitters::AutoSplitter; @@ -8,10 +10,18 @@ use eframe::egui; use livesplit_core::{Layout, SharedTimer, Timer}; use livesplit_hotkey::Hook; use parking_lot::RwLock; +use tungstenite::http::uri::Port; +use std::alloc::System; +use std::any::Any; +use std::arch::x86_64::_CMP_ORD_S; +use std::ffi::OsStr; +use std::os::linux; +use std::process; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::sync::Arc; use thread_priority::{set_current_thread_priority, ThreadBuilder, ThreadPriority}; +use std::net::Ipv4Addr; use crate::config::app_config::*; use crate::hotkey::*; @@ -39,6 +49,8 @@ pub struct LiveSplitCoreRenderer { global_hotkey_hook: Option, load_errors: Vec, show_edit_autosplitter_settings_dialog: std::sync::Arc, + address: Ipv4Addr, + port: u32, } fn show_children( @@ -101,6 +113,8 @@ impl LiveSplitCoreRenderer { global_hotkey_hook: None, load_errors: vec![], show_edit_autosplitter_settings_dialog: std::sync::Arc::new(AtomicBool::new(false)), + address: Ipv4Addr::new(0, 0, 0, 0), + port: 48879, } } @@ -1019,10 +1033,31 @@ pub fn app_init( // something equivalent to Arc> so it's safe // to clone them and pass the clone between threads. let timer = app.timer.clone(); - let settings = app.settings.clone(); let app_config = app.app_config.clone(); + + + // use sysinfo::*; + // use process_memory::*; + // // let mut x = 0_u32; + // let mut x = sysinfo::Pid::from(0); + // let s = System::new_all(); + // let count = s.processes_by_exact_name(OsStr::new("retroarch")); + // let mut p = s.processes_by_exact_name(OsStr::new("retroarch")); + // if count.count() == 1 { + // x = p.next().unwrap().pid(); + // } + + // let proc_handle = process_memory + // let process_handle = process_memory::ProcessHandle::try_into_process_handle(&x); + // println!("{:#?}",client.unwrap().); + + + // This thread deals with polling the SNES at a fixed rate. if app_config.read().unwrap().use_autosplitter == Some(true) { + if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::QUSB2SNES) { + //QUSB2SNES stuff here + let settings = app.settings.clone(); let _snes_polling_thread = ThreadBuilder::default() .name("SNES Polling Thread".to_owned()) // We could change this thread priority, but we probably @@ -1105,7 +1140,7 @@ pub fn app_init( // If the timer gets reset, we need to make a fresh snes state if let Ok(ThreadEvent::TimerReset) = sync_receiver.try_recv() { autosplitter.reset_game_tracking(); - //Reset the snes + // Reset the snes if app_config .read() .map_err(|e| { @@ -1127,5 +1162,123 @@ pub fn app_init( }) //TODO: fix this unwrap .unwrap(); + } + + else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::NWA) { + //NWA stuff here + let address = app.address.clone(); + let port = app.port.clone(); + + let _nwa_polling_thread = ThreadBuilder::default() + .name("NWA Polling Thread".to_owned()) + // We could change this thread priority, but we probably + // should leave it at the default to make sure we get timely + // polling of SNES state + .spawn(move |_| { + loop { + print_on_error(|| -> anyhow::Result<()> { + let mut client = NWASyncClient::connect(&address.to_string(), port).unwrap(); // TODO: Need to handle error + let cmd = "EMULATOR_INFO"; + let args = Some("0"); + let summary = client.execute_command(cmd, args); + println!("{:#?}",summary); + let cmd = "GAME_INFO"; + let args = None; + let summary = client.execute_command(cmd, args); + println!("{:#?}",summary); + // let cmd = "CORE_INFO"; + // let args = Some("quickerNES"); + // let summary = client.execute_command(cmd, args); + // println!("{:#?}",summary); + let cmd = "EMULATION_STATUS"; + let args = None; + let summary = client.execute_command(cmd, args); + println!("{:#?}",summary); + // let cmd = "CORES_LIST"; + // let args = None; + // let summary = client.execute_command(cmd, args); + // println!("{:#?}",summary); + let cmd = "MY_NAME_IS"; + let args = Some("Annelid"); + let summary = client.execute_command(cmd, args); + println!("{:#?}",summary); + let cmd = "CORE_CURRENT_INFO"; + let args = None; + let summary = client.execute_command(cmd, args); + println!("{:#?}",summary); + // let cmd = "CORE_MEMORIES"; + // let args = None; + // let summary = client.execute_command(cmd, args); + // println!("{:#?}",summary); + // let cmd = "LIST_BIZHAWK_DOMAINS"; + // let args = None; + // let summary = client.execute_command(cmd, args); + // println!("{:#?}",summary); + + let mut priorLevel = 0_u8; + loop { + let cmd = "CORE_READ"; + let args = Some("RAM;$0010;1"); + let summary = client.execute_command(cmd, args); + println!("{:#?}",summary); + let nwaResult = summary.unwrap(); + println!("{:#?}",nwaResult); + let mut level= 0_u8; + + match nwaResult { + EmulatorReply::Ascii(nwaResult) => println!("{:?}",nwaResult), + EmulatorReply::Binary(nwaResult) => level = nwaResult.first().unwrap().clone(), + EmulatorReply::Error(nwaResult) => println!("{:?}",nwaResult), + + } + println!("{:#?}",level); + if level == 1 && priorLevel == 0 { + timer + .write() + .map_err(|e| { + anyhow!("failed to acquire write lock on timer: {e}") + })? + .start() + .ok(); + } + else if level == 0 && priorLevel != 0 + && app_config + .read() + .map_err(|e| { + anyhow!("failed to acquire read lock on config: {e}") + })? + .reset_timer_on_game_reset + == Some(true) + { + timer + .write() + .map_err(|e| { + anyhow!("failed to acquire write lock on timer: {e}") + })? + .reset(true) + .ok(); + } + else if level > priorLevel && priorLevel < 100 { + timer + .write() + .map_err(|e| { + anyhow!("failed to acquire write lock on timer: {e}") + })? + .split() + .ok(); + } + priorLevel = level; + + std::thread::sleep(std::time::Duration::from_millis( + (1000.0 / polling_rate) as u64, + )); + } + }); + std::thread::sleep(std::time::Duration::from_millis(1000)); + } + }) + //TODO: fix this unwrap + .unwrap(); + }; } } From 460ea391730f3497ad72f60bce0c88e9b5496670 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 30 Jun 2025 00:40:20 -0400 Subject: [PATCH 14/61] Moved --- src/{autosplitters => }/nwa.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{autosplitters => }/nwa.rs (100%) diff --git a/src/autosplitters/nwa.rs b/src/nwa.rs similarity index 100% rename from src/autosplitters/nwa.rs rename to src/nwa.rs From 20d34733eb524706d4775847a818b5f493b8b7e5 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 30 Jun 2025 00:41:05 -0400 Subject: [PATCH 15/61] import NWA --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 63d7cf6..d82936d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ pub mod autosplitters; pub mod config; pub mod hotkey; pub mod livesplit_renderer; +pub mod nwa; pub mod routes; pub mod usb2snes; pub mod utils; From 443c049d3fe84fdff5adef67fa64acd6b1773539 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 30 Jun 2025 00:43:38 -0400 Subject: [PATCH 16/61] added release livesplit and package for attempt at reading process memory --- Cargo.lock | 33 ++++++++++++++++++++++++++------- Cargo.toml | 4 ++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63b6689..60d641f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,6 +195,7 @@ dependencies = [ "memoffset 0.9.1", "parking_lot", "process-memory", + "read-process-memory", "rfd", "roxmltree", "serde", @@ -777,7 +778,7 @@ version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn", @@ -1702,6 +1703,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -2040,7 +2047,7 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "livesplit-core" version = "0.13.0" -source = "git+https://github.com/LiveSplit/livesplit-core#ee36c2755d888184e93db31939562fbb2c715661" +source = "git+https://github.com/LiveSplit/livesplit-core#59a237fd59fc52133cef7e247888788871983110" dependencies = [ "base64-simd", "bytemuck", @@ -2073,7 +2080,7 @@ dependencies = [ [[package]] name = "livesplit-hotkey" version = "0.8.0" -source = "git+https://github.com/LiveSplit/livesplit-core#ee36c2755d888184e93db31939562fbb2c715661" +source = "git+https://github.com/LiveSplit/livesplit-core#59a237fd59fc52133cef7e247888788871983110" dependencies = [ "bitflags 2.9.1", "cfg-if", @@ -2090,7 +2097,7 @@ dependencies = [ [[package]] name = "livesplit-title-abbreviations" version = "0.3.0" -source = "git+https://github.com/LiveSplit/livesplit-core#ee36c2755d888184e93db31939562fbb2c715661" +source = "git+https://github.com/LiveSplit/livesplit-core#59a237fd59fc52133cef7e247888788871983110" [[package]] name = "lock_api" @@ -3037,6 +3044,18 @@ dependencies = [ "font-types", ] +[[package]] +name = "read-process-memory" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8497683b2f0b6887786f1928c118f26ecc6bb3d78bbb6ed23e8e7ba110af3bb0" +dependencies = [ + "libc", + "log", + "mach", + "winapi", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -3409,7 +3428,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1961e2ef424c1424204d3a5d6975f934f56b6d50ff5732382d84ebf460e147f7" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn", @@ -3469,7 +3488,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", @@ -3482,7 +3501,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", diff --git a/Cargo.toml b/Cargo.toml index a212b9f..ad05d16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,10 @@ serde_json = "1" tungstenite = "0" livesplit-core = { git = "https://github.com/LiveSplit/livesplit-core", features = ["software-rendering", "font-loading"] } +#livesplit-core = "0.13.0" livesplit-hotkey = { git = "https://github.com/LiveSplit/livesplit-core" } +#livesplit-hotkey = "0.8.0" +#livesplit-auto-splitting = "0.1.0" epaint = "0.31" eframe = { version = "0.31", features = ["glow"] } @@ -37,6 +40,7 @@ thread-priority = "2" anyhow = "1" sysinfo = "0.35.2" process-memory = "0.5.0" +read-process-memory = "0.1.6" # Remember to test with --release [profile.dev] From 32c7726900984f6312233edc56ad63447ef57b36 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 30 Jun 2025 00:44:35 -0400 Subject: [PATCH 17/61] cargo fmt --- src/config/app_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/app_config.rs b/src/config/app_config.rs index 850dcef..4587b1e 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -1,6 +1,6 @@ +use crate::autosplitters; use clap::Parser; use serde_derive::{Deserialize, Serialize}; -use crate::autosplitters; use crate::hotkey::*; use crate::utils::*; From bfc0f379ed64a803aced3d912d8a73195b74bc77 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 30 Jun 2025 00:48:27 -0400 Subject: [PATCH 18/61] swapped nwa with battletoads nwa implementation; added autosplitter type enum and NWASummary --- src/autosplitters.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/autosplitters.rs b/src/autosplitters.rs index f983061..e3a989f 100644 --- a/src/autosplitters.rs +++ b/src/autosplitters.rs @@ -1,5 +1,6 @@ pub mod json; -pub mod nwa; +// pub mod nwa; +pub mod battletoads; pub mod supermetroid; use anyhow::Result; @@ -9,6 +10,15 @@ use livesplit_core::TimeSpan; pub enum AType { QUSB2SNES, NWA, + ASL, + CUSTOM, +} + +#[derive(Debug, Copy, Clone)] +pub struct NWASummary { + pub start: bool, + pub reset: bool, + pub split: bool, } #[derive(Debug, Copy, Clone)] From fbb588456ef623438d2351dca5ac0a1a0ee7e604 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 30 Jun 2025 00:49:09 -0400 Subject: [PATCH 19/61] moved battletoads NWA to it's own class --- src/autosplitters/battletoads.rs | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/autosplitters/battletoads.rs diff --git a/src/autosplitters/battletoads.rs b/src/autosplitters/battletoads.rs new file mode 100644 index 0000000..6d75451 --- /dev/null +++ b/src/autosplitters/battletoads.rs @@ -0,0 +1,133 @@ +use super::NWASummary; +use crate::nwa; +use anyhow::Result; +use std::net::Ipv4Addr; + +pub enum Action { + start, + reset, + split, +} + +pub struct battletoadsAutoSplitter { + address: Ipv4Addr, + port: u32, + priorLevel: u8, + level: u8, + reset_timer_on_game_reset: bool, + client: nwa::NWASyncClient, +} + +impl battletoadsAutoSplitter { + pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: bool) -> Self { + battletoadsAutoSplitter { + address, // address: Ipv4Addr::new(0, 0, 0, 0), + port, // port: 48879, + priorLevel: 0_u8, + level: 0_u8, + reset_timer_on_game_reset, + client: nwa::NWASyncClient::connect(&address.to_string(), port).unwrap(), // TODO: Need to handle error + } + } + + pub fn clientID(&mut self) { + let cmd = "MY_NAME_IS"; + let args = Some("Annelid"); + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + } + + pub fn emuInfo(&mut self) { + let cmd = "EMULATOR_INFO"; + let args = Some("0"); + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + } + + pub fn emuGameInfo(&mut self) { + let cmd = "GAME_INFO"; + let args = None; + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + } + + pub fn emuStatus(&mut self) { + let cmd = "EMULATION_STATUS"; + let args = None; + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + } + + pub fn coreInfo(&mut self) { + let cmd = "CORE_CURRENT_INFO"; + let args = None; + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + } + + pub fn coreMemories(&mut self) { + let cmd = "CORE_MEMORIES"; + let args = None; + let summary = self.client.execute_command(cmd, args); + println!("{:#?}", summary); + } + + pub fn update(&mut self) -> Result { + self.priorLevel = self.level; + let cmd = "CORE_READ"; + let args = Some("RAM;$0010;1"); + let summary = self.client.execute_command(cmd, args).unwrap(); + // println!("{:#?}", summary); + match summary { + nwa::EmulatorReply::Binary(summary) => self.level = *summary.first().unwrap(), + nwa::EmulatorReply::Error(summary) => println!("{:?}", summary), + _ => println!("{:?}", summary), + } + // println!("{:#?}", level); + + let start = self.start(); + let reset = self.reset(); + let split = self.split(); + Ok(NWASummary { + start, + reset, + split, + }) + } + + fn start(&mut self) -> bool { + if self.level == 1 && self.priorLevel == 0 { + return true; + } + false + } + + fn reset(&mut self) -> bool { + if self.level == 0 && self.priorLevel != 0 && self.reset_timer_on_game_reset { + return true; + } + false + } + + fn split(&mut self) -> bool { + if self.level > self.priorLevel && self.priorLevel < 100 { + return true; + } + false + } +} + +// let cmd = "CORE_INFO"; +// let args = Some("quickerNES"); +// let summary = client.execute_command(cmd, args); +// println!("{:#?}",summary); + +// let cmd = "CORES_LIST"; +// let args = None; +// let summary = client.execute_command(cmd, args); +// println!("{:#?}",summary); + +// let cmd = "LIST_BIZHAWK_DOMAINS"; +// let args = None; +// let summary = client.execute_command(cmd, args); +// println!("{:#?}",summary); From 93de18738afa8e8d7279fe5b4908e0ca4f23ae28 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 30 Jun 2025 00:53:25 -0400 Subject: [PATCH 20/61] formatting; switched NWA with battletoads NWA; added attempt at livesplit implementation and process access --- src/livesplit_renderer.rs | 387 ++++++++++++++++++-------------------- 1 file changed, 182 insertions(+), 205 deletions(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 95a4686..1359716 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,7 +1,5 @@ use crate::autosplitters; -use crate::autosplitters::nwa; -use crate::autosplitters::nwa::EmulatorReply; -use crate::autosplitters::nwa::NWASyncClient; +use crate::autosplitters::battletoads; use crate::autosplitters::supermetroid::Settings; use crate::autosplitters::supermetroid::SuperMetroidAutoSplitter; use crate::autosplitters::AutoSplitter; @@ -10,18 +8,20 @@ use eframe::egui; use livesplit_core::{Layout, SharedTimer, Timer}; use livesplit_hotkey::Hook; use parking_lot::RwLock; -use tungstenite::http::uri::Port; use std::alloc::System; use std::any::Any; use std::arch::x86_64::_CMP_ORD_S; use std::ffi::OsStr; +use std::net::Ipv4Addr; +use std::ops::Deref; +use std::ops::DerefMut; use std::os::linux; use std::process; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::sync::Arc; use thread_priority::{set_current_thread_priority, ThreadBuilder, ThreadPriority}; -use std::net::Ipv4Addr; +use tungstenite::http::uri::Port; use crate::config::app_config::*; use crate::hotkey::*; @@ -49,8 +49,8 @@ pub struct LiveSplitCoreRenderer { global_hotkey_hook: Option, load_errors: Vec, show_edit_autosplitter_settings_dialog: std::sync::Arc, - address: Ipv4Addr, - port: u32, + // address: Ipv4Addr, + // port: u32, } fn show_children( @@ -113,8 +113,8 @@ impl LiveSplitCoreRenderer { global_hotkey_hook: None, load_errors: vec![], show_edit_autosplitter_settings_dialog: std::sync::Arc::new(AtomicBool::new(false)), - address: Ipv4Addr::new(0, 0, 0, 0), - port: 48879, + // address: Ipv4Addr::new(0, 0, 0, 0), + // port: 48879, } } @@ -1035,204 +1035,142 @@ pub fn app_init( let timer = app.timer.clone(); let app_config = app.app_config.clone(); - - // use sysinfo::*; - // use process_memory::*; - // // let mut x = 0_u32; - // let mut x = sysinfo::Pid::from(0); - // let s = System::new_all(); - // let count = s.processes_by_exact_name(OsStr::new("retroarch")); - // let mut p = s.processes_by_exact_name(OsStr::new("retroarch")); - // if count.count() == 1 { - // x = p.next().unwrap().pid(); - // } - - // let proc_handle = process_memory - // let process_handle = process_memory::ProcessHandle::try_into_process_handle(&x); - // println!("{:#?}",client.unwrap().); - - - // This thread deals with polling the SNES at a fixed rate. if app_config.read().unwrap().use_autosplitter == Some(true) { if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::QUSB2SNES) { - //QUSB2SNES stuff here - let settings = app.settings.clone(); - let _snes_polling_thread = ThreadBuilder::default() - .name("SNES Polling Thread".to_owned()) - // We could change this thread priority, but we probably - // should leave it at the default to make sure we get timely - // polling of SNES state - .spawn(move |_| { - loop { - let latency = Arc::new(RwLock::new((0.0, 0.0))); - print_on_error(|| -> anyhow::Result<()> { - let mut client = crate::usb2snes::SyncClient::connect() - .context("creating usb2snes connection")?; - client.set_name("annelid")?; - println!("Server version is {:?}", client.app_version()?); - let mut devices = client.list_device()?.to_vec(); - if devices.len() != 1 { - if devices.is_empty() { - Err(anyhow!("No devices present"))?; - } else { - Err(anyhow!("You need to select a device: {:#?}", devices))?; + //QUSB2SNES stuff here + let settings = app.settings.clone(); + let _snes_polling_thread = ThreadBuilder::default() + .name("SNES Polling Thread".to_owned()) + // We could change this thread priority, but we probably + // should leave it at the default to make sure we get timely + // polling of SNES state + .spawn(move |_| { + loop { + let latency = Arc::new(RwLock::new((0.0, 0.0))); + print_on_error(|| -> anyhow::Result<()> { + let mut client = crate::usb2snes::SyncClient::connect() + .context("creating usb2snes connection")?; + client.set_name("annelid")?; + println!("Server version is {:?}", client.app_version()?); + let mut devices = client.list_device()?.to_vec(); + if devices.len() != 1 { + if devices.is_empty() { + Err(anyhow!("No devices present"))?; + } else { + Err(anyhow!("You need to select a device: {:#?}", devices))?; + } } - } - let device = devices.pop().ok_or(anyhow!("Device list was empty"))?; - println!("Using device: {}", device); - client.attach(&device)?; - println!("Connected."); - println!("{:#?}", client.info()?); + let device = devices.pop().ok_or(anyhow!("Device list was empty"))?; + println!("Using device: {}", device); + client.attach(&device)?; + println!("Connected."); + println!("{:#?}", client.info()?); - let mut autosplitter: Box = - Box::new(SuperMetroidAutoSplitter::new(settings.clone())); - loop { - let summary = autosplitter.update(&mut client)?; - if summary.start { - timer - .write() - .map_err(|e| { - anyhow!("failed to acquire write lock on timer: {e}") - })? - .start() - .ok(); - } - if summary.reset - && app_config - .read() - .map_err(|e| { - anyhow!("failed to acquire read lock on config: {e}") - })? - .reset_timer_on_game_reset - == Some(true) - { - timer - .write() - .map_err(|e| { - anyhow!("failed to acquire write lock on timer: {e}") - })? - .reset(true) - .ok(); - } - if summary.split { - if let Some(t) = autosplitter.gametime_to_seconds() { + // TODO: make this generic as well based on user input or add game selector + let mut autosplitter: Box = + Box::new(SuperMetroidAutoSplitter::new(settings.clone())); + loop { + let summary = autosplitter.update(&mut client)?; + if summary.start { timer .write() .map_err(|e| { anyhow!("failed to acquire write lock on timer: {e}") })? - .set_game_time(t) + .start() + .ok(); + } + if summary.reset + && app_config + .read() + .map_err(|e| { + anyhow!("failed to acquire read lock on config: {e}") + })? + .reset_timer_on_game_reset + == Some(true) + { + timer + .write() + .map_err(|e| { + anyhow!("failed to acquire write lock on timer: {e}") + })? + .reset(true) + .ok(); + } + if summary.split { + if let Some(t) = autosplitter.gametime_to_seconds() { + timer + .write() + .map_err(|e| { + anyhow!( + "failed to acquire write lock on timer: {e}" + ) + })? + .set_game_time(t) + .ok(); + } + timer + .write() + .map_err(|e| { + anyhow!("failed to acquire write lock on timer: {e}") + })? + .split() .ok(); } - timer - .write() - .map_err(|e| { - anyhow!("failed to acquire write lock on timer: {e}") - })? - .split() - .ok(); - } - { - *latency.write() = - (summary.latency_average, summary.latency_stddev); - } - // If the timer gets reset, we need to make a fresh snes state - if let Ok(ThreadEvent::TimerReset) = sync_receiver.try_recv() { - autosplitter.reset_game_tracking(); - // Reset the snes - if app_config - .read() - .map_err(|e| { - anyhow!("failed to acquire read lock on config: {e}") - })? - .reset_game_on_timer_reset - == Some(true) { - client.reset()?; + *latency.write() = + (summary.latency_average, summary.latency_stddev); + } + // If the timer gets reset, we need to make a fresh snes state + if let Ok(ThreadEvent::TimerReset) = sync_receiver.try_recv() { + autosplitter.reset_game_tracking(); + // Reset the snes + if app_config + .read() + .map_err(|e| { + anyhow!("failed to acquire read lock on config: {e}") + })? + .reset_game_on_timer_reset + == Some(true) + { + client.reset()?; + } } + std::thread::sleep(std::time::Duration::from_millis( + (1000.0 / polling_rate) as u64, + )); } - std::thread::sleep(std::time::Duration::from_millis( - (1000.0 / polling_rate) as u64, - )); - } - }); - std::thread::sleep(std::time::Duration::from_millis(1000)); - } - }) - //TODO: fix this unwrap - .unwrap(); - } - - else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::NWA) { - //NWA stuff here - let address = app.address.clone(); - let port = app.port.clone(); - - let _nwa_polling_thread = ThreadBuilder::default() - .name("NWA Polling Thread".to_owned()) - // We could change this thread priority, but we probably - // should leave it at the default to make sure we get timely - // polling of SNES state - .spawn(move |_| { - loop { + }); + std::thread::sleep(std::time::Duration::from_millis(1000)); + } + }) + //TODO: fix this unwrap + .unwrap(); + } else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::NWA) { + //NWA stuff here + let _nwa_polling_thread = ThreadBuilder::default() + .name("NWA Polling Thread".to_owned()) + .spawn(move |_| loop { print_on_error(|| -> anyhow::Result<()> { - let mut client = NWASyncClient::connect(&address.to_string(), port).unwrap(); // TODO: Need to handle error - let cmd = "EMULATOR_INFO"; - let args = Some("0"); - let summary = client.execute_command(cmd, args); - println!("{:#?}",summary); - let cmd = "GAME_INFO"; - let args = None; - let summary = client.execute_command(cmd, args); - println!("{:#?}",summary); - // let cmd = "CORE_INFO"; - // let args = Some("quickerNES"); - // let summary = client.execute_command(cmd, args); - // println!("{:#?}",summary); - let cmd = "EMULATION_STATUS"; - let args = None; - let summary = client.execute_command(cmd, args); - println!("{:#?}",summary); - // let cmd = "CORES_LIST"; - // let args = None; - // let summary = client.execute_command(cmd, args); - // println!("{:#?}",summary); - let cmd = "MY_NAME_IS"; - let args = Some("Annelid"); - let summary = client.execute_command(cmd, args); - println!("{:#?}",summary); - let cmd = "CORE_CURRENT_INFO"; - let args = None; - let summary = client.execute_command(cmd, args); - println!("{:#?}",summary); - // let cmd = "CORE_MEMORIES"; - // let args = None; - // let summary = client.execute_command(cmd, args); - // println!("{:#?}",summary); - // let cmd = "LIST_BIZHAWK_DOMAINS"; - // let args = None; - // let summary = client.execute_command(cmd, args); - // println!("{:#?}",summary); - - let mut priorLevel = 0_u8; + // TODO: make this generic as well based on user input or add game selector + let mut client = battletoads::battletoadsAutoSplitter::new( + Ipv4Addr::new(0, 0, 0, 0), + 48879, + app_config + .read() + .unwrap() + .reset_timer_on_game_reset + .unwrap(), + ); + client.emuInfo(); + client.emuGameInfo(); + client.emuStatus(); + client.clientID(); + client.coreInfo(); loop { - let cmd = "CORE_READ"; - let args = Some("RAM;$0010;1"); - let summary = client.execute_command(cmd, args); - println!("{:#?}",summary); - let nwaResult = summary.unwrap(); - println!("{:#?}",nwaResult); - let mut level= 0_u8; - - match nwaResult { - EmulatorReply::Ascii(nwaResult) => println!("{:?}",nwaResult), - EmulatorReply::Binary(nwaResult) => level = nwaResult.first().unwrap().clone(), - EmulatorReply::Error(nwaResult) => println!("{:?}",nwaResult), - - } - println!("{:#?}",level); - if level == 1 && priorLevel == 0 { + let autoSplitStatus = client.update().unwrap(); + if autoSplitStatus.start == true { timer .write() .map_err(|e| { @@ -1241,15 +1179,7 @@ pub fn app_init( .start() .ok(); } - else if level == 0 && priorLevel != 0 - && app_config - .read() - .map_err(|e| { - anyhow!("failed to acquire read lock on config: {e}") - })? - .reset_timer_on_game_reset - == Some(true) - { + if autoSplitStatus.reset == true { timer .write() .map_err(|e| { @@ -1258,7 +1188,7 @@ pub fn app_init( .reset(true) .ok(); } - else if level > priorLevel && priorLevel < 100 { + if autoSplitStatus.split == true { timer .write() .map_err(|e| { @@ -1267,7 +1197,6 @@ pub fn app_init( .split() .ok(); } - priorLevel = level; std::thread::sleep(std::time::Duration::from_millis( (1000.0 / polling_rate) as u64, @@ -1275,10 +1204,58 @@ pub fn app_init( } }); std::thread::sleep(std::time::Duration::from_millis(1000)); - } - }) - //TODO: fix this unwrap + }) + //TODO: fix this unwrap + .unwrap(); + } else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::ASL) { + //unable to configure runtime + + // let test = livesplit_auto_splitting::Runtime::new(module, timer, settings_store); + // Livesplit autosplitter support + // use livesplit_auto_splitting::*; + // let test = ; + // let test = livesplit_auto_splitting::Timer; + // let module = livesplit_auto_splitting::Runtime:: + // livesplit_auto_splitting::Runtime::new(module, timer, settings_store) + // let x = livesplit_auto_splitting::Runtime::new(module, timer.write().unwrap().deref(), settings_store); + } else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::CUSTOM) + { + // process isn't consistently gotten + // reading crashes with either bad address as root or permission denied as user + + use process_memory::*; + // use sysinfo::*; + // let mut x = 0_u64; + let mut x = sysinfo::Pid::from(17696).as_u32(); + // let s = System::new_all(); + // for (pid, process) in s.processes() { + // println!("{} {:?}", pid, process.name()); + // } + // let count = s.processes().clone().("retroarch"); + // let count = s.processes_by_exact_name(OsStr::new("retroarch")).count(); + // let p = s.processes_by_exact_name(OsStr::new("retroarch")); + // if count == 2 { + // x = p.last().unwrap().pid().as_u32(); + // } + println!("{:?}", x); + + let arch = process_memory::Architecture::from_native(); + let process_handle = process_memory::ProcessHandle::try_into_process_handle(&( + x.try_into().unwrap(), + arch, + )) .unwrap(); - }; + let mut member = DataMember::::new_offset(process_handle, vec![0x10]); + member.set_offset(vec![0x10]); + + // The memory offset can now be correctly calculated: + // called `Result::unwrap()` on an `Err` value: Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" } + println!( + "Target memory location: {}", + member.clone().get_offset().unwrap() + ); + // The memory offset can now be used to retrieve and modify values: + // println!("Current value: {}", unsafe { member.read().unwrap() }); + } } } From 6c8f12de597bde2f4a818ef0545f1d014375acfb Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 30 Jun 2025 03:16:54 -0400 Subject: [PATCH 21/61] added attempt at generic object creation and access --- src/autosplitters.rs | 16 +++++++++++++--- src/livesplit_renderer.rs | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/autosplitters.rs b/src/autosplitters.rs index e3a989f..5d30a16 100644 --- a/src/autosplitters.rs +++ b/src/autosplitters.rs @@ -1,10 +1,9 @@ pub mod json; -// pub mod nwa; pub mod battletoads; pub mod supermetroid; - use anyhow::Result; -use livesplit_core::TimeSpan; +use std::net::Ipv4Addr; +use livesplit_core::{GameTime, TimeSpan}; #[derive(Debug, Copy, Clone, serde::Deserialize, serde::Serialize, PartialEq)] pub enum AType { @@ -14,6 +13,17 @@ pub enum AType { CUSTOM, } +// Not sure how to do this... +pub fn AutoSplitterSelector(game: &str,reset_timer_on_game_reset: bool) -> Object { + match game { + "Battletoads" => return battletoads::battletoadsAutoSplitter::new( + Ipv4Addr::new(0, 0, 0, 0), + 48879, reset_timer_on_game_reset + ), + &_ => todo!() + } +} + #[derive(Debug, Copy, Clone)] pub struct NWASummary { pub start: bool, diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 1359716..e985dda 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1153,6 +1153,7 @@ pub fn app_init( .name("NWA Polling Thread".to_owned()) .spawn(move |_| loop { print_on_error(|| -> anyhow::Result<()> { + let _test = AutoSplitterSelector("Battletoads", true); // TODO: make this generic as well based on user input or add game selector let mut client = battletoads::battletoadsAutoSplitter::new( Ipv4Addr::new(0, 0, 0, 0), From 365f324fe61c475a50cb203d6b2c1153fa384b73 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Mon, 30 Jun 2025 23:16:48 -0400 Subject: [PATCH 22/61] moved battletoads implementation --- src/autosplitters/{ => nwa}/battletoads.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/autosplitters/{ => nwa}/battletoads.rs (99%) diff --git a/src/autosplitters/battletoads.rs b/src/autosplitters/nwa/battletoads.rs similarity index 99% rename from src/autosplitters/battletoads.rs rename to src/autosplitters/nwa/battletoads.rs index 6d75451..d62f5d5 100644 --- a/src/autosplitters/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -1,5 +1,5 @@ use super::NWASummary; -use crate::nwa; +// use crate::nwa; use anyhow::Result; use std::net::Ipv4Addr; From b4a79b8cc570e1445a503b0a2ab32bea37fbdfc8 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 1 Jul 2025 22:16:10 -0400 Subject: [PATCH 23/61] example supermetroid NWA --- src/autosplitters/nwa/supermetroid.rs | 155 ++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/autosplitters/nwa/supermetroid.rs diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs new file mode 100644 index 0000000..efaa475 --- /dev/null +++ b/src/autosplitters/nwa/supermetroid.rs @@ -0,0 +1,155 @@ +use crate::autosplitters::NWASummary; +use crate::nwa; +use anyhow::Result; +use std::net::Ipv4Addr; + +pub enum Action { + start, + reset, + split, +} + +pub struct supermetroidAutoSplitter { + address: Ipv4Addr, + port: u32, + priorState: u8, + state: u8, + priorRoomID: u16, + roomID: u16, + reset_timer_on_game_reset: bool, + client: nwa::NWASyncClient, +} + +impl supermetroidAutoSplitter { + pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: bool) -> Self { + supermetroidAutoSplitter { + address, + port, + priorState: 0_u8, + state: 0_u8, + priorRoomID: 0_u16, + roomID: 0_u16, + reset_timer_on_game_reset, + client: nwa::NWASyncClient::connect(&address.to_string(), port).unwrap(), // TODO: Need to handle error + } + } + + pub fn clientID(&mut self) { + let cmd = "MY_NAME_IS"; + let args = Some("Annelid"); + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + } + + pub fn emuInfo(&mut self) { + let cmd = "EMULATOR_INFO"; + let args = Some("0"); + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + } + + pub fn emuGameInfo(&mut self) { + let cmd = "GAME_INFO"; + let args = None; + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + } + + pub fn emuStatus(&mut self) { + let cmd = "EMULATION_STATUS"; + let args = None; + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + } + + pub fn coreInfo(&mut self) { + let cmd = "CORE_CURRENT_INFO"; + let args = None; + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + } + + pub fn coreMemories(&mut self) { + let cmd = "CORE_MEMORIES"; + let args = None; + let summary = self.client.execute_command(cmd, args); + println!("{:#?}", summary); + } + + pub fn update(&mut self) -> Result { + // read memory for the game state + { + self.priorState = self.state; + let cmd = "CORE_READ"; + let args = Some("WRAM;$0998;1"); + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + match summary { + nwa::EmulatorReply::Binary(summary) => self.state = *summary.first().unwrap(), + nwa::EmulatorReply::Error(summary) => println!("{:?}", summary), + _ => println!("{:?}", summary), + } + println!("{:#?}", self.state); + } + + // read memory for room + { + self.priorRoomID = self.roomID; + let cmd = "CORE_READ"; + let args = Some("WRAM;$079B;2"); + let summary = self.client.execute_command(cmd, args).unwrap(); + println!("{:#?}", summary); + + match summary { + nwa::EmulatorReply::Binary(summary) => { + self.roomID = + // Have to reassemble the half word roomID + ((*summary.last().unwrap() as u16) << 8) | *summary.first().unwrap() as u16 + } + nwa::EmulatorReply::Error(summary) => println!("{:?}", summary), + _ => println!("{:?}", summary), + } + println!("{:#?}", self.roomID); + } + + // TODO: add the other memory reads + + let start = self.start(); + let reset = self.reset(); + let split = self.split(); + Ok(NWASummary { + start, + reset, + split, + }) + } + + fn start(&mut self) -> bool { + self.state == 0x1F && self.priorState == 0x1E + } + + fn reset(&mut self) -> bool { + self.roomID == 0 && self.priorRoomID != 0 && self.reset_timer_on_game_reset + } + + fn split(&mut self) -> bool { + self.roomID == 0xDF45 && self.priorState == 0x8 && self.state == 0x20 + + // TODO: add the rest of the splits + } +} + +// let cmd = "CORE_INFO"; +// let args = Some("quickerNES"); +// let summary = client.execute_command(cmd, args); +// println!("{:#?}",summary); + +// let cmd = "CORES_LIST"; +// let args = None; +// let summary = client.execute_command(cmd, args); +// println!("{:#?}",summary); + +// let cmd = "LIST_BIZHAWK_DOMAINS"; +// let args = None; +// let summary = client.execute_command(cmd, args); +// println!("{:#?}",summary); From 50ad5f30d87abb6b23b0add685093eab4a7d945e Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 1 Jul 2025 22:16:52 -0400 Subject: [PATCH 24/61] removed excess comments --- src/autosplitters/nwa/battletoads.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/autosplitters/nwa/battletoads.rs b/src/autosplitters/nwa/battletoads.rs index d62f5d5..a7dfa9e 100644 --- a/src/autosplitters/nwa/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -1,5 +1,5 @@ -use super::NWASummary; -// use crate::nwa; +use crate::autosplitters::NWASummary; +use crate::nwa; use anyhow::Result; use std::net::Ipv4Addr; @@ -21,8 +21,8 @@ pub struct battletoadsAutoSplitter { impl battletoadsAutoSplitter { pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: bool) -> Self { battletoadsAutoSplitter { - address, // address: Ipv4Addr::new(0, 0, 0, 0), - port, // port: 48879, + address, + port, priorLevel: 0_u8, level: 0_u8, reset_timer_on_game_reset, From 2d0ac26ff1a402974de03be2308348034b45a486 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 1 Jul 2025 22:17:10 -0400 Subject: [PATCH 25/61] rust directory file --- src/autosplitters/nwa.rs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/autosplitters/nwa.rs diff --git a/src/autosplitters/nwa.rs b/src/autosplitters/nwa.rs new file mode 100644 index 0000000..edde02a --- /dev/null +++ b/src/autosplitters/nwa.rs @@ -0,0 +1,2 @@ +pub mod battletoads; +pub mod supermetroid; From afb53fa0e7c99d6ae204f695eaf881873e6b3321 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 1 Jul 2025 22:18:24 -0400 Subject: [PATCH 26/61] formatting --- src/nwa.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nwa.rs b/src/nwa.rs index a37f28f..5293891 100644 --- a/src/nwa.rs +++ b/src/nwa.rs @@ -174,6 +174,7 @@ impl NWASyncClient { } self.get_reply() } + pub fn execute_raw_command(&mut self, cmd: &str, argString: Option<&str>) { if argString == None { self.connection.write(format!("{}\n", cmd).as_bytes()); From a6ad78b4c9fa215b7773a58996bbbe0030ee645a Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 1 Jul 2025 22:22:01 -0400 Subject: [PATCH 27/61] switched crate due to location change; added Game enum crate; removed used code; added drop down to attempt NWA game selection; added NWA super metroid example; added TODO labels for asl and custom memory access --- src/livesplit_renderer.rs | 286 +++++++++++++++++++++----------------- 1 file changed, 159 insertions(+), 127 deletions(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index e985dda..ee1b050 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,8 +1,9 @@ use crate::autosplitters; -use crate::autosplitters::battletoads; +use crate::autosplitters::nwa; use crate::autosplitters::supermetroid::Settings; use crate::autosplitters::supermetroid::SuperMetroidAutoSplitter; use crate::autosplitters::AutoSplitter; +use crate::autosplitters::Game; use anyhow::{anyhow, Context, Result}; use eframe::egui; use livesplit_core::{Layout, SharedTimer, Timer}; @@ -49,6 +50,7 @@ pub struct LiveSplitCoreRenderer { global_hotkey_hook: Option, load_errors: Vec, show_edit_autosplitter_settings_dialog: std::sync::Arc, + game: Game, // address: Ipv4Addr, // port: u32, } @@ -113,6 +115,7 @@ impl LiveSplitCoreRenderer { global_hotkey_hook: None, load_errors: vec![], show_edit_autosplitter_settings_dialog: std::sync::Arc::new(AtomicBool::new(false)), + game: Game::Battletoads, // address: Ipv4Addr::new(0, 0, 0, 0), // port: 48879, } @@ -640,17 +643,6 @@ impl LiveSplitCoreRenderer { { let show_deferred_viewport = self.show_edit_autosplitter_settings_dialog.clone(); let aSettings = self.settings.clone(); - // let local_change_binding= self.change_binding.clone(); - // let hStart = self.app_config.read().unwrap().hot_key_start.unwrap(); - // let hReset = self.app_config.read().unwrap().hot_key_reset.unwrap(); - // let hUndo = self.app_config.read().unwrap().hot_key_undo.unwrap(); - // let hSkip = self.app_config.read().unwrap().hot_key_skip.unwrap(); - // let hPause = self.app_config.read().unwrap().hot_key_pause.unwrap(); - // let hSwitchP = self.app_config.read().unwrap().hot_key_comparison_prev.unwrap(); - // let hSwitchN = self.app_config.read().unwrap().hot_key_comparison_next.unwrap(); - // let hToggleG = self.app_config.read().unwrap().hot_key_toggle_global_hotkeys.unwrap(); - // let mut hSelector = self.hotkey_selector.clone(); - // let mut globalHotkeys = self.app_config.clone(); ctx.show_viewport_deferred( egui::ViewportId::from_hash_of("deferred_viewport"), @@ -676,60 +668,6 @@ impl LiveSplitCoreRenderer { let mut roots = settings.write().roots(); show_children(&mut settings.write(), ui, ctx, &mut roots); }); - // ui.label("Hotkeys"); - // ui.label("Start / Split"); - // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hStart)); - // if response.clicked() { - // local_change_binding.store(true, Ordering::Relaxed); - // hSelector.write().unwrap().replace(1); - // } - // ui.label("Reset"); - // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hReset)); - // if response.clicked() { - // local_change_binding.store(true, Ordering::Relaxed); - // hSelector.write().unwrap().replace(2); - // } - // ui.label("Undo Split"); - // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hUndo)); - // if response.clicked() { - // local_change_binding.store(true, Ordering::Relaxed); - // hSelector.write().unwrap().replace(3); - // } - // ui.label("Skip Split"); - // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hSkip)); - // if response.clicked() { - // local_change_binding.store(true, Ordering::Relaxed); - // hSelector.write().unwrap().replace(4); - // } - // ui.label("Pause"); - // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hPause)); - // if response.clicked() { - // local_change_binding.store(true, Ordering::Relaxed); - // hSelector.write().unwrap().replace(5); - // } - // ui.label("Switch Comparison (Previous)"); - // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hSwitchP)); - // if response.clicked() { - // local_change_binding.store(true, Ordering::Relaxed); - // hSelector.write().unwrap().replace(6); - // } - // ui.label("Switch Comparison (Next)"); - // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hSwitchN)); - // if response.clicked() { - // local_change_binding.store(true, Ordering::Relaxed); - // hSelector.write().unwrap().replace(7); - // } - // ui.label("Toggle Global Hotkeys"); - // let response = ui.button(LiveSplitCoreRenderer::button_text_update(hToggleG)); - // if response.clicked() { - // local_change_binding.store(true, Ordering::Relaxed); - // hSelector.write().unwrap().replace(8); - // } - // let mut value = globalHotkeys.read().unwrap().global_hotkeys.unwrap(); - // let response = ui.checkbox(&mut value, "Global Hotkeys"); - // if response.clicked() { - // globalHotkeys.write().unwrap().global_hotkeys.replace(value); - // } }); if ctx.input(|i| i.viewport().close_requested()) { @@ -738,9 +676,6 @@ impl LiveSplitCoreRenderer { } }, ); - // if self.change_binding.load(Ordering::Relaxed) { - // self.update_keybinding(ctx); - // } } } } @@ -895,21 +830,51 @@ impl eframe::App for LiveSplitCoreRenderer { } }); ui.menu_button("Autosplitter", |ui| { - if ui.button("Configure").clicked() { - // self.show_settings_editor = true; - let show_deferred_viewport = true; - self.show_edit_autosplitter_settings_dialog - .store(show_deferred_viewport, Ordering::Relaxed); - ui.close_menu(); - } - if ui.button("Load Configuration").clicked() { - ui.close_menu(); - self.open_autosplitter_dialog(&document_dir).unwrap(); - } - if ui.button("Save Configuration").clicked() { - ui.close_menu(); - self.save_autosplitter_dialog(&document_dir).unwrap(); - } + ui.menu_button("NWA", |ui| { + if ui.button("Configure").clicked() { + // Fill out NWA config + // address + // port + } + // TODO: Fix this. It's not updating the value + egui::ComboBox::from_label("Game") + .selected_text(format!("{:?}", self.game)) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.game, + Game::Battletoads, + "Battletoads", + ); + ui.selectable_value( + &mut self.game, + Game::SuperMetroid, + "Super Metroid", + ); + }) + // ui.menu_button("Battletoads", |ui| { + // }); + // ui.menu_button("Super Metroid", |ui| { + // }); + }); + ui.menu_button("QUSB2SNES", |ui| { + ui.menu_button("Super Metroid", |ui| { + if ui.button("Configure").clicked() { + // self.show_settings_editor = true; + let show_deferred_viewport = true; + self.show_edit_autosplitter_settings_dialog + .store(show_deferred_viewport, Ordering::Relaxed); + ui.close_menu(); + } + if ui.button("Load Configuration").clicked() { + ui.close_menu(); + self.open_autosplitter_dialog(&document_dir).unwrap(); + } + if ui.button("Save Configuration").clicked() { + ui.close_menu(); + self.save_autosplitter_dialog(&document_dir).unwrap(); + } + }); + }); }); ui.separator(); ui.add(egui::widgets::Label::new(format!( @@ -1067,7 +1032,7 @@ pub fn app_init( println!("Connected."); println!("{:#?}", client.info()?); - // TODO: make this generic as well based on user input or add game selector + // TODO: make this generic as well based on user input or add game selector let mut autosplitter: Box = Box::new(SuperMetroidAutoSplitter::new(settings.clone())); loop { @@ -1149,67 +1114,134 @@ pub fn app_init( .unwrap(); } else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::NWA) { //NWA stuff here - let _nwa_polling_thread = ThreadBuilder::default() - .name("NWA Polling Thread".to_owned()) - .spawn(move |_| loop { - print_on_error(|| -> anyhow::Result<()> { - let _test = AutoSplitterSelector("Battletoads", true); - // TODO: make this generic as well based on user input or add game selector - let mut client = battletoads::battletoadsAutoSplitter::new( - Ipv4Addr::new(0, 0, 0, 0), - 48879, - app_config - .read() - .unwrap() - .reset_timer_on_game_reset - .unwrap(), - ); - client.emuInfo(); - client.emuGameInfo(); - client.emuStatus(); - client.clientID(); - client.coreInfo(); - loop { - let autoSplitStatus = client.update().unwrap(); - if autoSplitStatus.start == true { - timer + let game = app.game.clone(); + let _nwa_polling_thread = + ThreadBuilder::default() + .name("NWA Polling Thread".to_owned()) + .spawn(move |_| loop { + print_on_error(|| -> anyhow::Result<()> { + // let client: battletoadsAutoSplitter = autosplitters::AutoSplitterSelector("Battletoads", true).unwrap(); + // TODO: make this generic as well based on user input or add game selector + match game { + Game::Battletoads => { + let mut client = nwa::battletoads::battletoadsAutoSplitter::new( + Ipv4Addr::new(0, 0, 0, 0), + 48879, + app_config + .read() + .unwrap() + .reset_timer_on_game_reset + .unwrap(), + ); + client.emuInfo(); + client.emuGameInfo(); + client.emuStatus(); + client.clientID(); + client.coreInfo(); + client.coreMemories(); + loop { + println!("{:#?}", game); + let autoSplitStatus = client.update().unwrap(); + if autoSplitStatus.start == true { + timer .write() .map_err(|e| { anyhow!("failed to acquire write lock on timer: {e}") })? .start() .ok(); - } - if autoSplitStatus.reset == true { - timer + } + if autoSplitStatus.reset == true { + timer .write() .map_err(|e| { anyhow!("failed to acquire write lock on timer: {e}") })? .reset(true) .ok(); - } - if autoSplitStatus.split == true { - timer + } + if autoSplitStatus.split == true { + timer + .write() + .map_err(|e| { + anyhow!("failed to acquire write lock on timer: {e}") + })? + .split() + .ok(); + } + + std::thread::sleep(std::time::Duration::from_millis( + (1000.0 / polling_rate) as u64, + )); + } + } + Game::SuperMetroid => { + let mut client = + nwa::supermetroid::supermetroidAutoSplitter::new( + Ipv4Addr::new(0, 0, 0, 0), + 48879, + app_config + .read() + .unwrap() + .reset_timer_on_game_reset + .unwrap(), + ); + client.emuInfo(); + client.emuGameInfo(); + client.emuStatus(); + client.clientID(); + client.coreInfo(); + client.coreMemories(); + loop { + println!("{:#?}", game); + let autoSplitStatus = client.update().unwrap(); + if autoSplitStatus.start == true { + timer + .write() + .map_err(|e| { + anyhow!("failed to acquire write lock on timer: {e}") + })? + .start() + .ok(); + } + if autoSplitStatus.reset == true { + timer + .write() + .map_err(|e| { + anyhow!("failed to acquire write lock on timer: {e}") + })? + .reset(true) + .ok(); + } + if autoSplitStatus.split == true { + timer .write() .map_err(|e| { anyhow!("failed to acquire write lock on timer: {e}") })? .split() .ok(); + } + + std::thread::sleep(std::time::Duration::from_millis( + (1000.0 / polling_rate) as u64, + )); + } + } + _ => todo!(), } + // if app.game == Game::Battletoads { - std::thread::sleep(std::time::Duration::from_millis( - (1000.0 / polling_rate) as u64, - )); - } - }); - std::thread::sleep(std::time::Duration::from_millis(1000)); - }) - //TODO: fix this unwrap - .unwrap(); + // } else if app.game == Game::SuperMetroid { + + // } + }); + std::thread::sleep(std::time::Duration::from_millis(1000)); + }) + //TODO: fix this unwrap + .unwrap(); } else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::ASL) { - //unable to configure runtime + //TODO: unable to configure runtime // let test = livesplit_auto_splitting::Runtime::new(module, timer, settings_store); // Livesplit autosplitter support @@ -1221,8 +1253,8 @@ pub fn app_init( // let x = livesplit_auto_splitting::Runtime::new(module, timer.write().unwrap().deref(), settings_store); } else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::CUSTOM) { - // process isn't consistently gotten - // reading crashes with either bad address as root or permission denied as user + // TODO: process isn't consistently gotten + // TODO: reading crashes with either bad address as root or permission denied as user use process_memory::*; // use sysinfo::*; From 99f485120c0e1f817c59a46925436c588f77ed8d Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 1 Jul 2025 22:22:50 -0400 Subject: [PATCH 28/61] location update; generic game selector attempt --- src/autosplitters.rs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/autosplitters.rs b/src/autosplitters.rs index 5d30a16..383aa95 100644 --- a/src/autosplitters.rs +++ b/src/autosplitters.rs @@ -1,8 +1,9 @@ pub mod json; -pub mod battletoads; +pub mod nwa; pub mod supermetroid; use anyhow::Result; -use std::net::Ipv4Addr; +// use std::net::Ipv4Addr; +// use std::error::Error; use livesplit_core::{GameTime, TimeSpan}; #[derive(Debug, Copy, Clone, serde::Deserialize, serde::Serialize, PartialEq)] @@ -13,17 +14,24 @@ pub enum AType { CUSTOM, } -// Not sure how to do this... -pub fn AutoSplitterSelector(game: &str,reset_timer_on_game_reset: bool) -> Object { - match game { - "Battletoads" => return battletoads::battletoadsAutoSplitter::new( - Ipv4Addr::new(0, 0, 0, 0), - 48879, reset_timer_on_game_reset - ), - &_ => todo!() - } +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Game { + Battletoads, + SuperMetroid, + None, } +// // Not sure how to do this... +// pub fn AutoSplitterSelector(game: &str,reset_timer_on_game_reset: bool) -> Result> { +// match game { +// "Battletoads" => Ok(Game::Battletoads(battletoadsAutoSplitter::new( +// Ipv4Addr::new(0, 0, 0, 0), +// 48879, reset_timer_on_game_reset +// ))), +// _ => panic!("Worker type not found") +// } +// } + #[derive(Debug, Copy, Clone)] pub struct NWASummary { pub start: bool, From 96363298d50624a8444171d8852a181de3236fed Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 21:18:01 -0400 Subject: [PATCH 29/61] clippy and fmt run --- src/autosplitters.rs | 2 +- src/config/app_config.rs | 2 +- src/livesplit_renderer.rs | 25 ++++++++----------------- src/nwa.rs | 30 ++++++++++++------------------ 4 files changed, 22 insertions(+), 37 deletions(-) diff --git a/src/autosplitters.rs b/src/autosplitters.rs index 383aa95..f58d436 100644 --- a/src/autosplitters.rs +++ b/src/autosplitters.rs @@ -4,7 +4,7 @@ pub mod supermetroid; use anyhow::Result; // use std::net::Ipv4Addr; // use std::error::Error; -use livesplit_core::{GameTime, TimeSpan}; +use livesplit_core::TimeSpan; #[derive(Debug, Copy, Clone, serde::Deserialize, serde::Serialize, PartialEq)] pub enum AType { diff --git a/src/config/app_config.rs b/src/config/app_config.rs index 4587b1e..50da3b8 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -152,7 +152,7 @@ impl AppConfig { // }).unwrap; }) - .unwrap_or(Self::new()); + .unwrap_or_default(); self = saved_config; Ok(()) }); diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index ee1b050..7761a69 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -9,20 +9,11 @@ use eframe::egui; use livesplit_core::{Layout, SharedTimer, Timer}; use livesplit_hotkey::Hook; use parking_lot::RwLock; -use std::alloc::System; -use std::any::Any; -use std::arch::x86_64::_CMP_ORD_S; -use std::ffi::OsStr; use std::net::Ipv4Addr; -use std::ops::Deref; -use std::ops::DerefMut; -use std::os::linux; -use std::process; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::sync::Arc; use thread_priority::{set_current_thread_priority, ThreadBuilder, ThreadPriority}; -use tungstenite::http::uri::Port; use crate::config::app_config::*; use crate::hotkey::*; @@ -1114,7 +1105,7 @@ pub fn app_init( .unwrap(); } else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::NWA) { //NWA stuff here - let game = app.game.clone(); + let game = app.game; let _nwa_polling_thread = ThreadBuilder::default() .name("NWA Polling Thread".to_owned()) @@ -1142,7 +1133,7 @@ pub fn app_init( loop { println!("{:#?}", game); let autoSplitStatus = client.update().unwrap(); - if autoSplitStatus.start == true { + if autoSplitStatus.start { timer .write() .map_err(|e| { @@ -1151,7 +1142,7 @@ pub fn app_init( .start() .ok(); } - if autoSplitStatus.reset == true { + if autoSplitStatus.reset { timer .write() .map_err(|e| { @@ -1160,7 +1151,7 @@ pub fn app_init( .reset(true) .ok(); } - if autoSplitStatus.split == true { + if autoSplitStatus.split { timer .write() .map_err(|e| { @@ -1195,7 +1186,7 @@ pub fn app_init( loop { println!("{:#?}", game); let autoSplitStatus = client.update().unwrap(); - if autoSplitStatus.start == true { + if autoSplitStatus.start { timer .write() .map_err(|e| { @@ -1204,7 +1195,7 @@ pub fn app_init( .start() .ok(); } - if autoSplitStatus.reset == true { + if autoSplitStatus.reset { timer .write() .map_err(|e| { @@ -1213,7 +1204,7 @@ pub fn app_init( .reset(true) .ok(); } - if autoSplitStatus.split == true { + if autoSplitStatus.split { timer .write() .map_err(|e| { @@ -1259,7 +1250,7 @@ pub fn app_init( use process_memory::*; // use sysinfo::*; // let mut x = 0_u64; - let mut x = sysinfo::Pid::from(17696).as_u32(); + let x = sysinfo::Pid::from(17696).as_u32(); // let s = System::new_all(); // for (pid, process) in s.processes() { // println!("{} {:?}", pid, process.name()); diff --git a/src/nwa.rs b/src/nwa.rs index 5293891..4af5170 100644 --- a/src/nwa.rs +++ b/src/nwa.rs @@ -51,14 +51,14 @@ impl NWASyncClient { let co = TcpStream::connect_timeout(&addr[0], Duration::from_millis(1000))?; Ok(NWASyncClient { connection: co, - port: port, + port, addr: addr[0], }) } pub fn get_reply(&mut self) -> Result { let mut read_stream = BufReader::new(self.connection.try_clone().unwrap()); - let mut first_byte = [0 as u8; 1]; + let mut first_byte = [0_u8; 1]; if read_stream.read(&mut first_byte)? == 0 { return Err(std::io::Error::new( std::io::ErrorKind::ConnectionAborted, @@ -76,7 +76,7 @@ impl NWASyncClient { let rep = read_stream.read_until(b'\n', &mut line)?; //println!("{:?}", String::from_utf8(line.clone())); - if line[0] == b'\n' && map.len() == 0 { + if line[0] == b'\n' && map.is_empty() { return Ok(EmulatorReply::Ascii(AsciiReply::Ok)); } if line[0] == b'\n' { @@ -85,8 +85,8 @@ impl NWASyncClient { if rep == 0 { break; } - let mut key = [0 as u8; 100]; - let mut value = [0 as u8; 1024]; + let mut key = [0_u8; 100]; + let mut value = [0_u8; 1024]; let mut cpt = 0; while line[cpt] != b':' && line[cpt] != b'\n' { key[cpt] = line[cpt]; @@ -95,10 +95,7 @@ impl NWASyncClient { let end_key = cpt; // Should have stopped on : if line[cpt] == b'\n' { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Mal formed reply", - )); + return Err(std::io::Error::other("Mal formed reply")); } cpt += 1; let offset = cpt; @@ -138,7 +135,7 @@ impl NWASyncClient { if first_byte == 0 { let mut header = vec![0; 4]; let r_size = read_stream.read(&mut header)?; - println!(""); + println!(); //println!("Reading {:}", r_size); //println!("Header : {:?}", header); let header = header; @@ -155,10 +152,7 @@ impl NWASyncClient { //println!("Size : {:}", size); return Ok(EmulatorReply::Binary(data)); } - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Invalid reply", - )) + Err(std::io::Error::other("Invalid reply")) } pub fn execute_command( @@ -166,7 +160,7 @@ impl NWASyncClient { cmd: &str, argString: Option<&str>, ) -> Result { - if argString == None { + if argString.is_none() { self.connection.write(format!("{}\n", cmd).as_bytes())?; } else { self.connection @@ -176,7 +170,7 @@ impl NWASyncClient { } pub fn execute_raw_command(&mut self, cmd: &str, argString: Option<&str>) { - if argString == None { + if argString.is_none() { self.connection.write(format!("{}\n", cmd).as_bytes()); } else { self.connection @@ -200,7 +194,7 @@ impl NWASyncClient { if let Ok(usize) = self.connection.peek(&mut buf) { return true; } - return false; + false } pub fn close(&mut self) { @@ -208,7 +202,7 @@ impl NWASyncClient { } pub fn reconnected(&mut self) -> Result { self.connection = TcpStream::connect_timeout(&self.addr, Duration::from_millis(1000))?; - return Ok(true); + Ok(true) } } From 1deb27466050e6b7ae03507e6b81cd35d681c715 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 21:25:20 -0400 Subject: [PATCH 30/61] clippy and fmt changes --- data/BattleToads | 18 ++++++++++ src/autosplitters/nwa/battletoads.rs | 36 +++++++++---------- src/autosplitters/nwa/supermetroid.rs | 50 +++++++++++++-------------- src/livesplit_renderer.rs | 36 +++++++++---------- 4 files changed, 79 insertions(+), 61 deletions(-) create mode 100644 data/BattleToads diff --git a/data/BattleToads b/data/BattleToads new file mode 100644 index 0000000..4043779 --- /dev/null +++ b/data/BattleToads @@ -0,0 +1,18 @@ +0x000D = count of levels beaten +0x0010 = level ID + +; enum level_names +intro: = 0 +ragnarok_canyon: = 1 +wookie_hole: = 2 +TurboTunnel: = 3 +arctic_caverns: = 4 +revolution: = 5 +volkmire_inferno: = 6 +intruder_excluder: = 7 +karnath_lair: = 8 +rat_race: = 9 +clinger_winger: = $A +terra_tubes: = $B +SurfCity: = $C +armageddon: = $D \ No newline at end of file diff --git a/src/autosplitters/nwa/battletoads.rs b/src/autosplitters/nwa/battletoads.rs index a7dfa9e..2cdd4e6 100644 --- a/src/autosplitters/nwa/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -4,68 +4,68 @@ use anyhow::Result; use std::net::Ipv4Addr; pub enum Action { - start, - reset, - split, + Start, + Reset, + Split, } -pub struct battletoadsAutoSplitter { +pub struct BattletoadsAutoSplitter { address: Ipv4Addr, port: u32, - priorLevel: u8, + prior_level: u8, level: u8, reset_timer_on_game_reset: bool, client: nwa::NWASyncClient, } -impl battletoadsAutoSplitter { +impl BattletoadsAutoSplitter { pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: bool) -> Self { - battletoadsAutoSplitter { + BattletoadsAutoSplitter { address, port, - priorLevel: 0_u8, + prior_level: 0_u8, level: 0_u8, reset_timer_on_game_reset, client: nwa::NWASyncClient::connect(&address.to_string(), port).unwrap(), // TODO: Need to handle error } } - pub fn clientID(&mut self) { + pub fn client_id(&mut self) { let cmd = "MY_NAME_IS"; let args = Some("Annelid"); let summary = self.client.execute_command(cmd, args).unwrap(); println!("{:#?}", summary); } - pub fn emuInfo(&mut self) { + pub fn emu_info(&mut self) { let cmd = "EMULATOR_INFO"; let args = Some("0"); let summary = self.client.execute_command(cmd, args).unwrap(); println!("{:#?}", summary); } - pub fn emuGameInfo(&mut self) { + pub fn emu_game_info(&mut self) { let cmd = "GAME_INFO"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); println!("{:#?}", summary); } - pub fn emuStatus(&mut self) { + pub fn emu_status(&mut self) { let cmd = "EMULATION_STATUS"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); println!("{:#?}", summary); } - pub fn coreInfo(&mut self) { + pub fn core_info(&mut self) { let cmd = "CORE_CURRENT_INFO"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); println!("{:#?}", summary); } - pub fn coreMemories(&mut self) { + pub fn core_memories(&mut self) { let cmd = "CORE_MEMORIES"; let args = None; let summary = self.client.execute_command(cmd, args); @@ -73,7 +73,7 @@ impl battletoadsAutoSplitter { } pub fn update(&mut self) -> Result { - self.priorLevel = self.level; + self.prior_level = self.level; let cmd = "CORE_READ"; let args = Some("RAM;$0010;1"); let summary = self.client.execute_command(cmd, args).unwrap(); @@ -96,21 +96,21 @@ impl battletoadsAutoSplitter { } fn start(&mut self) -> bool { - if self.level == 1 && self.priorLevel == 0 { + if self.level == 1 && self.prior_level == 0 { return true; } false } fn reset(&mut self) -> bool { - if self.level == 0 && self.priorLevel != 0 && self.reset_timer_on_game_reset { + if self.level == 0 && self.prior_level != 0 && self.reset_timer_on_game_reset { return true; } false } fn split(&mut self) -> bool { - if self.level > self.priorLevel && self.priorLevel < 100 { + if self.level > self.prior_level && self.prior_level < 100 { return true; } false diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index efaa475..4c83caa 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -4,72 +4,72 @@ use anyhow::Result; use std::net::Ipv4Addr; pub enum Action { - start, - reset, - split, + Start, + Reset, + Split, } -pub struct supermetroidAutoSplitter { +pub struct SupermetroidAutoSplitter { address: Ipv4Addr, port: u32, - priorState: u8, + prior_state: u8, state: u8, - priorRoomID: u16, - roomID: u16, + prior_room_id: u16, + room_id: u16, reset_timer_on_game_reset: bool, client: nwa::NWASyncClient, } -impl supermetroidAutoSplitter { +impl SupermetroidAutoSplitter { pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: bool) -> Self { - supermetroidAutoSplitter { + SupermetroidAutoSplitter { address, port, - priorState: 0_u8, + prior_state: 0_u8, state: 0_u8, - priorRoomID: 0_u16, - roomID: 0_u16, + prior_room_id: 0_u16, + room_id: 0_u16, reset_timer_on_game_reset, client: nwa::NWASyncClient::connect(&address.to_string(), port).unwrap(), // TODO: Need to handle error } } - pub fn clientID(&mut self) { + pub fn client_id(&mut self) { let cmd = "MY_NAME_IS"; let args = Some("Annelid"); let summary = self.client.execute_command(cmd, args).unwrap(); println!("{:#?}", summary); } - pub fn emuInfo(&mut self) { + pub fn emu_info(&mut self) { let cmd = "EMULATOR_INFO"; let args = Some("0"); let summary = self.client.execute_command(cmd, args).unwrap(); println!("{:#?}", summary); } - pub fn emuGameInfo(&mut self) { + pub fn emu_game_info(&mut self) { let cmd = "GAME_INFO"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); println!("{:#?}", summary); } - pub fn emuStatus(&mut self) { + pub fn emu_status(&mut self) { let cmd = "EMULATION_STATUS"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); println!("{:#?}", summary); } - pub fn coreInfo(&mut self) { + pub fn core_info(&mut self) { let cmd = "CORE_CURRENT_INFO"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); println!("{:#?}", summary); } - pub fn coreMemories(&mut self) { + pub fn core_memories(&mut self) { let cmd = "CORE_MEMORIES"; let args = None; let summary = self.client.execute_command(cmd, args); @@ -79,7 +79,7 @@ impl supermetroidAutoSplitter { pub fn update(&mut self) -> Result { // read memory for the game state { - self.priorState = self.state; + self.prior_state = self.state; let cmd = "CORE_READ"; let args = Some("WRAM;$0998;1"); let summary = self.client.execute_command(cmd, args).unwrap(); @@ -94,7 +94,7 @@ impl supermetroidAutoSplitter { // read memory for room { - self.priorRoomID = self.roomID; + self.prior_room_id = self.room_id; let cmd = "CORE_READ"; let args = Some("WRAM;$079B;2"); let summary = self.client.execute_command(cmd, args).unwrap(); @@ -102,14 +102,14 @@ impl supermetroidAutoSplitter { match summary { nwa::EmulatorReply::Binary(summary) => { - self.roomID = + self.room_id = // Have to reassemble the half word roomID ((*summary.last().unwrap() as u16) << 8) | *summary.first().unwrap() as u16 } nwa::EmulatorReply::Error(summary) => println!("{:?}", summary), _ => println!("{:?}", summary), } - println!("{:#?}", self.roomID); + println!("{:#?}", self.room_id); } // TODO: add the other memory reads @@ -125,15 +125,15 @@ impl supermetroidAutoSplitter { } fn start(&mut self) -> bool { - self.state == 0x1F && self.priorState == 0x1E + self.state == 0x1F && self.prior_state == 0x1E } fn reset(&mut self) -> bool { - self.roomID == 0 && self.priorRoomID != 0 && self.reset_timer_on_game_reset + self.room_id == 0 && self.prior_room_id != 0 && self.reset_timer_on_game_reset } fn split(&mut self) -> bool { - self.roomID == 0xDF45 && self.priorState == 0x8 && self.state == 0x20 + self.room_id == 0xDF45 && self.prior_state == 0x8 && self.state == 0x20 // TODO: add the rest of the splits } diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 7761a69..54d7b01 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1115,7 +1115,7 @@ pub fn app_init( // TODO: make this generic as well based on user input or add game selector match game { Game::Battletoads => { - let mut client = nwa::battletoads::battletoadsAutoSplitter::new( + let mut client = nwa::battletoads::BattletoadsAutoSplitter::new( Ipv4Addr::new(0, 0, 0, 0), 48879, app_config @@ -1124,12 +1124,12 @@ pub fn app_init( .reset_timer_on_game_reset .unwrap(), ); - client.emuInfo(); - client.emuGameInfo(); - client.emuStatus(); - client.clientID(); - client.coreInfo(); - client.coreMemories(); + client.emu_info(); + client.emu_game_info(); + client.emu_status(); + client.client_id(); + client.core_info(); + client.core_memories(); loop { println!("{:#?}", game); let autoSplitStatus = client.update().unwrap(); @@ -1168,7 +1168,7 @@ pub fn app_init( } Game::SuperMetroid => { let mut client = - nwa::supermetroid::supermetroidAutoSplitter::new( + nwa::supermetroid::SupermetroidAutoSplitter::new( Ipv4Addr::new(0, 0, 0, 0), 48879, app_config @@ -1177,16 +1177,16 @@ pub fn app_init( .reset_timer_on_game_reset .unwrap(), ); - client.emuInfo(); - client.emuGameInfo(); - client.emuStatus(); - client.clientID(); - client.coreInfo(); - client.coreMemories(); + client.emu_info(); + client.emu_game_info(); + client.emu_status(); + client.client_id(); + client.core_info(); + client.core_memories(); loop { println!("{:#?}", game); - let autoSplitStatus = client.update().unwrap(); - if autoSplitStatus.start { + let auto_split_status = client.update().unwrap(); + if auto_split_status.start { timer .write() .map_err(|e| { @@ -1195,7 +1195,7 @@ pub fn app_init( .start() .ok(); } - if autoSplitStatus.reset { + if auto_split_status.reset { timer .write() .map_err(|e| { @@ -1204,7 +1204,7 @@ pub fn app_init( .reset(true) .ok(); } - if autoSplitStatus.split { + if auto_split_status.split { timer .write() .map_err(|e| { From 1165ab60801c69e4bdb03e4debf9b8b0fdbb86a7 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 21:28:47 -0400 Subject: [PATCH 31/61] clippy changes --- src/livesplit_renderer.rs | 2 +- src/nwa.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 54d7b01..c4053eb 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -645,7 +645,7 @@ impl LiveSplitCoreRenderer { class == egui::ViewportClass::Deferred, "This egui backend doesn't support multiple viewports" ); - egui::CentralPanel::default().show(ctx, |ui| { + egui::CentralPanel::default().show(ctx, |_ui| { let settings_editor = egui::containers::Window::new("Settings Editor"); settings_editor .open(&mut show_deferred_viewport.load(Ordering::Relaxed)) diff --git a/src/nwa.rs b/src/nwa.rs index 4af5170..c98c94e 100644 --- a/src/nwa.rs +++ b/src/nwa.rs @@ -111,7 +111,7 @@ impl NWASyncClient { } if map.contains_key("error") { if let Some(reason) = map.get("reason") { - let mut mkind = ErrorKind::InvalidError; + let mkind: ErrorKind; match map.get("error").unwrap().as_str() { "protocol_error" => mkind = ErrorKind::ProtocolError, "invalid_command" => mkind = ErrorKind::InvalidCommand, @@ -134,12 +134,12 @@ impl NWASyncClient { } if first_byte == 0 { let mut header = vec![0; 4]; - let r_size = read_stream.read(&mut header)?; + let _r_size = read_stream.read(&mut header)?; println!(); //println!("Reading {:}", r_size); //println!("Header : {:?}", header); let header = header; - let mut size: u32 = 0; + let mut size: u32; size = (header[0] as u32) << 24; size += (header[1] as u32) << 16; size += (header[2] as u32) << 8; From 30eab6ab7db0e0c1ebd33977b0ff43f2040ef455 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 21:34:43 -0400 Subject: [PATCH 32/61] clippy changes --- src/nwa.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/nwa.rs b/src/nwa.rs index c98c94e..b9f799b 100644 --- a/src/nwa.rs +++ b/src/nwa.rs @@ -111,14 +111,13 @@ impl NWASyncClient { } if map.contains_key("error") { if let Some(reason) = map.get("reason") { - let mkind: ErrorKind; - match map.get("error").unwrap().as_str() { - "protocol_error" => mkind = ErrorKind::ProtocolError, - "invalid_command" => mkind = ErrorKind::InvalidCommand, - "invalid_argument" => mkind = ErrorKind::InvalidArgument, - "not_allowed" => mkind = ErrorKind::NotAllowed, - _ => mkind = ErrorKind::InvalidError, - } + let mkind: ErrorKind = match map.get("error").unwrap().as_str() { + "protocol_error" => ErrorKind::ProtocolError, + "invalid_command" => ErrorKind::InvalidCommand, + "invalid_argument" => ErrorKind::InvalidArgument, + "not_allowed" => ErrorKind::NotAllowed, + _ => ErrorKind::InvalidError, + }; return Ok(EmulatorReply::Error(NWAError { kind: mkind, reason: reason.to_string(), @@ -148,7 +147,7 @@ impl NWASyncClient { let msize = size as usize; let mut data: Vec = vec![0; msize]; //println!("Size : {:}", size); - read_stream.read(&mut data)?; + read_stream.read_exact(&mut data)?; //println!("Size : {:}", size); return Ok(EmulatorReply::Binary(data)); } @@ -161,20 +160,20 @@ impl NWASyncClient { argString: Option<&str>, ) -> Result { if argString.is_none() { - self.connection.write(format!("{}\n", cmd).as_bytes())?; + self.connection.write_all(format!("{}\n", cmd).as_bytes())?; } else { self.connection - .write(format!("{} {}\n", cmd, argString.unwrap()).as_bytes())?; + .write_all(format!("{} {}\n", cmd, argString.unwrap()).as_bytes())?; } self.get_reply() } pub fn execute_raw_command(&mut self, cmd: &str, argString: Option<&str>) { if argString.is_none() { - self.connection.write(format!("{}\n", cmd).as_bytes()); + self.connection.write_all(format!("{}\n", cmd).as_bytes()); } else { self.connection - .write(format!("{} {}\n", cmd, argString.unwrap()).as_bytes()); + .write_all(format!("{} {}\n", cmd, argString.unwrap()).as_bytes()); } } @@ -191,7 +190,7 @@ impl NWASyncClient { } pub fn is_connected(&mut self) -> bool { let mut buf = vec![0; 0]; - if let Ok(usize) = self.connection.peek(&mut buf) { + if let Ok(_usize) = self.connection.peek(&mut buf) { return true; } false From b23de449b0a71a31925a978ceac10106d2d214c8 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 21:38:15 -0400 Subject: [PATCH 33/61] clippy changes --- src/config/app_config.rs | 4 ++-- src/livesplit_renderer.rs | 24 ++++++++++++------------ src/nwa.rs | 16 ++++++++-------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/config/app_config.rs b/src/config/app_config.rs index 50da3b8..0e0cd37 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -41,7 +41,7 @@ pub struct AppConfig { #[clap(skip)] pub hot_key_comparison_prev: Option, #[clap(skip)] - pub autosplitterType: Option, + pub autosplitter_type: Option, } // #[derive(clap::ValueEnum, Clone, Copy, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] @@ -95,7 +95,7 @@ impl AppConfig { reset_timer_on_game_reset: Some(false), reset_game_on_timer_reset: Some(false), global_hotkeys: Some(true), - autosplitterType: Some(autosplitters::AType::QUSB2SNES), + autosplitter_type: Some(autosplitters::AType::QUSB2SNES), } } diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index c4053eb..574e481 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -627,13 +627,13 @@ impl LiveSplitCoreRenderer { println!("registered"); Ok(()) } - pub fn AutoSplitterSettingsEditor(&mut self, ctx: &egui::Context) { + pub fn auto_splitter_settings_editor(&mut self, ctx: &egui::Context) { if self .show_edit_autosplitter_settings_dialog .load(Ordering::Relaxed) { let show_deferred_viewport = self.show_edit_autosplitter_settings_dialog.clone(); - let aSettings = self.settings.clone(); + let a_settings = self.settings.clone(); ctx.show_viewport_deferred( egui::ViewportId::from_hash_of("deferred_viewport"), @@ -655,7 +655,7 @@ impl LiveSplitCoreRenderer { .vscroll(true) .show(ctx, |ui| { ctx.move_to_top(ui.layer_id()); - let settings = aSettings.clone(); + let settings = a_settings.clone(); let mut roots = settings.write().roots(); show_children(&mut settings.write(), ui, ctx, &mut roots); }); @@ -878,7 +878,7 @@ impl eframe::App for LiveSplitCoreRenderer { } }); - self.AutoSplitterSettingsEditor(ctx); + self.auto_splitter_settings_editor(ctx); ctx.input(|i| { let scroll_delta = i.raw_scroll_delta; @@ -993,7 +993,7 @@ pub fn app_init( // This thread deals with polling the SNES at a fixed rate. if app_config.read().unwrap().use_autosplitter == Some(true) { - if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::QUSB2SNES) { + if app_config.read().unwrap().autosplitter_type == Some(autosplitters::AType::QUSB2SNES) { //QUSB2SNES stuff here let settings = app.settings.clone(); let _snes_polling_thread = ThreadBuilder::default() @@ -1103,7 +1103,7 @@ pub fn app_init( }) //TODO: fix this unwrap .unwrap(); - } else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::NWA) { + } else if app_config.read().unwrap().autosplitter_type == Some(autosplitters::AType::NWA) { //NWA stuff here let game = app.game; let _nwa_polling_thread = @@ -1132,8 +1132,8 @@ pub fn app_init( client.core_memories(); loop { println!("{:#?}", game); - let autoSplitStatus = client.update().unwrap(); - if autoSplitStatus.start { + let auto_split_status = client.update().unwrap(); + if auto_split_status.start { timer .write() .map_err(|e| { @@ -1142,7 +1142,7 @@ pub fn app_init( .start() .ok(); } - if autoSplitStatus.reset { + if auto_split_status.reset { timer .write() .map_err(|e| { @@ -1151,7 +1151,7 @@ pub fn app_init( .reset(true) .ok(); } - if autoSplitStatus.split { + if auto_split_status.split { timer .write() .map_err(|e| { @@ -1231,7 +1231,7 @@ pub fn app_init( }) //TODO: fix this unwrap .unwrap(); - } else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::ASL) { + } else if app_config.read().unwrap().autosplitter_type == Some(autosplitters::AType::ASL) { //TODO: unable to configure runtime // let test = livesplit_auto_splitting::Runtime::new(module, timer, settings_store); @@ -1242,7 +1242,7 @@ pub fn app_init( // let module = livesplit_auto_splitting::Runtime:: // livesplit_auto_splitting::Runtime::new(module, timer, settings_store) // let x = livesplit_auto_splitting::Runtime::new(module, timer.write().unwrap().deref(), settings_store); - } else if app_config.read().unwrap().autosplitterType == Some(autosplitters::AType::CUSTOM) + } else if app_config.read().unwrap().autosplitter_type == Some(autosplitters::AType::CUSTOM) { // TODO: process isn't consistently gotten // TODO: reading crashes with either bad address as root or permission denied as user diff --git a/src/nwa.rs b/src/nwa.rs index b9f799b..a8ee7b2 100644 --- a/src/nwa.rs +++ b/src/nwa.rs @@ -157,23 +157,23 @@ impl NWASyncClient { pub fn execute_command( &mut self, cmd: &str, - argString: Option<&str>, + arg_string: Option<&str>, ) -> Result { - if argString.is_none() { + if arg_string.is_none() { self.connection.write_all(format!("{}\n", cmd).as_bytes())?; } else { self.connection - .write_all(format!("{} {}\n", cmd, argString.unwrap()).as_bytes())?; + .write_all(format!("{} {}\n", cmd, arg_string.unwrap()).as_bytes())?; } self.get_reply() } - pub fn execute_raw_command(&mut self, cmd: &str, argString: Option<&str>) { - if argString.is_none() { + pub fn execute_raw_command(&mut self, cmd: &str, arg_string: Option<&str>) { + if arg_string.is_none() { self.connection.write_all(format!("{}\n", cmd).as_bytes()); } else { self.connection - .write_all(format!("{} {}\n", cmd, argString.unwrap()).as_bytes()); + .write_all(format!("{} {}\n", cmd, arg_string.unwrap()).as_bytes()); } } @@ -185,8 +185,8 @@ impl NWASyncClient { buf[2] = ((size >> 16) & 0xFF) as u8; buf[3] = ((size >> 8) & 0xFF) as u8; buf[4] = (size & 0xFF) as u8; - self.connection.write(&buf); - self.connection.write(&data); + self.connection.write_all(&buf); + self.connection.write_all(&data); } pub fn is_connected(&mut self) -> bool { let mut buf = vec![0; 0]; From 9564dd359ecf0995728bee690fa8813338e77aa3 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 21:40:03 -0400 Subject: [PATCH 34/61] clippy updates --- src/nwa.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/nwa.rs b/src/nwa.rs index a8ee7b2..9f6a15f 100644 --- a/src/nwa.rs +++ b/src/nwa.rs @@ -170,9 +170,11 @@ impl NWASyncClient { pub fn execute_raw_command(&mut self, cmd: &str, arg_string: Option<&str>) { if arg_string.is_none() { - self.connection.write_all(format!("{}\n", cmd).as_bytes()); + // TODO: handle the Err + let _ = self.connection.write_all(format!("{}\n", cmd).as_bytes()); } else { - self.connection + // TODO: handle the Err + let _ = self.connection .write_all(format!("{} {}\n", cmd, arg_string.unwrap()).as_bytes()); } } @@ -185,8 +187,10 @@ impl NWASyncClient { buf[2] = ((size >> 16) & 0xFF) as u8; buf[3] = ((size >> 8) & 0xFF) as u8; buf[4] = (size & 0xFF) as u8; - self.connection.write_all(&buf); - self.connection.write_all(&data); + // TODO: handle the Err + let _ = self.connection.write_all(&buf); + // TODO: handle the Err + let _ = self.connection.write_all(&data); } pub fn is_connected(&mut self) -> bool { let mut buf = vec![0; 0]; @@ -197,7 +201,8 @@ impl NWASyncClient { } pub fn close(&mut self) { - self.connection.shutdown(Shutdown::Both); + // TODO: handle the Err + let _ = self.connection.shutdown(Shutdown::Both); } pub fn reconnected(&mut self) -> Result { self.connection = TcpStream::connect_timeout(&self.addr, Duration::from_millis(1000))?; From 111c15a1a229a28e5f70b43f13157584e88864a2 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 21:42:54 -0400 Subject: [PATCH 35/61] clippy changes --- src/autosplitters/nwa/battletoads.rs | 8 ++++++++ src/autosplitters/nwa/supermetroid.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/autosplitters/nwa/battletoads.rs b/src/autosplitters/nwa/battletoads.rs index 2cdd4e6..ce913f1 100644 --- a/src/autosplitters/nwa/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -115,6 +115,14 @@ impl BattletoadsAutoSplitter { } false } + + pub fn set_address(&mut self, address: Ipv4Addr) { + self.address = address; + } + + pub fn set_port(&mut self, port: u32) { + self.port = port; + } } // let cmd = "CORE_INFO"; diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index 4c83caa..1e6b769 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -137,6 +137,14 @@ impl SupermetroidAutoSplitter { // TODO: add the rest of the splits } + + pub fn set_address(&mut self, address: Ipv4Addr) { + self.address = address; + } + + pub fn set_port(&mut self, port: u32) { + self.port = port; + } } // let cmd = "CORE_INFO"; From 0a327b11bd044d08913f953dd4bba013f168fabc Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 21:43:37 -0400 Subject: [PATCH 36/61] fmt changes --- src/autosplitters/nwa/battletoads.rs | 4 ++-- src/autosplitters/nwa/supermetroid.rs | 4 ++-- src/nwa.rs | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/autosplitters/nwa/battletoads.rs b/src/autosplitters/nwa/battletoads.rs index ce913f1..069553e 100644 --- a/src/autosplitters/nwa/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -115,11 +115,11 @@ impl BattletoadsAutoSplitter { } false } - + pub fn set_address(&mut self, address: Ipv4Addr) { self.address = address; } - + pub fn set_port(&mut self, port: u32) { self.port = port; } diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index 1e6b769..e6ce4c5 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -137,11 +137,11 @@ impl SupermetroidAutoSplitter { // TODO: add the rest of the splits } - + pub fn set_address(&mut self, address: Ipv4Addr) { self.address = address; } - + pub fn set_port(&mut self, port: u32) { self.port = port; } diff --git a/src/nwa.rs b/src/nwa.rs index 9f6a15f..b1d6b9e 100644 --- a/src/nwa.rs +++ b/src/nwa.rs @@ -174,7 +174,8 @@ impl NWASyncClient { let _ = self.connection.write_all(format!("{}\n", cmd).as_bytes()); } else { // TODO: handle the Err - let _ = self.connection + let _ = self + .connection .write_all(format!("{} {}\n", cmd, arg_string.unwrap()).as_bytes()); } } From b6dab704bc8db455677992626c77b8aa27cb3eef Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 21:47:41 -0400 Subject: [PATCH 37/61] removed unused struct data --- src/autosplitters/nwa/battletoads.rs | 20 ++++++++++---------- src/autosplitters/nwa/supermetroid.rs | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/autosplitters/nwa/battletoads.rs b/src/autosplitters/nwa/battletoads.rs index 069553e..3d093e3 100644 --- a/src/autosplitters/nwa/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -10,8 +10,8 @@ pub enum Action { } pub struct BattletoadsAutoSplitter { - address: Ipv4Addr, - port: u32, + // address: Ipv4Addr, + // port: u32, prior_level: u8, level: u8, reset_timer_on_game_reset: bool, @@ -21,8 +21,8 @@ pub struct BattletoadsAutoSplitter { impl BattletoadsAutoSplitter { pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: bool) -> Self { BattletoadsAutoSplitter { - address, - port, + // address, + // port, prior_level: 0_u8, level: 0_u8, reset_timer_on_game_reset, @@ -116,13 +116,13 @@ impl BattletoadsAutoSplitter { false } - pub fn set_address(&mut self, address: Ipv4Addr) { - self.address = address; - } + // pub fn set_address(&mut self, address: Ipv4Addr) { + // self.address = address; + // } - pub fn set_port(&mut self, port: u32) { - self.port = port; - } + // pub fn set_port(&mut self, port: u32) { + // self.port = port; + // } } // let cmd = "CORE_INFO"; diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index e6ce4c5..1ae3664 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -10,8 +10,8 @@ pub enum Action { } pub struct SupermetroidAutoSplitter { - address: Ipv4Addr, - port: u32, + // address: Ipv4Addr, + // port: u32, prior_state: u8, state: u8, prior_room_id: u16, @@ -23,8 +23,8 @@ pub struct SupermetroidAutoSplitter { impl SupermetroidAutoSplitter { pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: bool) -> Self { SupermetroidAutoSplitter { - address, - port, + // address, + // port, prior_state: 0_u8, state: 0_u8, prior_room_id: 0_u16, @@ -138,13 +138,13 @@ impl SupermetroidAutoSplitter { // TODO: add the rest of the splits } - pub fn set_address(&mut self, address: Ipv4Addr) { - self.address = address; - } + // pub fn set_address(&mut self, address: Ipv4Addr) { + // self.address = address; + // } - pub fn set_port(&mut self, port: u32) { - self.port = port; - } + // pub fn set_port(&mut self, port: u32) { + // self.port = port; + // } } // let cmd = "CORE_INFO"; From bf8f6499632e023a0d4a9c3507427fd550d20c90 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 22:01:13 -0400 Subject: [PATCH 38/61] clippy changes --- src/autosplitters/json.rs | 4 ++-- src/autosplitters/nwa/battletoads.rs | 16 ++++++++-------- src/autosplitters/nwa/supermetroid.rs | 18 +++++++++--------- src/config/app_config.rs | 8 ++++---- src/livesplit_renderer.rs | 20 ++++++++++---------- src/nwa.rs | 6 +++--- src/usb2snes.rs | 14 +++++++------- src/utils.rs | 2 +- src/widget/glow_canvas.rs | 3 +-- 9 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/autosplitters/json.rs b/src/autosplitters/json.rs index 27bdb7b..e64a39c 100644 --- a/src/autosplitters/json.rs +++ b/src/autosplitters/json.rs @@ -128,7 +128,7 @@ where .strip_prefix("0x") .or_else(|| s.strip_prefix("0X")) .unwrap_or(s); - u32::from_str_radix(s, 16).map_err(|e| de::Error::custom(format!("invalid hex: {}", e))) + u32::from_str_radix(s, 16).map_err(|e| de::Error::custom(format!("invalid hex: {e}"))) } fn string_to_bool<'de, D>(deserializer: D) -> Result @@ -139,6 +139,6 @@ where match s.trim() { "1" => Ok(true), "0" => Ok(false), - _ => Err(de::Error::custom(format!("invalid boolean string: {}", s))), + _ => Err(de::Error::custom(format!("invalid boolean string: {s}"))), } } diff --git a/src/autosplitters/nwa/battletoads.rs b/src/autosplitters/nwa/battletoads.rs index 3d093e3..53b43a9 100644 --- a/src/autosplitters/nwa/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -34,42 +34,42 @@ impl BattletoadsAutoSplitter { let cmd = "MY_NAME_IS"; let args = Some("Annelid"); let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn emu_info(&mut self) { let cmd = "EMULATOR_INFO"; let args = Some("0"); let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn emu_game_info(&mut self) { let cmd = "GAME_INFO"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn emu_status(&mut self) { let cmd = "EMULATION_STATUS"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn core_info(&mut self) { let cmd = "CORE_CURRENT_INFO"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn core_memories(&mut self) { let cmd = "CORE_MEMORIES"; let args = None; let summary = self.client.execute_command(cmd, args); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn update(&mut self) -> Result { @@ -80,8 +80,8 @@ impl BattletoadsAutoSplitter { // println!("{:#?}", summary); match summary { nwa::EmulatorReply::Binary(summary) => self.level = *summary.first().unwrap(), - nwa::EmulatorReply::Error(summary) => println!("{:?}", summary), - _ => println!("{:?}", summary), + nwa::EmulatorReply::Error(summary) => println!("{summary:?}"), + _ => println!("{summary:?}"), } // println!("{:#?}", level); diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index 1ae3664..686b47f 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -38,42 +38,42 @@ impl SupermetroidAutoSplitter { let cmd = "MY_NAME_IS"; let args = Some("Annelid"); let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn emu_info(&mut self) { let cmd = "EMULATOR_INFO"; let args = Some("0"); let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn emu_game_info(&mut self) { let cmd = "GAME_INFO"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn emu_status(&mut self) { let cmd = "EMULATION_STATUS"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn core_info(&mut self) { let cmd = "CORE_CURRENT_INFO"; let args = None; let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn core_memories(&mut self) { let cmd = "CORE_MEMORIES"; let args = None; let summary = self.client.execute_command(cmd, args); - println!("{:#?}", summary); + println!("{summary:#?}"); } pub fn update(&mut self) -> Result { @@ -87,7 +87,7 @@ impl SupermetroidAutoSplitter { match summary { nwa::EmulatorReply::Binary(summary) => self.state = *summary.first().unwrap(), nwa::EmulatorReply::Error(summary) => println!("{:?}", summary), - _ => println!("{:?}", summary), + _ => println!("{summary:?}"), } println!("{:#?}", self.state); } @@ -98,7 +98,7 @@ impl SupermetroidAutoSplitter { let cmd = "CORE_READ"; let args = Some("WRAM;$079B;2"); let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); match summary { nwa::EmulatorReply::Binary(summary) => { @@ -107,7 +107,7 @@ impl SupermetroidAutoSplitter { ((*summary.last().unwrap() as u16) << 8) | *summary.first().unwrap() as u16 } nwa::EmulatorReply::Error(summary) => println!("{:?}", summary), - _ => println!("{:?}", summary), + _ => println!("{summary:?}"), } println!("{:#?}", self.room_id); } diff --git a/src/config/app_config.rs b/src/config/app_config.rs index 0e0cd37..aa48f72 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -103,7 +103,7 @@ impl AppConfig { use std::io::Write; let project_dirs = directories::ProjectDirs::from("", "", "annelid") // get directories .ok_or("Unable to load computer configuration directory"); - println!("project_dirs = {:#?}", project_dirs); + println!("project_dirs = {project_dirs:#?}"); let config_dir = project_dirs.unwrap(); // get preferences directory println!("project_dirs = {:#?}", config_dir.preference_dir()); @@ -114,7 +114,7 @@ impl AppConfig { let mut config_path = config_dir.preference_dir().to_path_buf(); config_path.push("settings.toml"); - println!("Saving to {:#?}", config_path); + println!("Saving to {config_path:#?}"); let f = std::fs::OpenOptions::new() .create(true) .write(true) @@ -131,7 +131,7 @@ impl AppConfig { pub fn load_app_config(mut self) -> Self { let project_dirs = directories::ProjectDirs::from("", "", "annelid") // get directories .ok_or("Unable to load computer configuration directory"); - println!("project_dirs = {:#?}", project_dirs); + println!("project_dirs = {project_dirs:#?}"); let config_dir = project_dirs.unwrap(); // get preferences directory println!("project_dirs = {:#?}", config_dir.preference_dir()); @@ -140,7 +140,7 @@ impl AppConfig { use std::io::Read; let mut config_path = config_dir.preference_dir().to_path_buf(); config_path.push("settings.toml"); - println!("Loading from {:#?}", config_path); + println!("Loading from {config_path:#?}"); let saved_config: AppConfig = std::fs::File::open(config_path) .and_then(|mut f| { let mut buffer = String::new(); diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 574e481..6414eb0 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -170,25 +170,25 @@ impl LiveSplitCoreRenderer { .clone(); if let Some(layout) = config.recent_layout { let f = std::fs::File::open(&layout) - .with_context(|| format!("Failed to open layout file \"{}\"", layout))?; + .with_context(|| format!("Failed to open layout file \"{layout}\""))?; self.load_layout(&f, ctx) - .with_context(|| format!("Failed to load layout file \"{}\"", layout))?; + .with_context(|| format!("Failed to load layout file \"{layout}\""))?; } if let Some(splits) = config.recent_splits { let f = std::fs::File::open(&splits) - .with_context(|| format!("Failed to open splits file \"{}\"", splits))?; + .with_context(|| format!("Failed to open splits file \"{splits}\""))?; let path = std::path::Path::new(&splits) .parent() .ok_or(anyhow!("failed to find parent directory"))?; self.load_splits(&f, path.to_path_buf()) - .with_context(|| format!("Failed to load splits file \"{}\"", splits))?; + .with_context(|| format!("Failed to load splits file \"{splits}\""))?; } if let Some(autosplitter) = config.recent_autosplitter { let f = std::fs::File::open(&autosplitter).with_context(|| { - format!("Failed to open autosplitter config \"{}\"", autosplitter) + format!("Failed to open autosplitter config \"{autosplitter}\"") })?; self.load_autosplitter(&f).with_context(|| { - format!("Failed to load autosplitter config \"{}\"", autosplitter) + format!("Failed to load autosplitter config \"{autosplitter}\"") })?; } Ok(()) @@ -1018,7 +1018,7 @@ pub fn app_init( } } let device = devices.pop().ok_or(anyhow!("Device list was empty"))?; - println!("Using device: {}", device); + println!("Using device: {device}"); client.attach(&device)?; println!("Connected."); println!("{:#?}", client.info()?); @@ -1131,7 +1131,7 @@ pub fn app_init( client.core_info(); client.core_memories(); loop { - println!("{:#?}", game); + println!("{game:#?}"); let auto_split_status = client.update().unwrap(); if auto_split_status.start { timer @@ -1184,7 +1184,7 @@ pub fn app_init( client.core_info(); client.core_memories(); loop { - println!("{:#?}", game); + println!("{game:#?}"); let auto_split_status = client.update().unwrap(); if auto_split_status.start { timer @@ -1261,7 +1261,7 @@ pub fn app_init( // if count == 2 { // x = p.last().unwrap().pid().as_u32(); // } - println!("{:?}", x); + println!("{x:?}"); let arch = process_memory::Architecture::from_native(); let process_handle = process_memory::ProcessHandle::try_into_process_handle(&( diff --git a/src/nwa.rs b/src/nwa.rs index b1d6b9e..7365adf 100644 --- a/src/nwa.rs +++ b/src/nwa.rs @@ -43,7 +43,7 @@ pub struct NWASyncClient { impl NWASyncClient { pub fn connect(ip: &str, port: u32) -> Result { - let addr: Vec<_> = format!("{}:{}", ip, port) + let addr: Vec<_> = format!("{ip}:{port}") .to_socket_addrs() .expect("Can't resolve address") .collect(); @@ -160,7 +160,7 @@ impl NWASyncClient { arg_string: Option<&str>, ) -> Result { if arg_string.is_none() { - self.connection.write_all(format!("{}\n", cmd).as_bytes())?; + self.connection.write_all(format!("{cmd}\n").as_bytes())?; } else { self.connection .write_all(format!("{} {}\n", cmd, arg_string.unwrap()).as_bytes())?; @@ -171,7 +171,7 @@ impl NWASyncClient { pub fn execute_raw_command(&mut self, cmd: &str, arg_string: Option<&str>) { if arg_string.is_none() { // TODO: handle the Err - let _ = self.connection.write_all(format!("{}\n", cmd).as_bytes()); + let _ = self.connection.write_all(format!("{cmd}\n").as_bytes()); } else { // TODO: handle the Err let _ = self diff --git a/src/usb2snes.rs b/src/usb2snes.rs index fcc08fd..220119d 100644 --- a/src/usb2snes.rs +++ b/src/usb2snes.rs @@ -127,7 +127,7 @@ impl SyncClient { args: &[Cow], ) -> Result<()> { if self.devel { - println!("Send command : {:?}", command); + println!("Send command : {command:?}"); } let nspace = space.map(|sp| sp.to_string()); let query = USB2SnesQuery { @@ -139,7 +139,7 @@ impl SyncClient { let json = serde_json::to_string(&query)?; if self.devel { let json = serde_json::to_string_pretty(&query)?; - println!("{}", json); + println!("{json}"); } let message = Message::text(json); Ok(self.client.send(message)?) @@ -156,7 +156,7 @@ impl SyncClient { }; if self.devel { println!("Reply:"); - println!("{}", textreply); + println!("{textreply}"); } Ok(serde_json::from_str(&textreply)?) } @@ -274,8 +274,8 @@ impl SyncClient { Command::GetAddress, Some(Space::SNES), &[ - Cow::Owned(format!("{:x}", address)), - Cow::Owned(format!("{:x}", size)), + Cow::Owned(format!("{address:x}")), + Cow::Owned(format!("{size:x}")), ], )?; let mut data: Vec = Vec::with_capacity(size); @@ -298,8 +298,8 @@ impl SyncClient { let mut args = Vec::with_capacity(pairs.len() * 2); let mut total_size = 0; for &(address, size) in pairs.iter() { - args.push(Cow::Owned(format!("{:x}", address))); - args.push(Cow::Owned(format!("{:x}", size))); + args.push(Cow::Owned(format!("{address:x}"))); + args.push(Cow::Owned(format!("{size:x}"))); total_size += size; } self.send_command_with_space(Command::GetAddress, Some(Space::SNES), &args)?; diff --git a/src/utils.rs b/src/utils.rs index 3bb4a84..43c817c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -25,7 +25,7 @@ where match f() { Ok(()) => {} Err(e) => { - println!("{}", e); + println!("{e}"); } } } diff --git a/src/widget/glow_canvas.rs b/src/widget/glow_canvas.rs index 1f9eb71..5f59059 100644 --- a/src/widget/glow_canvas.rs +++ b/src/widget/glow_canvas.rs @@ -335,8 +335,7 @@ fn gl_debug(source: u32, typ: u32, id: u32, severity: u32, message: &str) { || typ == glow::DEBUG_TYPE_PORTABILITY { println!( - "source: {}, type: {}, id: {}, severity: {}: {}", - source_name, type_name, id, severity_name, message + "source: {source_name}, type: {type_name}, id: {id}, severity: {severity_name}: {message}" ); panic!(); } From 8f4b213ff6e140d032a4a94c5351cb8a89287a9e Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 22:02:25 -0400 Subject: [PATCH 39/61] fmt --- src/autosplitters/nwa/supermetroid.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index 686b47f..a1f8471 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -139,11 +139,11 @@ impl SupermetroidAutoSplitter { } // pub fn set_address(&mut self, address: Ipv4Addr) { - // self.address = address; + // self.address = address; // } // pub fn set_port(&mut self, port: u32) { - // self.port = port; + // self.port = port; // } } From c939bfdd01230fd8aff1081917622e7fa9eabb9b Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 22:06:32 -0400 Subject: [PATCH 40/61] clippy fixes --- src/autosplitters/nwa/supermetroid.rs | 6 +++--- src/utils.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index a1f8471..458142a 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -83,10 +83,10 @@ impl SupermetroidAutoSplitter { let cmd = "CORE_READ"; let args = Some("WRAM;$0998;1"); let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{:#?}", summary); + println!("{summary:#?}"); match summary { nwa::EmulatorReply::Binary(summary) => self.state = *summary.first().unwrap(), - nwa::EmulatorReply::Error(summary) => println!("{:?}", summary), + nwa::EmulatorReply::Error(summary) => println!("{summary:?}"), _ => println!("{summary:?}"), } println!("{:#?}", self.state); @@ -106,7 +106,7 @@ impl SupermetroidAutoSplitter { // Have to reassemble the half word roomID ((*summary.last().unwrap() as u16) << 8) | *summary.first().unwrap() as u16 } - nwa::EmulatorReply::Error(summary) => println!("{:?}", summary), + nwa::EmulatorReply::Error(summary) => println!("{summary:?}"), _ => println!("{summary:?}"), } println!("{:#?}", self.room_id); diff --git a/src/utils.rs b/src/utils.rs index 43c817c..e7681dc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,11 +8,11 @@ where match f() { Ok(()) => {} Err(e) => { - println!("about to show messagebox due to: {}", e); + println!("about to show messagebox due to: {e}"); MessageDialog::new() .set_level(MessageLevel::Error) .set_title("Error") - .set_description(format!("{}", e)) + .set_description(format!("{e}")) .show(); } } From 8e40de7e3dae844fd05f6aaca0bf879489bd52b6 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 22:18:04 -0400 Subject: [PATCH 41/61] removed custom autosplitter test --- src/livesplit_renderer.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 6414eb0..93c973e 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1246,11 +1246,12 @@ pub fn app_init( { // TODO: process isn't consistently gotten // TODO: reading crashes with either bad address as root or permission denied as user + // This is also linux only - use process_memory::*; + // use process_memory::*; // use sysinfo::*; // let mut x = 0_u64; - let x = sysinfo::Pid::from(17696).as_u32(); + // let x = sysinfo::Pid::from(17696).as_u32(); // let s = System::new_all(); // for (pid, process) in s.processes() { // println!("{} {:?}", pid, process.name()); @@ -1261,23 +1262,23 @@ pub fn app_init( // if count == 2 { // x = p.last().unwrap().pid().as_u32(); // } - println!("{x:?}"); + // println!("{x:?}"); - let arch = process_memory::Architecture::from_native(); - let process_handle = process_memory::ProcessHandle::try_into_process_handle(&( - x.try_into().unwrap(), - arch, - )) - .unwrap(); - let mut member = DataMember::::new_offset(process_handle, vec![0x10]); - member.set_offset(vec![0x10]); + // let arch = process_memory::Architecture::from_native(); + // let process_handle = process_memory::ProcessHandle::try_into_process_handle(&( + // x.try_into().unwrap(), + // arch, + // )) + // .unwrap(); + // let mut member = DataMember::::new_offset(process_handle, vec![0x10]); + // member.set_offset(vec![0x10]); // The memory offset can now be correctly calculated: // called `Result::unwrap()` on an `Err` value: Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" } - println!( - "Target memory location: {}", - member.clone().get_offset().unwrap() - ); + // println!( + // "Target memory location: {}", + // member.clone().get_offset().unwrap() + // ); // The memory offset can now be used to retrieve and modify values: // println!("Current value: {}", unsafe { member.read().unwrap() }); } From 7da563d927b33922a954b253ae2512356a3f8085 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Sat, 12 Jul 2025 22:19:46 -0400 Subject: [PATCH 42/61] fmt --- src/livesplit_renderer.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 93c973e..9be6b55 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1266,8 +1266,8 @@ pub fn app_init( // let arch = process_memory::Architecture::from_native(); // let process_handle = process_memory::ProcessHandle::try_into_process_handle(&( - // x.try_into().unwrap(), - // arch, + // x.try_into().unwrap(), + // arch, // )) // .unwrap(); // let mut member = DataMember::::new_offset(process_handle, vec![0x10]); @@ -1276,8 +1276,8 @@ pub fn app_init( // The memory offset can now be correctly calculated: // called `Result::unwrap()` on an `Err` value: Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" } // println!( - // "Target memory location: {}", - // member.clone().get_offset().unwrap() + // "Target memory location: {}", + // member.clone().get_offset().unwrap() // ); // The memory offset can now be used to retrieve and modify values: // println!("Current value: {}", unsafe { member.read().unwrap() }); From c85951abf59ba2909bdda902e3c738452d93f626 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Wed, 23 Jul 2025 03:00:24 -0400 Subject: [PATCH 43/61] Added relevant code from config editor commit fe42788eb22968d0694107b46e3f7e7010f82d1b --- src/autosplitters/nwa/battletoads.rs | 7 ++-- src/autosplitters/nwa/supermetroid.rs | 7 ++-- src/config/app_config.rs | 46 +++++++++++++++++++-------- src/hotkey.rs | 28 ++++++++++++++++ src/livesplit_renderer.rs | 43 ++++++++++++------------- 5 files changed, 89 insertions(+), 42 deletions(-) diff --git a/src/autosplitters/nwa/battletoads.rs b/src/autosplitters/nwa/battletoads.rs index 53b43a9..7890ab5 100644 --- a/src/autosplitters/nwa/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -1,4 +1,5 @@ use crate::autosplitters::NWASummary; +use crate::config::app_config::YesOrNo; use crate::nwa; use anyhow::Result; use std::net::Ipv4Addr; @@ -14,12 +15,12 @@ pub struct BattletoadsAutoSplitter { // port: u32, prior_level: u8, level: u8, - reset_timer_on_game_reset: bool, + reset_timer_on_game_reset: YesOrNo, client: nwa::NWASyncClient, } impl BattletoadsAutoSplitter { - pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: bool) -> Self { + pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: YesOrNo) -> Self { BattletoadsAutoSplitter { // address, // port, @@ -103,7 +104,7 @@ impl BattletoadsAutoSplitter { } fn reset(&mut self) -> bool { - if self.level == 0 && self.prior_level != 0 && self.reset_timer_on_game_reset { + if self.level == 0 && self.prior_level != 0 && self.reset_timer_on_game_reset == YesOrNo::Yes { return true; } false diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index 458142a..24d9b7d 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -1,4 +1,5 @@ use crate::autosplitters::NWASummary; +use crate::config::app_config::YesOrNo; use crate::nwa; use anyhow::Result; use std::net::Ipv4Addr; @@ -16,12 +17,12 @@ pub struct SupermetroidAutoSplitter { state: u8, prior_room_id: u16, room_id: u16, - reset_timer_on_game_reset: bool, + reset_timer_on_game_reset: YesOrNo, client: nwa::NWASyncClient, } impl SupermetroidAutoSplitter { - pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: bool) -> Self { + pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: YesOrNo) -> Self { SupermetroidAutoSplitter { // address, // port, @@ -129,7 +130,7 @@ impl SupermetroidAutoSplitter { } fn reset(&mut self) -> bool { - self.room_id == 0 && self.prior_room_id != 0 && self.reset_timer_on_game_reset + self.room_id == 0 && self.prior_room_id != 0 && self.reset_timer_on_game_reset == YesOrNo::Yes } fn split(&mut self) -> bool { diff --git a/src/config/app_config.rs b/src/config/app_config.rs index aa48f72..bd54d53 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -15,17 +15,17 @@ pub struct AppConfig { #[clap(name = "load-autosplitter", short = 'a', long, value_parser)] pub recent_autosplitter: Option, #[clap(name = "use-autosplitter", long, action)] - pub use_autosplitter: Option, + pub use_autosplitter: Option, #[clap(name = "polling-rate", long, short = 'p', value_parser)] pub polling_rate: Option, #[clap(name = "frame-rate", long, short = 'f', value_parser)] pub frame_rate: Option, #[clap(name = "reset-timer-on-game-reset", long, value_parser)] - pub reset_timer_on_game_reset: Option, + pub reset_timer_on_game_reset: Option, #[clap(name = "reset-game-on-timer-reset", long, value_parser)] - pub reset_game_on_timer_reset: Option, + pub reset_game_on_timer_reset: Option, #[clap(name = "global-hotkeys", long, short = 'g', value_parser)] - pub global_hotkeys: Option, + pub global_hotkeys: Option, #[clap(skip)] pub hot_key_start: Option, #[clap(skip)] @@ -44,12 +44,30 @@ pub struct AppConfig { pub autosplitter_type: Option, } -// #[derive(clap::ValueEnum, Clone, Copy, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] -// pub enum YesOrNo { -// #[default] -// Yes, -// No, -// } +#[derive(clap::ValueEnum, Clone, Copy, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] +pub enum YesOrNo { + #[default] + Yes, + No, +} + +impl From for YesOrNo { + fn from(b: bool) -> Self { + match b { + true => YesOrNo::Yes, + false => YesOrNo::No, + } + } +} + +impl From for bool { + fn from(yes: YesOrNo) -> Self { + match yes { + YesOrNo::Yes => true, + YesOrNo::No => false, + } + } +} pub const DEFAULT_FRAME_RATE: f32 = 30.0; pub const DEFAULT_POLLING_RATE: f32 = 20.0; @@ -89,12 +107,12 @@ impl AppConfig { key: egui::Key::Num4, modifiers, }), - use_autosplitter: Some(false), + use_autosplitter: Some(YesOrNo::Yes), frame_rate: Some(DEFAULT_FRAME_RATE), polling_rate: Some(DEFAULT_POLLING_RATE), - reset_timer_on_game_reset: Some(false), - reset_game_on_timer_reset: Some(false), - global_hotkeys: Some(true), + reset_timer_on_game_reset: Some(YesOrNo::No), + reset_game_on_timer_reset: Some(YesOrNo::No), + global_hotkeys: Some(YesOrNo::Yes), autosplitter_type: Some(autosplitters::AType::QUSB2SNES), } } diff --git a/src/hotkey.rs b/src/hotkey.rs index 55ce377..46c8a34 100644 --- a/src/hotkey.rs +++ b/src/hotkey.rs @@ -1,4 +1,5 @@ use std::ops::BitOr; +use core::fmt; use serde_derive::{Deserialize, Serialize}; @@ -293,3 +294,30 @@ pub fn to_livesplit_modifiers(modifiers: &::egui::Modifiers) -> livesplit_hotkey }; mods } + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum HotkeyAction { + Start, + Reset, + Undo, + Skip, + Pause, + ComparisonPrevious, + ComparisonNext, + ToggleGlobalHotkeys, +} + +impl fmt::Display for HotkeyAction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + HotkeyAction::Start => write!(f, "Start / Split"), + HotkeyAction::Reset => write!(f, "Reset"), + HotkeyAction::Undo => write!(f, "Undo Split"), + HotkeyAction::Skip => write!(f, "Skip Split"), + HotkeyAction::Pause => write!(f, "Pause"), + HotkeyAction::ComparisonPrevious => write!(f, "Switch Comparison (Previous)"), + HotkeyAction::ComparisonNext => write!(f, "Switch Comparison (Next)"), + HotkeyAction::ToggleGlobalHotkeys => write!(f, "Toggle Global Hotkeys"), + } + } +} diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 9be6b55..a860849 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,18 +1,18 @@ use crate::autosplitters; use crate::autosplitters::nwa; -use crate::autosplitters::supermetroid::Settings; -use crate::autosplitters::supermetroid::SuperMetroidAutoSplitter; -use crate::autosplitters::AutoSplitter; +use crate::autosplitters::{supermetroid::{Settings, SuperMetroidAutoSplitter}, AutoSplitter}; use crate::autosplitters::Game; use anyhow::{anyhow, Context, Result}; use eframe::egui; +use egui::{Key, Modifiers}; use livesplit_core::{Layout, SharedTimer, Timer}; use livesplit_hotkey::Hook; use parking_lot::RwLock; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; use std::net::Ipv4Addr; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; -use std::sync::Arc; use thread_priority::{set_current_thread_priority, ThreadBuilder, ThreadPriority}; use crate::config::app_config::*; @@ -30,17 +30,16 @@ pub struct LiveSplitCoreRenderer { layout_state: Option, image_cache: livesplit_core::settings::ImageCache, timer: SharedTimer, - // show_settings_editor: bool, - settings: std::sync::Arc>, + settings: Arc>, can_exit: bool, is_exiting: bool, thread_chan: std::sync::mpsc::SyncSender, - pub app_config: std::sync::Arc>, + pub app_config: Arc>, app_config_processed: bool, glow_canvas: GlowCanvas, global_hotkey_hook: Option, load_errors: Vec, - show_edit_autosplitter_settings_dialog: std::sync::Arc, + show_edit_autosplitter_settings_dialog: Arc, game: Game, // address: Ipv4Addr, // port: u32, @@ -94,25 +93,25 @@ impl LiveSplitCoreRenderer { image_cache: livesplit_core::settings::ImageCache::new(), layout_state: None, // show_settings_editor: false, - settings: std::sync::Arc::new( + settings: Arc::new( RwLock::new(autosplitters::supermetroid::Settings::new()), ), can_exit: false, is_exiting: false, thread_chan: chan, - app_config: std::sync::Arc::new(std::sync::RwLock::new(config)), + app_config: Arc::new(std::sync::RwLock::new(config)), app_config_processed: false, glow_canvas: GlowCanvas::new(), global_hotkey_hook: None, load_errors: vec![], - show_edit_autosplitter_settings_dialog: std::sync::Arc::new(AtomicBool::new(false)), + show_edit_autosplitter_settings_dialog: Arc::new(AtomicBool::new(false)), game: Game::Battletoads, // address: Ipv4Addr::new(0, 0, 0, 0), // port: 48879, } } - pub fn confirm_save(&mut self, gl: &std::sync::Arc) -> Result<()> { + pub fn confirm_save(&mut self, gl: &Arc) -> Result<()> { use rfd::{MessageButtons, MessageDialog, MessageDialogResult, MessageLevel}; let empty_path = "".to_owned(); let document_dir = match directories::UserDirs::new() { @@ -560,7 +559,7 @@ impl LiveSplitCoreRenderer { .map_err(|e| println!("reset lock failed: {e}")); if app_cfg .read() - .map(|g| g.use_autosplitter == Some(true)) + .map(|g| g.use_autosplitter == Some(YesOrNo::Yes)) .unwrap_or(false) { tc.try_send(ThreadEvent::TimerReset).unwrap_or(()); @@ -812,7 +811,7 @@ impl eframe::App for LiveSplitCoreRenderer { if ui.button("Reset").clicked() { // TODO: fix this unwrap self.timer.write().unwrap().reset(true).ok(); - if self.app_config.read().unwrap().use_autosplitter == Some(true) { + if self.app_config.read().unwrap().use_autosplitter == Some(YesOrNo::Yes) { self.thread_chan .try_send(ThreadEvent::TimerReset) .unwrap_or(()); @@ -890,7 +889,7 @@ impl eframe::App for LiveSplitCoreRenderer { }); { let config = self.app_config.read().unwrap(); - if config.global_hotkeys != Some(true) { + if config.global_hotkeys != Some(YesOrNo::Yes) { ctx.input_mut(|input| { if let Some(hot_key) = config.hot_key_start { if input.consume_key(hot_key.modifiers, hot_key.key) { @@ -902,7 +901,7 @@ impl eframe::App for LiveSplitCoreRenderer { if input.consume_key(hot_key.modifiers, hot_key.key) { // TODO: fix this unwrap self.timer.write().unwrap().reset(true).ok(); - if config.use_autosplitter == Some(true) { + if config.use_autosplitter == Some(YesOrNo::Yes) { self.thread_chan .try_send(ThreadEvent::TimerReset) .unwrap_or(()); @@ -955,7 +954,7 @@ pub fn app_init( let context = cc.egui_ctx.clone(); context.set_visuals(egui::Visuals::dark()); // app.load_app_config(); - if app.app_config.read().unwrap().global_hotkeys == Some(true) { + if app.app_config.read().unwrap().global_hotkeys == Some(YesOrNo::Yes) { messagebox_on_error(|| app.enable_global_hotkeys()); } let frame_rate = app @@ -992,7 +991,7 @@ pub fn app_init( let app_config = app.app_config.clone(); // This thread deals with polling the SNES at a fixed rate. - if app_config.read().unwrap().use_autosplitter == Some(true) { + if app_config.read().unwrap().use_autosplitter == Some(YesOrNo::Yes) { if app_config.read().unwrap().autosplitter_type == Some(autosplitters::AType::QUSB2SNES) { //QUSB2SNES stuff here let settings = app.settings.clone(); @@ -1044,7 +1043,7 @@ pub fn app_init( anyhow!("failed to acquire read lock on config: {e}") })? .reset_timer_on_game_reset - == Some(true) + == Some(YesOrNo::Yes) { timer .write() @@ -1088,7 +1087,7 @@ pub fn app_init( anyhow!("failed to acquire read lock on config: {e}") })? .reset_game_on_timer_reset - == Some(true) + == Some(YesOrNo::Yes) { client.reset()?; } From c6f1d6435224ee38ad7d4242874a2fe52274506c Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Wed, 23 Jul 2025 03:16:10 -0400 Subject: [PATCH 44/61] fmt --- src/autosplitters/nwa/battletoads.rs | 5 ++++- src/autosplitters/nwa/supermetroid.rs | 4 +++- src/hotkey.rs | 2 +- src/livesplit_renderer.rs | 11 ++++++----- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/autosplitters/nwa/battletoads.rs b/src/autosplitters/nwa/battletoads.rs index 7890ab5..ca076e4 100644 --- a/src/autosplitters/nwa/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -104,7 +104,10 @@ impl BattletoadsAutoSplitter { } fn reset(&mut self) -> bool { - if self.level == 0 && self.prior_level != 0 && self.reset_timer_on_game_reset == YesOrNo::Yes { + if self.level == 0 + && self.prior_level != 0 + && self.reset_timer_on_game_reset == YesOrNo::Yes + { return true; } false diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index 24d9b7d..b7a4f7b 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -130,7 +130,9 @@ impl SupermetroidAutoSplitter { } fn reset(&mut self) -> bool { - self.room_id == 0 && self.prior_room_id != 0 && self.reset_timer_on_game_reset == YesOrNo::Yes + self.room_id == 0 + && self.prior_room_id != 0 + && self.reset_timer_on_game_reset == YesOrNo::Yes } fn split(&mut self) -> bool { diff --git a/src/hotkey.rs b/src/hotkey.rs index 46c8a34..567f0cc 100644 --- a/src/hotkey.rs +++ b/src/hotkey.rs @@ -1,5 +1,5 @@ -use std::ops::BitOr; use core::fmt; +use std::ops::BitOr; use serde_derive::{Deserialize, Serialize}; diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index a860849..36f4acf 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,18 +1,21 @@ use crate::autosplitters; use crate::autosplitters::nwa; -use crate::autosplitters::{supermetroid::{Settings, SuperMetroidAutoSplitter}, AutoSplitter}; use crate::autosplitters::Game; +use crate::autosplitters::{ + supermetroid::{Settings, SuperMetroidAutoSplitter}, + AutoSplitter, +}; use anyhow::{anyhow, Context, Result}; use eframe::egui; use egui::{Key, Modifiers}; use livesplit_core::{Layout, SharedTimer, Timer}; use livesplit_hotkey::Hook; use parking_lot::RwLock; +use std::net::Ipv4Addr; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; -use std::net::Ipv4Addr; use thread_priority::{set_current_thread_priority, ThreadBuilder, ThreadPriority}; use crate::config::app_config::*; @@ -93,9 +96,7 @@ impl LiveSplitCoreRenderer { image_cache: livesplit_core::settings::ImageCache::new(), layout_state: None, // show_settings_editor: false, - settings: Arc::new( - RwLock::new(autosplitters::supermetroid::Settings::new()), - ), + settings: Arc::new(RwLock::new(autosplitters::supermetroid::Settings::new())), can_exit: false, is_exiting: false, thread_chan: chan, From 9461a4df44de05aff8c8bb284c6b6256ead297ad Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Wed, 23 Jul 2025 03:19:05 -0400 Subject: [PATCH 45/61] clippy fix --- src/livesplit_renderer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 36f4acf..4a32669 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -7,7 +7,6 @@ use crate::autosplitters::{ }; use anyhow::{anyhow, Context, Result}; use eframe::egui; -use egui::{Key, Modifiers}; use livesplit_core::{Layout, SharedTimer, Timer}; use livesplit_hotkey::Hook; use parking_lot::RwLock; From 594e1a6be0bd8f751b479357f683b7501e1992cf Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Wed, 23 Jul 2025 03:34:48 -0400 Subject: [PATCH 46/61] updated to new appImage github addess --- .github/workflows/onpush.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/onpush.yml b/.github/workflows/onpush.yml index 55d6c7e..b6f6536 100644 --- a/.github/workflows/onpush.yml +++ b/.github/workflows/onpush.yml @@ -180,7 +180,7 @@ jobs: run: | sudo apt-get install -y wget libfuse-dev libgtk-3-dev cargo install --force cargo-appimage - wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage -O appimagetool + wget https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$(uname -m).AppImage -O appimagetool chmod a+x appimagetool - name: Install Rust targets (macOS) From 15511d4c956f87a90e446ff32a2ca46e8c3f0ba7 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 16:27:45 -0400 Subject: [PATCH 47/61] removed unneeded code and imports --- src/autosplitters.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/autosplitters.rs b/src/autosplitters.rs index f58d436..6ba0e1c 100644 --- a/src/autosplitters.rs +++ b/src/autosplitters.rs @@ -2,8 +2,6 @@ pub mod json; pub mod nwa; pub mod supermetroid; use anyhow::Result; -// use std::net::Ipv4Addr; -// use std::error::Error; use livesplit_core::TimeSpan; #[derive(Debug, Copy, Clone, serde::Deserialize, serde::Serialize, PartialEq)] @@ -18,20 +16,9 @@ pub enum AType { pub enum Game { Battletoads, SuperMetroid, - None, + // None, } -// // Not sure how to do this... -// pub fn AutoSplitterSelector(game: &str,reset_timer_on_game_reset: bool) -> Result> { -// match game { -// "Battletoads" => Ok(Game::Battletoads(battletoadsAutoSplitter::new( -// Ipv4Addr::new(0, 0, 0, 0), -// 48879, reset_timer_on_game_reset -// ))), -// _ => panic!("Worker type not found") -// } -// } - #[derive(Debug, Copy, Clone)] pub struct NWASummary { pub start: bool, From 62dd8a057b61ccd3ef10e1a612665f9444c8fd51 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 16:33:45 -0400 Subject: [PATCH 48/61] removed unneeded imports and code; added Game crate import for NWA game selection; moved NWA splitter object creation to helper function; removed NWA duplication --- src/livesplit_renderer.rs | 159 ++++++++++---------------------------- 1 file changed, 39 insertions(+), 120 deletions(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 4a32669..564b38f 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,16 +1,14 @@ -use crate::autosplitters; -use crate::autosplitters::nwa; -use crate::autosplitters::Game; use crate::autosplitters::{ + self, + nwa::nwaobject, supermetroid::{Settings, SuperMetroidAutoSplitter}, - AutoSplitter, + AutoSplitter, Game, }; use anyhow::{anyhow, Context, Result}; use eframe::egui; use livesplit_core::{Layout, SharedTimer, Timer}; use livesplit_hotkey::Hook; use parking_lot::RwLock; -use std::net::Ipv4Addr; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -43,8 +41,6 @@ pub struct LiveSplitCoreRenderer { load_errors: Vec, show_edit_autosplitter_settings_dialog: Arc, game: Game, - // address: Ipv4Addr, - // port: u32, } fn show_children( @@ -84,7 +80,6 @@ impl LiveSplitCoreRenderer { pub fn new( timer: SharedTimer, layout: Layout, - // settings: Arc>, chan: std::sync::mpsc::SyncSender, config: AppConfig, ) -> Self { @@ -94,7 +89,6 @@ impl LiveSplitCoreRenderer { renderer: livesplit_core::rendering::software::BorrowedRenderer::new(), image_cache: livesplit_core::settings::ImageCache::new(), layout_state: None, - // show_settings_editor: false, settings: Arc::new(RwLock::new(autosplitters::supermetroid::Settings::new())), can_exit: false, is_exiting: false, @@ -106,8 +100,6 @@ impl LiveSplitCoreRenderer { load_errors: vec![], show_edit_autosplitter_settings_dialog: Arc::new(AtomicBool::new(false)), game: Game::Battletoads, - // address: Ipv4Addr::new(0, 0, 0, 0), - // port: 48879, } } @@ -835,11 +827,11 @@ impl eframe::App for LiveSplitCoreRenderer { Game::Battletoads, "Battletoads", ); - ui.selectable_value( - &mut self.game, - Game::SuperMetroid, - "Super Metroid", - ); + // ui.selectable_value( + // &mut self.game, + // Game::SuperMetroid, + // "Super Metroid", + // ); }) // ui.menu_button("Battletoads", |ui| { // }); @@ -1105,131 +1097,58 @@ pub fn app_init( } else if app_config.read().unwrap().autosplitter_type == Some(autosplitters::AType::NWA) { //NWA stuff here let game = app.game; - let _nwa_polling_thread = - ThreadBuilder::default() - .name("NWA Polling Thread".to_owned()) - .spawn(move |_| loop { - print_on_error(|| -> anyhow::Result<()> { - // let client: battletoadsAutoSplitter = autosplitters::AutoSplitterSelector("Battletoads", true).unwrap(); - // TODO: make this generic as well based on user input or add game selector - match game { - Game::Battletoads => { - let mut client = nwa::battletoads::BattletoadsAutoSplitter::new( - Ipv4Addr::new(0, 0, 0, 0), - 48879, - app_config - .read() - .unwrap() - .reset_timer_on_game_reset - .unwrap(), - ); - client.emu_info(); - client.emu_game_info(); - client.emu_status(); - client.client_id(); - client.core_info(); - client.core_memories(); - loop { - println!("{game:#?}"); - let auto_split_status = client.update().unwrap(); - if auto_split_status.start { - timer - .write() - .map_err(|e| { - anyhow!("failed to acquire write lock on timer: {e}") - })? - .start() - .ok(); - } - if auto_split_status.reset { - timer - .write() - .map_err(|e| { - anyhow!("failed to acquire write lock on timer: {e}") - })? - .reset(true) - .ok(); - } - if auto_split_status.split { - timer - .write() - .map_err(|e| { - anyhow!("failed to acquire write lock on timer: {e}") - })? - .split() - .ok(); - } + let _nwa_polling_thread = ThreadBuilder::default() + .name("NWA Polling Thread".to_owned()) + .spawn(move |_| loop { + let mut client = nwaobject(game, app_config.clone()); - std::thread::sleep(std::time::Duration::from_millis( - (1000.0 / polling_rate) as u64, - )); - } - } - Game::SuperMetroid => { - let mut client = - nwa::supermetroid::SupermetroidAutoSplitter::new( - Ipv4Addr::new(0, 0, 0, 0), - 48879, - app_config - .read() - .unwrap() - .reset_timer_on_game_reset - .unwrap(), - ); - client.emu_info(); - client.emu_game_info(); - client.emu_status(); - client.client_id(); - client.core_info(); - client.core_memories(); - loop { - println!("{game:#?}"); - let auto_split_status = client.update().unwrap(); - if auto_split_status.start { - timer + print_on_error(|| -> anyhow::Result<()> { + client.emu_info(); + client.emu_game_info(); + client.emu_status(); + client.client_id(); + client.core_info(); + client.core_memories(); + loop { + // println!("{game:#?}"); + let auto_split_status = client.update().unwrap(); + if auto_split_status.start { + timer .write() .map_err(|e| { anyhow!("failed to acquire write lock on timer: {e}") })? .start() .ok(); - } - if auto_split_status.reset { - timer + } + if auto_split_status.reset { + timer .write() .map_err(|e| { anyhow!("failed to acquire write lock on timer: {e}") })? .reset(true) .ok(); - } - if auto_split_status.split { - timer + } + if auto_split_status.split { + timer .write() .map_err(|e| { anyhow!("failed to acquire write lock on timer: {e}") })? .split() .ok(); - } - - std::thread::sleep(std::time::Duration::from_millis( - (1000.0 / polling_rate) as u64, - )); - } - } - _ => todo!(), } - // if app.game == Game::Battletoads { - // } else if app.game == Game::SuperMetroid { - - // } - }); - std::thread::sleep(std::time::Duration::from_millis(1000)); - }) - //TODO: fix this unwrap - .unwrap(); + std::thread::sleep(std::time::Duration::from_millis( + (1000.0 / polling_rate) as u64, + )); + } + }); + std::thread::sleep(std::time::Duration::from_millis(1000)); + }) + //TODO: fix this unwrap + .unwrap(); } else if app_config.read().unwrap().autosplitter_type == Some(autosplitters::AType::ASL) { //TODO: unable to configure runtime From 853ecf11f5d0174fc4d93d09f05d7e5c5f019eb5 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 16:34:01 -0400 Subject: [PATCH 49/61] commented out debug printing --- src/nwa.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nwa.rs b/src/nwa.rs index 7365adf..776cde6 100644 --- a/src/nwa.rs +++ b/src/nwa.rs @@ -134,7 +134,7 @@ impl NWASyncClient { if first_byte == 0 { let mut header = vec![0; 4]; let _r_size = read_stream.read(&mut header)?; - println!(); + // println!(); //println!("Reading {:}", r_size); //println!("Header : {:?}", header); let header = header; From 5b54c3fb2a0efb6384a7c9e61f9d792c8961168c Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 16:44:35 -0400 Subject: [PATCH 50/61] added Game enum --- src/autosplitters.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/autosplitters.rs b/src/autosplitters.rs index 6ba0e1c..8cee72f 100644 --- a/src/autosplitters.rs +++ b/src/autosplitters.rs @@ -12,13 +12,6 @@ pub enum AType { CUSTOM, } -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum Game { - Battletoads, - SuperMetroid, - // None, -} - #[derive(Debug, Copy, Clone)] pub struct NWASummary { pub start: bool, From 43d3a5598bede6a3ecc40706f555a1c56f24da51 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 16:45:15 -0400 Subject: [PATCH 51/61] moved Game import --- src/livesplit_renderer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 564b38f..2fa11b1 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,8 +1,8 @@ use crate::autosplitters::{ self, - nwa::nwaobject, + nwa::{nwaobject, Game}, supermetroid::{Settings, SuperMetroidAutoSplitter}, - AutoSplitter, Game, + AutoSplitter, }; use anyhow::{anyhow, Context, Result}; use eframe::egui; From a22673696470c97a6bfb91bb999e02d4d8b7e2b5 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 16:47:04 -0400 Subject: [PATCH 52/61] added imports; moved Game enum; added object creation helper function; added dynamic trait class; --- src/autosplitters/nwa.rs | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/autosplitters/nwa.rs b/src/autosplitters/nwa.rs index edde02a..58bc927 100644 --- a/src/autosplitters/nwa.rs +++ b/src/autosplitters/nwa.rs @@ -1,2 +1,62 @@ +use crate::{autosplitters::NWASummary, config::app_config::AppConfig, nwa::NWASyncClient}; +use anyhow::Result; +use std::{net::Ipv4Addr, sync::Arc}; + pub mod battletoads; pub mod supermetroid; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Game { + Battletoads, + SuperMetroid, + // None, +} + +pub fn nwaobject(game: Game, app_config: Arc>) -> Box { + match game { + Game::Battletoads => Box::new(battletoads::BattletoadsAutoSplitter { + prior_level: 0, + level: 0, + reset_timer_on_game_reset: app_config + .read() + .unwrap() + .reset_timer_on_game_reset + .unwrap(), + client: NWASyncClient::connect(&Ipv4Addr::new(0, 0, 0, 0).to_string(), 48879).unwrap(), + }), + Game::SuperMetroid => Box::new(supermetroid::SupermetroidAutoSplitter { + prior_state: 0, + state: 0, + prior_room_id: 0, + room_id: 0, + reset_timer_on_game_reset: app_config + .read() + .unwrap() + .reset_timer_on_game_reset + .unwrap(), + client: NWASyncClient::connect(&Ipv4Addr::new(0, 0, 0, 0).to_string(), 48879).unwrap(), + }), + } +} + +pub trait Splitter { + fn client_id(&mut self); + + fn emu_info(&mut self); + + fn emu_game_info(&mut self); + + fn emu_status(&mut self); + + fn core_info(&mut self); + + fn core_memories(&mut self); + + fn update(&mut self) -> Result; + + fn start(&mut self) -> bool; + + fn reset(&mut self) -> bool; + + fn split(&mut self) -> bool; +} From 10c0e9e736931e9c46a8422a5d87787d102a7758 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 16:49:03 -0400 Subject: [PATCH 53/61] changed to impl dynamic class; commented out debug info; removed unneeded code --- src/autosplitters/nwa/battletoads.rs | 77 ++++++++--------------- src/autosplitters/nwa/supermetroid.rs | 91 +++++++++------------------ 2 files changed, 56 insertions(+), 112 deletions(-) diff --git a/src/autosplitters/nwa/battletoads.rs b/src/autosplitters/nwa/battletoads.rs index ca076e4..f7e0ed1 100644 --- a/src/autosplitters/nwa/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -1,79 +1,60 @@ +use crate::autosplitters::nwa::Splitter; use crate::autosplitters::NWASummary; use crate::config::app_config::YesOrNo; use crate::nwa; use anyhow::Result; -use std::net::Ipv4Addr; - -pub enum Action { - Start, - Reset, - Split, -} pub struct BattletoadsAutoSplitter { - // address: Ipv4Addr, - // port: u32, - prior_level: u8, - level: u8, - reset_timer_on_game_reset: YesOrNo, - client: nwa::NWASyncClient, + pub prior_level: u8, + pub level: u8, + pub reset_timer_on_game_reset: YesOrNo, + pub client: nwa::NWASyncClient, } -impl BattletoadsAutoSplitter { - pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: YesOrNo) -> Self { - BattletoadsAutoSplitter { - // address, - // port, - prior_level: 0_u8, - level: 0_u8, - reset_timer_on_game_reset, - client: nwa::NWASyncClient::connect(&address.to_string(), port).unwrap(), // TODO: Need to handle error - } - } - - pub fn client_id(&mut self) { +impl Splitter for BattletoadsAutoSplitter { + fn client_id(&mut self) { let cmd = "MY_NAME_IS"; let args = Some("Annelid"); - let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + self.client.execute_command(cmd, args).unwrap(); + // println!("{summary:#?}"); } - pub fn emu_info(&mut self) { + fn emu_info(&mut self) { let cmd = "EMULATOR_INFO"; let args = Some("0"); - let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + self.client.execute_command(cmd, args).unwrap(); + // println!("{summary:#?}"); } - pub fn emu_game_info(&mut self) { + fn emu_game_info(&mut self) { let cmd = "GAME_INFO"; let args = None; - let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + self.client.execute_command(cmd, args).unwrap(); + // println!("{summary:#?}"); } - pub fn emu_status(&mut self) { + fn emu_status(&mut self) { let cmd = "EMULATION_STATUS"; let args = None; - let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + self.client.execute_command(cmd, args).unwrap(); + // println!("{summary:#?}"); } - pub fn core_info(&mut self) { + fn core_info(&mut self) { let cmd = "CORE_CURRENT_INFO"; let args = None; - let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + self.client.execute_command(cmd, args).unwrap(); + // println!("{summary:#?}"); } - pub fn core_memories(&mut self) { + fn core_memories(&mut self) { let cmd = "CORE_MEMORIES"; let args = None; - let summary = self.client.execute_command(cmd, args); - println!("{summary:#?}"); + self.client.execute_command(cmd, args); + // println!("{summary:#?}"); } - pub fn update(&mut self) -> Result { + fn update(&mut self) -> Result { self.prior_level = self.level; let cmd = "CORE_READ"; let args = Some("RAM;$0010;1"); @@ -119,14 +100,6 @@ impl BattletoadsAutoSplitter { } false } - - // pub fn set_address(&mut self, address: Ipv4Addr) { - // self.address = address; - // } - - // pub fn set_port(&mut self, port: u32) { - // self.port = port; - // } } // let cmd = "CORE_INFO"; diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index b7a4f7b..7e1491a 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -1,96 +1,75 @@ +use crate::autosplitters::nwa::Splitter; use crate::autosplitters::NWASummary; use crate::config::app_config::YesOrNo; use crate::nwa; use anyhow::Result; -use std::net::Ipv4Addr; - -pub enum Action { - Start, - Reset, - Split, -} pub struct SupermetroidAutoSplitter { - // address: Ipv4Addr, - // port: u32, - prior_state: u8, - state: u8, - prior_room_id: u16, - room_id: u16, - reset_timer_on_game_reset: YesOrNo, - client: nwa::NWASyncClient, + pub prior_state: u8, + pub state: u8, + pub prior_room_id: u16, + pub room_id: u16, + pub reset_timer_on_game_reset: YesOrNo, + pub client: nwa::NWASyncClient, } -impl SupermetroidAutoSplitter { - pub fn new(address: Ipv4Addr, port: u32, reset_timer_on_game_reset: YesOrNo) -> Self { - SupermetroidAutoSplitter { - // address, - // port, - prior_state: 0_u8, - state: 0_u8, - prior_room_id: 0_u16, - room_id: 0_u16, - reset_timer_on_game_reset, - client: nwa::NWASyncClient::connect(&address.to_string(), port).unwrap(), // TODO: Need to handle error - } - } - - pub fn client_id(&mut self) { +impl Splitter for SupermetroidAutoSplitter { + fn client_id(&mut self) { let cmd = "MY_NAME_IS"; let args = Some("Annelid"); - let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + self.client.execute_command(cmd, args).unwrap(); + // println!("{summary:#?}"); } - pub fn emu_info(&mut self) { + fn emu_info(&mut self) { let cmd = "EMULATOR_INFO"; let args = Some("0"); - let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + self.client.execute_command(cmd, args).unwrap(); + // println!("{summary:#?}"); } - pub fn emu_game_info(&mut self) { + fn emu_game_info(&mut self) { let cmd = "GAME_INFO"; let args = None; - let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + self.client.execute_command(cmd, args).unwrap(); + // println!("{summary:#?}"); } - pub fn emu_status(&mut self) { + fn emu_status(&mut self) { let cmd = "EMULATION_STATUS"; let args = None; - let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + self.client.execute_command(cmd, args).unwrap(); + // println!("{summary:#?}"); } - pub fn core_info(&mut self) { + fn core_info(&mut self) { let cmd = "CORE_CURRENT_INFO"; let args = None; - let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + self.client.execute_command(cmd, args).unwrap(); + // println!("{summary:#?}"); } - pub fn core_memories(&mut self) { + fn core_memories(&mut self) { let cmd = "CORE_MEMORIES"; let args = None; - let summary = self.client.execute_command(cmd, args); - println!("{summary:#?}"); + self.client.execute_command(cmd, args); + // println!("{summary:#?}"); } - pub fn update(&mut self) -> Result { + fn update(&mut self) -> Result { // read memory for the game state { self.prior_state = self.state; let cmd = "CORE_READ"; let args = Some("WRAM;$0998;1"); let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + // println!("{summary:#?}"); match summary { nwa::EmulatorReply::Binary(summary) => self.state = *summary.first().unwrap(), nwa::EmulatorReply::Error(summary) => println!("{summary:?}"), _ => println!("{summary:?}"), } - println!("{:#?}", self.state); + // println!("{:#?}", self.state); } // read memory for room @@ -99,7 +78,7 @@ impl SupermetroidAutoSplitter { let cmd = "CORE_READ"; let args = Some("WRAM;$079B;2"); let summary = self.client.execute_command(cmd, args).unwrap(); - println!("{summary:#?}"); + // println!("{summary:#?}"); match summary { nwa::EmulatorReply::Binary(summary) => { @@ -110,7 +89,7 @@ impl SupermetroidAutoSplitter { nwa::EmulatorReply::Error(summary) => println!("{summary:?}"), _ => println!("{summary:?}"), } - println!("{:#?}", self.room_id); + // println!("{:#?}", self.room_id); } // TODO: add the other memory reads @@ -140,14 +119,6 @@ impl SupermetroidAutoSplitter { // TODO: add the rest of the splits } - - // pub fn set_address(&mut self, address: Ipv4Addr) { - // self.address = address; - // } - - // pub fn set_port(&mut self, port: u32) { - // self.port = port; - // } } // let cmd = "CORE_INFO"; From 45c8bf39615cc92c80e22cb8f4b41054338fcf04 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 16:49:29 -0400 Subject: [PATCH 54/61] applied fix --- src/config/app_config.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/config/app_config.rs b/src/config/app_config.rs index bd54d53..16e30bf 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -5,7 +5,7 @@ use serde_derive::{Deserialize, Serialize}; use crate::hotkey::*; use crate::utils::*; -#[derive(Default, Deserialize, Serialize, Parser, Debug, Clone)] +#[derive(Deserialize, Serialize, Parser, Debug, Clone)] #[clap(author, version, about, long_about = None)] pub struct AppConfig { #[clap(name = "load-splits", short = 's', long, value_parser)] @@ -178,8 +178,8 @@ impl AppConfig { } } -// impl Default for AppConfig { -// fn default() -> Self { -// AppConfig::new() -// } -// } +impl Default for AppConfig { + fn default() -> Self { + AppConfig::new() + } +} From 9605385e4a0910b091910f00ffdbe714c2305cd0 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 16:51:36 -0400 Subject: [PATCH 55/61] cliipy fixes --- src/autosplitters/nwa/battletoads.rs | 2 +- src/autosplitters/nwa/supermetroid.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autosplitters/nwa/battletoads.rs b/src/autosplitters/nwa/battletoads.rs index f7e0ed1..f94d1f8 100644 --- a/src/autosplitters/nwa/battletoads.rs +++ b/src/autosplitters/nwa/battletoads.rs @@ -50,7 +50,7 @@ impl Splitter for BattletoadsAutoSplitter { fn core_memories(&mut self) { let cmd = "CORE_MEMORIES"; let args = None; - self.client.execute_command(cmd, args); + let _ = self.client.execute_command(cmd, args); // println!("{summary:#?}"); } diff --git a/src/autosplitters/nwa/supermetroid.rs b/src/autosplitters/nwa/supermetroid.rs index 7e1491a..cbd426b 100644 --- a/src/autosplitters/nwa/supermetroid.rs +++ b/src/autosplitters/nwa/supermetroid.rs @@ -52,7 +52,7 @@ impl Splitter for SupermetroidAutoSplitter { fn core_memories(&mut self) { let cmd = "CORE_MEMORIES"; let args = None; - self.client.execute_command(cmd, args); + let _ = self.client.execute_command(cmd, args); // println!("{summary:#?}"); } From d137b148b406faecc5a470ecb952506c7c569eee Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 19:56:02 -0400 Subject: [PATCH 56/61] removed unneeded code; added helper function to move dropdown filling to autosplitters/nwa --- src/autosplitters/nwa.rs | 5 +++++ src/livesplit_renderer.rs | 26 +++++--------------------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/autosplitters/nwa.rs b/src/autosplitters/nwa.rs index 58bc927..b5ec3b0 100644 --- a/src/autosplitters/nwa.rs +++ b/src/autosplitters/nwa.rs @@ -12,6 +12,11 @@ pub enum Game { // None, } +pub fn fill_drop_down(ui: &mut egui::Ui, game: &mut Game) { + ui.selectable_value(game, Game::Battletoads, "Battletoads"); + ui.selectable_value(game, Game::SuperMetroid, "Super Metroid"); +} + pub fn nwaobject(game: Game, app_config: Arc>) -> Box { match game { Game::Battletoads => Box::new(battletoads::BattletoadsAutoSplitter { diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 2fa11b1..61d237d 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -1,6 +1,6 @@ use crate::autosplitters::{ self, - nwa::{nwaobject, Game}, + nwa::{fill_drop_down, nwaobject, Game}, supermetroid::{Settings, SuperMetroidAutoSplitter}, AutoSplitter, }; @@ -732,10 +732,7 @@ impl eframe::App for LiveSplitCoreRenderer { ); self.glow_canvas .paint_layer(ctx, egui::LayerId::background(), viewport); - // //self.glow_canvas.paint_immediate(frame.gl().unwrap(), viewport); - // let settings_editor = egui::containers::Window::new("Settings Editor"); egui::Area::new("livesplit".into()) - // .enabled(!self.show_settings_editor) .movable(false) .show(ctx, |ui| { ui.set_width(ctx.input(|i| i.screen_rect.width())); @@ -819,24 +816,11 @@ impl eframe::App for LiveSplitCoreRenderer { // port } // TODO: Fix this. It's not updating the value - egui::ComboBox::from_label("Game") - .selected_text(format!("{:?}", self.game)) + egui::ComboBox::from_id_salt("Game") + .selected_text(format!("{:?}", &mut self.game)) .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.game, - Game::Battletoads, - "Battletoads", - ); - // ui.selectable_value( - // &mut self.game, - // Game::SuperMetroid, - // "Super Metroid", - // ); - }) - // ui.menu_button("Battletoads", |ui| { - // }); - // ui.menu_button("Super Metroid", |ui| { - // }); + fill_drop_down(ui, &mut self.game); + }); }); ui.menu_button("QUSB2SNES", |ui| { ui.menu_button("Super Metroid", |ui| { From 7429f58e1b08512b1487e8dd2e52da2689dfe9a8 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Fri, 1 Aug 2025 20:57:30 -0400 Subject: [PATCH 57/61] set combobox close behavior --- src/livesplit_renderer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index 61d237d..bfd0133 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -818,6 +818,7 @@ impl eframe::App for LiveSplitCoreRenderer { // TODO: Fix this. It's not updating the value egui::ComboBox::from_id_salt("Game") .selected_text(format!("{:?}", &mut self.game)) + .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) .show_ui(ui, |ui| { fill_drop_down(ui, &mut self.game); }); From 07d128d59464ed53246368ec4a7eeab17b9a99c9 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 19 Aug 2025 16:51:59 -0400 Subject: [PATCH 58/61] Changed to passing NWA variables --- src/autosplitters/nwa.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/autosplitters/nwa.rs b/src/autosplitters/nwa.rs index b5ec3b0..703845f 100644 --- a/src/autosplitters/nwa.rs +++ b/src/autosplitters/nwa.rs @@ -1,6 +1,6 @@ use crate::{autosplitters::NWASummary, config::app_config::AppConfig, nwa::NWASyncClient}; use anyhow::Result; -use std::{net::Ipv4Addr, sync::Arc}; +use std::sync::Arc; pub mod battletoads; pub mod supermetroid; @@ -17,7 +17,7 @@ pub fn fill_drop_down(ui: &mut egui::Ui, game: &mut Game) { ui.selectable_value(game, Game::SuperMetroid, "Super Metroid"); } -pub fn nwaobject(game: Game, app_config: Arc>) -> Box { +pub fn nwaobject(game: Game, app_config: Arc>, ip: &String, port: u32) -> Box { match game { Game::Battletoads => Box::new(battletoads::BattletoadsAutoSplitter { prior_level: 0, @@ -27,7 +27,7 @@ pub fn nwaobject(game: Game, app_config: Arc>) -> B .unwrap() .reset_timer_on_game_reset .unwrap(), - client: NWASyncClient::connect(&Ipv4Addr::new(0, 0, 0, 0).to_string(), 48879).unwrap(), + client: NWASyncClient::connect(ip, port).unwrap(), }), Game::SuperMetroid => Box::new(supermetroid::SupermetroidAutoSplitter { prior_state: 0, @@ -39,7 +39,7 @@ pub fn nwaobject(game: Game, app_config: Arc>) -> B .unwrap() .reset_timer_on_game_reset .unwrap(), - client: NWASyncClient::connect(&Ipv4Addr::new(0, 0, 0, 0).to_string(), 48879).unwrap(), + client: NWASyncClient::connect(ip, port).unwrap(), }), } } From 66644688ecaf643a9c5920e537bc09197c3694b6 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 19 Aug 2025 16:54:44 -0400 Subject: [PATCH 59/61] added NWA autosplitter settings config window --- src/livesplit_renderer.rs | 78 ++++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index bfd0133..a90e53f 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -9,10 +9,10 @@ use eframe::egui; use livesplit_core::{Layout, SharedTimer, Timer}; use livesplit_hotkey::Hook; use parking_lot::RwLock; -use std::sync::{ +use std::{net::Ipv4Addr, sync::{ atomic::{AtomicBool, Ordering}, Arc, -}; +}}; use thread_priority::{set_current_thread_priority, ThreadBuilder, ThreadPriority}; use crate::config::app_config::*; @@ -40,7 +40,10 @@ pub struct LiveSplitCoreRenderer { global_hotkey_hook: Option, load_errors: Vec, show_edit_autosplitter_settings_dialog: Arc, + show_nwa_autosplitter_settings_dialog: Arc, game: Game, + address: Arc>, + port: Arc>, } fn show_children( @@ -99,7 +102,10 @@ impl LiveSplitCoreRenderer { global_hotkey_hook: None, load_errors: vec![], show_edit_autosplitter_settings_dialog: Arc::new(AtomicBool::new(false)), + show_nwa_autosplitter_settings_dialog: Arc::new(AtomicBool::new(false)), game: Game::Battletoads, + address: Arc::new(RwLock::new(Ipv4Addr::new(0, 0, 0, 0).to_string())), + port: Arc::new(RwLock::new(48879)), } } @@ -618,6 +624,54 @@ impl LiveSplitCoreRenderer { println!("registered"); Ok(()) } + + pub fn nwa_auto_splitter_settings_editor(&mut self, ctx: &egui::Context) { + if self + .show_nwa_autosplitter_settings_dialog + .load(Ordering::Relaxed) + { + let show_deferred_viewport = self.show_nwa_autosplitter_settings_dialog.clone(); + let mut _game = self.game.clone(); + let mut _adr = self.address.write().clone(); + let mut _port = self.port.write().clone(); + + ctx.show_viewport_deferred( + egui::ViewportId::from_hash_of("NWA_deferred_viewport"), + egui::ViewportBuilder::default() + .with_title("NWA AutoSplitter Settings Editor") + .with_inner_size([200.0, 500.0]), + move |ctx, class| { + assert!( + class == egui::ViewportClass::Deferred, + "This egui backend doesn't support multiple viewports" + ); + // TODO: Fix this. It's not updating the value; probably move this into config + egui::CentralPanel::default().show(ctx, |ui| { + let mut game = _game.clone(); + let mut adr = _adr.clone(); + let port = _port.clone(); + ui.label("NWA address"); + ui.text_edit_singleline(&mut adr); + ui.label("NWA port"); + ui.text_edit_singleline(&mut port.to_string()); + ui.label("NWA Game"); + egui::ComboBox::from_id_salt("Game") + .selected_text(format!("{:?}", game)) + .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) + .show_ui(ui, |ui| { + fill_drop_down(ui, &mut game); + }); + }); + + if ctx.input(|i| i.viewport().close_requested()) { + // Tell parent to close us. + show_deferred_viewport.store(false, Ordering::Relaxed); + } + }, + ); + } + } + pub fn auto_splitter_settings_editor(&mut self, ctx: &egui::Context) { if self .show_edit_autosplitter_settings_dialog @@ -811,22 +865,15 @@ impl eframe::App for LiveSplitCoreRenderer { ui.menu_button("Autosplitter", |ui| { ui.menu_button("NWA", |ui| { if ui.button("Configure").clicked() { - // Fill out NWA config - // address - // port + let show_deferred_viewport = true; + self.show_nwa_autosplitter_settings_dialog + .store(show_deferred_viewport, Ordering::Relaxed); + ui.close_menu(); } - // TODO: Fix this. It's not updating the value - egui::ComboBox::from_id_salt("Game") - .selected_text(format!("{:?}", &mut self.game)) - .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) - .show_ui(ui, |ui| { - fill_drop_down(ui, &mut self.game); - }); }); ui.menu_button("QUSB2SNES", |ui| { ui.menu_button("Super Metroid", |ui| { if ui.button("Configure").clicked() { - // self.show_settings_editor = true; let show_deferred_viewport = true; self.show_edit_autosplitter_settings_dialog .store(show_deferred_viewport, Ordering::Relaxed); @@ -855,6 +902,7 @@ impl eframe::App for LiveSplitCoreRenderer { }); self.auto_splitter_settings_editor(ctx); + self.nwa_auto_splitter_settings_editor(ctx); ctx.input(|i| { let scroll_delta = i.raw_scroll_delta; @@ -1082,10 +1130,12 @@ pub fn app_init( } else if app_config.read().unwrap().autosplitter_type == Some(autosplitters::AType::NWA) { //NWA stuff here let game = app.game; + let address = app.address.read().clone(); + let port = app.port.read().clone(); let _nwa_polling_thread = ThreadBuilder::default() .name("NWA Polling Thread".to_owned()) .spawn(move |_| loop { - let mut client = nwaobject(game, app_config.clone()); + let mut client = nwaobject(game, app_config.clone(), &address, port); print_on_error(|| -> anyhow::Result<()> { client.emu_info(); From d171f4b6a5e12b84a4bb5de44fe599f3dc256c95 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 19 Aug 2025 16:55:40 -0400 Subject: [PATCH 60/61] fmt --- src/autosplitters/nwa.rs | 7 ++++++- src/livesplit_renderer.rs | 13 ++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/autosplitters/nwa.rs b/src/autosplitters/nwa.rs index 703845f..73ca734 100644 --- a/src/autosplitters/nwa.rs +++ b/src/autosplitters/nwa.rs @@ -17,7 +17,12 @@ pub fn fill_drop_down(ui: &mut egui::Ui, game: &mut Game) { ui.selectable_value(game, Game::SuperMetroid, "Super Metroid"); } -pub fn nwaobject(game: Game, app_config: Arc>, ip: &String, port: u32) -> Box { +pub fn nwaobject( + game: Game, + app_config: Arc>, + ip: &String, + port: u32, +) -> Box { match game { Game::Battletoads => Box::new(battletoads::BattletoadsAutoSplitter { prior_level: 0, diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index a90e53f..a452c78 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -9,10 +9,13 @@ use eframe::egui; use livesplit_core::{Layout, SharedTimer, Timer}; use livesplit_hotkey::Hook; use parking_lot::RwLock; -use std::{net::Ipv4Addr, sync::{ - atomic::{AtomicBool, Ordering}, - Arc, -}}; +use std::{ + net::Ipv4Addr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; use thread_priority::{set_current_thread_priority, ThreadBuilder, ThreadPriority}; use crate::config::app_config::*; @@ -648,7 +651,7 @@ impl LiveSplitCoreRenderer { // TODO: Fix this. It's not updating the value; probably move this into config egui::CentralPanel::default().show(ctx, |ui| { let mut game = _game.clone(); - let mut adr = _adr.clone(); + let mut adr = _adr.clone(); let port = _port.clone(); ui.label("NWA address"); ui.text_edit_singleline(&mut adr); From 54a433d954295cafe59624049f54b41f90e95577 Mon Sep 17 00:00:00 2001 From: Douglas Kirby Date: Tue, 19 Aug 2025 16:56:55 -0400 Subject: [PATCH 61/61] clippy --- src/autosplitters/nwa.rs | 2 +- src/livesplit_renderer.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/autosplitters/nwa.rs b/src/autosplitters/nwa.rs index 73ca734..a43766a 100644 --- a/src/autosplitters/nwa.rs +++ b/src/autosplitters/nwa.rs @@ -20,7 +20,7 @@ pub fn fill_drop_down(ui: &mut egui::Ui, game: &mut Game) { pub fn nwaobject( game: Game, app_config: Arc>, - ip: &String, + ip: &str, port: u32, ) -> Box { match game { diff --git a/src/livesplit_renderer.rs b/src/livesplit_renderer.rs index a452c78..2577caf 100644 --- a/src/livesplit_renderer.rs +++ b/src/livesplit_renderer.rs @@ -634,9 +634,9 @@ impl LiveSplitCoreRenderer { .load(Ordering::Relaxed) { let show_deferred_viewport = self.show_nwa_autosplitter_settings_dialog.clone(); - let mut _game = self.game.clone(); + let mut _game = self.game; let mut _adr = self.address.write().clone(); - let mut _port = self.port.write().clone(); + let mut _port = *self.port.write(); ctx.show_viewport_deferred( egui::ViewportId::from_hash_of("NWA_deferred_viewport"), @@ -650,9 +650,9 @@ impl LiveSplitCoreRenderer { ); // TODO: Fix this. It's not updating the value; probably move this into config egui::CentralPanel::default().show(ctx, |ui| { - let mut game = _game.clone(); + let mut game = _game; let mut adr = _adr.clone(); - let port = _port.clone(); + let port = _port; ui.label("NWA address"); ui.text_edit_singleline(&mut adr); ui.label("NWA port"); @@ -1134,7 +1134,7 @@ pub fn app_init( //NWA stuff here let game = app.game; let address = app.address.read().clone(); - let port = app.port.read().clone(); + let port = *app.port.read(); let _nwa_polling_thread = ThreadBuilder::default() .name("NWA Polling Thread".to_owned()) .spawn(move |_| loop {