diff --git a/Cargo.lock b/Cargo.lock index 3b2637e..594a4f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,7 +399,7 @@ dependencies = [ [[package]] name = "boilr" -version = "1.8.0" +version = "1.9.0" dependencies = [ "base64 0.21.0", "color-eyre", diff --git a/Cargo.toml b/Cargo.toml index 636c1e9..4c07201 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "boilr" -version = "1.8.0" +version = "1.9.0" [dependencies] base64 = "^0.21.0" diff --git a/src/platforms/gamepass/game_pass_games.ps1 b/src/platforms/gamepass/game_pass_games.ps1 new file mode 100644 index 0000000..7be5521 --- /dev/null +++ b/src/platforms/gamepass/game_pass_games.ps1 @@ -0,0 +1,16 @@ +Get-AppxPackage | +Where-Object { -not $_.IsFramework } | +ForEach-Object { + try { + $manifest = Get-AppxPackageManifest $_ + $application = $manifest.Package.Applications.Application; + [PSCustomObject]@{ + kind = $application.Id + display_name = $manifest.Package.Properties.DisplayName + install_location = $_.InstallLocation + family_name = $_.PackageFamilyName + } + } + catch {} +} | +ConvertTo-Json -depth 5 \ No newline at end of file diff --git a/src/platforms/gamepass/mod.rs b/src/platforms/gamepass/mod.rs new file mode 100644 index 0000000..fb17f4e --- /dev/null +++ b/src/platforms/gamepass/mod.rs @@ -0,0 +1,3 @@ +mod platform; + +pub use platform::*; \ No newline at end of file diff --git a/src/platforms/gamepass/platform.rs b/src/platforms/gamepass/platform.rs new file mode 100644 index 0000000..703ca61 --- /dev/null +++ b/src/platforms/gamepass/platform.rs @@ -0,0 +1,154 @@ +use serde::Serialize; + +use crate::platforms::{load_settings, FromSettingsString, GamesPlatform}; +use serde::Deserialize; +use std::io::Error; +use std::path::{Path, PathBuf}; +use std::process::Command; +use steam_shortcuts_util::Shortcut; + +use crate::platforms::ShortcutToImport; + +#[derive(Clone, Deserialize, Default)] +pub struct GamePassPlatForm { + settings: GamePassSettings, +} + +#[derive(Serialize,Deserialize, Default, Clone)] +pub struct GamePassSettings { + enabled: bool, +} + + +impl GamesPlatform for GamePassPlatForm { + fn name(&self) -> &str { + "Game Pass" + } + + fn code_name(&self) -> &str { + "gamepass" + } + + fn enabled(&self) -> bool { + self.settings.enabled + } + + fn get_shortcut_info(&self) -> eyre::Result> { + let command = include_str!("./game_pass_games.ps1"); + let res = run_powershell_command(command)?; + let apps: Vec = serde_json::from_str(&res)?; + let expanded_search = false; + + let windows_dir = std::env::var("WinDir").unwrap_or("C:\\Windows".to_string()); + let explorer = Path::new(&windows_dir) + .join("explorer.exe") + .to_string_lossy() + .to_string(); + let games_iter = apps + .iter() + .filter(|app| { + app.kind.is_game() + || (expanded_search + && (app.display_name.contains("DisplayName") + || app.display_name.contains("ms-resource"))) + }) + .filter(|game| game.microsoft_game_path().exists() || game.appx_manifest().exists()) + .map(|game| { + let launch_url = format!("shell:AppsFolder\\{}", game.aum_id()); + let shortcut = Shortcut::new( + "0", + &game.display_name, + &explorer, + &windows_dir, + "", + "", + &launch_url, + ); + ShortcutToImport { + shortcut: shortcut.to_owned(), + needs_proton: false, + needs_symlinks: false, + } + }); + Ok(games_iter.collect()) + } + + fn get_settings_serilizable(&self) -> String { + toml::to_string(&self.settings).unwrap_or_default() + } + + fn render_ui(&mut self, ui: &mut egui::Ui) { + ui.heading("Game Pass"); + ui.checkbox(&mut self.settings.enabled, "Import from Game Pass"); + } +} + +#[derive(Deserialize, Debug)] +struct AppInfo { + kind: Kind, + display_name: String, + install_location: String, + family_name: String, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +enum Kind { + Null, + Str(String), + Array(Vec), +} + +impl AppInfo { + fn aum_id(&self) -> String { + format!("{}!{}", self.family_name, self.kind.as_ref()) + } + + fn microsoft_game_path(&self) -> PathBuf { + Path::new(&self.install_location).join("MicrosoftGames.config") + } + + fn appx_manifest(&self) -> PathBuf { + Path::new(&self.install_location).join("AppxManifest.xml") + } +} + +impl AsRef for Kind { + fn as_ref(&self) -> &str { + match self { + Kind::Null => "", + Kind::Str(s) => s.as_ref(), + Kind::Array(array) => array.iter().next().map(|s| s.as_ref()).unwrap_or(""), + } + } +} + +impl Kind { + fn is_game(&self) -> bool { + match self { + Kind::Str(string) => string.eq("Game"), + Kind::Array(strings) => strings.iter().any(|s| s == "Game"), + _ => false, + } + } +} + +fn run_powershell_command(cmd: &str) -> Result { + let output = Command::new("powershell").arg("/c").arg(cmd).output()?; + + match output.status.success() { + true => Ok(String::from_utf8_lossy(&output.stdout).to_string()), + false => Err(Error::new( + std::io::ErrorKind::Other, + String::from_utf8_lossy(&output.stderr).to_string(), + )), + } +} + +impl FromSettingsString for GamePassPlatForm { + fn from_settings_string>(s: S) -> Self { + GamePassPlatForm { + settings: load_settings(s), + } + } +} diff --git a/src/platforms/mod.rs b/src/platforms/mod.rs index a536c16..60be009 100644 --- a/src/platforms/mod.rs +++ b/src/platforms/mod.rs @@ -17,6 +17,10 @@ mod amazon; #[cfg(not(target_family = "unix"))] mod playnite; +#[cfg(not(target_family = "unix"))] +mod gamepass; + + mod gog; diff --git a/src/platforms/platforms_load.rs b/src/platforms/platforms_load.rs index 99acdd6..d9e5cd9 100644 --- a/src/platforms/platforms_load.rs +++ b/src/platforms/platforms_load.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use super::GamesPlatform; use crate::settings::load_setting_sections; -const PLATFORM_NAMES: [&str; 13] = [ +const PLATFORM_NAMES: [&str; 14] = [ "amazon", "bottles", "epic_games", @@ -18,6 +18,7 @@ const PLATFORM_NAMES: [&str; 13] = [ "uplay", "minigalaxy", "playnite", + "gamepass" ]; pub type Platforms = Vec>; @@ -34,9 +35,11 @@ pub fn load_platform, B: AsRef>( //Windows only platforms use super::amazon::AmazonPlatform; use super::playnite::PlaynitePlatform; + use super::gamepass::GamePassPlatForm; match name { "amazon" => return load::(s), "playnite" => return load::(s), + "gamepass" => return load::(s), _ => {} } }