Modular platforms (#241)

This commit is contained in:
Philip Kristoffersen
2022-10-02 21:13:31 +02:00
committed by GitHub
parent d6773c7adb
commit dcbf77b610
94 changed files with 2188 additions and 1750 deletions
+285
View File
@@ -0,0 +1,285 @@
use serde::Deserialize;
use super::{HeroicGame, HeroicGameType, HeroicSettings};
use crate::platforms::{GamesPlatform, FromSettingsString, load_settings};
use crate::platforms::{to_shortcuts, NeedsPorton, ShortcutToImport};
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
#[derive(Clone)]
pub struct HeroicPlatform {
pub settings: HeroicSettings,
pub(crate) heroic_games: Option<Vec<HeroicGameType>>,
}
#[derive(Deserialize, Debug, Clone, Copy)]
pub enum InstallationMode {
FlatPak,
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 {
let home_dir = std::env::var("HOME").unwrap_or_else(|_| "".to_string());
match install_mode {
InstallationMode::FlatPak => Path::new(&home_dir)
.join(".var/app/com.heroicgameslauncher.hgl/config/legendary/installed.json"),
InstallationMode::UserBin => Path::new(&home_dir).join(".config/legendary/installed.json"),
}
}
fn get_gog_installed_location(install_mode: &InstallationMode) -> PathBuf {
let home_dir = std::env::var("HOME").unwrap_or_else(|_| "".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")
}
}
}
fn get_shortcuts_from_install_mode(
install_mode: &InstallationMode,
) -> eyre::Result<Vec<HeroicGame>> {
let installed_path = get_installed_json_location(install_mode);
get_shortcuts_from_location(installed_path)
}
fn get_shortcuts_from_location<P: AsRef<Path>>(path: P) -> eyre::Result<Vec<HeroicGame>> {
let installed_json_path = path.as_ref();
if installed_json_path.exists() {
let json = std::fs::read_to_string(installed_json_path)?;
let games_map = serde_json::from_str::<HashMap<String, HeroicGame>>(&json)?;
let mut games = vec![];
for game in games_map.values() {
games.push(game.clone());
}
Ok(games)
} else {
Ok(vec![])
}
}
impl HeroicPlatform {
pub fn get_heroic_games(&self) -> eyre::Result<Vec<HeroicGameType>> {
let install_modes = vec![InstallationMode::FlatPak, InstallationMode::UserBin];
let mut heroic_games = self.get_epic_games(&install_modes)?;
let gog_games = get_gog_games(&self.settings, &install_modes)?;
heroic_games.extend(gog_games);
Ok(heroic_games)
}
}
impl NeedsPorton<HeroicPlatform> for HeroicGameType{
#[cfg(not(target_family = "unix"))]
fn needs_proton(&self, _platform: &HeroicPlatform) -> bool {
false
}
#[cfg(target_family = "unix")]
fn needs_proton(&self, _platform: &HeroicPlatform) -> bool {
match self {
HeroicGameType::Epic(_game) => true,
HeroicGameType::Gog(_, is_windows) => *is_windows,
HeroicGameType::Heroic { .. } => false,
}
}
fn create_symlinks(&self, _platform: &HeroicPlatform) -> bool {
false
}
}
impl HeroicPlatform {
pub fn get_epic_games(&self, install_modes: &[InstallationMode]) -> eyre::Result<Vec<HeroicGameType>> {
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)
});
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));
}
}
}
}
Ok(shortcuts)
}
}
fn get_gog_games(
settings: &HeroicSettings,
install_modes: &[InstallationMode],
) -> eyre::Result<Vec<HeroicGameType>> {
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::<HeroicGogConfig>(&config_string) {
for c in config.installed {
gog_paths.push((install_mode, c));
}
}
}
}
}
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 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(|(_, 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())
} else {
None
}
})
.collect();
let direct_shortcuts = crate::platforms::get_gog_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));
}
Ok(gog_shortcuts)
}
impl FromSettingsString for HeroicPlatform{
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
HeroicPlatform{
heroic_games: None,
settings : load_settings(s)
}
}
}
impl GamesPlatform for HeroicPlatform{
fn name(&self) -> &str {
"Heroic"
}
fn enabled(&self) -> bool {
self.settings.enabled
}
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
to_shortcuts(self, self.get_heroic_games())
}
fn render_ui(&mut self, ui: &mut egui::Ui) {
ui.heading("Heroic");
ui.checkbox(&mut self.settings.enabled, "Import from Heroic");
ui.checkbox(&mut self.settings.default_launch_through_heroic, "Always launch games through Heroic");
let safe_mode_header = match (self.settings.default_launch_through_heroic,self.settings.launch_games_through_heroic.len()) {
(false,0) => "Force games to launch through Heroic Launcher".to_string(),
(false,1) => "One game forced to launch through Heroic Launcher".to_string(),
(false,x) => format!("{} games forced to launch through Heroic Launcher", x),
(true,0) => "Force games to launch directly".to_string(),
(true,1) => "One game forced to launch directly".to_string(),
(true,x) => format!("{} games forced to launch directly", x),
};
egui::CollapsingHeader::new(safe_mode_header)
.id_source("Heroic_Launcher_safe_launch")
.show(ui, |ui| {
if
self.settings.default_launch_through_heroic{
ui.label("Some games work best when launched directly, select those games below and BoilR will create shortcuts that launch the games directly.");
} else{
ui.label("Some games must be started from the Heroic Launcher, select those games below and BoilR will create shortcuts that opens the games through the Heroic Launcher.");
}
let manifests =self.heroic_games.get_or_insert_with(||{
let heroic_setting = self.settings.clone();
let heroic_platform = HeroicPlatform{
settings:heroic_setting,
heroic_games:None
};
heroic_platform.get_heroic_games().unwrap_or_default()
});
let safe_open_games = &mut self.settings.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.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.to_string());
}else{
safe_open_games.retain(|m| m!= display_name && m!= key);
}
}
}
}) ;
}
fn get_settings_serilizable(&self) -> String {
toml::to_string(&self.settings).unwrap_or_default()
}
fn code_name(&self) -> &str {
"heroic"
}
}