From 7a48815c3c2e052d9488dc328eacfddaa831d65c Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 18 Dec 2023 20:24:51 -0500 Subject: [PATCH 1/8] Use steamlocate crate for Resonite path discovery --- Cargo.lock | 130 +++++++++++++++++++++++++++ crates/resolute/Cargo.toml | 1 + crates/resolute/src/discover.rs | 32 +++++++ crates/resolute/src/error.rs | 4 +- crates/resolute/src/lib.rs | 2 +- crates/resolute/src/path_discover.rs | 99 -------------------- crates/tauri-app/src/main.rs | 41 +++------ 7 files changed, 179 insertions(+), 130 deletions(-) create mode 100644 crates/resolute/src/discover.rs delete mode 100644 crates/resolute/src/path_discover.rs diff --git a/Cargo.lock b/Cargo.lock index e6d0c19..d9fc9ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -642,6 +642,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.3.2" @@ -820,6 +835,15 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -830,6 +854,18 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -1868,6 +1904,28 @@ dependencies = [ "treediff", ] +[[package]] +name = "keyvalues-parser" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4c8354918309196302015ac9cae43362f1a13d0d5c5539a33b4c2fd2cd6d25" +dependencies = [ + "pest", + "pest_derive", + "thiserror", +] + +[[package]] +name = "keyvalues-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2312e27dd8631f35faf9a588db95c3caad3295d9901c2d9d4aca9604f7a2d3a" +dependencies = [ + "keyvalues-parser", + "serde", + "thiserror", +] + [[package]] name = "kuchiki" version = "0.8.1" @@ -2344,6 +2402,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2432,6 +2496,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "pest_meta" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "phf" version = "0.8.0" @@ -2939,6 +3048,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "steamlocate", "thiserror", "tokio", "url", @@ -3400,6 +3510,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "steamlocate" +version = "2.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff6c1d1ba5920d7b250b98c29199670bd1e4fc1e666d6fb63b5fad45ace7fd7" +dependencies = [ + "crc", + "dirs", + "keyvalues-parser", + "keyvalues-serde", + "serde", + "winreg 0.51.0", +] + [[package]] name = "string_cache" version = "0.8.7" @@ -4141,6 +4265,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uds_windows" version = "1.0.2" diff --git a/crates/resolute/Cargo.toml b/crates/resolute/Cargo.toml index 6882c84..dc27f74 100644 --- a/crates/resolute/Cargo.toml +++ b/crates/resolute/Cargo.toml @@ -18,6 +18,7 @@ reqwest = { version = "0.11", features = ["default", "stream"] } futures-util = "0.3" path-clean = "1.0" sha2 = "0.10" +steamlocate = "2.0.0-beta.1" [target.'cfg(windows)'.dependencies] winreg = "0.52" diff --git a/crates/resolute/src/discover.rs b/crates/resolute/src/discover.rs new file mode 100644 index 0000000..0f25b7a --- /dev/null +++ b/crates/resolute/src/discover.rs @@ -0,0 +1,32 @@ +use std::path::PathBuf; + +use log::debug; +use steamlocate::SteamDir; + +use crate::Result; + +pub const RESONITE_APP: u32 = 2_519_830; + +/// Searches for a potenial Resonite game directory +pub fn discover_resonite(steam: Option) -> Result> { + // Find a Steam installation if one isn't provided + let steam = match steam { + Some(steam) => steam, + None => { + let steam = SteamDir::locate()?; + debug!("Steam installation located at {}", steam.path().display()); + steam + } + }; + + // Check the Steam installation for Resonite + let resonite_details = steam.find_app(RESONITE_APP)?; + match resonite_details { + Some((resonite, library)) => { + let resonite_dir = library.resolve_app_dir(&resonite); + debug!("Resonite installation located at {}", resonite_dir.display()); + Ok(Some(resonite_dir)) + } + None => Ok(None), + } +} diff --git a/crates/resolute/src/error.rs b/crates/resolute/src/error.rs index a70cf81..cef20e7 100644 --- a/crates/resolute/src/error.rs +++ b/crates/resolute/src/error.rs @@ -21,8 +21,8 @@ pub enum Error { #[error("checksum error for {2}: calculated hash {1} doesn't match expected hash {0}")] Checksum(String, String, String), - #[error("unsupported platform for operation: {0}")] - UnsupportedPlatform(String), + #[error("resonite discovery error: {0}")] + Discovery(#[from] steamlocate::Error), } /// Alias for a `Result` with the error type `download::Error`. diff --git a/crates/resolute/src/lib.rs b/crates/resolute/src/lib.rs index cebef83..65a6aa7 100644 --- a/crates/resolute/src/lib.rs +++ b/crates/resolute/src/lib.rs @@ -1,8 +1,8 @@ +pub mod discover; pub mod download; mod error; pub mod manifest; pub mod mods; -pub mod path_discover; pub use error::Error; pub use error::Result; diff --git a/crates/resolute/src/path_discover.rs b/crates/resolute/src/path_discover.rs deleted file mode 100644 index 1be2db4..0000000 --- a/crates/resolute/src/path_discover.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::{env, path::PathBuf}; - -use log::debug; -use tokio::fs; - -use crate::{Error, Result}; - -/// Searches for a potenial Resonite game directory -pub async fn discover_resonite() -> Result> { - let steam_path = discover_steam().await?; - match steam_path { - Some(steam_path) => { - // Verify there is a Resonite game directory in the Steam directory - let resonite_path = steam_path.join("steamapps/common/Resonite"); - if fs::try_exists(&resonite_path).await? { - debug!("Resonite found at {}", resonite_path.display()); - Ok(Some(resonite_path)) - } else { - Ok(None) - } - } - - None => Ok(None), - } -} - -/// Searches for a potential Steam installation directory by reading the Windows registry or defaulting to the standard path -#[cfg(target_os = "windows")] -pub async fn discover_steam() -> Result> { - use std::ffi::OsString; - - use winreg::{enums::HKEY_CURRENT_USER, types::FromRegValue, RegKey}; - - // Locate a Steam installation from the registry or the default installation path - let steam_path = PathBuf::from({ - let hklm = RegKey::predef(HKEY_CURRENT_USER); - hklm.open_subkey("Software\\Valve\\Steamm") - .and_then(|key| { - debug!("Opened Steam registry key, reading SteamPath value from it"); - OsString::from_reg_value(&key.get_raw_value("SteamPath")?) - }) - .or_else(|err| { - debug!( - "Error reading SteamPath value from registry (trying the default path next): {}", - err - ); - - // Get the program files path from the environment - let mut program_files = env::var_os("ProgramFiles(x86)") - .ok_or_else(|| Error::Path(format!("unable to get program files x86 directory: {}", err)))?; - program_files.push("\\Steam"); - - Ok::(program_files) - }) - .unwrap() - }); - - // Confirm the existence of the Steam directory - if fs::try_exists(&steam_path).await? { - debug!("Steam found at {}, canonicalizing path", steam_path.display()); - Ok(Some(fs::canonicalize(steam_path).await?)) - } else { - Ok(None) - } -} - -#[cfg(target_os = "linux")] -pub async fn discover_steam() -> Result> { - // Get the user's home directory from the environment - let home = - PathBuf::from(env::var_os("HOME").ok_or_else(|| Error::Path("unable to get home directory".to_owned()))?); - - // Check for a traditional Steam installation - let traditional_path = home.join(".steam"); - if fs::try_exists(&traditional_path).await? { - debug!( - "Steam found at traditional path {}, canonicalizing path", - traditional_path.display() - ); - return Ok(Some(fs::canonicalize(traditional_path).await?)); - } - - // Check for a Flatpak Steam installation - let flatpak_path = home.join(".var/app/com.valvesoftware.Steam/.local/share/Steam"); - if fs::try_exists(&flatpak_path).await? { - debug!( - "Steam found at flatpak path {}, canonicalizing path", - flatpak_path.display() - ); - return Ok(Some(fs::canonicalize(flatpak_path).await?)); - } - - Ok(None) -} - -#[cfg(target_os = "macos")] -pub async fn discover_steam() -> Result> { - Err(Error::UnsupportedPlatform("macos".to_owned())) -} diff --git a/crates/tauri-app/src/main.rs b/crates/tauri-app/src/main.rs index 70b78fd..543d4a2 100644 --- a/crates/tauri-app/src/main.rs +++ b/crates/tauri-app/src/main.rs @@ -6,10 +6,10 @@ use std::{io, path::PathBuf}; use anyhow::Context; use log::{debug, error, info, warn}; use resolute::{ + discover::discover_resonite, download::Downloader, manifest, mods::{self, ModVersion, ResoluteMod, ResoluteModMap}, - path_discover::discover_resonite, }; use sha2::{Digest, Sha256}; use tauri::{AppHandle, Manager, Window, WindowEvent}; @@ -139,40 +139,25 @@ async fn autodiscover_resonite_path(app: AppHandle) -> Result<(), anyhow::Error> // If the path isn't already configured, try to find one automatically if !path_configured { info!("Resonite path not configured, running autodiscovery"); - let found_path = discover_resonite().await?; - - match found_path { - Some(resonite_path) => { - info!("Discovered Resonite path: {}", resonite_path.display()); - - // On Windows, strip the UNC prefix from the string if it's there - #[cfg(target_os = "windows")] - let plain = { - let plain = resonite_path.to_str().ok_or_else(|| { - resolute::Error::Path("unable to convert discovered resonite path to string".to_owned()) - })?; - if plain.starts_with(r#"\\?\"#) { - plain.strip_prefix(r#"\\?\"#).ok_or_else(|| { - resolute::Error::Path("unable to strip unc prefix from discovered resonite path".to_owned()) - })? - } else { - plain - } - }; - - #[cfg(not(target_os = "windows"))] - let plain = resonite_path; - - settings::set(&app, "resonitePath", plain)? - } + // Run discovery + let resonite_dir = tauri::async_runtime::spawn_blocking(|| discover_resonite(None)) + .await + .context("Unable to spawn blocking task for discovery")??; + + // If discovery found a path, save it to the setting + match resonite_dir { + Some(resonite_dir) => { + info!("Discovered Resonite path: {}", resonite_dir.display()); + settings::set(&app, "resonitePath", resonite_dir)? + } None => { info!("Autodiscovery didn't find a Resonite path"); } } } - Ok::<(), anyhow::Error>(()) + Ok(()) } #[tauri::command] From d543968a36fdb4e47808763b6ca32f7acb3e0d89 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 19 Dec 2023 05:42:55 -0500 Subject: [PATCH 2/8] Remove winreg dependency --- Cargo.lock | 11 ----------- crates/resolute/Cargo.toml | 3 --- 2 files changed, 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9fc9ce..096b7b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3052,7 +3052,6 @@ dependencies = [ "thiserror", "tokio", "url", - "winreg 0.52.0", ] [[package]] @@ -5003,16 +5002,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wry" version = "0.24.6" diff --git a/crates/resolute/Cargo.toml b/crates/resolute/Cargo.toml index dc27f74..eb48084 100644 --- a/crates/resolute/Cargo.toml +++ b/crates/resolute/Cargo.toml @@ -19,6 +19,3 @@ futures-util = "0.3" path-clean = "1.0" sha2 = "0.10" steamlocate = "2.0.0-beta.1" - -[target.'cfg(windows)'.dependencies] -winreg = "0.52" From 35f3c0c45c51aeefd95e579be077c817671196a6 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 20 Dec 2023 18:57:03 -0500 Subject: [PATCH 3/8] Correct newline formatting of release links in changelogs --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a780127..e58d0ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: failOnError: true configurationJson: | { - "template": "#{{CHANGELOG}}\n\n**Full changelog:** [`#{{FROM_TAG}}...#{{TO_TAG}}`](#{{RELEASE_DIFF}})\n**Previous release:** [#{{FROM_TAG}}](https://github.com/#{{OWNER}}/#{{REPO}}/releases/tag/#{{FROM_TAG}})\n**All releases:** https://github.com/#{{OWNER}}/#{{REPO}}/releases", + "template": "#{{CHANGELOG}}\n\n**Full changelog:** [`#{{FROM_TAG}}...#{{TO_TAG}}`](#{{RELEASE_DIFF}}) \n**Previous release:** [#{{FROM_TAG}}](https://github.com/#{{OWNER}}/#{{REPO}}/releases/tag/#{{FROM_TAG}}) \n**All releases:** https://github.com/#{{OWNER}}/#{{REPO}}/releases", "pr_template": "- PR ##{{NUMBER}}: #{{TITLE}} (@#{{AUTHOR}})", "categories": [ { From 65b5c184e8caaa9a22b77489ae6352089b936c93 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 25 Dec 2023 16:21:22 -0500 Subject: [PATCH 4/8] Update steamlocate to 2.0.0-beta.2 --- Cargo.lock | 4 ++-- crates/resolute/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 096b7b9..9ab9d1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3511,9 +3511,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "steamlocate" -version = "2.0.0-beta.1" +version = "2.0.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff6c1d1ba5920d7b250b98c29199670bd1e4fc1e666d6fb63b5fad45ace7fd7" +checksum = "c3b6a4810c4e7fecb0123a9a8ba99b335c17d92e636c265ef99108ee4734c812" dependencies = [ "crc", "dirs", diff --git a/crates/resolute/Cargo.toml b/crates/resolute/Cargo.toml index eb48084..70cb9d9 100644 --- a/crates/resolute/Cargo.toml +++ b/crates/resolute/Cargo.toml @@ -18,4 +18,4 @@ reqwest = { version = "0.11", features = ["default", "stream"] } futures-util = "0.3" path-clean = "1.0" sha2 = "0.10" -steamlocate = "2.0.0-beta.1" +steamlocate = "2.0.0-beta.2" From 8c3acc25263d3922f247b1b0f220f9eb3fa0b91d Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 25 Dec 2023 16:46:15 -0500 Subject: [PATCH 5/8] Only log debug level and higher for release builds --- crates/tauri-app/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/tauri-app/src/main.rs b/crates/tauri-app/src/main.rs index 0a0a065..1c3ab7b 100644 --- a/crates/tauri-app/src/main.rs +++ b/crates/tauri-app/src/main.rs @@ -40,12 +40,14 @@ fn main() -> anyhow::Result<()> { tauri_plugin_log::Builder::default() .targets(vec![LogTarget::Stdout, LogTarget::Webview]) .with_colors(ColoredLevelConfig::default()) + .level_for("rustls", log::LevelFilter::Debug) .build() }, #[cfg(not(debug_assertions))] { tauri_plugin_log::Builder::default() .targets(vec![LogTarget::Stdout, LogTarget::LogDir]) + .level(log::LevelFilter::Debug) .build() }, ) From f5c681af2054013dc07fee0bceeb1eb8b4349c24 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 25 Dec 2023 17:32:39 -0500 Subject: [PATCH 6/8] Add a button to manually run Resonite path discovery --- crates/tauri-app/src/main.rs | 25 +++++++++- .../settings/ResonitePathSetting.vue | 50 +++++++++++++++++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/crates/tauri-app/src/main.rs b/crates/tauri-app/src/main.rs index 0a0a065..0e03ebc 100644 --- a/crates/tauri-app/src/main.rs +++ b/crates/tauri-app/src/main.rs @@ -63,6 +63,7 @@ fn main() -> anyhow::Result<()> { show_window, load_manifest, install_version, + discover_resonite_path, verify_resonite_path, hash_file ]) @@ -159,7 +160,7 @@ async fn autodiscover_resonite_path(app: AppHandle) -> Result<(), anyhow::Error> // Run discovery let resonite_dir = tauri::async_runtime::spawn_blocking(|| discover_resonite(None)) .await - .context("Unable to spawn blocking task for discovery")??; + .context("Unable to spawn blocking task for Resonite path autodiscovery")??; // If discovery found a path, save it to the setting match resonite_dir { @@ -244,6 +245,28 @@ async fn install_version(app: AppHandle, rmod: ResoluteMod, version: ModVersion) Ok(()) } +#[tauri::command] +async fn discover_resonite_path() -> Result, String> { + let path = tauri::async_runtime::spawn_blocking(|| discover_resonite(None)) + .await + .map_err(|err| { + error!("Unable to spawn blocking task for Resonite path discovery: {}", err); + format!("Unable to spawn blocking task for Resonite path discovery: {}", err) + })? + .map_err(|err| { + error!("Unable to discover Resonite path: {}", err); + format!("Unable to discover Resonite path: {}", err) + })?; + + match path { + Some(path) => path.to_str().map(|path| Some(path.to_owned())).ok_or_else(|| { + error!("Unable to convert discovered Resonite path ({:?}) to a String", path); + "Unable to convert discovered Resonite path to a String".to_owned() + }), + None => Ok(None), + } +} + #[tauri::command] async fn verify_resonite_path(app: AppHandle) -> Result { let resonite_path: String = settings::require(&app, "resonitePath").map_err(|err| err.to_string())?; diff --git a/ui/src/components/settings/ResonitePathSetting.vue b/ui/src/components/settings/ResonitePathSetting.vue index dd53c7a..c63aa4d 100644 --- a/ui/src/components/settings/ResonitePathSetting.vue +++ b/ui/src/components/settings/ResonitePathSetting.vue @@ -6,7 +6,18 @@ readonly >