Heroic gog support (#72)

* Only use icons if they exsists

* Initial gog support from heroic

* Find gog linux games

* Update readme to include gog games

* rename appName to app_name
This commit is contained in:
Philip Kristoffersen
2022-04-03 08:23:55 +02:00
committed by GitHub
parent 59e712f409
commit b15ace49a0
9 changed files with 278 additions and 122 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ Optionally you can set BoilR up to automatically download artwork from [SteamGri
- [x] [Lutris](https://github.com/lutris/lutris) - [x] [Lutris](https://github.com/lutris/lutris)
- [x] [Legendary](https://github.com/derrod/legendary) - [x] [Legendary](https://github.com/derrod/legendary)
- [x] [Rare](https://github.com/Dummerle/Rare/releases) - [x] [Rare](https://github.com/Dummerle/Rare/releases)
- [x] [Heroic Launcher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher) (Only Linux & Epic Games for now) - [x] [Heroic Launcher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher) (Linux Only)
- [ ] XBox/Microsoft Store integration - [ ] XBox/Microsoft Store integration
+17 -4
View File
@@ -23,15 +23,18 @@ pub(crate) struct PlayTask {
pub task_type: String, pub task_type: String,
#[serde(alias = "workingDir")] #[serde(alias = "workingDir")]
pub working_dir: Option<String>, pub working_dir: Option<String>,
pub arguments: Option<String>,
} }
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct GogShortcut { pub struct GogShortcut {
pub name: String, pub name: String,
pub game_folder: String, pub game_folder: String,
pub path: String, pub path: String,
pub working_dir: String, pub working_dir: String,
pub game_id: String, pub game_id: String,
pub arguments: String,
} }
impl From<GogShortcut> for ShortcutOwned { impl From<GogShortcut> for ShortcutOwned {
@@ -44,14 +47,24 @@ impl From<GogShortcut> for ShortcutOwned {
} else { } else {
exe.to_str().unwrap_or("").to_string() exe.to_str().unwrap_or("").to_string()
}; };
let mut exe_string = exe.to_string_lossy().to_string();
if exe_string.contains(" ") && !exe_string.starts_with("\""){
exe_string = format!("\"{}\"",exe_string);
}
let mut working_dir_string = gogs.working_dir;
if working_dir_string.contains(" ") && !working_dir_string.starts_with("\""){
working_dir_string = format!("\"{}\"",working_dir_string);
}
let shortcut = Shortcut::new( let shortcut = Shortcut::new(
"0", "0",
gogs.name.as_str(), gogs.name.as_str(),
exe.to_str().unwrap(), &exe_string,
gogs.working_dir.as_str(), &working_dir_string,
icon.as_str(), icon.as_str(),
"", "",
"", &gogs.arguments,
); );
let mut owned_shortcut = shortcut.to_owned(); let mut owned_shortcut = shortcut.to_owned();
owned_shortcut.tags.push("Gog".to_owned()); owned_shortcut.tags.push("Gog".to_owned());
+123 -97
View File
@@ -12,6 +12,128 @@ pub struct GogPlatform {
pub settings: GogSettings, pub settings: GogSettings,
} }
pub fn get_shortcuts_from_config(wine_c_drive : Option<String>, config_path: PathBuf) -> Result<Vec<GogShortcut>, GogErrors> {
let install_locations = get_install_locations(config_path)?;
#[cfg(target_family = "unix")]
let install_locations = if let Some(wine_c_drive) = &wine_c_drive {
fix_paths(wine_c_drive, install_locations)
} else {
install_locations
};
let mut game_folders = vec![];
for install_location in install_locations {
let path = Path::new(&install_location);
if path.exists() {
let dirs = path.read_dir();
if let Ok(dirs) = dirs {
for dir in dirs.flatten() {
if let Ok(file_type) = dir.file_type() {
if file_type.is_dir() {
game_folders.push(dir.path());
}
}
}
}
}
}
let shortcuts = get_shortcuts_from_game_folders(game_folders);
Ok(shortcuts)
}
pub fn get_shortcuts_from_game_folders(game_folders: Vec<PathBuf>) -> Vec<GogShortcut> {
let games = get_games_from_game_folders(game_folders);
let shortcuts = get_shortcuts_from_games(games);
shortcuts
}
fn get_shortcuts_from_games(games: Vec<(GogGame, PathBuf)>) -> Vec<GogShortcut> {
let mut shortcuts = vec![];
for (game, game_folder) in games {
if let Some(folder_path) = game_folder.to_str() {
if let Some(tasks) = &game.play_tasks {
if let Some(primary_task) = tasks.iter().find(|t| {
t.is_primary.unwrap_or_default()
&& t.task_type == "FileTask"
&& t.category.as_ref().unwrap_or(&String::from("")) == "game"
}) {
if let Some(task_path) = &primary_task.path {
let full_path = game_folder.join(&task_path);
if let Some(full_path) = full_path.to_str() {
let folder_path = folder_path.to_string();
let working_dir = match &primary_task.working_dir {
Some(working_dir) => game_folder
.join(working_dir)
.to_str()
.unwrap_or_else(|| folder_path.as_str())
.to_string(),
None => folder_path.to_string(),
};
#[cfg(target_family = "unix")]
let working_dir = working_dir.replace("\\", "/");
let full_path_string = full_path.to_string();
#[cfg(target_family = "unix")]
let full_path_string = full_path_string.replace("\\", "/");
let arguments = primary_task.arguments.as_ref().unwrap_or(&"".to_string()).clone();
let shortcut = GogShortcut {
name: game.name,
game_folder: folder_path,
working_dir,
game_id: game.game_id,
path: full_path_string,
arguments,
};
shortcuts.push(shortcut);
}
}
}
}
}
}
shortcuts
}
fn get_games_from_game_folders(game_folders: Vec<PathBuf>) -> Vec<(GogGame, PathBuf)> {
let mut games = vec![];
for game_folder in &game_folders {
let mut game_folder = game_folder;
let deep_game_folder = Path::new(&game_folder).join("game").to_path_buf();
if deep_game_folder.exists() {
game_folder = &deep_game_folder;
}
if let Ok(files) = game_folder.read_dir() {
for file in files.flatten() {
if let Some(file_name) = file.file_name().to_str() {
if file_name.starts_with("goggame-") {
if let Some(extension) = file.path().extension() {
if let Some(extension) = extension.to_str() {
if extension == "info" {
// Finally we know we can parse this as a game
if let Ok(content) = std::fs::read_to_string(file.path()) {
if let Ok(gog_game) =
serde_json::from_str::<GogGame>(&content)
{
games.push((gog_game, game_folder.clone()));
}
}
}
}
}
}
}
}
}
}
games
}
impl Platform<GogShortcut, GogErrors> for GogPlatform { impl Platform<GogShortcut, GogErrors> for GogPlatform {
fn enabled(&self) -> bool { fn enabled(&self) -> bool {
self.settings.enabled self.settings.enabled
@@ -40,103 +162,7 @@ impl Platform<GogShortcut, GogErrors> for GogPlatform {
if !config_path.exists() { if !config_path.exists() {
return Err(GogErrors::ConfigFileNotFound { path: config_path }); return Err(GogErrors::ConfigFileNotFound { path: config_path });
} }
let install_locations = get_install_locations(config_path)?; get_shortcuts_from_config(self.settings.wine_c_drive.clone(),config_path)
#[cfg(target_family = "unix")]
let install_locations = if let Some(wine_c_drive) = &self.settings.wine_c_drive {
fix_paths(wine_c_drive, install_locations)
} else {
install_locations
};
let mut game_folders = vec![];
for install_location in install_locations {
let path = Path::new(&install_location);
if path.exists() {
let dirs = path.read_dir();
if let Ok(dirs) = dirs {
for dir in dirs.flatten() {
if let Ok(file_type) = dir.file_type() {
if file_type.is_dir() {
game_folders.push(dir.path());
}
}
}
}
}
}
let mut games = vec![];
for game_folder in &game_folders {
if let Ok(files) = game_folder.read_dir() {
for file in files.flatten() {
if let Some(file_name) = file.file_name().to_str() {
if file_name.starts_with("goggame-") {
if let Some(extension) = file.path().extension() {
if let Some(extension) = extension.to_str() {
if extension == "info" {
// Finally we know we can parse this as a game
if let Ok(content) = std::fs::read_to_string(file.path()) {
if let Ok(gog_game) =
serde_json::from_str::<GogGame>(&content)
{
games.push((gog_game, game_folder));
}
}
}
}
}
}
}
}
}
}
let mut shortcuts = vec![];
for (game, game_folder) in games {
if let Some(folder_path) = game_folder.to_str() {
if let Some(tasks) = &game.play_tasks {
if let Some(primary_task) = tasks.iter().find(|t| {
t.is_primary.unwrap_or_default()
&& t.task_type == "FileTask"
&& t.category.as_ref().unwrap_or(&String::from("")) == "game"
}) {
if let Some(task_path) = &primary_task.path {
let full_path = game_folder.join(&task_path);
if let Some(full_path) = full_path.to_str() {
let folder_path = folder_path.to_string();
let working_dir = match &primary_task.working_dir {
Some(working_dir) => game_folder
.join(working_dir)
.to_str()
.unwrap_or_else(|| folder_path.as_str())
.to_string(),
None => folder_path.to_string(),
};
#[cfg(target_family = "unix")]
let working_dir = working_dir.replace("\\", "/");
let full_path_string = full_path.to_string();
#[cfg(target_family = "unix")]
let full_path_string = full_path_string.replace("\\", "/");
let shortcut = GogShortcut {
name: game.name,
game_folder: folder_path,
working_dir,
game_id: game.game_id,
path: full_path_string,
};
shortcuts.push(shortcut);
}
}
}
}
}
}
Ok(shortcuts)
} }
fn settings_valid(&self) -> crate::platform::SettingsValidity { fn settings_valid(&self) -> crate::platform::SettingsValidity {
+1
View File
@@ -5,3 +5,4 @@ mod gog_settings;
pub use gog_platform::*; pub use gog_platform::*;
pub use gog_settings::*; pub use gog_settings::*;
pub use gog_game::GogShortcut;
+2
View File
@@ -13,6 +13,8 @@ pub struct HeroicGame {
pub launch_parameters: String, pub launch_parameters: String,
} }
impl HeroicGame{ impl HeroicGame{
pub fn is_installed(&self) -> bool{ pub fn is_installed(&self) -> bool{
Path::new(&self.install_path).join(&self.executable).exists() Path::new(&self.install_path).join(&self.executable).exists()
+22
View File
@@ -0,0 +1,22 @@
use steam_shortcuts_util::shortcut::ShortcutOwned;
use crate::gog::GogShortcut;
use super::HeroicGame;
#[derive(Clone)]
pub enum HeroicGameType{
Epic(HeroicGame),
//The bool is if it is windows (true) or not (false)
Gog(GogShortcut,bool)
}
impl From<HeroicGameType> for ShortcutOwned {
fn from(heroic_game_type: HeroicGameType) -> Self {
match heroic_game_type{
HeroicGameType::Epic(epic) => epic.into(),
HeroicGameType::Gog(gog,_) => gog.into(),
}
}
}
+107 -16
View File
@@ -1,4 +1,7 @@
use super::{HeroicGame, HeroicSettings}; use serde::{Deserialize};
use super::{HeroicGame, HeroicGameType, HeroicSettings};
use crate::gog::{ get_shortcuts_from_game_folders};
use crate::platform::{Platform, SettingsValidity}; use crate::platform::{Platform, SettingsValidity};
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
@@ -15,6 +18,19 @@ enum InstallationMode {
UserBin, UserBin,
} }
#[derive(Deserialize)]
struct HeroicGogConfig {
installed: Vec<HeroicGogPath>,
}
#[derive(Deserialize)]
struct HeroicGogPath {
platform: String,
#[serde(alias = "appName")]
app_name: String,
install_path: String,
}
fn get_installed_json_location(install_mode: &InstallationMode) -> PathBuf { fn get_installed_json_location(install_mode: &InstallationMode) -> PathBuf {
let home_dir = std::env::var("HOME").unwrap_or("".to_string()); let home_dir = std::env::var("HOME").unwrap_or("".to_string());
match install_mode { match install_mode {
@@ -25,7 +41,17 @@ fn get_installed_json_location(install_mode: &InstallationMode) -> PathBuf {
.to_path_buf() .to_path_buf()
} }
fn get_gog_installed_location(install_mode: &InstallationMode) -> PathBuf {
let home_dir = std::env::var("HOME").unwrap_or("".to_string());
match install_mode {
InstallationMode::FlatPak => Path::new(&home_dir)
.join(".var/app/com.heroicgameslauncher.hgl/config/heroic/gog_store/installed.json"),
InstallationMode::UserBin => {
Path::new(&home_dir).join(".config/heroic/gog_store/installed.json")
}
}
.to_path_buf()
}
fn get_shortcuts_from_install_mode( fn get_shortcuts_from_install_mode(
install_mode: &InstallationMode, install_mode: &InstallationMode,
@@ -48,7 +74,7 @@ fn get_shortcuts_from_location<P: AsRef<Path>>(path: P) -> Result<Vec<HeroicGame
return Ok(vec![]); return Ok(vec![]);
} }
impl Platform<HeroicGame, Box<dyn Error>> for HeroicPlatform { impl Platform<HeroicGameType, Box<dyn Error>> for HeroicPlatform {
fn enabled(&self) -> bool { fn enabled(&self) -> bool {
self.settings.enabled self.settings.enabled
} }
@@ -56,19 +82,16 @@ impl Platform<HeroicGame, Box<dyn Error>> for HeroicPlatform {
fn name(&self) -> &str { fn name(&self) -> &str {
"Heroic" "Heroic"
} }
fn get_shortcuts(&self) -> Result<Vec<HeroicGame>, Box<dyn Error>> { fn get_shortcuts(&self) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
let install_modes = vec![InstallationMode::FlatPak, InstallationMode::UserBin]; let install_modes = vec![InstallationMode::FlatPak, InstallationMode::UserBin];
let mut shortcuts :Vec<HeroicGame> = install_modes
.iter()
.filter_map(|install_mode| get_shortcuts_from_install_mode(install_mode).ok())
.flatten()
.filter(|s| s.is_installed())
.collect();
shortcuts.sort_by_key(|m| format!("{}-{}-{}",m.launch_parameters,m.executable,&m.app_name)); let mut heroic_games = get_epic_games(&install_modes)?;
shortcuts.dedup_by_key(|m| format!("{}-{}-{}",m.launch_parameters,m.executable,&m.app_name)); if let Ok(gog_games) = get_gog_games(&install_modes) {
heroic_games.extend(gog_games);
Ok(shortcuts) } else {
println!("Did not find any GOG games in heroic")
}
Ok(heroic_games)
} }
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
@@ -86,7 +109,75 @@ impl Platform<HeroicGame, Box<dyn Error>> for HeroicPlatform {
} }
} }
fn needs_proton(&self, _input: &HeroicGame) -> bool { fn needs_proton(&self, input: &HeroicGameType) -> bool {
return true; match input{
HeroicGameType::Epic(_) => true,
HeroicGameType::Gog(_, is_windows) => *is_windows,
}
} }
} }
fn get_epic_games(
install_modes: &[InstallationMode],
) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
let mut shortcuts: Vec<HeroicGame> = install_modes
.iter()
.filter_map(|install_mode| get_shortcuts_from_install_mode(install_mode).ok())
.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));
let mut epic_shortcuts = vec![];
for shortcut in shortcuts {
epic_shortcuts.push(HeroicGameType::Epic(shortcut));
}
Ok(epic_shortcuts)
}
fn get_gog_games(
install_modes: &[InstallationMode],
) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
let gog_paths: Vec<HeroicGogPath> = install_modes
.iter()
.filter_map(|install_mode| {
let config = get_gog_installed_location(install_mode);
if config.exists() {
Some(config)
} else {
None
}
})
.filter_map(|config_path| std::fs::read_to_string(config_path).ok())
.filter_map(|config_string| serde_json::from_str::<HeroicGogConfig>(&config_string).ok())
.map(|config| config.installed)
.flatten()
.collect();
let mut is_windows_map = HashMap::new();
for path in gog_paths.iter() {
is_windows_map.insert(path.app_name.clone(), path.platform == "windows");
}
let game_folders = gog_paths
.iter()
.filter_map(|p| {
let path = Path::new(&p.install_path);
if path.exists() {
Some(path.to_path_buf())
} else {
None
}
})
.collect();
let shortcuts = get_shortcuts_from_game_folders(game_folders);
let mut gog_shortcuts = vec![];
for shortcut in shortcuts {
let is_windows = is_windows_map.get(&shortcut.game_id).unwrap_or(&false);
gog_shortcuts.push(HeroicGameType::Gog(shortcut, *is_windows));
}
Ok(gog_shortcuts)
}
+2 -1
View File
@@ -1,8 +1,9 @@
mod heroic_game; mod heroic_game;
mod heroic_platform; mod heroic_platform;
mod settings; mod settings;
mod heroic_game_type;
pub use heroic_game::*; pub use heroic_game::*;
pub use heroic_platform::*; pub use heroic_platform::*;
pub use settings::*; pub use settings::*;
pub use heroic_game_type::*;
+2 -2
View File
@@ -106,9 +106,9 @@ fn fix_shortcut_icons(
for shortcut in shortcuts { for shortcut in shortcuts {
#[cfg(not(target_family = "unix"))] #[cfg(not(target_family = "unix"))]
let replace_icon = shortcut.icon.trim().eq(""); let replace_icon = shortcut.icon.trim().eq("") || !Path::new(shortcut.icon.trim()).exists();
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
let replace_icon = shortcut.icon.trim().eq("") || shortcut.icon.eq(&shortcut.exe); let replace_icon = shortcut.icon.trim().eq("") || !Path::new(shortcut.icon.trim()).exists() ||shortcut.icon.eq(&shortcut.exe);
if replace_icon { if replace_icon {
let app_id = steam_shortcuts_util::app_id_generator::calculate_app_id( let app_id = steam_shortcuts_util::app_id_generator::calculate_app_id(
&shortcut.exe, &shortcut.exe,