From d7deae57adba450836af62780ae3b171da7ad932 Mon Sep 17 00:00:00 2001 From: Philip Date: Fri, 1 Oct 2021 22:26:29 +0200 Subject: [PATCH] Initial Origin Support --- src/defaultconfig.toml | 5 ++ src/egs/mod.rs | 2 +- src/main.rs | 9 +++ src/origin/mod.rs | 6 ++ src/origin/origin_game.rs | 18 +++++ src/origin/origin_platform.rs | 125 ++++++++++++++++++++++++++++++++++ src/origin/settings.rs | 8 +++ src/settings.rs | 11 +-- 8 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 src/origin/mod.rs create mode 100644 src/origin/origin_game.rs create mode 100644 src/origin/origin_platform.rs create mode 100644 src/origin/settings.rs diff --git a/src/defaultconfig.toml b/src/defaultconfig.toml index b9f58c2..889f903 100644 --- a/src/defaultconfig.toml +++ b/src/defaultconfig.toml @@ -1,8 +1,13 @@ debug= false +[origin] +enabled = false + [epic_games] +enabled = false [legendary] +enabled = false [steam] diff --git a/src/egs/mod.rs b/src/egs/mod.rs index 48e9d67..72d1fe4 100644 --- a/src/egs/mod.rs +++ b/src/egs/mod.rs @@ -3,7 +3,7 @@ mod manifest_item; mod settings; mod epic_platform; -pub use manifest_item::*; +pub(crate) use manifest_item::*; use get_manifests::get_egs_manifests; pub use settings::EpicGamesLauncherSettings; pub use epic_platform::*; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 63ace47..4c04b64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,13 @@ use crate::{ itch::ItchPlatform, + origin::OriginPlatform, steamgriddb::{download_images, CachedSearch}, }; use std::{fs::File, io::Write, path::Path}; mod egs; mod itch; mod legendary; +mod origin; mod platform; mod settings; mod steam; @@ -93,6 +95,13 @@ async fn run_sync() -> Result<(), Box> { &mut new_user_shortcuts, ); + update_platform_shortcuts( + &OriginPlatform { + settings: settings.origin.clone(), + }, + &mut new_user_shortcuts, + ); + let shortcuts = new_user_shortcuts.iter().map(|f| f.borrow()).collect(); save_shortcuts(&shortcuts, Path::new(&shortcut_info.path)); diff --git a/src/origin/mod.rs b/src/origin/mod.rs new file mode 100644 index 0000000..012912e --- /dev/null +++ b/src/origin/mod.rs @@ -0,0 +1,6 @@ +mod settings; +mod origin_platform; +mod origin_game; + +pub use origin_platform::*; +pub use settings::*; \ No newline at end of file diff --git a/src/origin/origin_game.rs b/src/origin/origin_game.rs new file mode 100644 index 0000000..601d12e --- /dev/null +++ b/src/origin/origin_game.rs @@ -0,0 +1,18 @@ +use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut}; +pub struct OriginGame { + pub id: String, + pub title: String, +} + +impl From for ShortcutOwned { + fn from(game: OriginGame) -> Self { + let launch = format!("origin2://game/launch?offerIds={}&autoDownload=1", game.id); + let shortcut = Shortcut::new(0, game.title.as_str(), launch.as_str(), "", "", "", ""); + let mut owned_shortcut = shortcut.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()); + + owned_shortcut + } +} diff --git a/src/origin/origin_platform.rs b/src/origin/origin_platform.rs new file mode 100644 index 0000000..d124f0e --- /dev/null +++ b/src/origin/origin_platform.rs @@ -0,0 +1,125 @@ +use crate::platform::Platform; +use failure::*; +use nom::bytes::complete::take_until; +use std::{ + fs::DirEntry, + path::{Path, PathBuf}, +}; + +use super::{origin_game::OriginGame, OriginSettings}; + +pub struct OriginPlatform { + pub settings: OriginSettings, +} + +impl Platform for OriginPlatform { + fn enabled(&self) -> bool { + self.settings.enabled + } + + fn name(&self) -> &str { + "Origin" + } + + fn get_shortcuts(&self) -> Result, OriginErrors> { + let origin_folder = Path::new( + &self + .settings + .path + .clone() + .unwrap_or_else(|| get_default_location()), + ) + .join("LocalContent"); + if !origin_folder.exists() { + return Err(OriginErrors::PathNotFound { + path: origin_folder.to_str().unwrap().to_string(), + }); + } + let game_folders = + origin_folder + .read_dir() + .map_err(|e| OriginErrors::CouldNotReadGameDir { + path: origin_folder, + error: format!("{:?}", e), + })?; + let games = game_folders + .filter_map(|folder| folder.ok()) + .filter_map(|game_folder| { + let game_title = game_folder.file_name().to_string_lossy().to_string(); + let mfst_content = get_folder_mfst_file_content(&game_folder.path()); + let id = match mfst_content { + Some(c) => parse_id_from_file(c.as_str()) + .ok() + .map(|(_, id_str)| String::from(id_str)), + None => None, + }; + id.map(|id| OriginGame { + id: id.to_string(), + title: game_title, + }) + }); + Ok(games.collect()) + } +} + +fn get_folder_mfst_file_content(game_folder_path: &Path) -> Option { + let game_folder_files = game_folder_path.read_dir(); + if let Ok(game_folder_files) = game_folder_files { + let mfst_file = game_folder_files + .filter_map(|file| file.ok()) + .filter(is_mfst_file) + .next() + .map(|file| std::fs::read_to_string(&file.path())); + return match mfst_file { + Some(mfst_file) => mfst_file.ok(), + None => None, + }; + } + None +} + +fn is_mfst_file(file: &DirEntry) -> bool { + file.path() + .extension() + .map(|ex| ex.to_str().unwrap_or("") == "mfst") + .unwrap_or(false) +} + +fn parse_id_from_file(i: &str) -> nom::IResult<&str, &str> { + let (i, _) = take_until("&id=")(i)?; + let (i, _) = nom::bytes::complete::tag("&id=")(i)?; + take_until("&")(i) +} + +#[cfg(target_os = "linux")] +fn get_default_location() -> String { + //If we don't have a home drive we have to just die + let home = std::env::var("HOME").expect("Expected a home variable to be defined"); + format!("{}/.origin/", home) +} + +#[cfg(target_os = "windows")] +fn get_default_location() -> String { + let key = "PROGRAMDATA"; + let program_data = std::env::var(key).expect("Expected a APPDATA variable to be defined"); + Path::new(&program_data) + .join("Origin") + .to_str() + .unwrap() + .to_string() +} + +#[derive(Debug, Fail)] +pub enum OriginErrors { + #[fail( + display = "Origin path: {} could not be found. Try to specify a different path for Origin.", + path + )] + PathNotFound { path: String }, + + #[fail( + display = "Could not read Origin directory: {:?}. Error: {}", + path, error + )] + CouldNotReadGameDir { path: PathBuf, error: String }, +} diff --git a/src/origin/settings.rs b/src/origin/settings.rs new file mode 100644 index 0000000..fd2d699 --- /dev/null +++ b/src/origin/settings.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize,Clone)] + +pub struct OriginSettings { + pub enabled: bool, + pub path: Option, +} diff --git a/src/settings.rs b/src/settings.rs index f769274..1d09a57 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,4 +1,4 @@ -use crate::{egs::EpicGamesLauncherSettings, itch::ItchSettings, legendary::LegendarySettings, steam::SteamSettings, steamgriddb::SteamGridDbSettings}; +use crate::{egs::EpicGamesLauncherSettings, itch::ItchSettings, legendary::LegendarySettings, origin::OriginSettings, steam::SteamSettings, steamgriddb::SteamGridDbSettings}; use config::{Config, ConfigError, Environment, File}; use serde::Deserialize; @@ -11,11 +11,10 @@ pub struct Settings { pub legendary: LegendarySettings, pub itch: ItchSettings, pub steamgrid_db: SteamGridDbSettings, - pub steam: SteamSettings + pub steam: SteamSettings, + pub origin: OriginSettings, } -//https://github.com/JosefNemec/Playnite/tree/master/source/Plugins/OriginLibrary -//https://github.com/JosefNemec/Playnite/blob/master/source/Plugins/OriginLibrary/Origin.cs#L109 impl Settings { pub fn new() -> Result { let mut s = Config::new(); @@ -33,10 +32,6 @@ impl Settings { #[cfg(target_os = "windows")] let enable_epic = true; - s.set_default("legendary.enabled", enable_legendary)?; - s.set_default("epic_games.enabled", enable_epic)?; - - // Start off by merging in the "default" configuration file s.merge(File::with_name("config.toml").required(false))?;