diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs index 4f0be0e..4510a65 100644 --- a/apps/app/src/main.rs +++ b/apps/app/src/main.rs @@ -324,12 +324,9 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> { /// external game-directory root for the one-click StarLight install flow. #[tauri::command] fn get_launcher_root_dir() -> api::Result { - let exe = std::env::current_exe().map_err(|error| { - theseus::Error::from(theseus::ErrorKind::FSError(error.to_string())) - })?; - let dir = exe.parent().ok_or_else(|| { + let dir = theseus::default_game_dir_root().ok_or_else(|| { theseus::Error::from(theseus::ErrorKind::FSError( - "Launcher executable has no parent directory".to_string(), + "Could not determine a default game directory root".to_string(), )) })?; Ok(dir.to_string_lossy().into_owned()) diff --git a/packages/app-lib/src/lib.rs b/packages/app-lib/src/lib.rs index 7c9da96..31bcdd0 100644 --- a/packages/app-lib/src/lib.rs +++ b/packages/app-lib/src/lib.rs @@ -39,7 +39,7 @@ pub use state::{DirectoryInfo, State}; pub use storage::*; pub use util::fetch::build_proxied_client; pub use util::file_lock::{LockingProcess, get_locking_processes}; -pub use util::platform::is_process_elevated; +pub use util::platform::{default_game_dir_root, is_process_elevated}; pub use util::proxy::{ProxyConfig, ProxyMode, ProxyTestResult}; pub use util::symlink::SymlinkCapability; diff --git a/packages/app-lib/src/util/platform.rs b/packages/app-lib/src/util/platform.rs index 1c381b4..0003cd8 100644 --- a/packages/app-lib/src/util/platform.rs +++ b/packages/app-lib/src/util/platform.rs @@ -1,5 +1,6 @@ //! Platform-related code use daedalus::minecraft::{Os, OsRule}; +use std::path::PathBuf; /// (e.g. "Run as administrator" on Windows). #[cfg(target_os = "windows")] @@ -143,3 +144,31 @@ pub fn classpath_separator(java_arch: &str) -> &'static str { Os::Windows | Os::WindowsArm64 => ";", } } + +/// Root directory used as the default for one-click StarLight instance +/// installs. Portable builds live next to their executable, which is a fine +/// place for game data. System installs (e.g. a Linux `.deb` under `/usr/bin`) +/// do not, so fall back to the user's home directory there. +pub fn default_game_dir_root() -> Option { + let exe_dir = std::env::current_exe().ok()?.parent()?.to_path_buf(); + if cfg!(target_os = "linux") && is_system_install_dir(&exe_dir) { + return dirs::home_dir(); + } + Some(exe_dir) +} + +#[cfg(target_os = "linux")] +fn is_system_install_dir(path: &std::path::Path) -> bool { + const SYSTEM_PREFIXES: &[&str] = &[ + "/usr/", "/opt/", "/snap/", "/bin/", "/sbin/", "/lib/", "/lib64/", + ]; + let display = path.to_string_lossy(); + SYSTEM_PREFIXES + .iter() + .any(|prefix| display.starts_with(prefix)) +} + +#[cfg(not(target_os = "linux"))] +fn is_system_install_dir(_path: &std::path::Path) -> bool { + false +}