Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Resonite path detection #34

Merged
merged 7 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/resolute/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ reqwest = { version = "0.11", features = ["default", "stream"] }
futures-util = "0.3"
path-clean = "1.0"
sha2 = "0.10"

[target.'cfg(windows)'.dependencies]
winreg = "0.52"
3 changes: 3 additions & 0 deletions crates/resolute/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ 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),
}

/// Alias for a `Result` with the error type `download::Error`.
Expand Down
1 change: 1 addition & 0 deletions crates/resolute/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod download;
mod error;
pub mod manifest;
pub mod mods;
pub mod path_discover;

pub use error::Error;
pub use error::Result;
99 changes: 99 additions & 0 deletions crates/resolute/src/path_discover.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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<Option<PathBuf>> {
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<Option<PathBuf>> {
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::<OsString, Error>(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<Option<PathBuf>> {
// 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<Option<PathBuf>> {
Err(Error::UnsupportedPlatform("macos".to_owned()))
}
52 changes: 52 additions & 0 deletions crates/tauri-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use resolute::{
download::Downloader,
manifest,
mods::{self, ModVersion, ResoluteMod, ResoluteModMap},
path_discover::discover_resonite,
};
use tauri::{AppHandle, Manager, Window, WindowEvent};
use tauri_plugin_log::{fern::colors::ColoredLevelConfig, LogTarget};
Expand Down Expand Up @@ -83,6 +84,14 @@ fn main() -> anyhow::Result<()> {
}
});

// Discover the Resonite path if it isn't configured already
let handle = app.app_handle();
tauri::async_runtime::spawn(async move {
if let Err(err) = autodiscover_resonite_path(handle).await {
warn!("Unable to autodiscover Resonite path: {}", err);
}
});

Ok(())
})
.run(tauri::generate_context!())
Expand Down Expand Up @@ -121,6 +130,49 @@ async fn create_app_dirs(app: AppHandle) -> Result<(), String> {
}
}

/// Auto-discovers a Resonite path if the setting isn't configured
async fn autodiscover_resonite_path(app: AppHandle) -> Result<(), anyhow::Error> {
let path_configured = settings::get::<String>(&app, "resonitePath")?.is_some();

// 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)?
}

None => {
info!("Autodiscovery didn't find a Resonite path");
}
}
}

Ok::<(), anyhow::Error>(())
}

#[tauri::command]
fn show_window(window: Window) {
window.show().expect("unable to show main window");
Expand Down
11 changes: 11 additions & 0 deletions crates/tauri-app/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,14 @@ pub fn require<T: serde::de::DeserializeOwned>(app: &AppHandle, setting: &str) -
anyhow!("setting not configured: {}", setting)
})
}

/// Store a setting value into the given app's default setting store
pub fn set<T: serde::ser::Serialize>(app: &AppHandle, setting: &str, value: T) -> Result<()> {
let json_value = serde_json::to_value(value)?;
let stores = app.state::<StoreCollection<Wry>>();

with_store(app.clone(), stores, ".settings.dat", |store| {
store.insert(setting.to_owned(), json_value).and_then(|_| store.save())
})
.with_context(|| format!("Unable to store {} setting", setting))
}