From 1bcafd8207eb63c9579cf5e0a1b74b2c8d4fec8e Mon Sep 17 00:00:00 2001 From: Philip Kristoffersen Date: Wed, 8 Jun 2022 21:26:16 +0200 Subject: [PATCH] Add feature to launch gog games through heroic (#168) --- src/heroic/heroic_game.rs | 100 +++++++-------------- src/heroic/heroic_game_type.rs | 54 +++++++++++- src/heroic/heroic_platform.rs | 153 +++++++++++++++++---------------- src/heroic/settings.rs | 13 +++ src/ui/ui_settings.rs | 11 ++- src/ui/uiapp.rs | 9 +- 6 files changed, 190 insertions(+), 150 deletions(-) diff --git a/src/heroic/heroic_game.rs b/src/heroic/heroic_game.rs index 0e27a6a..7f0bb19 100644 --- a/src/heroic/heroic_game.rs +++ b/src/heroic/heroic_game.rs @@ -1,7 +1,5 @@ -use std::path::Path; - -use super::heroic_platform::InstallationMode; use serde::Deserialize; +use std::path::Path; use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut}; #[derive(Deserialize, Debug, Clone)] @@ -12,86 +10,54 @@ pub struct HeroicGame { pub install_path: String, pub executable: String, pub launch_parameters: String, - #[serde(skip_deserializing)] - pub install_mode: Option, - #[serde(skip_deserializing)] - pub launch_through_heroic: bool, } impl HeroicGame { pub fn is_installed(&self) -> bool { - if self.launch_through_heroic { - true - } else { - Path::new(&self.install_path) - .join(&self.executable) - .exists() - } + Path::new(&self.install_path) + .join(&self.executable) + .exists() } } impl From for ShortcutOwned { fn from(game: HeroicGame) -> Self { - let mut owned_shortcut = if game.launch_through_heroic && game.install_mode.is_some() { - let launch_parameter = format!("heroic://launch/{}", game.app_name); - let (exe, parameter) = match game.install_mode.unwrap() { - InstallationMode::FlatPak => ( - "flatpak", - format!( - "run com.heroicgameslauncher.hgl {} --no-gui", - launch_parameter - ), - ), - InstallationMode::UserBin => ("heroic", launch_parameter), - }; - Shortcut::new( - "0", - game.title.as_str(), - exe, - "", - "", - "", - parameter.as_str(), - ) - .to_owned() - } else { - let target_path = Path::new(&game.install_path).join(game.executable); + let target_path = Path::new(&game.install_path).join(game.executable); - #[cfg(target_family = "unix")] - let mut target = target_path.to_string_lossy().to_string(); - #[cfg(target_family = "unix")] - { - if !target.starts_with('\"') && !target.ends_with('\"') { - target = format!("\"{}\"", target); - } + #[cfg(target_family = "unix")] + let mut target = target_path.to_string_lossy().to_string(); + #[cfg(target_family = "unix")] + { + if !target.starts_with('\"') && !target.ends_with('\"') { + target = format!("\"{}\"", target); } + } - #[cfg(target_family = "unix")] - let mut install_path = game.install_path.to_string(); - #[cfg(target_family = "unix")] - { - if !install_path.starts_with('\"') && !install_path.ends_with('\"') { - install_path = format!("\"{}\"", install_path); - } + #[cfg(target_family = "unix")] + let mut install_path = game.install_path.to_string(); + #[cfg(target_family = "unix")] + { + if !install_path.starts_with('\"') && !install_path.ends_with('\"') { + install_path = format!("\"{}\"", install_path); } - #[cfg(target_os = "windows")] - let install_path = game.install_path.to_string(); + } + #[cfg(target_os = "windows")] + let install_path = game.install_path.to_string(); - #[cfg(target_os = "windows")] - let target = target_path.to_string_lossy().to_string(); + #[cfg(target_os = "windows")] + let target = target_path.to_string_lossy().to_string(); - let shortcut = Shortcut::new( - "0", - game.title.as_str(), - &target, - &install_path, - &target, - "", - game.launch_parameters.as_str(), - ); + let shortcut = Shortcut::new( + "0", + game.title.as_str(), + &target, + &install_path, + &target, + "", + game.launch_parameters.as_str(), + ); - shortcut.to_owned() - }; + let mut owned_shortcut = shortcut.to_owned(); owned_shortcut.tags.push("Heroic".to_owned()); owned_shortcut.tags.push("Ready TO Play".to_owned()); owned_shortcut.tags.push("Installed".to_owned()); diff --git a/src/heroic/heroic_game_type.rs b/src/heroic/heroic_game_type.rs index 13dd7c9..d7b2636 100644 --- a/src/heroic/heroic_game_type.rs +++ b/src/heroic/heroic_game_type.rs @@ -1,6 +1,6 @@ -use steam_shortcuts_util::shortcut::ShortcutOwned; +use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut}; -use super::HeroicGame; +use super::{HeroicGame, InstallationMode}; use crate::gog::GogShortcut; #[derive(Clone)] @@ -8,6 +8,38 @@ pub enum HeroicGameType { Epic(HeroicGame), //The bool is if it is windows (true) or not (false) Gog(GogShortcut, bool), + //The string is the app name + Heroic { + title: String, + app_name: String, + install_mode: InstallationMode, + }, +} + +impl HeroicGameType { + pub fn app_name(&self) -> &str { + match self { + HeroicGameType::Epic(g) => g.app_name.as_ref(), + HeroicGameType::Gog(g, _) => g.game_id.as_ref(), + HeroicGameType::Heroic { + title, + app_name, + install_mode, + } => &app_name, + } + } + + pub(crate) fn title(&self) -> &str { + match self { + HeroicGameType::Epic(g) => g.title.as_ref(), + HeroicGameType::Gog(g, _) => g.name.as_ref(), + HeroicGameType::Heroic { + title, + app_name, + install_mode, + } => title.as_ref(), + } + } } impl From for ShortcutOwned { @@ -15,6 +47,24 @@ impl From for ShortcutOwned { match heroic_game_type { HeroicGameType::Epic(epic) => epic.into(), HeroicGameType::Gog(gog, _) => gog.into(), + HeroicGameType::Heroic { + title, + app_name, + install_mode, + } => { + let launch_parameter = format!("heroic://launch/{}", app_name); + let (exe, parameter) = match install_mode { + InstallationMode::FlatPak => ( + "flatpak", + format!( + "run com.heroicgameslauncher.hgl {} --no-gui", + launch_parameter + ), + ), + InstallationMode::UserBin => ("heroic", launch_parameter), + }; + Shortcut::new("0", title.as_str(), exe, "", "", "", parameter.as_str()).to_owned() + } } } } diff --git a/src/heroic/heroic_platform.rs b/src/heroic/heroic_platform.rs index 10d6446..aa5f0e2 100644 --- a/src/heroic/heroic_platform.rs +++ b/src/heroic/heroic_platform.rs @@ -13,7 +13,7 @@ pub struct HeroicPlatform { pub settings: HeroicSettings, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Copy)] pub enum InstallationMode { FlatPak, UserBin, @@ -74,6 +74,20 @@ fn get_shortcuts_from_location>(path: P) -> Result Vec { + let install_modes = vec![InstallationMode::FlatPak, InstallationMode::UserBin]; + + let mut heroic_games = self.get_epic_games(&install_modes); + if let Ok(gog_games) = get_gog_games(&self.settings, &install_modes) { + heroic_games.extend(gog_games); + } else { + println!("Did not find any GOG games in heroic") + } + heroic_games + } +} + impl Platform> for HeroicPlatform { fn enabled(&self) -> bool { self.settings.enabled @@ -82,16 +96,9 @@ impl Platform> for HeroicPlatform { fn name(&self) -> &str { "Heroic" } - fn get_shortcuts(&self) -> Result, Box> { - let install_modes = vec![InstallationMode::FlatPak, InstallationMode::UserBin]; - let mut heroic_games = self.get_epic_games(&install_modes)?; - if let Ok(gog_games) = get_gog_games(&install_modes) { - heroic_games.extend(gog_games); - } else { - println!("Did not find any GOG games in heroic") - } - Ok(heroic_games) + fn get_shortcuts(&self) -> Result, Box> { + Ok(self.get_heroic_games()) } #[cfg(target_family = "unix")] @@ -111,90 +118,91 @@ impl Platform> for HeroicPlatform { fn needs_proton(&self, input: &HeroicGameType) -> bool { match input { - HeroicGameType::Epic(game) => !game.launch_through_heroic, + HeroicGameType::Epic(_game) => true, HeroicGameType::Gog(_, is_windows) => *is_windows, + HeroicGameType::Heroic { .. } => false, } } } impl HeroicPlatform { - pub fn get_epic_games( - &self, - install_modes: &[InstallationMode], - ) -> Result, Box> { - let shortcuts = self.get_heroic_games(install_modes); - let mut epic_shortcuts = vec![]; - for shortcut in shortcuts { - epic_shortcuts.push(HeroicGameType::Epic(shortcut)); - } - Ok(epic_shortcuts) - } + pub fn get_epic_games(&self, install_modes: &[InstallationMode]) -> Vec { + let mut shortcuts = vec![]; + for install_mode in install_modes { + if let Ok(mut games) = get_shortcuts_from_install_mode(install_mode) { + games.sort_by_key(|m| { + format!("{}-{}-{}", m.launch_parameters, m.executable, &m.app_name) + }); + games.dedup_by_key(|m| { + format!("{}-{}-{}", m.launch_parameters, m.executable, &m.app_name) + }); - pub fn get_heroic_games(&self, install_modes: &[InstallationMode]) -> Vec { - let mut shortcuts: Vec = install_modes - .iter() - .filter_map(|install_mode| { - let mut shortcuts = get_shortcuts_from_install_mode(install_mode).ok(); - if let Some(shortcuts) = shortcuts.as_mut() { - for shortcut in shortcuts { - shortcut.install_mode = Some(install_mode.clone()); - - let game_in_list = self - .settings - .launch_games_through_heroic - .contains(&shortcut.app_name) - || self - .settings - .launch_games_through_heroic - .contains(&shortcut.title); - - shortcut.launch_through_heroic = - if self.settings.default_launch_through_heroic { - !game_in_list - } else { - game_in_list - }; + for game in games { + if self.settings.is_heroic_launch(&game.app_name) { + shortcuts.push(HeroicGameType::Heroic { + title: game.title, + app_name: game.app_name, + install_mode: *install_mode, + }); + } else { + if game.is_installed() { + shortcuts.push(HeroicGameType::Epic(game)); + } } } - shortcuts - }) - .flatten() - .filter(|s| s.is_installed()) - .collect(); - shortcuts - .sort_by_key(|m| format!("{}-{}-{}", m.launch_parameters, m.executable, &m.app_name)); - shortcuts - .dedup_by_key(|m| format!("{}-{}-{}", m.launch_parameters, m.executable, &m.app_name)); + } + } shortcuts } } fn get_gog_games( + settings: &HeroicSettings, install_modes: &[InstallationMode], ) -> Result, Box> { - let gog_paths: Vec = install_modes - .iter() - .filter_map(|install_mode| { - let config = get_gog_installed_location(install_mode); - if config.exists() { - Some(config) - } else { - None + let mut gog_paths = vec![]; + for install_mode in install_modes { + let config = get_gog_installed_location(install_mode); + if config.exists() { + if let Ok(config_string) = std::fs::read_to_string(config) { + if let Ok(config) = serde_json::from_str::(&config_string) { + for c in config.installed { + gog_paths.push((install_mode, c)); + } + } } - }) - .filter_map(|config_path| std::fs::read_to_string(config_path).ok()) - .filter_map(|config_string| serde_json::from_str::(&config_string).ok()) - .flat_map(|config| config.installed) - .collect(); + } + } let mut is_windows_map = HashMap::new(); - for path in gog_paths.iter() { + for (_, path) in gog_paths.iter() { is_windows_map.insert(path.app_name.clone(), path.platform == "windows"); } + let mut gog_shortcuts = vec![]; + + let heroic_games = gog_paths + .iter() + .filter(|(_, p)| settings.is_heroic_launch(&p.app_name)) + .filter_map(|(install_mode, p)| { + let path = Path::new(&p.install_path); + if path.exists() { + let title = path.file_name(); + Some(HeroicGameType::Heroic { + title: title.unwrap_or_default().to_string_lossy().to_string(), + app_name: p.app_name.clone(), + install_mode: **install_mode, + }) + } else { + None + } + }); + gog_shortcuts.extend(heroic_games); + let game_folders = gog_paths .iter() - .filter_map(|p| { + .filter(|(_, p)| !settings.is_heroic_launch(&p.app_name)) + .filter_map(|(_, p)| { let path = Path::new(&p.install_path); if path.exists() { Some(path.to_path_buf()) @@ -203,9 +211,8 @@ fn get_gog_games( } }) .collect(); - let shortcuts = get_shortcuts_from_game_folders(game_folders); - let mut gog_shortcuts = vec![]; - for shortcut in shortcuts { + let direct_shortcuts = get_shortcuts_from_game_folders(game_folders); + for shortcut in direct_shortcuts { let is_windows = is_windows_map.get(&shortcut.game_id).unwrap_or(&false); gog_shortcuts.push(HeroicGameType::Gog(shortcut, *is_windows)); } diff --git a/src/heroic/settings.rs b/src/heroic/settings.rs index f5e290e..b1b172b 100644 --- a/src/heroic/settings.rs +++ b/src/heroic/settings.rs @@ -6,3 +6,16 @@ pub struct HeroicSettings { pub launch_games_through_heroic: Vec, pub default_launch_through_heroic: bool, } + +impl HeroicSettings { + pub fn is_heroic_launch>(&self, app_name: S) -> bool { + let contains = self + .launch_games_through_heroic + .contains(&app_name.as_ref().to_string()); + if self.default_launch_through_heroic { + !contains + } else { + contains + } + } +} diff --git a/src/ui/ui_settings.rs b/src/ui/ui_settings.rs index 9b2cc75..9f835ee 100644 --- a/src/ui/ui_settings.rs +++ b/src/ui/ui_settings.rs @@ -98,21 +98,20 @@ self.settings.heroic.default_launch_through_heroic{ let manifests =self.heroic_games.get_or_insert_with(||{ let heroic_setting = self.settings.heroic.clone(); - let install_modes = vec![crate::heroic::InstallationMode::FlatPak, crate::heroic::InstallationMode::UserBin]; let heroic_platform =HeroicPlatform{ settings:heroic_setting }; - heroic_platform.get_heroic_games(&install_modes) + heroic_platform.get_heroic_games() }); let safe_open_games = &mut self.settings.heroic.launch_games_through_heroic; for manifest in manifests{ - let key = &manifest.app_name; - let display_name = &manifest.title; - let mut safe_open = safe_open_games.contains(display_name) || safe_open_games.contains(key); + let key = manifest.app_name(); + let display_name = manifest.title(); + let mut safe_open = safe_open_games.contains(&display_name.to_string()) || safe_open_games.contains(&key.to_string()); if ui.checkbox(&mut safe_open, display_name).clicked(){ if safe_open{ - safe_open_games.push(key.clone()); + safe_open_games.push(key.to_string()); }else{ safe_open_games.retain(|m| m!= display_name && m!= key); } diff --git a/src/ui/uiapp.rs b/src/ui/uiapp.rs index 75418da..40d2ac8 100644 --- a/src/ui/uiapp.rs +++ b/src/ui/uiapp.rs @@ -8,7 +8,12 @@ use tokio::{ sync::watch::{self, Receiver}, }; -use crate::{egs::ManifestItem, heroic::HeroicGame, settings::Settings, sync::SyncProgress}; +use crate::{ + egs::ManifestItem, + heroic::{HeroicGame, HeroicGameType}, + settings::Settings, + sync::SyncProgress, +}; use super::{ ui_colors::{ @@ -36,7 +41,7 @@ pub struct MyEguiApp { pub(crate) games_to_sync: Receiver)>>>, pub(crate) status_reciever: Receiver, pub(crate) epic_manifests: Option>, - pub(crate) heroic_games: Option>, + pub(crate) heroic_games: Option>, pub(crate) image_selected_state: ImageSelectState, pub(crate) backup_state: BackupState, pub(crate) disconect_state: DiconnectState,