From d1389fd7571eda554be55a059a7a4a5e6dde1d4c Mon Sep 17 00:00:00 2001 From: Philip Kristoffersen Date: Sun, 22 May 2022 14:53:23 +0200 Subject: [PATCH] Open urls with exe (#146) * Launch Epic Games in safe mode through the exe * Launch through exe for amazon games * Remove unused comment * Launch Uplay games through exe * Fix epic tests * Always launch origin games through the exe * Fix that compat_folder is an option on unix --- src/amazon/amazon_game.rs | 7 +++- src/amazon/amazon_platform.rs | 26 ++++++++++-- src/amazon/amazon_settings.rs | 1 + src/egs/get_manifests.rs | 75 ++++++++++++++++++++++++++++++++--- src/egs/manifest_item.rs | 22 +++++----- src/egs/mod.rs | 2 +- src/egs/settings.rs | 1 + src/origin/origin_game.rs | 28 ++++++------- src/origin/origin_platform.rs | 54 +++++++++++++++++++------ src/ui/ui_settings.rs | 14 +++++++ src/uplay/game.rs | 9 ++++- src/uplay/platform.rs | 50 +++++++++++++++++++++-- 12 files changed, 233 insertions(+), 56 deletions(-) diff --git a/src/amazon/amazon_game.rs b/src/amazon/amazon_game.rs index b76880a..67d90b6 100644 --- a/src/amazon/amazon_game.rs +++ b/src/amazon/amazon_game.rs @@ -1,14 +1,19 @@ +use std::path::{PathBuf, Path}; + use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut}; #[derive(Debug, Clone)] pub struct AmazonGame { pub title: String, pub id: String, + pub launcher_path:PathBuf, } impl From for ShortcutOwned { fn from(game: AmazonGame) -> Self { let launch = format!("amazon-games://play/{}", game.id); - Shortcut::new("0", game.title.as_str(), launch.as_str(), "", "", "", "").to_owned() + let exe = game.launcher_path.to_string_lossy().to_string(); + let start_dir= game.launcher_path.parent().unwrap_or(Path::new("")).to_string_lossy().to_string(); + Shortcut::new("0", game.title.as_str(), exe.as_str(), start_dir.as_str(), "", "", launch.as_str()).to_owned() } } diff --git a/src/amazon/amazon_platform.rs b/src/amazon/amazon_platform.rs index 59b86ec..90e14b3 100644 --- a/src/amazon/amazon_platform.rs +++ b/src/amazon/amazon_platform.rs @@ -30,7 +30,9 @@ impl Platform> for AmazonPlatform { fn get_shortcuts(&self) -> Result, Box> { let sqllite_path = - get_sqlite_path().expect("This should enver get called if settings are invalid"); + get_sqlite_path().expect("This should never get called if settings are invalid"); + let launcher_path = + get_launcher_path().expect("This should never get called if settings are invalid"); let mut result = vec![]; let connection = sqlite::open(sqllite_path)?; let mut statement = @@ -39,7 +41,7 @@ impl Platform> for AmazonPlatform { let id = statement.read::(0); let title = statement.read::(1); if let (Ok(id), Ok(title)) = (id, title) { - result.push(AmazonGame { title, id }); + result.push(AmazonGame { title, id , launcher_path:launcher_path.clone()}); } } Ok(result) @@ -47,7 +49,8 @@ impl Platform> for AmazonPlatform { fn settings_valid(&self) -> crate::platform::SettingsValidity { let path = get_sqlite_path(); - if path.is_some() { + let launcher = get_launcher_path(); + if path.is_some() && launcher.is_some(){ crate::platform::SettingsValidity::Valid } else { crate::platform::SettingsValidity::Invalid { @@ -84,3 +87,20 @@ fn get_sqlite_path() -> Option { Err(_e) => None, } } + +fn get_launcher_path() -> Option { + match std::env::var("LOCALAPPDATA") { + Ok(localdata) => { + let path = Path::new(&localdata) + .join("Amazon Games") + .join("App") + .join("Amazon Games.exe"); + if path.exists() { + Some(path) + } else { + None + } + } + Err(_e) => None, + } +} \ No newline at end of file diff --git a/src/amazon/amazon_settings.rs b/src/amazon/amazon_settings.rs index b5d6745..fb1672e 100644 --- a/src/amazon/amazon_settings.rs +++ b/src/amazon/amazon_settings.rs @@ -3,4 +3,5 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct AmazonSettings { pub enabled: bool, + pub launcher_location: Option } diff --git a/src/egs/get_manifests.rs b/src/egs/get_manifests.rs index e78d2e4..150e4aa 100644 --- a/src/egs/get_manifests.rs +++ b/src/egs/get_manifests.rs @@ -34,6 +34,8 @@ pub(crate) fn get_egs_manifests( let manifest_dir_path = get_manifest_dir_path(settings)?; let manifest_dir_result = std::fs::read_dir(&manifest_dir_path); + #[cfg(target_os = "windows")] + let launcher_path = launcher_location_from_registry().unwrap_or_else(guess_default_launcher_location); match manifest_dir_result { Ok(manifest_dir) => { let manifests = manifest_dir @@ -59,6 +61,10 @@ pub(crate) fn get_egs_manifests( || settings.safe_launch.contains(&manifest.get_key()) { manifest.safe_launch = true; + #[cfg(target_os = "windows")] + { + manifest.launcher_path = Some(launcher_path.clone()); + } } } Ok(manifests) @@ -84,7 +90,7 @@ fn get_manifest_dir_path( }); } } else { - let path = get_default_location(); + let path = get_default_manifests_location(); match path { Some(path) => Ok(path.to_str().unwrap().to_string()), @@ -93,7 +99,7 @@ fn get_manifest_dir_path( } } -pub fn get_default_location() -> Option { +pub fn get_default_manifests_location() -> Option { #[cfg(target_family = "unix")] { //No path defined for epic gamestore, and we cannot guess on linux @@ -102,9 +108,9 @@ pub fn get_default_location() -> Option { #[cfg(target_os = "windows")] { - let path = match location_from_registry() { + let path = match manifest_location_from_registry() { Some(path) => path, - None => guess_default_location(), + None => guess_default_manifest_location(), }; if path.exists() { Some(path) @@ -113,8 +119,9 @@ pub fn get_default_location() -> Option { } } } + #[cfg(target_os = "windows")] -fn location_from_registry() -> Option { +fn manifest_location_from_registry() -> Option { use winreg::enums::*; use winreg::RegKey; @@ -132,7 +139,7 @@ fn location_from_registry() -> Option { } #[cfg(target_os = "windows")] -fn guess_default_location() -> PathBuf { +fn guess_default_manifest_location() -> PathBuf { let key = "SYSTEMDRIVE"; let system_drive = env::var(key).expect("We are on windows, we must know what the SYSTEMDRIVE is"); @@ -146,6 +153,42 @@ fn guess_default_location() -> PathBuf { path } +#[cfg(target_os = "windows")] +fn launcher_location_from_registry() -> Option { + use winreg::enums::*; + use winreg::RegKey; + + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + + if let Ok(launcher) = hklm.open_subkey("SOFTWARE\\Classes\\com.epicgames.launcher\\shell\\open\\command") { + let launch_string: Result = launcher.get_value(""); + if let Ok(launch_string) = launch_string { + let path = Path::new(&launch_string[1..launch_string.len()-4]); + if path.exists() { + return Some(path.to_path_buf()); + } + } + } + None +} + +#[cfg(target_os = "windows")] +fn guess_default_launcher_location() -> PathBuf { + let key = "SYSTEMDRIVE"; + let system_drive = + env::var(key).expect("We are on windows, we must know what the SYSTEMDRIVE is"); + + let path = Path::new(format!("{}\\", system_drive).as_str()) + .join("Program Files (x86)") + .join("Epic Games") + .join("Launcher") + .join("Portal") + .join("Binaries") + .join("Win64") + .join("EpicGamesLauncher.exe"); + path +} + fn is_game_installed(manifest: &ManifestItem) -> bool { Path::new(manifest.manifest_location.as_str()).exists() } @@ -165,3 +208,23 @@ fn get_manifest_item(dir_entry: DirEntry) -> Option { } None } + +//Commented out because it will change from machine to machine +// #[cfg(test)] +// pub mod test{ +// use super::guess_default_launcher_location; +// use super::launcher_location_from_registry; + +// #[test] +// pub fn test_launcher_registry(){ +// let launcher = launcher_location_from_registry(); +// assert_eq!(Some(std::path::Path::new("C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe").to_path_buf()),launcher); +// } + + +// #[test] +// pub fn test_launcher_guess(){ +// let launcher = guess_default_launcher_location(); +// assert_eq!(std::path::Path::new("C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe").to_path_buf(),launcher); +// } +// } \ No newline at end of file diff --git a/src/egs/manifest_item.rs b/src/egs/manifest_item.rs index 1656f3c..970f409 100644 --- a/src/egs/manifest_item.rs +++ b/src/egs/manifest_item.rs @@ -1,9 +1,9 @@ -use std::{collections::HashMap, path::Path}; +use std::{collections::HashMap, path::{Path, PathBuf}}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize}; use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut}; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone)] pub(crate) struct ManifestItem { #[serde(alias = "LaunchExecutable")] pub launch_executable: String, @@ -34,6 +34,9 @@ pub(crate) struct ManifestItem { #[serde(default)] pub safe_launch: bool, + + //This is not acutally in the manifest, but it will get added by get_manifests.rs + pub launcher_path: Option, } fn exe_shortcut(manifest: ManifestItem) -> ShortcutOwned { @@ -59,11 +62,11 @@ fn launcher_shortcut(manifest: ManifestItem) -> ShortcutOwned { Shortcut::new( "0", manifest.display_name.as_str(), - url.as_str(), - "", + manifest.launcher_path.as_ref().map(|p| p.to_string_lossy().to_string()).unwrap_or_default().as_str(), + manifest.launcher_path.map(|p| p.parent().unwrap_or(Path::new("")).to_string_lossy().to_string()).unwrap_or_default().as_str(), icon.as_str(), "", - "", + url.as_str(), ) .to_owned() } @@ -147,9 +150,8 @@ mod tests { let mut manifest: ManifestItem = serde_json::from_str(json).unwrap(); manifest.is_managed = true; let shortcut: ShortcutOwned = manifest.clone().into(); - - assert_eq!(shortcut.exe, manifest.get_launch_url()); - assert_eq!(shortcut.launch_options, ""); + + assert_eq!(shortcut.launch_options, manifest.get_launch_url()); } #[test] fn generates_shortcut_not_managed() { @@ -175,7 +177,7 @@ mod tests { let shortcut: ShortcutOwned = manifest.into(); let expected ="com.epicgames.launcher://apps/2a09fb19b47f46dfb11ebd382f132a8f%3A88f4bb0bb06e4962a2042d5e20fb6ace%3A63a665088eb1480298f1e57943b225d8?action=launch&silent=true"; - let actual = shortcut.exe; + let actual = shortcut.launch_options; assert_eq!(expected, actual); } } diff --git a/src/egs/mod.rs b/src/egs/mod.rs index 86aab01..9a7efe3 100644 --- a/src/egs/mod.rs +++ b/src/egs/mod.rs @@ -4,7 +4,7 @@ mod manifest_item; mod settings; pub use epic_platform::*; -pub use get_manifests::get_default_location; +pub use get_manifests::get_default_manifests_location; use get_manifests::get_egs_manifests; pub(crate) use manifest_item::*; pub use settings::EpicGamesLauncherSettings; diff --git a/src/egs/settings.rs b/src/egs/settings.rs index 9464bca..6f073c0 100644 --- a/src/egs/settings.rs +++ b/src/egs/settings.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; pub struct EpicGamesLauncherSettings { pub enabled: bool, pub location: Option, + pub launcher_exe : Option, #[cfg(target_family = "unix")] pub create_symlinks: bool, diff --git a/src/origin/origin_game.rs b/src/origin/origin_game.rs index 6d761bf..8c69f9d 100644 --- a/src/origin/origin_game.rs +++ b/src/origin/origin_game.rs @@ -6,7 +6,7 @@ use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut}; pub struct OriginGame { pub id: String, pub title: String, - pub origin_location: Option, + pub origin_location: PathBuf, pub origin_compat_folder: Option, } @@ -20,21 +20,17 @@ impl From for ShortcutOwned { "\"origin2://game/launch?offerIds={}&autoDownload=1&authCode=&cmdParams=\"", game.id) }; - let mut owned_shortcut = if let Some(origin_location) = game.origin_location { - let origin_location = format!("\"{}\"", origin_location.to_string_lossy()); - Shortcut::new( - "0", - game.title.as_str(), - &origin_location, - "", - "", - "", - launch.as_str(), - ) - .to_owned() - } else { - Shortcut::new("0", game.title.as_str(), launch.as_str(), "", "", "", "").to_owned() - }; + let origin_location = format!("\"{}\"",game.origin_location.to_string_lossy()); + let mut owned_shortcut = Shortcut::new( + "0", + game.title.as_str(), + &origin_location, + "", + "", + "", + launch.as_str(), + ) + .to_owned(); owned_shortcut.tags.push("Origin".to_owned()); owned_shortcut.tags.push("Ready TO Play".to_owned()); owned_shortcut.tags.push("Installed".to_owned()); diff --git a/src/origin/origin_platform.rs b/src/origin/origin_platform.rs index fe4b0f6..e65308d 100644 --- a/src/origin/origin_platform.rs +++ b/src/origin/origin_platform.rs @@ -28,13 +28,13 @@ impl Platform for OriginPlatform { fn get_shortcuts(&self) -> Result, OriginErrors> { let origin_folders = get_default_locations(); - if origin_folders.local_content_path.is_none() { + if origin_folders.is_none() { return Err(OriginErrors::PathNotFound { path: "Default path".to_string(), }); } - - let origin_folder = origin_folders.local_content_path.unwrap(); + let origin_folders = origin_folders.unwrap(); + let origin_folder = origin_folders.local_content_path; let origin_exe = origin_folders.exe_path; let game_folders = origin_folder.join("LocalContent").read_dir().map_err(|e| { OriginErrors::CouldNotReadGameDir { @@ -115,15 +115,15 @@ fn parse_id_from_file(i: &str) -> nom::IResult<&str, &str> { #[derive(Default)] struct OriginPathData { //~/.steam/steam/steamapps/compatdata/X/pfx/drive_c/Program Files (x86)/Origin/Origin.exe - exe_path: Option, + exe_path: PathBuf, //~/.steam/steam/steamapps/compatdata/X/pfx/drive_c/ProgramData/Origin/LocalContent - local_content_path: Option, + local_content_path: PathBuf, //~/.steam/steam/steamapps/compatdata/X compat_folder: Option, } #[cfg(target_family = "unix")] -fn get_default_locations() -> OriginPathData { +fn get_default_locations() -> Option { let mut res = OriginPathData::default(); if let Ok(home) = std::env::var("HOME") { let compat_folder_path = Path::new(&home) @@ -150,29 +150,57 @@ fn get_default_locations() -> OriginPathData { .join("Origin"); if origin_exe_path.exists() && origin_local_content.exists() { - res.exe_path = Some(origin_exe_path); - res.local_content_path = Some(origin_local_content); + res.exe_path = origin_exe_path; + res.local_content_path = origin_local_content; res.compat_folder = Some(dir.path().to_path_buf()); + return Some(res); } } } } - res + None } #[cfg(target_os = "windows")] -fn get_default_locations() -> OriginPathData { +fn get_default_locations() -> Option { let mut res = OriginPathData::default(); - let key = "PROGRAMDATA"; let program_data = std::env::var(key); if let Ok(program_data) = program_data { let origin_folder = Path::new(&program_data).join("Origin"); if origin_folder.exists() { - res.local_content_path = Some(origin_folder); + res.local_content_path = origin_folder; + } else { + return None; + } + let exe_path = get_exe_path(); + if exe_path.is_none() { + return None; + } else { + res.exe_path = exe_path.unwrap(); } } - res + Some(res) +} + +#[cfg(target_os = "windows")] +fn get_exe_path() -> Option { + use winreg::enums::*; + use winreg::RegKey; + //Computer\HKEY_CLASSES_ROOT\eadm\shell\open\command + + let hklm = RegKey::predef(HKEY_CLASSES_ROOT); + if let Ok(launcher_key) = hklm.open_subkey("eadm\\shell\\open\\command") { + let launcher_string: Result = launcher_key.get_value(""); + if let Ok(launcher_string) = launcher_string { + let path = Path::new(&launcher_string[1..launcher_string.len() - 6]); + println!("{:?}",path); + if path.exists() { + return Some(path.to_path_buf()); + } + } + } + None } #[derive(Debug, Fail)] diff --git a/src/ui/ui_settings.rs b/src/ui/ui_settings.rs index f616982..69e7609 100644 --- a/src/ui/ui_settings.rs +++ b/src/ui/ui_settings.rs @@ -258,6 +258,20 @@ impl MyEguiApp { epic_settings.location = Some(epic_location.to_string()); } }); + ui.horizontal(|ui| { + let mut empty_string = "".to_string(); + let epic_location = epic_settings.launcher_exe.as_mut().unwrap_or(&mut empty_string); + ui.label("Epic Launcher Location: ").on_hover_text( + "The location of the Epic launcher exe", + ); + if ui.text_edit_singleline(epic_location).changed() { + if epic_location.is_empty(){ + epic_settings.launcher_exe = None; + }else{ + epic_settings.launcher_exe = Some(epic_location.to_string()); + } + } + }); let safe_mode_header = match epic_settings.safe_launch.len() { 0 => "Force games to launch through Epic Launcher".to_string(), diff --git a/src/uplay/game.rs b/src/uplay/game.rs index 839f805..071659f 100644 --- a/src/uplay/game.rs +++ b/src/uplay/game.rs @@ -1,3 +1,5 @@ +use std::path::{PathBuf, Path}; + use steam_shortcuts_util::shortcut::{Shortcut, ShortcutOwned}; #[derive(Clone)] @@ -5,11 +7,14 @@ pub(crate) struct Game { pub(crate) name: String, pub(crate) icon: String, pub(crate) id: String, + pub(crate) launcher: PathBuf } impl From for ShortcutOwned { fn from(game: Game) -> Self { - let launch = format!("uplay://launch/{}", game.id); - Shortcut::new("0", &game.name, &launch, "", &game.icon, "", "").to_owned() + let launch = format!("\"uplay://launch/{}/0\"", game.id); + let start_dir = game.launcher.parent().unwrap_or(Path::new("")).to_string_lossy(); + let exe = format!("\"{}\"",game.launcher.to_string_lossy()); + Shortcut::new("0", &game.name, &exe, &start_dir, &game.icon, "", &launch).to_owned() } } diff --git a/src/uplay/platform.rs b/src/uplay/platform.rs index 915bc56..1271252 100644 --- a/src/uplay/platform.rs +++ b/src/uplay/platform.rs @@ -1,5 +1,7 @@ use crate::platform::Platform; use std::error::Error; +use std::path::Path; +use std::path::PathBuf; use super::{game::Game, settings::UplaySettings}; @@ -24,7 +26,23 @@ impl Platform> for Uplay { } fn settings_valid(&self) -> crate::platform::SettingsValidity { - crate::platform::SettingsValidity::Valid + #[cfg(target_family = "unix")] + { + //Linux not supported yet + return crate::platform::SettingsValidity::Invalid { + reason: "Linux not supported yet".to_string(), + }; + } + #[cfg(target_os = "windows")] + { + if get_launcher_path().is_some() { + return crate::platform::SettingsValidity::Valid; + } else { + return crate::platform::SettingsValidity::Invalid { + reason: "Could not find UPlay instalation".to_string(), + }; + } + } } fn get_shortcuts(&self) -> Result, Box> { @@ -53,17 +71,36 @@ impl Platform> for Uplay { } } } +#[cfg(target_os = "windows")] +fn get_launcher_path() -> Option { + use winreg::enums::*; + use winreg::RegKey; + + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + if let Ok(launcher_key) = hklm.open_subkey("SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher") { + let launcher_dir: Result = launcher_key.get_value("InstallDir"); + if let Ok(launcher_dir) = launcher_dir { + let path = Path::new(&launcher_dir).join("upc.exe"); + if path.exists() { + return Some(path.to_path_buf()); + } + } + } + + None +} #[cfg(target_os = "windows")] fn get_games_from_winreg() -> Result, Box> { - use std::path::Path; - use winreg::enums::*; use winreg::RegKey; let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); let mut games = vec![]; let mut installed_ids = vec![]; + let launcher_path = + get_launcher_path().expect("This should only be called if launcher is found"); + if let Ok(installs) = hklm.open_subkey("SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher\\Installs") { for i in installs.enum_keys().filter_map(|i| i.ok()) { if let Ok(install) = installs.open_subkey(&i) { @@ -85,7 +122,12 @@ fn get_games_from_winreg() -> Result, Box> { let name: Result = subkey.get_value("DisplayName"); if let Ok(name) = name { let icon: String = subkey.get_value("DisplayIcon").unwrap_or_default(); - games.push(Game { name, icon, id }) + games.push(Game { + name, + icon, + id, + launcher: launcher_path.clone(), + }) } } }