mirror of
https://github.com/djibux/BoilR.git
synced 2026-09-01 05:53:41 +02:00
Modular platforms (#241)
This commit is contained in:
Generated
+153
-4
@@ -18,6 +18,15 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e"
|
checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler"
|
name = "adler"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -121,6 +130,21 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.66"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@@ -172,11 +196,14 @@ version = "1.5.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"color-eyre",
|
||||||
"config",
|
"config",
|
||||||
"copypasta",
|
"copypasta",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
|
"dyn-clone",
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
|
"eyre",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
"image",
|
"image",
|
||||||
@@ -368,6 +395,33 @@ dependencies = [
|
|||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-eyre"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"color-spantrace",
|
||||||
|
"eyre",
|
||||||
|
"indenter",
|
||||||
|
"once_cell",
|
||||||
|
"owo-colors",
|
||||||
|
"tracing-error",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-spantrace"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"owo-colors",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-error",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "color_quant"
|
name = "color_quant"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -669,6 +723,12 @@ dependencies = [
|
|||||||
"wio",
|
"wio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eframe"
|
name = "eframe"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
@@ -829,6 +889,16 @@ dependencies = [
|
|||||||
"threadpool",
|
"threadpool",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "eyre"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
|
||||||
|
dependencies = [
|
||||||
|
"indenter",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fake-simd"
|
name = "fake-simd"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -1099,6 +1169,12 @@ dependencies = [
|
|||||||
"weezl",
|
"weezl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.26.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gl_generator"
|
name = "gl_generator"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -1355,6 +1431,12 @@ dependencies = [
|
|||||||
"tiff",
|
"tiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indenter"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
@@ -1790,9 +1872,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.3.7"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
|
checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
@@ -1887,6 +1969,15 @@ dependencies = [
|
|||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
@@ -1972,6 +2063,12 @@ dependencies = [
|
|||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "owo-colors"
|
||||||
|
version = "3.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -2302,6 +2399,12 @@ dependencies = [
|
|||||||
"ordered-multimap",
|
"ordered-multimap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusty-leveldb"
|
name = "rusty-leveldb"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -2479,6 +2582,15 @@ dependencies = [
|
|||||||
"opaque-debug",
|
"opaque-debug",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shared_library"
|
name = "shared_library"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -2670,9 +2782,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.25.2"
|
version = "0.26.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1594a36887d0f70096702bffadfb67dfbfe76ad4bf84605e86157dc9fce9961a"
|
checksum = "7890fff842b8db56f2033ebee8f6efe1921475c3830c115995552914fb967580"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -2717,6 +2829,15 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "threadpool"
|
name = "threadpool"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
@@ -2888,6 +3009,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f"
|
checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-error"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
||||||
|
dependencies = [
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596"
|
||||||
|
dependencies = [
|
||||||
|
"sharded-slab",
|
||||||
|
"thread_local",
|
||||||
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2953,6 +3096,12 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68b90931029ab9b034b300b797048cf23723400aa757e8a2bfb9d748102f9821"
|
checksum = "68b90931029ab9b034b300b797048cf23723400aa757e8a2bfb9d748102f9821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|||||||
+4
-2
@@ -16,8 +16,10 @@ serde_json = "^1.0.83"
|
|||||||
sqlite = "^0.27.0"
|
sqlite = "^0.27.0"
|
||||||
steam_shortcuts_util = "^1.1.8"
|
steam_shortcuts_util = "^1.1.8"
|
||||||
steamgriddb_api = "^0.3.1"
|
steamgriddb_api = "^0.3.1"
|
||||||
sysinfo = "^0.25.2"
|
sysinfo = "^0.26.4"
|
||||||
|
eyre = "^0.6.8"
|
||||||
|
color-eyre = "^0.6"
|
||||||
|
dyn-clone = "^1.0.9"
|
||||||
|
|
||||||
[dependencies.dashmap]
|
[dependencies.dashmap]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct AmazonGame {
|
|
||||||
pub title: String,
|
|
||||||
pub id: String,
|
|
||||||
pub launcher_path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AmazonGame> for ShortcutOwned {
|
|
||||||
fn from(game: AmazonGame) -> Self {
|
|
||||||
let launch = format!("amazon-games://play/{}", game.id);
|
|
||||||
let exe = game.launcher_path.to_string_lossy().to_string();
|
|
||||||
let start_dir = game
|
|
||||||
.launcher_path
|
|
||||||
.parent()
|
|
||||||
.unwrap_or_else(|| Path::new(""))
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
Shortcut::new(
|
|
||||||
"0",
|
|
||||||
game.title.as_str(),
|
|
||||||
exe.as_str(),
|
|
||||||
start_dir.as_str(),
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
launch.as_str(),
|
|
||||||
)
|
|
||||||
.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
use std::{
|
|
||||||
error::Error,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use sqlite::State;
|
|
||||||
|
|
||||||
use crate::platform::Platform;
|
|
||||||
|
|
||||||
use super::{AmazonGame, AmazonSettings};
|
|
||||||
|
|
||||||
pub struct AmazonPlatform {
|
|
||||||
pub settings: AmazonSettings,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Platform<AmazonGame, Box<dyn Error>> for AmazonPlatform {
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn enabled(&self) -> bool {
|
|
||||||
self.settings.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn enabled(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Amazon"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<AmazonGame>, Box<dyn Error>> {
|
|
||||||
let sqllite_path =
|
|
||||||
get_sqlite_path().expect("This should never get called if settings are invalid");
|
|
||||||
let launcher_path =
|
|
||||||
get_launcher_path().expect("This should never get called if settings are invalid");
|
|
||||||
let mut result = vec![];
|
|
||||||
let connection = sqlite::open(sqllite_path)?;
|
|
||||||
let mut statement =
|
|
||||||
connection.prepare("SELECT Id, ProductTitle FROM DbSet WHERE Installed = 1")?;
|
|
||||||
while let State::Row = statement.next().unwrap() {
|
|
||||||
let id = statement.read::<String>(0);
|
|
||||||
let title = statement.read::<String>(1);
|
|
||||||
if let (Ok(id), Ok(title)) = (id, title) {
|
|
||||||
result.push(AmazonGame {
|
|
||||||
title,
|
|
||||||
id,
|
|
||||||
launcher_path: launcher_path.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
|
||||||
let path = get_sqlite_path();
|
|
||||||
let launcher = get_launcher_path();
|
|
||||||
if path.is_some() && launcher.is_some() {
|
|
||||||
crate::platform::SettingsValidity::Valid
|
|
||||||
} else {
|
|
||||||
crate::platform::SettingsValidity::Invalid {
|
|
||||||
reason: "Could not find Amazon Games installation".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn create_symlinks(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &AmazonGame) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_sqlite_path() -> Option<PathBuf> {
|
|
||||||
match std::env::var("LOCALAPPDATA") {
|
|
||||||
Ok(localdata) => {
|
|
||||||
let path = Path::new(&localdata)
|
|
||||||
.join("Amazon Games")
|
|
||||||
.join("Data")
|
|
||||||
.join("Games")
|
|
||||||
.join("Sql")
|
|
||||||
.join("GameInstallInfo.sqlite");
|
|
||||||
if path.exists() {
|
|
||||||
Some(path)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_e) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_launcher_path() -> Option<PathBuf> {
|
|
||||||
match std::env::var("LOCALAPPDATA") {
|
|
||||||
Ok(localdata) => {
|
|
||||||
let path = Path::new(&localdata)
|
|
||||||
.join("Amazon Games")
|
|
||||||
.join("App")
|
|
||||||
.join("Amazon Games.exe");
|
|
||||||
if path.exists() {
|
|
||||||
Some(path)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_e) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
|
||||||
pub struct AmazonSettings {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub launcher_location: Option<String>,
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
pub mod amazon_platform;
|
|
||||||
pub use amazon_platform::*;
|
|
||||||
|
|
||||||
pub mod amazon_game;
|
|
||||||
pub use amazon_game::*;
|
|
||||||
|
|
||||||
pub mod amazon_settings;
|
|
||||||
pub use amazon_settings::*;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
|
||||||
pub struct BottlesSettings {
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
|
||||||
+2
-2
@@ -9,7 +9,7 @@ pub fn get_config_folder() -> PathBuf {
|
|||||||
let home = std::env::var("HOME");
|
let home = std::env::var("HOME");
|
||||||
match (config_home, home) {
|
match (config_home, home) {
|
||||||
(Ok(p), _) => Path::new(&p).to_path_buf(),
|
(Ok(p), _) => Path::new(&p).to_path_buf(),
|
||||||
(Err(_), Ok(home)) => Path::new(&home).join(".config").join("boilr").to_path_buf(),
|
(Err(_), Ok(home)) => Path::new(&home).join(".config").join("boilr"),
|
||||||
_ => Path::new("").to_path_buf(),
|
_ => Path::new("").to_path_buf(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,5 +49,5 @@ pub fn get_backups_flder() -> PathBuf {
|
|||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
pub fn get_boilr_links_path() -> PathBuf {
|
pub fn get_boilr_links_path() -> PathBuf {
|
||||||
get_config_folder().join("links").to_path_buf()
|
get_config_folder().join("links")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,47 +8,6 @@ prefer_animated = false
|
|||||||
banned_images = []
|
banned_images = []
|
||||||
only_download_boilr_images = false
|
only_download_boilr_images = false
|
||||||
|
|
||||||
[origin]
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[gog]
|
|
||||||
enabled = true
|
|
||||||
create_symlinks = true
|
|
||||||
|
|
||||||
[epic_games]
|
|
||||||
enabled = true
|
|
||||||
safe_launch = []
|
|
||||||
|
|
||||||
[uplay]
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[legendary]
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[itch]
|
|
||||||
enabled = true
|
|
||||||
create_symlinks = true
|
|
||||||
|
|
||||||
[lutris]
|
|
||||||
enabled = false
|
|
||||||
executable = "lutris"
|
|
||||||
flatpak_image = "net.lutris.Lutris//beta"
|
|
||||||
flatpak = false
|
|
||||||
|
|
||||||
[heroic]
|
|
||||||
enabled = true
|
|
||||||
launch_games_through_heroic =[]
|
|
||||||
default_launch_through_heroic = true
|
|
||||||
|
|
||||||
[amazon]
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[flatpak]
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[bottles]
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[steam]
|
[steam]
|
||||||
create_collections = false
|
create_collections = false
|
||||||
optimize_for_big_picture = false
|
optimize_for_big_picture = false
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
use crate::platform::{Platform, SettingsValidity};
|
|
||||||
|
|
||||||
use super::{get_egs_manifests, EpicGamesLauncherSettings, ManifestItem};
|
|
||||||
|
|
||||||
pub struct EpicPlatform {
|
|
||||||
settings: EpicGamesLauncherSettings,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EpicPlatform {
|
|
||||||
pub fn new(settings: &EpicGamesLauncherSettings) -> Self {
|
|
||||||
EpicPlatform {
|
|
||||||
settings: settings.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Platform<ManifestItem, String> for EpicPlatform {
|
|
||||||
fn enabled(&self) -> bool {
|
|
||||||
self.settings.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"EGS"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<ManifestItem>, String> {
|
|
||||||
get_egs_manifests(&self.settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn create_symlinks(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn settings_valid(&self) -> SettingsValidity {
|
|
||||||
let shortcuts_res = self.get_shortcuts();
|
|
||||||
match shortcuts_res {
|
|
||||||
Ok(_) => SettingsValidity::Valid,
|
|
||||||
Err(err) => SettingsValidity::Invalid {
|
|
||||||
reason: format!("{}", err),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &ManifestItem) -> bool {
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
return true;
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
mod platform;
|
|
||||||
mod settings;
|
|
||||||
|
|
||||||
pub use platform::*;
|
|
||||||
pub use settings::*;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
|
||||||
pub struct FlatpakSettings {
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct GogSettings {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub location: Option<String>,
|
|
||||||
pub wine_c_drive: Option<String>,
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
pub create_symlinks: bool,
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
mod gog_config;
|
|
||||||
mod gog_game;
|
|
||||||
mod gog_platform;
|
|
||||||
mod gog_settings;
|
|
||||||
|
|
||||||
pub use gog_game::GogShortcut;
|
|
||||||
pub use gog_platform::*;
|
|
||||||
pub use gog_settings::*;
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use super::{HeroicGame, HeroicGameType, HeroicSettings};
|
|
||||||
use crate::gog::get_shortcuts_from_game_folders;
|
|
||||||
use crate::platform::{Platform, SettingsValidity};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
pub struct HeroicPlatform {
|
|
||||||
pub settings: HeroicSettings,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
) -> Result<Vec<HeroicGame>, Box<dyn Error>> {
|
|
||||||
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) -> Result<Vec<HeroicGame>, Box<dyn Error>> {
|
|
||||||
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) -> Vec<HeroicGameType> {
|
|
||||||
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<HeroicGameType, Box<dyn Error>> for HeroicPlatform {
|
|
||||||
fn enabled(&self) -> bool {
|
|
||||||
self.settings.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Heroic"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
|
|
||||||
Ok(self.get_heroic_games())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn create_symlinks(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
|
||||||
let shortcuts_res = self.get_shortcuts();
|
|
||||||
match shortcuts_res {
|
|
||||||
Ok(_) => SettingsValidity::Valid,
|
|
||||||
Err(err) => SettingsValidity::Invalid {
|
|
||||||
reason: format!("{}", err),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_proton(&self, input: &HeroicGameType) -> bool {
|
|
||||||
match input {
|
|
||||||
HeroicGameType::Epic(_game) => true,
|
|
||||||
HeroicGameType::Gog(_, is_windows) => *is_windows,
|
|
||||||
HeroicGameType::Heroic { .. } => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HeroicPlatform {
|
|
||||||
pub fn get_epic_games(&self, install_modes: &[InstallationMode]) -> 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shortcuts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn get_gog_games(
|
|
||||||
settings: &HeroicSettings,
|
|
||||||
install_modes: &[InstallationMode],
|
|
||||||
) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
|
|
||||||
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 = 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(gog_shortcuts)
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct ItchSettings {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub location: Option<String>,
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
pub create_symlinks: bool,
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
use super::{LegendaryGame, LegendarySettings};
|
|
||||||
use crate::platform::{Platform, SettingsValidity};
|
|
||||||
use serde_json::from_str;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
pub struct LegendaryPlatform {
|
|
||||||
settings: LegendarySettings,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LegendaryPlatform {
|
|
||||||
pub fn new(settings: LegendarySettings) -> LegendaryPlatform {
|
|
||||||
Self { settings }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Platform<LegendaryGame, Box<dyn Error>> for LegendaryPlatform {
|
|
||||||
fn enabled(&self) -> bool {
|
|
||||||
self.settings.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Legendary"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<LegendaryGame>, Box<dyn Error>> {
|
|
||||||
let legendary_string = self
|
|
||||||
.settings
|
|
||||||
.executable
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| "legendary".to_string());
|
|
||||||
let legendary = legendary_string.as_str();
|
|
||||||
execute_legendary_command(legendary)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn create_symlinks(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
|
||||||
let shortcuts_res = self.get_shortcuts();
|
|
||||||
match shortcuts_res {
|
|
||||||
Ok(_) => SettingsValidity::Valid,
|
|
||||||
Err(err) => SettingsValidity::Invalid {
|
|
||||||
reason: format!("{}", err),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &LegendaryGame) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_legendary_command(program: &str) -> Result<Vec<LegendaryGame>, Box<dyn Error>> {
|
|
||||||
let legendary_command = Command::new(program)
|
|
||||||
.arg("list-installed")
|
|
||||||
.arg("--json")
|
|
||||||
.output()?;
|
|
||||||
let json = String::from_utf8_lossy(&legendary_command.stdout);
|
|
||||||
let legendary_ouput = from_str(&json)?;
|
|
||||||
Ok(legendary_ouput)
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
mod legendary_game;
|
|
||||||
mod legendary_platform;
|
|
||||||
mod settings;
|
|
||||||
|
|
||||||
pub use legendary_game::*;
|
|
||||||
pub use legendary_platform::*;
|
|
||||||
pub use settings::*;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
|
||||||
pub struct LegendarySettings {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub executable: Option<String>,
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
use super::game_list_parser::parse_lutris_games;
|
|
||||||
use super::lutris_game::LutrisGame;
|
|
||||||
use super::settings::LutrisSettings;
|
|
||||||
use crate::platform::{Platform, SettingsValidity};
|
|
||||||
use std::error::Error;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
pub struct LutrisPlatform {
|
|
||||||
pub settings: LutrisSettings,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Platform<LutrisGame, Box<dyn Error>> for LutrisPlatform {
|
|
||||||
fn enabled(&self) -> bool {
|
|
||||||
self.settings.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Lutris"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<LutrisGame>, Box<dyn Error>> {
|
|
||||||
let output = get_lutris_command_output(&self.settings)?;
|
|
||||||
let games = parse_lutris_games(output.as_str());
|
|
||||||
let mut res = vec![];
|
|
||||||
for mut game in games {
|
|
||||||
if game.runner != "steam" {
|
|
||||||
game.settings = Some(self.settings.clone());
|
|
||||||
res.push(game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn create_symlinks(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
|
||||||
let shortcuts_res = self.get_shortcuts();
|
|
||||||
match shortcuts_res {
|
|
||||||
Ok(_) => SettingsValidity::Valid,
|
|
||||||
Err(err) => SettingsValidity::Invalid {
|
|
||||||
reason: format!("{}", err),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &LutrisGame) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_lutris_command_output(settings: &LutrisSettings) -> Result<String, Box<dyn Error>> {
|
|
||||||
let output = if settings.flatpak {
|
|
||||||
let flatpak_image = &settings.flatpak_image;
|
|
||||||
#[cfg(not(feature = "flatpak"))]{
|
|
||||||
let mut command = Command::new("flatpak");
|
|
||||||
command.arg("run").arg(flatpak_image).arg("-lo").arg("--json").output()?
|
|
||||||
}
|
|
||||||
#[cfg(feature = "flatpak")]
|
|
||||||
{
|
|
||||||
let mut command = Command::new("flatpak-spawn");
|
|
||||||
command.arg("--host").arg("flatpak").arg("run").arg(flatpak_image).arg("-lo").arg("--json").output()?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut command = Command::new(&settings.executable);
|
|
||||||
command.arg("-lo").arg("--json").output()?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
pub mod game_list_parser;
|
|
||||||
pub mod lutris_game;
|
|
||||||
pub mod lutris_platform;
|
|
||||||
pub mod settings;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
|
||||||
pub struct LutrisSettings {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub executable: String,
|
|
||||||
pub flatpak: bool,
|
|
||||||
pub flatpak_image: String,
|
|
||||||
}
|
|
||||||
+6
-14
@@ -1,25 +1,16 @@
|
|||||||
mod amazon;
|
|
||||||
mod config;
|
mod config;
|
||||||
mod egs;
|
|
||||||
mod flatpak;
|
|
||||||
mod gog;
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
mod heroic;
|
|
||||||
mod itch;
|
|
||||||
mod legendary;
|
|
||||||
mod lutris;
|
|
||||||
mod migration;
|
mod migration;
|
||||||
mod origin;
|
mod platforms;
|
||||||
mod platform;
|
|
||||||
mod settings;
|
mod settings;
|
||||||
mod steam;
|
mod steam;
|
||||||
mod steamgriddb;
|
mod steamgriddb;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod uplay;
|
|
||||||
mod bottles;
|
|
||||||
|
|
||||||
fn main() {
|
use color_eyre::eyre::Result;
|
||||||
|
|
||||||
|
fn main() -> Result<()>{
|
||||||
|
color_eyre::install()?;
|
||||||
ensure_config_folder();
|
ensure_config_folder();
|
||||||
migration::migrate_config();
|
migration::migrate_config();
|
||||||
|
|
||||||
@@ -29,6 +20,7 @@ fn main() {
|
|||||||
} else {
|
} else {
|
||||||
ui::run_ui(args);
|
ui::run_ui(args);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_config_folder() {
|
fn ensure_config_folder() {
|
||||||
|
|||||||
+4
-1
@@ -1,5 +1,7 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::{settings::save_settings, platforms::get_platforms};
|
||||||
|
|
||||||
pub fn migrate_config() {
|
pub fn migrate_config() {
|
||||||
let version = &crate::settings::Settings::new()
|
let version = &crate::settings::Settings::new()
|
||||||
.map(|s| s.config_version)
|
.map(|s| s.config_version)
|
||||||
@@ -35,7 +37,8 @@ pub fn migrate_config() {
|
|||||||
if save_version {
|
if save_version {
|
||||||
if let Ok(mut settings) = crate::settings::Settings::new() {
|
if let Ok(mut settings) = crate::settings::Settings::new() {
|
||||||
settings.config_version = Some(1);
|
settings.config_version = Some(1);
|
||||||
crate::ui::MyEguiApp::save_settings_to_file(&settings);
|
let platforms = get_platforms();
|
||||||
|
save_settings(&settings, &platforms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
mod origin_game;
|
|
||||||
mod origin_platform;
|
|
||||||
mod settings;
|
|
||||||
|
|
||||||
pub use origin_platform::*;
|
|
||||||
pub use settings::*;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
|
||||||
|
|
||||||
pub struct OriginSettings {
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
|
||||||
|
|
||||||
pub trait Platform<T, E>
|
|
||||||
where
|
|
||||||
T: Into<ShortcutOwned>,
|
|
||||||
{
|
|
||||||
fn enabled(&self) -> bool;
|
|
||||||
|
|
||||||
fn name(&self) -> &str;
|
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<T>, E>;
|
|
||||||
|
|
||||||
fn settings_valid(&self) -> SettingsValidity;
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn create_symlinks(&self) -> bool;
|
|
||||||
|
|
||||||
// HOME/.local/share/Steam/config/config.vdf
|
|
||||||
fn needs_proton(&self, input: &T) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum SettingsValidity {
|
|
||||||
Valid,
|
|
||||||
Invalid { reason: String },
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlite::State;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
||||||
|
|
||||||
|
use crate::platforms::{to_shortcuts_simple, ShortcutToImport, FromSettingsString, load_settings, GamesPlatform};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AmazonPlatform {
|
||||||
|
pub settings: AmazonSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl FromSettingsString for AmazonPlatform{
|
||||||
|
fn from_settings_string<S:AsRef<str>>(s:S) -> Self {
|
||||||
|
AmazonPlatform { settings: load_settings(s) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_sqlite_path() -> eyre::Result<PathBuf> {
|
||||||
|
let localdata = std::env::var("LOCALAPPDATA")?;
|
||||||
|
let path = Path::new(&localdata)
|
||||||
|
.join("Amazon Games")
|
||||||
|
.join("Data")
|
||||||
|
.join("Games")
|
||||||
|
.join("Sql")
|
||||||
|
.join("GameInstallInfo.sqlite");
|
||||||
|
if path.exists() {
|
||||||
|
Ok(path)
|
||||||
|
} else {
|
||||||
|
Err(eyre::format_err!(
|
||||||
|
"Amazong GameInstallInfo.sqlite not found at {:?}",
|
||||||
|
path
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_launcher_path() -> eyre::Result<PathBuf> {
|
||||||
|
let localdata = std::env::var("LOCALAPPDATA")?;
|
||||||
|
let path = Path::new(&localdata)
|
||||||
|
.join("Amazon Games")
|
||||||
|
.join("App")
|
||||||
|
.join("Amazon Games.exe");
|
||||||
|
if path.exists() {
|
||||||
|
Ok(path)
|
||||||
|
} else {
|
||||||
|
Err(eyre::format_err!(
|
||||||
|
"Could not find Amazon Games.exe at {:?}",
|
||||||
|
path
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GamesPlatform for AmazonPlatform{
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Amazon"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
|
to_shortcuts_simple(self.get_amazon_games())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Amazon");
|
||||||
|
ui.checkbox(&mut self.settings.enabled, "Import from Amazon");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"amazon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl AmazonPlatform {
|
||||||
|
|
||||||
|
fn get_amazon_games(&self) -> eyre::Result<Vec<AmazonGame>> {
|
||||||
|
let sqllite_path = get_sqlite_path()?;
|
||||||
|
let launcher_path = get_launcher_path()?;
|
||||||
|
let mut result = vec![];
|
||||||
|
let connection = sqlite::open(sqllite_path)?;
|
||||||
|
let mut statement =
|
||||||
|
connection.prepare("SELECT Id, ProductTitle FROM DbSet WHERE Installed = 1")?;
|
||||||
|
while let State::Row = statement.next().unwrap() {
|
||||||
|
let id = statement.read::<String>(0);
|
||||||
|
let title = statement.read::<String>(1);
|
||||||
|
if let (Ok(id), Ok(title)) = (id, title) {
|
||||||
|
result.push(AmazonGame {
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
launcher_path: launcher_path.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AmazonGame {
|
||||||
|
pub title: String,
|
||||||
|
pub id: String,
|
||||||
|
pub launcher_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AmazonGame> for ShortcutOwned {
|
||||||
|
fn from(game: AmazonGame) -> Self {
|
||||||
|
let launch = format!("amazon-games://play/{}", game.id);
|
||||||
|
let exe = game.launcher_path.to_string_lossy().to_string();
|
||||||
|
let start_dir = game
|
||||||
|
.launcher_path
|
||||||
|
.parent()
|
||||||
|
.unwrap_or_else(|| Path::new(""))
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
Shortcut::new(
|
||||||
|
"0",
|
||||||
|
game.title.as_str(),
|
||||||
|
exe.as_str(),
|
||||||
|
start_dir.as_str(),
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
launch.as_str(),
|
||||||
|
)
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct AmazonSettings {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub launcher_location: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AmazonSettings{
|
||||||
|
fn default() -> Self {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
let enabled = false;
|
||||||
|
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
let enabled = true;
|
||||||
|
|
||||||
|
Self { enabled, launcher_location: Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
mod amazon_platform;
|
||||||
|
|
||||||
|
pub use amazon_platform::AmazonPlatform;
|
||||||
|
pub use amazon_platform::AmazonGame;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mod platform;
|
||||||
|
|
||||||
|
pub use platform::BottlesPlatform;
|
||||||
@@ -1,24 +1,46 @@
|
|||||||
use std::{error::Error, process::Command};
|
use std::process::Command;
|
||||||
|
|
||||||
use eframe::epaint::ahash::HashMap;
|
use eframe::epaint::ahash::HashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::platform::Platform;
|
|
||||||
|
|
||||||
use super::BottlesSettings;
|
|
||||||
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
||||||
|
|
||||||
|
use crate::platforms::{to_shortcuts_simple, ShortcutToImport, FromSettingsString, load_settings, GamesPlatform};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct BottlesPlatform {
|
pub struct BottlesPlatform {
|
||||||
pub settings: BottlesSettings,
|
pub settings: BottlesSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromSettingsString for BottlesPlatform{
|
||||||
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
|
BottlesPlatform { settings: load_settings(s) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BottlesApp {
|
pub struct BottlesApp {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub bottle: String,
|
pub bottle: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct BottlesSettings {
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BottlesSettings{
|
||||||
|
fn default() -> Self {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
let enabled = true;
|
||||||
|
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
let enabled = false;
|
||||||
|
|
||||||
|
Self { enabled }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<BottlesApp> for ShortcutOwned {
|
impl From<BottlesApp> for ShortcutOwned {
|
||||||
fn from(app: BottlesApp) -> Self {
|
fn from(app: BottlesApp) -> Self {
|
||||||
//
|
//
|
||||||
@@ -30,65 +52,17 @@ impl From<BottlesApp> for ShortcutOwned {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform<BottlesApp, Box<dyn Error>> for BottlesPlatform {
|
fn get_bottles() -> eyre::Result<Vec<Bottle>> {
|
||||||
fn enabled(&self) -> bool {
|
let json = get_bottles_output()?;
|
||||||
self.settings.enabled
|
let map: HashMap<String, Bottle> = serde_json::from_str(json.as_str())?;
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Bottles"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<BottlesApp>, Box<dyn Error>> {
|
|
||||||
let mut res = vec![];
|
|
||||||
let bottles = get_bottles();
|
|
||||||
dbg!(&bottles);
|
|
||||||
for bottle in bottles {
|
|
||||||
for (_id,program) in bottle.external_programs {
|
|
||||||
res.push(BottlesApp{
|
|
||||||
name:program.name,
|
|
||||||
bottle: bottle.name.clone()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
|
||||||
if let Ok(s) =self.get_shortcuts(){
|
|
||||||
if !s.is_empty() {
|
|
||||||
return crate::platform::SettingsValidity::Valid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
crate::platform::SettingsValidity::Invalid { reason: String::from("Nothing found")}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn create_symlinks(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &BottlesApp) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_bottles() -> Vec<Bottle> {
|
|
||||||
let output_result = get_bottles_output();
|
|
||||||
match output_result {
|
|
||||||
Ok(json) => {
|
|
||||||
let map : HashMap<String,Bottle>= serde_json::from_str(json.as_str()).unwrap_or_default();
|
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
for (_, value) in map {
|
for (_, value) in map {
|
||||||
res.push(value);
|
res.push(value);
|
||||||
}
|
}
|
||||||
res
|
Ok(res)
|
||||||
},
|
|
||||||
Err(_err) => vec![],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bottles_output() -> Result<String, Box<dyn Error>> {
|
fn get_bottles_output() -> eyre::Result<String> {
|
||||||
let output = {
|
let output = {
|
||||||
#[cfg(not(feature = "flatpak"))]
|
#[cfg(not(feature = "flatpak"))]
|
||||||
{
|
{
|
||||||
@@ -134,3 +108,46 @@ struct Program {
|
|||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BottlesPlatform {
|
||||||
|
fn get_botttles(&self) -> eyre::Result<Vec<BottlesApp>> {
|
||||||
|
let mut res = vec![];
|
||||||
|
let bottles = get_bottles()?;
|
||||||
|
for bottle in bottles {
|
||||||
|
for (_id, program) in bottle.external_programs {
|
||||||
|
res.push(BottlesApp {
|
||||||
|
name: program.name,
|
||||||
|
bottle: bottle.name.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl GamesPlatform for BottlesPlatform{
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Bottles"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
|
to_shortcuts_simple(self.get_botttles())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Bottles");
|
||||||
|
ui.checkbox(&mut self.settings.enabled, "Import from Bottles");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"bottles"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
use crate::platforms::{
|
||||||
|
load_settings, to_shortcuts, FromSettingsString, NeedsPorton, ShortcutToImport, GamesPlatform,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{get_egs_manifests, settings::EpicGamesLauncherSettings, ManifestItem};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EpicPlatform {
|
||||||
|
pub(crate) settings: EpicGamesLauncherSettings,
|
||||||
|
pub(crate) epic_manifests: Option<Vec<ManifestItem>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSettingsString for EpicPlatform {
|
||||||
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
|
EpicPlatform {
|
||||||
|
settings: load_settings(s),
|
||||||
|
epic_manifests: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NeedsPorton<EpicPlatform> for ManifestItem {
|
||||||
|
fn needs_proton(&self, _platform: &EpicPlatform) -> bool {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
return true;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_symlinks(&self, _platform: &EpicPlatform) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GamesPlatform for EpicPlatform{
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Epic"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"epic_games"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
|
to_shortcuts(self, get_egs_manifests(&self.settings))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
self.render_epic_settings(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use super::{get_manifests::get_egs_manifests, EpicPlatform};
|
||||||
|
|
||||||
|
impl EpicPlatform {
|
||||||
|
pub fn render_epic_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
let epic_settings = &mut self.settings;
|
||||||
|
ui.heading("Epic Games");
|
||||||
|
ui.checkbox(&mut epic_settings.enabled, "Import from Epic Games");
|
||||||
|
if epic_settings.enabled {
|
||||||
|
let safe_mode_header = match epic_settings.safe_launch.len() {
|
||||||
|
0 => "Force games to launch through Epic Launcher".to_string(),
|
||||||
|
1 => "One game forced to launch through Epic Launcher".to_string(),
|
||||||
|
x => format!("{} games forced to launch through Epic Launcher", x),
|
||||||
|
};
|
||||||
|
|
||||||
|
egui::CollapsingHeader::new(safe_mode_header)
|
||||||
|
.id_source("Epic_Launcher_safe_launch")
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.label("Some games must be started from the Epic Launcher, select those games below and BoilR will create shortcuts that opens the games through the Epic Launcher.");
|
||||||
|
let manifests =self.epic_manifests.get_or_insert_with(||{
|
||||||
|
let manifests = get_egs_manifests(epic_settings);
|
||||||
|
manifests.unwrap_or_default()
|
||||||
|
});
|
||||||
|
let mut safe_open_games = epic_settings.safe_launch.clone();
|
||||||
|
for manifest in manifests{
|
||||||
|
let key = manifest.get_key();
|
||||||
|
let display_name = &manifest.display_name;
|
||||||
|
let mut safe_open = safe_open_games.contains(display_name) || safe_open_games.contains(&key);
|
||||||
|
if ui.checkbox(&mut safe_open, display_name).clicked(){
|
||||||
|
if safe_open{
|
||||||
|
safe_open_games.push(key);
|
||||||
|
}else{
|
||||||
|
safe_open_games.retain(|m| m!= display_name && m!= &key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
epic_settings.safe_launch = safe_open_games;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::{EpicGamesLauncherSettings, ManifestItem};
|
use super::settings::EpicGamesLauncherSettings;
|
||||||
|
use super::ManifestItem;
|
||||||
|
|
||||||
use std::fs::{DirEntry, File};
|
use std::fs::{DirEntry, File};
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
@@ -6,8 +7,8 @@ use std::path::Path;
|
|||||||
|
|
||||||
pub(crate) fn get_egs_manifests(
|
pub(crate) fn get_egs_manifests(
|
||||||
settings: &EpicGamesLauncherSettings,
|
settings: &EpicGamesLauncherSettings,
|
||||||
) -> Result<Vec<ManifestItem>, String> {
|
) -> eyre::Result<Vec<ManifestItem>> {
|
||||||
let locations = crate::egs::get_locations();
|
let locations = super::get_locations();
|
||||||
match locations {
|
match locations {
|
||||||
Some(locations) => {
|
Some(locations) => {
|
||||||
let manifest_dir_path = locations.manifest_folder_path;
|
let manifest_dir_path = locations.manifest_folder_path;
|
||||||
@@ -15,14 +16,12 @@ pub(crate) fn get_egs_manifests(
|
|||||||
|
|
||||||
match manifest_dir_result {
|
match manifest_dir_result {
|
||||||
Ok(manifest_dir) => {
|
Ok(manifest_dir) => {
|
||||||
let all_manifests = manifest_dir
|
let mut all_manifests = manifest_dir
|
||||||
.filter_map(|dir| dir.ok())
|
.filter_map(|dir| dir.ok())
|
||||||
.filter_map(get_manifest_item);
|
.filter_map(get_manifest_item);
|
||||||
let mut manifests = vec![];
|
|
||||||
for mut manifest in all_manifests {
|
|
||||||
manifest.manifest_location = manifest.manifest_location;
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
{
|
{
|
||||||
|
for mut manifest in &mut all_manifests {
|
||||||
if let Some(compat_folder) = locations.compat_folder_path.as_ref() {
|
if let Some(compat_folder) = locations.compat_folder_path.as_ref() {
|
||||||
//Strip off the c:\\
|
//Strip off the c:\\
|
||||||
manifest.manifest_location = compat_folder
|
manifest.manifest_location = compat_folder
|
||||||
@@ -42,23 +41,14 @@ pub(crate) fn get_egs_manifests(
|
|||||||
.to_string();
|
.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if is_game_installed(&manifest) && is_game_launchable(&manifest) {
|
|
||||||
manifests.push(manifest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let mut manifests :Vec<ManifestItem> = all_manifests
|
||||||
|
.filter(is_game_installed)
|
||||||
|
.filter(is_game_launchable)
|
||||||
|
.collect();
|
||||||
|
|
||||||
manifests.sort_by_key(|m| {
|
manifests.sort_by_key(|m| m.dedupe_key());
|
||||||
format!(
|
manifests.dedup_by_key(|m| m.dedupe_key());
|
||||||
"{}-{}-{}",
|
|
||||||
m.install_location, m.launch_executable, m.is_managed
|
|
||||||
)
|
|
||||||
});
|
|
||||||
manifests.dedup_by_key(|m| {
|
|
||||||
format!(
|
|
||||||
"{}-{}-{}",
|
|
||||||
m.install_location, m.launch_executable, m.is_managed
|
|
||||||
)
|
|
||||||
});
|
|
||||||
for mut manifest in &mut manifests {
|
for mut manifest in &mut manifests {
|
||||||
manifest.launcher_path = Some(locations.launcher_path.clone());
|
manifest.launcher_path = Some(locations.launcher_path.clone());
|
||||||
manifest.compat_folder = locations.compat_folder_path.clone();
|
manifest.compat_folder = locations.compat_folder_path.clone();
|
||||||
@@ -70,16 +60,18 @@ pub(crate) fn get_egs_manifests(
|
|||||||
}
|
}
|
||||||
Ok(manifests)
|
Ok(manifests)
|
||||||
}
|
}
|
||||||
Err(err) => Err(format!(
|
Err(err) => Err(eyre::format_err!(
|
||||||
"Could not read dir at: {:?} error: {:?}",
|
"Could not read dir at: {:?} error: {:?}",
|
||||||
manifest_dir_path, err
|
manifest_dir_path,
|
||||||
|
err
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => Err("Manifests not found".to_string()),
|
None => Err(eyre::format_err!("Manifests not found")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn is_game_installed(manifest: &ManifestItem) -> bool {
|
fn is_game_installed(manifest: &ManifestItem) -> bool {
|
||||||
Path::new(manifest.manifest_location.as_str()).exists()
|
Path::new(manifest.manifest_location.as_str()).exists()
|
||||||
}
|
}
|
||||||
@@ -124,6 +124,7 @@ fn launcher_shortcut(manifest: ManifestItem) -> ShortcutOwned {
|
|||||||
.to_owned()
|
.to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl From<ManifestItem> for ShortcutOwned {
|
impl From<ManifestItem> for ShortcutOwned {
|
||||||
fn from(manifest: ManifestItem) -> Self {
|
fn from(manifest: ManifestItem) -> Self {
|
||||||
let mut owned_shortcut = if manifest.needs_launcher() {
|
let mut owned_shortcut = if manifest.needs_launcher() {
|
||||||
@@ -163,6 +164,13 @@ impl ManifestItem {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dedupe_key(&self)-> String {
|
||||||
|
format!(
|
||||||
|
"{}-{}-{}",
|
||||||
|
self.install_location, self.launch_executable, self.is_managed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn needs_launcher(&self) -> bool {
|
fn needs_launcher(&self) -> bool {
|
||||||
if self.safe_launch {
|
if self.safe_launch {
|
||||||
return true;
|
return true;
|
||||||
@@ -3,9 +3,10 @@ mod get_manifests;
|
|||||||
mod manifest_item;
|
mod manifest_item;
|
||||||
mod paths;
|
mod paths;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
mod epic_ui;
|
||||||
|
|
||||||
pub use epic_platform::*;
|
pub use epic_platform::*;
|
||||||
use get_manifests::get_egs_manifests;
|
use get_manifests::get_egs_manifests;
|
||||||
pub(crate) use manifest_item::*;
|
pub(crate) use manifest_item::*;
|
||||||
pub use paths::*;
|
use paths::*;
|
||||||
pub use settings::EpicGamesLauncherSettings;
|
|
||||||
@@ -5,3 +5,12 @@ pub struct EpicGamesLauncherSettings {
|
|||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub safe_launch: Vec<String>,
|
pub safe_launch: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for EpicGamesLauncherSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: true,
|
||||||
|
safe_launch: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,4 +2,4 @@ mod platform;
|
|||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
pub use platform::*;
|
pub use platform::*;
|
||||||
pub use settings::*;
|
pub(crate) use settings::*;
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::platform::{Platform, SettingsValidity};
|
use crate::platforms::{NeedsPorton, to_shortcuts, ShortcutToImport, GamesPlatform, FromSettingsString, load_settings};
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use super::FlatpakSettings;
|
use super::FlatpakSettings;
|
||||||
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
||||||
@@ -24,16 +23,19 @@ impl From<FlatpakApp> for ShortcutOwned {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform<FlatpakApp, Box<dyn Error>> for FlatpakPlatform {
|
impl NeedsPorton<FlatpakPlatform> for FlatpakApp{
|
||||||
fn enabled(&self) -> bool {
|
fn needs_proton(&self, _platform: &FlatpakPlatform) -> bool {
|
||||||
self.settings.enabled
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
fn create_symlinks(&self, _platform: &FlatpakPlatform) -> bool {
|
||||||
"Flatpak"
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<FlatpakApp>, Box<dyn Error>> {
|
impl FlatpakPlatform {
|
||||||
|
|
||||||
|
fn get_flatpak_apps(&self) -> eyre::Result<Vec<FlatpakApp>> {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
let mut command = Command::new("flatpak");
|
let mut command = Command::new("flatpak");
|
||||||
let output = command
|
let output = command
|
||||||
@@ -56,23 +58,41 @@ impl Platform<FlatpakApp, Box<dyn Error>> for FlatpakPlatform {
|
|||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
|
||||||
let shortcuts_res = self.get_shortcuts();
|
impl FromSettingsString for FlatpakPlatform{
|
||||||
match shortcuts_res {
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
Ok(_) => SettingsValidity::Valid,
|
FlatpakPlatform {
|
||||||
Err(err) => SettingsValidity::Invalid {
|
settings: load_settings(s),
|
||||||
reason: format!("{}", err),
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
impl GamesPlatform for FlatpakPlatform{
|
||||||
fn create_symlinks(&self) -> bool {
|
fn name(&self) -> &str {
|
||||||
false
|
"Flatpak"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &FlatpakApp) -> bool {
|
|
||||||
false
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
|
to_shortcuts(self, self.get_flatpak_apps())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Flatpak");
|
||||||
|
ui.checkbox(&mut self.settings.enabled, "Import from Flatpak");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"flatpak"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct FlatpakSettings {
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FlatpakSettings{
|
||||||
|
fn default() -> Self {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
let enabled = true;
|
||||||
|
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
let enabled = false;
|
||||||
|
|
||||||
|
Self { enabled }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,44 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::{gog::gog_config::GogConfig, platform::Platform};
|
use crate::platforms::{to_shortcuts, ShortcutToImport, NeedsPorton, GamesPlatform, FromSettingsString, load_settings};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
gog_config::GogConfig,
|
||||||
gog_game::{GogGame, GogShortcut},
|
gog_game::{GogGame, GogShortcut},
|
||||||
GogSettings,
|
GogSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct GogPlatform {
|
pub struct GogPlatform {
|
||||||
pub settings: GogSettings,
|
pub settings: GogSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_shortcuts_from_config(
|
impl GogPlatform {
|
||||||
|
fn get_shortcuts(&self) -> eyre::Result<Vec<GogShortcut>> {
|
||||||
|
let gog_location = self
|
||||||
|
.settings
|
||||||
|
.location
|
||||||
|
.as_ref()
|
||||||
|
.map(|location| Path::new(&location).to_path_buf())
|
||||||
|
.unwrap_or_else(default_location);
|
||||||
|
if !gog_location.exists() {
|
||||||
|
return Err(eyre::format_err!("Could not find path: {:?}", gog_location));
|
||||||
|
}
|
||||||
|
let config_path = gog_location.join("config.json");
|
||||||
|
if !config_path.exists() {
|
||||||
|
return Err(eyre::format_err!(
|
||||||
|
"Config file not found: {:?}",
|
||||||
|
config_path
|
||||||
|
));
|
||||||
|
}
|
||||||
|
get_shortcuts_from_config(self.settings.wine_c_drive.clone(), config_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcuts_from_config(
|
||||||
_wine_c_drive: Option<String>,
|
_wine_c_drive: Option<String>,
|
||||||
config_path: PathBuf,
|
config_path: PathBuf,
|
||||||
) -> Result<Vec<GogShortcut>, String> {
|
) -> eyre::Result<Vec<GogShortcut>> {
|
||||||
let install_locations = get_install_locations(config_path)?;
|
let install_locations = get_install_locations(config_path)?;
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
let install_locations = if let Some(wine_c_drive) = &_wine_c_drive {
|
let install_locations = if let Some(wine_c_drive) = &_wine_c_drive {
|
||||||
@@ -38,11 +62,11 @@ pub fn get_shortcuts_from_config(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let shortcuts = get_shortcuts_from_game_folders(game_folders);
|
let shortcuts = get_gog_shortcuts_from_game_folders(game_folders);
|
||||||
Ok(shortcuts)
|
Ok(shortcuts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_shortcuts_from_game_folders(game_folders: Vec<PathBuf>) -> Vec<GogShortcut> {
|
pub fn get_gog_shortcuts_from_game_folders(game_folders: Vec<PathBuf>) -> Vec<GogShortcut> {
|
||||||
let games = get_games_from_game_folders(game_folders);
|
let games = get_games_from_game_folders(game_folders);
|
||||||
|
|
||||||
get_shortcuts_from_games(games)
|
get_shortcuts_from_games(games)
|
||||||
@@ -137,53 +161,25 @@ fn get_games_from_game_folders(game_folders: Vec<PathBuf>) -> Vec<(GogGame, Path
|
|||||||
games
|
games
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform<GogShortcut, String> for GogPlatform {
|
impl NeedsPorton<GogPlatform> for GogShortcut{
|
||||||
fn enabled(&self) -> bool {
|
#[cfg(target_family = "unix")]
|
||||||
self.settings.enabled
|
fn needs_proton(&self, _platform: &GogPlatform) -> bool {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
#[cfg(not(target_family = "unix"))]
|
||||||
"Gog"
|
fn needs_proton(&self, _platform: &GogPlatform) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
fn create_symlinks(&self, _platform: &GogPlatform) -> bool {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
fn create_symlinks(&self) -> bool {
|
fn create_symlinks(&self, platform: &GogPlatform) -> bool {
|
||||||
self.settings.create_symlinks
|
platform.settings.create_symlinks
|
||||||
}
|
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<GogShortcut>, String> {
|
|
||||||
let gog_location = self
|
|
||||||
.settings
|
|
||||||
.location
|
|
||||||
.as_ref()
|
|
||||||
.map(|location| Path::new(&location).to_path_buf())
|
|
||||||
.unwrap_or_else(default_location);
|
|
||||||
if !gog_location.exists() {
|
|
||||||
return Err(format!("Could not find path: {:?}", gog_location));
|
|
||||||
}
|
|
||||||
let config_path = gog_location.join("config.json");
|
|
||||||
if !config_path.exists() {
|
|
||||||
return Err(format!("Config file not found: {:?}", config_path));
|
|
||||||
}
|
|
||||||
get_shortcuts_from_config(self.settings.wine_c_drive.clone(), config_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
|
||||||
use crate::platform::*;
|
|
||||||
let shortcuts_res = self.get_shortcuts();
|
|
||||||
match shortcuts_res {
|
|
||||||
Ok(_) => SettingsValidity::Valid,
|
|
||||||
Err(err) => SettingsValidity::Invalid {
|
|
||||||
reason: err.to_string(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &GogShortcut) -> bool {
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
return true;
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,11 +198,9 @@ fn fix_paths(wine_c_drive: &str, paths: Vec<String>) -> Vec<String> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_install_locations(path: PathBuf) -> Result<Vec<String>, String> {
|
fn get_install_locations(path: PathBuf) -> eyre::Result<Vec<String>> {
|
||||||
let data_res = std::fs::read_to_string(&path)
|
let data_res = std::fs::read_to_string(&path)?;
|
||||||
.map_err(|e| format!("Config file not read {:?} , error: {:?} ", path.clone(), e))?;
|
let config: GogConfig = serde_json::from_str(&data_res)?;
|
||||||
let config: GogConfig = serde_json::from_str(&data_res)
|
|
||||||
.map_err(|e| format!("Config file not read {:?} , error: {:?} ", path.clone(), e))?;
|
|
||||||
let path_vec = match config.library_path {
|
let path_vec = match config.library_path {
|
||||||
Some(path) => vec![path],
|
Some(path) => vec![path],
|
||||||
None => vec![],
|
None => vec![],
|
||||||
@@ -227,3 +221,49 @@ pub fn default_location() -> PathBuf {
|
|||||||
Path::new(&home).join("Games/gog-galaxy/drive_c/ProgramData/GOG.com/Galaxy")
|
Path::new(&home).join("Games/gog-galaxy/drive_c/ProgramData/GOG.com/Galaxy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl FromSettingsString for GogPlatform{
|
||||||
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
|
GogPlatform {
|
||||||
|
settings: load_settings(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GamesPlatform for GogPlatform{
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"GOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
|
to_shortcuts(self, self.get_shortcuts())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("GoG Galaxy");
|
||||||
|
ui.checkbox(&mut self.settings.enabled, "Import from GoG Galaxy");
|
||||||
|
if self.settings.enabled {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut empty_string = "".to_string();
|
||||||
|
let gog_location = self.settings.location.as_mut().unwrap_or(&mut empty_string);
|
||||||
|
ui.label("GoG Galaxy Folder: ");
|
||||||
|
if ui.text_edit_singleline(gog_location).changed() {
|
||||||
|
self.settings.location = Some(gog_location.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"gog"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct GogSettings {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub location: Option<String>,
|
||||||
|
pub wine_c_drive: Option<String>,
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
pub create_symlinks: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GogSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
enabled: false,
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
enabled: true,
|
||||||
|
location:None,
|
||||||
|
wine_c_drive: None,
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
create_symlinks: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
mod gog_config;
|
||||||
|
mod gog_game;
|
||||||
|
mod gog_platform;
|
||||||
|
mod gog_settings;
|
||||||
|
|
||||||
|
pub(crate) use gog_settings::GogSettings;
|
||||||
|
pub use gog_platform::get_gog_shortcuts_from_game_folders;
|
||||||
|
pub use gog_platform::GogPlatform;
|
||||||
|
pub use gog_game::GogShortcut;
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
||||||
|
|
||||||
use super::{HeroicGame, InstallationMode};
|
use super::{HeroicGame, InstallationMode};
|
||||||
use crate::gog::GogShortcut;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum HeroicGameType {
|
pub enum HeroicGameType {
|
||||||
Epic(HeroicGame),
|
Epic(HeroicGame),
|
||||||
//The bool is if it is windows (true) or not (false)
|
//The bool is if it is windows (true) or not (false)
|
||||||
Gog(GogShortcut, bool),
|
Gog(crate::platforms::GogShortcut, bool),
|
||||||
//The string is the app name
|
//The string is the app name
|
||||||
Heroic {
|
Heroic {
|
||||||
title: String,
|
title: String,
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,3 +19,19 @@ impl HeroicSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for HeroicSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
let enabled = true;
|
||||||
|
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
let enabled = false;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
enabled,
|
||||||
|
launch_games_through_heroic: Default::default(),
|
||||||
|
default_launch_through_heroic: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_itch_butler_db_test() {
|
fn parse_itch_butler_db_test() {
|
||||||
let content = include_bytes!("../testdata/itch/butler.db-wal");
|
let content = include_bytes!("../../testdata/itch/butler.db-wal");
|
||||||
let result = parse_butler_db(content);
|
let result = parse_butler_db(content);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let (_r, paths) = result.unwrap();
|
let (_r, paths) = result.unwrap();
|
||||||
@@ -98,7 +98,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_itch_butler_db_test_other() {
|
fn parse_itch_butler_db_test_other() {
|
||||||
let content = include_bytes!("../testdata/itch/other-butler.db-wal");
|
let content = include_bytes!("../../testdata/itch/other-butler.db-wal");
|
||||||
let result = parse_butler_db(content);
|
let result = parse_butler_db(content);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let (_r, paths) = result.unwrap();
|
let (_r, paths) = result.unwrap();
|
||||||
@@ -3,6 +3,10 @@ use std::path::Path;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
||||||
|
|
||||||
|
use crate::platforms::NeedsPorton;
|
||||||
|
|
||||||
|
use super::ItchPlatform;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
|
||||||
pub struct ItchGame {
|
pub struct ItchGame {
|
||||||
@@ -33,3 +37,21 @@ impl From<ItchGame> for ShortcutOwned {
|
|||||||
owned_shortcut
|
owned_shortcut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl NeedsPorton<ItchPlatform> for ItchGame{
|
||||||
|
fn needs_proton(&self, _platform: &ItchPlatform) -> bool {
|
||||||
|
self.executable.ends_with("exe")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
fn create_symlinks(&self, platform: &ItchPlatform) -> bool {
|
||||||
|
platform.settings.create_symlinks
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
fn create_symlinks(&self, _platform: &ItchPlatform) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,46 +1,37 @@
|
|||||||
use super::butler_db_parser::*;
|
use crate::platforms::{to_shortcuts, ShortcutToImport, load_settings, FromSettingsString, GamesPlatform};
|
||||||
|
use super::itch_game::ItchGame;
|
||||||
use super::receipt::Receipt;
|
use super::receipt::Receipt;
|
||||||
use super::{ItchGame, ItchSettings};
|
use super::ItchSettings;
|
||||||
use crate::platform::{Platform, SettingsValidity};
|
use super::{butler_db_parser::*};
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use is_executable::IsExecutable;
|
use is_executable::IsExecutable;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct ItchPlatform {
|
pub struct ItchPlatform {
|
||||||
settings: ItchSettings,
|
pub settings: ItchSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ItchPlatform {
|
impl ItchPlatform {
|
||||||
pub fn new(settings: ItchSettings) -> ItchPlatform {
|
fn get_itch_games(&self) -> eyre::Result<Vec<ItchGame>> {
|
||||||
ItchPlatform { settings }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Platform<ItchGame, String> for ItchPlatform {
|
|
||||||
fn enabled(&self) -> bool {
|
|
||||||
self.settings.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Itch"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<ItchGame>, String> {
|
|
||||||
let itch_location = self.settings.location.clone();
|
let itch_location = self.settings.location.clone();
|
||||||
let itch_location = itch_location.unwrap_or_else(get_default_location);
|
let itch_location = itch_location.unwrap_or_else(get_default_location);
|
||||||
|
|
||||||
let itch_db_location = Path::new(&itch_location).join("db").join("butler.db-wal");
|
let itch_db_location = Path::new(&itch_location).join("db").join("butler.db-wal");
|
||||||
if !itch_db_location.exists() {
|
if !itch_db_location.exists() {
|
||||||
return Err(format!("Path not found: {:?}", itch_db_location.to_str()));
|
return Err(eyre::format_err!(
|
||||||
|
"Path not found: {:?}",
|
||||||
|
itch_db_location.to_str()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let shortcut_bytes = std::fs::read(&itch_db_location).unwrap();
|
let shortcut_bytes = std::fs::read(&itch_db_location).unwrap();
|
||||||
|
|
||||||
let paths = match parse_butler_db(&shortcut_bytes) {
|
let paths = match parse_butler_db(&shortcut_bytes) {
|
||||||
Ok((_, shortcuts)) => Ok(shortcuts),
|
Ok((_, shortcuts)) => Ok(shortcuts),
|
||||||
Err(e) => Err(format!(
|
Err(e) => Err(eyre::format_err!(
|
||||||
"Could not parse path: {:?} , error: {:?}",
|
"Could not parse path: {:?} , error: {:?}",
|
||||||
itch_db_location.to_str(),
|
itch_db_location.to_str(),
|
||||||
e
|
e
|
||||||
@@ -52,31 +43,6 @@ impl Platform<ItchGame, String> for ItchPlatform {
|
|||||||
let res = paths.iter().filter_map(|e| dbpath_to_game(*e)).collect();
|
let res = paths.iter().filter_map(|e| dbpath_to_game(*e)).collect();
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn create_symlinks(&self) -> bool {
|
|
||||||
self.settings.create_symlinks
|
|
||||||
}
|
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
|
||||||
let shortcuts_res = self.get_shortcuts();
|
|
||||||
match shortcuts_res {
|
|
||||||
Ok(_) => SettingsValidity::Valid,
|
|
||||||
Err(err) => SettingsValidity::Invalid {
|
|
||||||
reason: err.to_string(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn needs_proton(&self, _input: &ItchGame) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn needs_proton(&self, input: &ItchGame) -> bool {
|
|
||||||
//We can only really guess here
|
|
||||||
input.executable.ends_with("exe")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dbpath_to_game(paths: &DbPaths) -> Option<ItchGame> {
|
fn dbpath_to_game(paths: &DbPaths) -> Option<ItchGame> {
|
||||||
@@ -86,13 +52,11 @@ fn dbpath_to_game(paths: &DbPaths) -> Option<ItchGame> {
|
|||||||
if !&recipt.exists() {
|
if !&recipt.exists() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
paths
|
||||||
let executable = paths
|
|
||||||
.paths
|
.paths
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| Path::new(&paths.base_path).join(&p).is_executable());
|
.filter(|p| Path::new(&paths.base_path).join(&p).is_executable())
|
||||||
match executable {
|
.find_map(|executable| {
|
||||||
Some(executable) => {
|
|
||||||
let gz_bytes = std::fs::read(&recipt).unwrap();
|
let gz_bytes = std::fs::read(&recipt).unwrap();
|
||||||
let mut d = GzDecoder::new(gz_bytes.as_slice());
|
let mut d = GzDecoder::new(gz_bytes.as_slice());
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
@@ -104,9 +68,7 @@ fn dbpath_to_game(paths: &DbPaths) -> Option<ItchGame> {
|
|||||||
executable: executable.to_owned(),
|
executable: executable.to_owned(),
|
||||||
title: re.game.title,
|
title: re.game.title,
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
@@ -127,3 +89,68 @@ pub fn get_default_location() -> String {
|
|||||||
.to_string()
|
.to_string()
|
||||||
//C:\Users\phili\AppData\Local\itch
|
//C:\Users\phili\AppData\Local\itch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl FromSettingsString for ItchPlatform {
|
||||||
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
|
ItchPlatform {
|
||||||
|
settings: load_settings(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl GamesPlatform for ItchPlatform{
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Itch"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
|
to_shortcuts(self, self.get_itch_games())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Itch.io");
|
||||||
|
ui.checkbox(&mut self.settings.enabled, "Import from Itch.io");
|
||||||
|
if self.settings.enabled {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut empty_string = "".to_string();
|
||||||
|
let itch_location = self
|
||||||
|
.settings
|
||||||
|
.location
|
||||||
|
.as_mut()
|
||||||
|
.unwrap_or(&mut empty_string);
|
||||||
|
ui.label("Itch.io Folder: ");
|
||||||
|
if ui.text_edit_singleline(itch_location).changed() {
|
||||||
|
self.settings.location = if itch_location.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(itch_location.to_string())
|
||||||
|
};
|
||||||
|
} else if !itch_location.is_empty() && ui
|
||||||
|
.button("Reset")
|
||||||
|
.on_hover_text("Reset the itch path, let BoilR guess again")
|
||||||
|
.clicked() {
|
||||||
|
self.settings.location = None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
ui.checkbox(&mut self.settings.create_symlinks, "Create symlinks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"itch"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,5 @@ mod itch_platform;
|
|||||||
mod receipt;
|
mod receipt;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
pub use itch_game::*;
|
pub use itch_platform::ItchPlatform;
|
||||||
pub use itch_platform::*;
|
pub use settings::ItchSettings;
|
||||||
pub use settings::*;
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct ItchSettings {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub location: Option<String>,
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
pub create_symlinks: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ItchSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: true,
|
||||||
|
location: Default::default(),
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
create_symlinks: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
use super::legendary_game::LegendaryGame;
|
||||||
|
use super::LegendarySettings;
|
||||||
|
use crate::platforms::{to_shortcuts_simple, GamesPlatform, ShortcutToImport, FromSettingsString, load_settings};
|
||||||
|
use serde_json::from_str;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LegendaryPlatform {
|
||||||
|
pub settings: LegendarySettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LegendaryPlatform {
|
||||||
|
fn get_shortcuts(&self) -> eyre::Result<Vec<LegendaryGame>> {
|
||||||
|
let legendary_string = self
|
||||||
|
.settings
|
||||||
|
.executable
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "legendary".to_string());
|
||||||
|
let legendary = legendary_string.as_str();
|
||||||
|
execute_legendary_command(legendary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_legendary_command(program: &str) -> eyre::Result<Vec<LegendaryGame>> {
|
||||||
|
let legendary_command = Command::new(program)
|
||||||
|
.arg("list-installed")
|
||||||
|
.arg("--json")
|
||||||
|
.output()?;
|
||||||
|
let json = String::from_utf8_lossy(&legendary_command.stdout);
|
||||||
|
let games = from_str(&json)?;
|
||||||
|
Ok(games)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GamesPlatform for LegendaryPlatform {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Legendary"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
|
to_shortcuts_simple(self.get_shortcuts())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Legendary & Rare");
|
||||||
|
ui.checkbox(&mut self.settings.enabled, "Import from Legendary & Rare");
|
||||||
|
if self.settings.enabled {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut empty_string = "".to_string();
|
||||||
|
let legendary_location = self
|
||||||
|
.settings
|
||||||
|
.executable
|
||||||
|
.as_mut()
|
||||||
|
.unwrap_or(&mut empty_string);
|
||||||
|
ui.label("Legendary Executable: ")
|
||||||
|
.on_hover_text("The location of the legendary executable to use");
|
||||||
|
if ui.text_edit_singleline(legendary_location).changed() {
|
||||||
|
self.settings.executable = Some(legendary_location.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"legendary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSettingsString for LegendaryPlatform{
|
||||||
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
|
LegendaryPlatform{
|
||||||
|
settings:load_settings(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mod legendary_game;
|
||||||
|
mod legendary_platform;
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
|
||||||
|
pub use settings::LegendarySettings;
|
||||||
|
pub use legendary_platform::LegendaryPlatform;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct LegendarySettings {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub executable: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LegendarySettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
let enabled = true;
|
||||||
|
#[cfg(target_family = "windows")]
|
||||||
|
let enabled = false;
|
||||||
|
Self {
|
||||||
|
enabled,
|
||||||
|
executable: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::lutris::settings::LutrisSettings;
|
|
||||||
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use super::LutrisSettings;
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct LutrisGame {
|
pub struct LutrisGame {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
use super::game_list_parser::parse_lutris_games;
|
||||||
|
use super::lutris_game::LutrisGame;
|
||||||
|
use super::settings::LutrisSettings;
|
||||||
|
use crate::platforms::{to_shortcuts_simple, ShortcutToImport, FromSettingsString, load_settings, GamesPlatform};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LutrisPlatform {
|
||||||
|
pub settings: LutrisSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LutrisPlatform {
|
||||||
|
fn get_shortcuts(&self) -> eyre::Result<Vec<LutrisGame>> {
|
||||||
|
let output = get_lutris_command_output(&self.settings)?;
|
||||||
|
let games = parse_lutris_games(output.as_str());
|
||||||
|
let mut res = vec![];
|
||||||
|
for mut game in games {
|
||||||
|
if game.runner != "steam" {
|
||||||
|
game.settings = Some(self.settings.clone());
|
||||||
|
res.push(game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_lutris_command_output(settings: &LutrisSettings) -> eyre::Result<String> {
|
||||||
|
let output = if settings.flatpak {
|
||||||
|
let flatpak_image = &settings.flatpak_image;
|
||||||
|
#[cfg(not(feature = "flatpak"))]
|
||||||
|
{
|
||||||
|
let mut command = Command::new("flatpak");
|
||||||
|
command
|
||||||
|
.arg("run")
|
||||||
|
.arg(flatpak_image)
|
||||||
|
.arg("-lo")
|
||||||
|
.arg("--json")
|
||||||
|
.output()?
|
||||||
|
}
|
||||||
|
#[cfg(feature = "flatpak")]
|
||||||
|
{
|
||||||
|
let mut command = Command::new("flatpak-spawn");
|
||||||
|
command
|
||||||
|
.arg("--host")
|
||||||
|
.arg("flatpak")
|
||||||
|
.arg("run")
|
||||||
|
.arg(flatpak_image)
|
||||||
|
.arg("-lo")
|
||||||
|
.arg("--json")
|
||||||
|
.output()?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut command = Command::new(&settings.executable);
|
||||||
|
command.arg("-lo").arg("--json").output()?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSettingsString for LutrisPlatform{
|
||||||
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
|
LutrisPlatform { settings: load_settings(s) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GamesPlatform for LutrisPlatform{
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Lutris"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
|
to_shortcuts_simple(self.get_shortcuts())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Lutris");
|
||||||
|
ui.checkbox(&mut self.settings.enabled, "Import from Lutris");
|
||||||
|
if self.settings.enabled {
|
||||||
|
ui.checkbox(&mut self.settings.flatpak, "Flatpak version");
|
||||||
|
if !self.settings.flatpak {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let lutris_location = &mut self.settings.executable;
|
||||||
|
ui.label("Lutris Location: ");
|
||||||
|
ui.text_edit_singleline(lutris_location);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let flatpak_image = &mut self.settings.flatpak_image;
|
||||||
|
ui.label("Flatpak image");
|
||||||
|
ui.text_edit_singleline(flatpak_image);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"lutris"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mod game_list_parser;
|
||||||
|
mod lutris_game;
|
||||||
|
mod lutris_platform;
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
pub(crate) use lutris_platform::LutrisPlatform;
|
||||||
|
pub use settings::LutrisSettings;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct LutrisSettings {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub executable: String,
|
||||||
|
pub flatpak: bool,
|
||||||
|
pub flatpak_image: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LutrisSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
let enabled = true;
|
||||||
|
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
let enabled = false;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
enabled,
|
||||||
|
executable: "lutris".to_string(),
|
||||||
|
flatpak: true,
|
||||||
|
flatpak_image: "net.lutris.Lutris//beta".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
mod platform;
|
||||||
|
mod egs;
|
||||||
|
mod amazon;
|
||||||
|
mod bottles;
|
||||||
|
mod uplay;
|
||||||
|
mod itch;
|
||||||
|
mod flatpak;
|
||||||
|
mod origin;
|
||||||
|
mod gog;
|
||||||
|
mod heroic;
|
||||||
|
mod lutris;
|
||||||
|
mod legendary;
|
||||||
|
mod platforms_load;
|
||||||
|
|
||||||
|
pub(crate) use platform::*;
|
||||||
|
|
||||||
|
pub(crate) use gog::get_gog_shortcuts_from_game_folders;
|
||||||
|
pub(crate) use gog::GogShortcut;
|
||||||
|
pub(crate) use platforms_load::FromSettingsString;
|
||||||
|
pub(crate) use platforms_load::load_settings;
|
||||||
|
pub use platforms_load::Platforms;
|
||||||
|
pub use platforms_load::get_platforms;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mod origin_game;
|
||||||
|
mod origin_platform;
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
pub use settings::OriginSettings;
|
||||||
|
pub(crate) use origin_platform::OriginPlatform;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::platform::{Platform, SettingsValidity};
|
use crate::platforms::{to_shortcuts, GamesPlatform, NeedsPorton, ShortcutToImport, FromSettingsString, load_settings};
|
||||||
use nom::bytes::complete::take_until;
|
use nom::bytes::complete::take_until;
|
||||||
use std::{
|
use std::{
|
||||||
fs::DirEntry,
|
fs::DirEntry,
|
||||||
@@ -7,39 +7,37 @@ use std::{
|
|||||||
|
|
||||||
use super::{origin_game::OriginGame, OriginSettings};
|
use super::{origin_game::OriginGame, OriginSettings};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct OriginPlatform {
|
pub struct OriginPlatform {
|
||||||
pub settings: OriginSettings,
|
pub settings: OriginSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform<OriginGame, String> for OriginPlatform {
|
impl NeedsPorton<OriginPlatform> for OriginGame {
|
||||||
fn enabled(&self) -> bool {
|
#[cfg(target_os = "windows")]
|
||||||
self.settings.enabled
|
fn needs_proton(&self, _platform: &OriginPlatform) -> bool {
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Origin"
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn create_symlinks(&self) -> bool {
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<OriginGame>, String> {
|
#[cfg(target_family = "unix")]
|
||||||
|
fn needs_proton(&self, _platform: &OriginPlatform) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_symlinks(&self, _platform: &OriginPlatform) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OriginPlatform {
|
||||||
|
fn get_shortcuts(&self) -> eyre::Result<Vec<OriginGame>> {
|
||||||
let origin_folders = get_default_locations();
|
let origin_folders = get_default_locations();
|
||||||
if origin_folders.is_none() {
|
if origin_folders.is_none() {
|
||||||
return Err(String::from("Default path not found"));
|
return Err(eyre::format_err!("Default path not found"));
|
||||||
}
|
}
|
||||||
let origin_folders = origin_folders.unwrap();
|
let origin_folders = origin_folders.unwrap();
|
||||||
let origin_folder = origin_folders.local_content_path;
|
let origin_folder = origin_folders.local_content_path;
|
||||||
let origin_exe = origin_folders.exe_path;
|
let origin_exe = origin_folders.exe_path;
|
||||||
let game_folders = origin_folder.join("LocalContent").read_dir().map_err(|e| {
|
let game_folders = origin_folder.join("LocalContent").read_dir()?;
|
||||||
format!(
|
|
||||||
"Could not read game dir: {} , error: {:?}",
|
|
||||||
origin_folder.join("LocalContent").to_string_lossy(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let games = game_folders
|
let games = game_folders
|
||||||
.filter_map(|folder| folder.ok())
|
.filter_map(|folder| folder.ok())
|
||||||
.filter_map(|game_folder| {
|
.filter_map(|game_folder| {
|
||||||
@@ -60,25 +58,6 @@ impl Platform<OriginGame, String> for OriginPlatform {
|
|||||||
});
|
});
|
||||||
Ok(games.collect())
|
Ok(games.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
|
||||||
let shortcuts_res = self.get_shortcuts();
|
|
||||||
match shortcuts_res {
|
|
||||||
Ok(_) => SettingsValidity::Valid,
|
|
||||||
Err(err) => SettingsValidity::Invalid {
|
|
||||||
reason: err.to_string(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &OriginGame) -> bool {
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
return false;
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_folder_mfst_file_content(game_folder_path: &Path) -> Option<String> {
|
fn get_folder_mfst_file_content(game_folder_path: &Path) -> Option<String> {
|
||||||
@@ -200,3 +179,39 @@ fn get_exe_path() -> Option<PathBuf> {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GamesPlatform for OriginPlatform {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Origin"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
|
to_shortcuts(self, self.get_shortcuts())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Origin");
|
||||||
|
ui.checkbox(&mut self.settings.enabled, "Import from Origin");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"origin"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl FromSettingsString for OriginPlatform{
|
||||||
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
|
OriginPlatform { settings: load_settings(s) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
|
||||||
|
pub struct OriginSettings {
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OriginSettings{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { enabled: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
||||||
|
use dyn_clone::DynClone;
|
||||||
|
|
||||||
|
pub trait GamesPlatform
|
||||||
|
where
|
||||||
|
Self: std::marker::Send,
|
||||||
|
Self: std::marker::Sync,
|
||||||
|
Self: DynClone,
|
||||||
|
{
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str;
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool;
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>>;
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String;
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
dyn_clone::clone_trait_object!(GamesPlatform);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ShortcutToImport {
|
||||||
|
pub shortcut: ShortcutOwned,
|
||||||
|
pub needs_proton: bool,
|
||||||
|
pub needs_symlinks: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub(crate) fn to_shortcuts<T, P>(
|
||||||
|
platform: &P,
|
||||||
|
into_shortcuts: Result<Vec<T>, eyre::ErrReport>,
|
||||||
|
) -> eyre::Result<Vec<ShortcutToImport>>
|
||||||
|
where
|
||||||
|
T: Into<ShortcutOwned>,
|
||||||
|
T: NeedsPorton<P>,
|
||||||
|
{
|
||||||
|
let shortcuts = into_shortcuts?;
|
||||||
|
let mut shortcut_info = vec![];
|
||||||
|
for m in shortcuts {
|
||||||
|
let needs_proton = m.needs_proton(platform);
|
||||||
|
let needs_symlinks = m.create_symlinks(platform);
|
||||||
|
let shortcut = m.into();
|
||||||
|
shortcut_info.push(ShortcutToImport {
|
||||||
|
shortcut,
|
||||||
|
needs_proton,
|
||||||
|
needs_symlinks,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(shortcut_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn to_shortcuts_simple<T>(
|
||||||
|
into_shortcuts: Result<Vec<T>, eyre::ErrReport>,
|
||||||
|
) -> eyre::Result<Vec<ShortcutToImport>>
|
||||||
|
where
|
||||||
|
T: Into<ShortcutOwned>,
|
||||||
|
{
|
||||||
|
let shortcuts = into_shortcuts?;
|
||||||
|
let mut shortcut_info = vec![];
|
||||||
|
for m in shortcuts {
|
||||||
|
let needs_proton = false;
|
||||||
|
let needs_symlinks = false;
|
||||||
|
let shortcut = m.into();
|
||||||
|
shortcut_info.push(ShortcutToImport {
|
||||||
|
shortcut,
|
||||||
|
needs_proton,
|
||||||
|
needs_symlinks,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(shortcut_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NeedsPorton<P> {
|
||||||
|
fn needs_proton(&self, platform: &P) -> bool;
|
||||||
|
|
||||||
|
fn create_symlinks(&self, platform: &P) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
use crate::settings::load_setting_sections;
|
||||||
|
|
||||||
|
use super::GamesPlatform;
|
||||||
|
use super::amazon::AmazonPlatform;
|
||||||
|
use super::bottles::BottlesPlatform;
|
||||||
|
use super::egs::EpicPlatform;
|
||||||
|
use super::flatpak::FlatpakPlatform;
|
||||||
|
use super::gog::GogPlatform;
|
||||||
|
use super::heroic::HeroicPlatform;
|
||||||
|
use super::itch::ItchPlatform;
|
||||||
|
use super::legendary::LegendaryPlatform;
|
||||||
|
use super::lutris::LutrisPlatform;
|
||||||
|
use super::origin::OriginPlatform;
|
||||||
|
use super::uplay::UplayPlatform;
|
||||||
|
|
||||||
|
const PLATFORM_NAMES: [&str; 11] = [
|
||||||
|
"amazon",
|
||||||
|
"bottles",
|
||||||
|
"epic_games",
|
||||||
|
"flatpak",
|
||||||
|
"gog",
|
||||||
|
"heroic",
|
||||||
|
"itch",
|
||||||
|
"legendary",
|
||||||
|
"lutris",
|
||||||
|
"origin",
|
||||||
|
"uplay",
|
||||||
|
];
|
||||||
|
|
||||||
|
pub type Platforms = Vec<Box<dyn GamesPlatform>>;
|
||||||
|
|
||||||
|
pub fn load_platform<A: AsRef<str>, B: AsRef<str>>(
|
||||||
|
name: A,
|
||||||
|
settings_string: B,
|
||||||
|
) -> eyre::Result<Box<dyn GamesPlatform>> {
|
||||||
|
let name = name.as_ref();
|
||||||
|
let s = settings_string.as_ref();
|
||||||
|
match name {
|
||||||
|
"amazon" => load::<AmazonPlatform>(s),
|
||||||
|
"bottles" => load::<BottlesPlatform>(s),
|
||||||
|
"epic_games" => load::<EpicPlatform>(s),
|
||||||
|
"uplay" => load::<UplayPlatform>(s),
|
||||||
|
"itch" => load::<ItchPlatform>(s),
|
||||||
|
"flatpak" => load::<FlatpakPlatform>(s),
|
||||||
|
"gog" => load::<GogPlatform>(s),
|
||||||
|
"heroic" => load::<HeroicPlatform>(s),
|
||||||
|
"legendary" => load::<LegendaryPlatform>(s),
|
||||||
|
"lutris" => load::<LutrisPlatform>(s),
|
||||||
|
"origin" => load::<OriginPlatform>(s),
|
||||||
|
_ => Err(eyre::format_err!("Unknown platform named {name}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_platforms() -> Platforms {
|
||||||
|
let sections = load_setting_sections();
|
||||||
|
let sections = match sections {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"Could not load platform settings, using defaults: Error: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
HashMap::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut platforms = vec![];
|
||||||
|
for name in PLATFORM_NAMES {
|
||||||
|
let default =String::from("");
|
||||||
|
let settings = sections.get(name).unwrap_or(&default);
|
||||||
|
match load_platform(name, settings) {
|
||||||
|
Ok(platform) => platforms.push(platform),
|
||||||
|
Err(e) => eprintln!("Could not load platform {name}, gave error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
platforms
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_settings<Setting, S: AsRef<str>>(input: S) -> Setting
|
||||||
|
where
|
||||||
|
Setting: Default,
|
||||||
|
Setting: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let str = input.as_ref();
|
||||||
|
match toml::from_str(str) {
|
||||||
|
Ok(k) => k,
|
||||||
|
Err(err) => {
|
||||||
|
if !str.is_empty(){
|
||||||
|
eprintln!("Error reading settings file {:?}", err);
|
||||||
|
}
|
||||||
|
Setting::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn load<T>(s: &str) -> eyre::Result<Box<dyn GamesPlatform>>
|
||||||
|
where
|
||||||
|
T: FromSettingsString,
|
||||||
|
T: GamesPlatform,
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
Ok(Box::new(T::from_settings_string(s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub trait FromSettingsString {
|
||||||
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self;
|
||||||
|
}
|
||||||
@@ -3,15 +3,15 @@ use std::path::{Path, PathBuf};
|
|||||||
use steam_shortcuts_util::shortcut::{Shortcut, ShortcutOwned};
|
use steam_shortcuts_util::shortcut::{Shortcut, ShortcutOwned};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct Game {
|
pub(crate) struct UplayGame {
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
pub(crate) icon: String,
|
pub(crate) icon: String,
|
||||||
pub(crate) id: String,
|
pub(crate) id: String,
|
||||||
pub(crate) launcher: PathBuf,
|
pub(crate) launcher: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Game> for ShortcutOwned {
|
impl From<UplayGame> for ShortcutOwned {
|
||||||
fn from(game: Game) -> Self {
|
fn from(game: UplayGame) -> Self {
|
||||||
let launch = format!("\"uplay://launch/{}/0\"", game.id);
|
let launch = format!("\"uplay://launch/{}/0\"", game.id);
|
||||||
let start_dir = game
|
let start_dir = game
|
||||||
.launcher
|
.launcher
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mod game;
|
||||||
|
mod platform;
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
pub use platform::UplayPlatform;
|
||||||
|
pub use settings::UplaySettings;
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use std::path::Path;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::platforms::FromSettingsString;
|
||||||
|
use crate::platforms::GamesPlatform;
|
||||||
|
use crate::platforms::load_settings;
|
||||||
|
use crate::platforms::to_shortcuts_simple;
|
||||||
|
use crate::platforms::ShortcutToImport;
|
||||||
|
|
||||||
|
use super::{game::UplayGame, settings::UplaySettings};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct UplayPlatform {
|
||||||
|
pub settings: UplaySettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_uplay_games() -> eyre::Result<Vec<UplayGame>> {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
Err(eyre::format_err!("Uplay is not supported on Linux"))
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
get_games_from_winreg()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn get_launcher_path() -> eyre::Result<PathBuf> {
|
||||||
|
use winreg::enums::*;
|
||||||
|
use winreg::RegKey;
|
||||||
|
|
||||||
|
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||||
|
let launcher_key = hklm.open_subkey("SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher")?;
|
||||||
|
let launcher_dir: String = launcher_key.get_value("InstallDir")?;
|
||||||
|
let path = Path::new(&launcher_dir).join("upc.exe");
|
||||||
|
if path.exists() {
|
||||||
|
Ok(path)
|
||||||
|
} else {
|
||||||
|
Err(eyre::eyre!(
|
||||||
|
"Could not find uplay launcher at path {:?}",
|
||||||
|
path
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn get_games_from_winreg() -> eyre::Result<Vec<UplayGame>> {
|
||||||
|
use winreg::enums::*;
|
||||||
|
use winreg::RegKey;
|
||||||
|
|
||||||
|
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||||
|
let mut games = vec![];
|
||||||
|
let mut installed_ids = vec![];
|
||||||
|
let launcher_path = get_launcher_path()?;
|
||||||
|
|
||||||
|
if let Ok(installs) = hklm.open_subkey("SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher\\Installs") {
|
||||||
|
for i in installs.enum_keys().filter_map(|i| i.ok()) {
|
||||||
|
if let Ok(install) = installs.open_subkey(&i) {
|
||||||
|
let install_dir: Result<String, _> = install.get_value("InstallDir");
|
||||||
|
if let Ok(folder) = install_dir {
|
||||||
|
let path = Path::new(&folder);
|
||||||
|
if path.exists() {
|
||||||
|
installed_ids.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in installed_ids {
|
||||||
|
let path = format!("SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Uplay Install {}",id);
|
||||||
|
let subkey = hklm.open_subkey(path);
|
||||||
|
if let Ok(subkey) = subkey {
|
||||||
|
let name: Result<String, _> = subkey.get_value("DisplayName");
|
||||||
|
if let Ok(name) = name {
|
||||||
|
let icon: String = subkey.get_value("DisplayIcon").unwrap_or_default();
|
||||||
|
games.push(UplayGame {
|
||||||
|
name,
|
||||||
|
icon,
|
||||||
|
id,
|
||||||
|
launcher: launcher_path.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(games)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSettingsString for UplayPlatform {
|
||||||
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
|
UplayPlatform {
|
||||||
|
settings: load_settings(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl GamesPlatform for UplayPlatform{
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Uplay"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
|
to_shortcuts_simple(get_uplay_games())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Uplay");
|
||||||
|
ui.checkbox(&mut self.settings.enabled, "Import from Uplay");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"uplay"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct UplaySettings {
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UplaySettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
let enabled = false;
|
||||||
|
#[cfg(target_family = "windows")]
|
||||||
|
let enabled = true;
|
||||||
|
Self { enabled }
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
-24
@@ -1,40 +1,19 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
amazon::AmazonSettings, config::get_config_file, egs::EpicGamesLauncherSettings,
|
config::get_config_file, platforms::Platforms, steam::SteamSettings,
|
||||||
flatpak::FlatpakSettings, gog::GogSettings, itch::ItchSettings, legendary::LegendarySettings,
|
steamgriddb::SteamGridDbSettings,
|
||||||
lutris::settings::LutrisSettings, origin::OriginSettings, steam::SteamSettings,
|
|
||||||
steamgriddb::SteamGridDbSettings, uplay::UplaySettings,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
use crate::heroic::HeroicSettings;
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
use crate::bottles::BottlesSettings;
|
|
||||||
|
|
||||||
use config::{Config, ConfigError, Environment, File};
|
use config::{Config, ConfigError, Environment, File};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::env;
|
use std::{collections::HashMap, env};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub config_version: Option<usize>,
|
pub config_version: Option<usize>,
|
||||||
pub blacklisted_games: Vec<u32>,
|
pub blacklisted_games: Vec<u32>,
|
||||||
pub epic_games: EpicGamesLauncherSettings,
|
|
||||||
pub legendary: LegendarySettings,
|
|
||||||
pub itch: ItchSettings,
|
|
||||||
pub steamgrid_db: SteamGridDbSettings,
|
pub steamgrid_db: SteamGridDbSettings,
|
||||||
pub steam: SteamSettings,
|
pub steam: SteamSettings,
|
||||||
pub origin: OriginSettings,
|
|
||||||
pub gog: GogSettings,
|
|
||||||
pub uplay: UplaySettings,
|
|
||||||
pub lutris: LutrisSettings,
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
pub heroic: HeroicSettings,
|
|
||||||
pub amazon: AmazonSettings,
|
|
||||||
pub flatpak: FlatpakSettings,
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
pub bottles: BottlesSettings,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
@@ -65,6 +44,62 @@ impl Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_setting_sections() -> eyre::Result<HashMap<String, String>> {
|
||||||
|
let config_file_path = get_config_file();
|
||||||
|
let content = std::fs::read_to_string(config_file_path)?;
|
||||||
|
let mut result = HashMap::new();
|
||||||
|
let lines = content.lines();
|
||||||
|
let mut current_section_lines: Vec<String> = vec![];
|
||||||
|
let mut current_section_name: Option<String> = Option::None;
|
||||||
|
for line in lines {
|
||||||
|
if line.starts_with('[') && line.ends_with(']') {
|
||||||
|
add_sections(¤t_section_name, ¤t_section_lines, &mut result);
|
||||||
|
current_section_name = Some(line[1..line.len() - 1].to_string());
|
||||||
|
current_section_lines.clear();
|
||||||
|
} else {
|
||||||
|
current_section_lines.push(line.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_sections(¤t_section_name, ¤t_section_lines, &mut result);
|
||||||
|
|
||||||
|
let blacklisted_sections = ["steamgrid_db", "steam"];
|
||||||
|
for section in blacklisted_sections {
|
||||||
|
let _ = result.remove(section);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_settings(settings: &Settings, platforms: &Platforms) {
|
||||||
|
let mut toml = toml::to_string(&settings).unwrap();
|
||||||
|
|
||||||
|
for platform in platforms {
|
||||||
|
let section_name = format!("[{}]", platform.code_name());
|
||||||
|
toml.push('\n');
|
||||||
|
toml.push_str(section_name.as_str());
|
||||||
|
toml.push('\n');
|
||||||
|
let platform_string = platform.get_settings_serilizable();
|
||||||
|
toml.push_str(platform_string.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_path = crate::config::get_config_file();
|
||||||
|
std::fs::write(config_path, toml).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_sections(
|
||||||
|
current_section_name: &Option<String>,
|
||||||
|
current_section_lines: &Vec<String>,
|
||||||
|
result: &mut HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
if let Some(old_section_name) = current_section_name {
|
||||||
|
let mut section_string = String::new();
|
||||||
|
for line in current_section_lines {
|
||||||
|
section_string.push_str(line);
|
||||||
|
section_string.push('\n');
|
||||||
|
}
|
||||||
|
result.insert(old_section_name.to_string(), section_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn sanitize_auth_key(result: &mut Settings) {
|
fn sanitize_auth_key(result: &mut Settings) {
|
||||||
if let Some(auth_key) = result.steamgrid_db.auth_key.as_ref() {
|
if let Some(auth_key) = result.steamgrid_db.auth_key.as_ref() {
|
||||||
if auth_key == "Write your authentication key between these quotes" {
|
if auth_key == "Write your authentication key between these quotes" {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod collections;
|
mod collections;
|
||||||
mod installed_games;
|
mod installed_games;
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
mod proton_vdf_util;
|
mod proton_vdf_util;
|
||||||
mod restarter;
|
mod restarter;
|
||||||
mod settings;
|
mod settings;
|
||||||
@@ -7,6 +8,7 @@ mod utils;
|
|||||||
|
|
||||||
pub use collections::*;
|
pub use collections::*;
|
||||||
pub use installed_games::*;
|
pub use installed_games::*;
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
pub use proton_vdf_util::*;
|
pub use proton_vdf_util::*;
|
||||||
pub use restarter::*;
|
pub use restarter::*;
|
||||||
pub use settings::SteamSettings;
|
pub use settings::SteamSettings;
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ pub struct ShortcutInfo {
|
|||||||
pub shortcuts: Vec<ShortcutOwned>,
|
pub shortcuts: Vec<ShortcutOwned>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Clone, Debug)]
|
#[derive(Default, PartialEq, Eq, Clone, Debug)]
|
||||||
pub struct SteamUsersInfo {
|
pub struct SteamUsersInfo {
|
||||||
pub steam_user_data_folder: String,
|
pub steam_user_data_folder: String,
|
||||||
pub shortcut_path: Option<String>,
|
pub shortcut_path: Option<String>,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ImageType {
|
pub enum ImageType {
|
||||||
Hero,
|
Hero,
|
||||||
Grid,
|
Grid,
|
||||||
|
|||||||
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
mod symlinks;
|
pub mod symlinks;
|
||||||
mod synchronization;
|
mod synchronization;
|
||||||
|
|
||||||
pub use synchronization::download_images;
|
pub use synchronization::download_images;
|
||||||
pub use synchronization::get_platform_shortcuts;
|
|
||||||
pub use synchronization::run_sync;
|
|
||||||
|
|
||||||
pub use synchronization::IsBoilRShortcut;
|
pub use synchronization::IsBoilRShortcut;
|
||||||
pub use synchronization::SyncProgress;
|
pub use synchronization::SyncProgress;
|
||||||
pub use synchronization::*;
|
pub use synchronization::*;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+21
-139
@@ -1,29 +1,20 @@
|
|||||||
use steam_shortcuts_util::{shortcut::ShortcutOwned, shortcuts_to_bytes, calculate_app_id_for_shortcut, Shortcut};
|
use steam_shortcuts_util::{
|
||||||
|
calculate_app_id_for_shortcut, shortcut::ShortcutOwned, shortcuts_to_bytes, Shortcut,
|
||||||
|
};
|
||||||
use tokio::sync::watch::Sender;
|
use tokio::sync::watch::Sender;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
egs::EpicPlatform,
|
platforms::{GamesPlatform, ShortcutToImport},
|
||||||
legendary::LegendaryPlatform,
|
|
||||||
lutris::lutris_platform::LutrisPlatform,
|
|
||||||
platform::Platform,
|
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
steam::{
|
steam::{
|
||||||
get_shortcuts_for_user, get_shortcuts_paths, setup_proton_games, write_collections,
|
get_shortcuts_for_user, get_shortcuts_paths, write_collections,
|
||||||
Collection, ShortcutInfo, SteamUsersInfo,
|
Collection, ShortcutInfo, SteamUsersInfo,
|
||||||
},
|
},
|
||||||
steamgriddb::{download_images_for_users, ImageType},
|
steamgriddb::{download_images_for_users, ImageType},
|
||||||
uplay::Uplay,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
use std::{collections::HashMap, error::Error};
|
||||||
use crate::heroic::HeroicPlatform;
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
use crate::flatpak::FlatpakPlatform;
|
|
||||||
|
|
||||||
use std::{error::Error, collections::HashMap};
|
|
||||||
|
|
||||||
use crate::{gog::GogPlatform, itch::ItchPlatform, origin::OriginPlatform};
|
|
||||||
use std::{fs::File, io::Write, path::Path};
|
use std::{fs::File, io::Write, path::Path};
|
||||||
|
|
||||||
pub const BOILR_TAG: &str = "boilr";
|
pub const BOILR_TAG: &str = "boilr";
|
||||||
@@ -56,23 +47,22 @@ pub fn disconnect_shortcut(settings: &Settings, app_id: u32) -> Result<(), Strin
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_sync(
|
pub fn sync_shortcuts(
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
|
platform_shortcuts: &[(String, Vec<ShortcutOwned>)],
|
||||||
sender: &mut Option<Sender<SyncProgress>>,
|
sender: &mut Option<Sender<SyncProgress>>,
|
||||||
renames: &HashMap<u32,String>
|
renames: &HashMap<u32, String>,
|
||||||
) -> Result<Vec<SteamUsersInfo>, String> {
|
) -> Result<Vec<SteamUsersInfo>, String> {
|
||||||
if let Some(sender) = &sender {
|
|
||||||
let _ = sender.send(SyncProgress::Starting);
|
|
||||||
}
|
|
||||||
let mut userinfo_shortcuts = get_shortcuts_paths(&settings.steam)
|
let mut userinfo_shortcuts = get_shortcuts_paths(&settings.steam)
|
||||||
.map_err(|e| format!("Getting shortcut paths failed: {e}"))?;
|
.map_err(|e| format!("Getting shortcut paths failed: {e}"))?;
|
||||||
|
|
||||||
let platform_shortcuts = get_platform_shortcuts(settings);
|
|
||||||
let mut all_shortcuts: Vec<ShortcutOwned> = platform_shortcuts
|
let mut all_shortcuts: Vec<ShortcutOwned> = platform_shortcuts
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|s| s.1.clone())
|
.flat_map(|s| s.1.clone())
|
||||||
.filter(|s| !settings.blacklisted_games.contains(&s.app_id))
|
.filter(|s| !settings.blacklisted_games.contains(&s.app_id))
|
||||||
.collect();
|
.collect();
|
||||||
|
for shortcut in &mut all_shortcuts{
|
||||||
|
shortcut.dev_kit_game_id = BOILR_TAG.to_string();
|
||||||
|
}
|
||||||
if let Some(sender) = &sender {
|
if let Some(sender) = &sender {
|
||||||
let _ = sender.send(SyncProgress::FoundGames {
|
let _ = sender.send(SyncProgress::FoundGames {
|
||||||
games_found: all_shortcuts.len(),
|
games_found: all_shortcuts.len(),
|
||||||
@@ -88,7 +78,7 @@ pub fn run_sync(
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
""
|
"",
|
||||||
);
|
);
|
||||||
shortcut.app_id = calculate_app_id_for_shortcut(&new_shortcut);
|
shortcut.app_id = calculate_app_id_for_shortcut(&new_shortcut);
|
||||||
}
|
}
|
||||||
@@ -118,7 +108,7 @@ pub fn run_sync(
|
|||||||
save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path));
|
save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path));
|
||||||
|
|
||||||
if settings.steam.create_collections {
|
if settings.steam.create_collections {
|
||||||
match write_shortcut_collections(&user.user_id, &platform_shortcuts) {
|
match write_shortcut_collections(&user.user_id, platform_shortcuts) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(_e) => eprintln!("Could not write collections, make sure steam is shut down"),
|
Err(_e) => eprintln!("Could not write collections, make sure steam is shut down"),
|
||||||
}
|
}
|
||||||
@@ -127,7 +117,6 @@ pub fn run_sync(
|
|||||||
let duration = start_time.elapsed();
|
let duration = start_time.elapsed();
|
||||||
println!("Finished synchronizing games in: {:?}", duration);
|
println!("Finished synchronizing games in: {:?}", duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(userinfo_shortcuts)
|
Ok(userinfo_shortcuts)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,47 +200,14 @@ fn write_shortcut_collections<S: AsRef<str>>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_platform_shortcuts(settings: &Settings) -> Vec<(String, Vec<ShortcutOwned>)> {
|
pub fn get_platform_shortcuts(
|
||||||
let mut platform_results = vec![
|
platform: Box<dyn GamesPlatform>,
|
||||||
update_platform_shortcuts(&EpicPlatform::new(&settings.epic_games)),
|
) -> eyre::Result<Vec<ShortcutToImport>> {
|
||||||
update_platform_shortcuts(&LegendaryPlatform::new(settings.legendary.clone())),
|
if platform.enabled() {
|
||||||
update_platform_shortcuts(&ItchPlatform::new(settings.itch.clone())),
|
platform.get_shortcut_info()
|
||||||
update_platform_shortcuts(&OriginPlatform {
|
}else{
|
||||||
settings: settings.origin.clone(),
|
Ok(vec![])
|
||||||
}),
|
|
||||||
update_platform_shortcuts(&GogPlatform {
|
|
||||||
settings: settings.gog.clone(),
|
|
||||||
}),
|
|
||||||
update_platform_shortcuts(&Uplay {
|
|
||||||
settings: settings.uplay.clone(),
|
|
||||||
}),
|
|
||||||
update_platform_shortcuts(&LutrisPlatform {
|
|
||||||
settings: settings.lutris.clone(),
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
platform_results.push(update_platform_shortcuts(&HeroicPlatform {
|
|
||||||
settings: settings.heroic.clone(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
platform_results.push(update_platform_shortcuts(&FlatpakPlatform {
|
|
||||||
settings: settings.flatpak.clone(),
|
|
||||||
}));
|
|
||||||
platform_results.push(update_platform_shortcuts(&crate::bottles::BottlesPlatform {
|
|
||||||
settings: settings.bottles.clone(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
platform_results.push(update_platform_shortcuts(&crate::amazon::AmazonPlatform {
|
|
||||||
settings: settings.amazon.clone(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
platform_results.iter().filter_map(|p| p.clone()).collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_shortcuts(shortcuts: &[ShortcutOwned], path: &Path) {
|
fn save_shortcuts(shortcuts: &[ShortcutOwned], path: &Path) {
|
||||||
@@ -280,77 +236,3 @@ fn save_shortcuts(shortcuts: &[ShortcutOwned], path: &Path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_platform_shortcuts<P, T, E>(platform: &P) -> Option<(String, Vec<ShortcutOwned>)>
|
|
||||||
where
|
|
||||||
P: Platform<T, E>,
|
|
||||||
E: std::fmt::Debug + std::fmt::Display,
|
|
||||||
T: Into<ShortcutOwned>,
|
|
||||||
T: Clone,
|
|
||||||
{
|
|
||||||
if platform.enabled() {
|
|
||||||
if let crate::platform::SettingsValidity::Invalid { reason } = platform.settings_valid() {
|
|
||||||
eprintln!(
|
|
||||||
"Setting for platform {} are invalid, reason: {}",
|
|
||||||
platform.name(),
|
|
||||||
reason
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut current_shortcuts = vec![];
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
if platform.create_symlinks() {
|
|
||||||
let name = platform.name();
|
|
||||||
super::symlinks::ensure_links_folder_created(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let shortcuts_to_add_result = platform.get_shortcuts();
|
|
||||||
|
|
||||||
match shortcuts_to_add_result {
|
|
||||||
Ok(shortcuts_to_add) => {
|
|
||||||
let mut shortcuts_to_proton = vec![];
|
|
||||||
let mut shortcuts_to_add_transformed = vec![];
|
|
||||||
for shortcut in shortcuts_to_add {
|
|
||||||
let mut shortcut_owned: ShortcutOwned = shortcut.clone().into();
|
|
||||||
shortcut_owned.dev_kit_game_id =
|
|
||||||
format!("{}-{}", BOILR_TAG, shortcut_owned.app_id);
|
|
||||||
shortcuts_to_add_transformed.push((shortcut, shortcut_owned));
|
|
||||||
}
|
|
||||||
|
|
||||||
let shortcuts_to_add = shortcuts_to_add_transformed;
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"Found {} game(s) for platform {}",
|
|
||||||
shortcuts_to_add.len(),
|
|
||||||
platform.name()
|
|
||||||
);
|
|
||||||
|
|
||||||
for (orign_shortcut, shortcut_owned) in shortcuts_to_add {
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
let shortcut_owned = if platform.create_symlinks() {
|
|
||||||
crate::sync::symlinks::create_sym_links(&shortcut_owned)
|
|
||||||
} else {
|
|
||||||
shortcut_owned
|
|
||||||
};
|
|
||||||
if platform.needs_proton(&orign_shortcut) {
|
|
||||||
shortcuts_to_proton.push(format!("{}", shortcut_owned.app_id));
|
|
||||||
}
|
|
||||||
current_shortcuts.push(shortcut_owned.clone());
|
|
||||||
}
|
|
||||||
if !shortcuts_to_proton.is_empty() {
|
|
||||||
setup_proton_games(shortcuts_to_proton.as_slice());
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = platform.name();
|
|
||||||
return Some((name.to_string(), current_shortcuts));
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Error getting shortcuts from platform: {}", platform.name());
|
|
||||||
eprintln!("{}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|||||||
+12
-12
@@ -43,8 +43,12 @@ pub mod ui_images {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_image_from_path(path: &std::path::Path) -> Result<egui::ColorImage, image::ImageError> {
|
pub fn load_image_from_path(
|
||||||
let image = image::io::Reader::open(path)?.with_guessed_format()?.decode()?;
|
path: &std::path::Path,
|
||||||
|
) -> Result<egui::ColorImage, image::ImageError> {
|
||||||
|
let image = image::io::Reader::open(path)?
|
||||||
|
.with_guessed_format()?
|
||||||
|
.decode()?;
|
||||||
let size = [image.width() as _, image.height() as _];
|
let size = [image.width() as _, image.height() as _];
|
||||||
let image_buffer = image.to_rgba8();
|
let image_buffer = image.to_rgba8();
|
||||||
let pixels = image_buffer.as_flat_samples();
|
let pixels = image_buffer.as_flat_samples();
|
||||||
@@ -59,12 +63,8 @@ pub mod ui_images {
|
|||||||
let size = [image.width() as _, image.height() as _];
|
let size = [image.width() as _, image.height() as _];
|
||||||
let image_buffer = image.to_rgba8();
|
let image_buffer = image.to_rgba8();
|
||||||
let pixels = image_buffer.as_flat_samples();
|
let pixels = image_buffer.as_flat_samples();
|
||||||
Ok(ColorImage::from_rgba_unmultiplied(
|
Ok(ColorImage::from_rgba_unmultiplied(size, pixels.as_slice()))
|
||||||
size,
|
|
||||||
pixels.as_slice(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -95,9 +95,9 @@ mod tests {
|
|||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// #[test]
|
||||||
pub fn test_image_load_animated_webp2() {
|
// pub fn test_image_load_animated_webp2() {
|
||||||
let res = load_image_from_path(std::path::Path::new("src/testdata/tunic.webp"));
|
// let res = load_image_from_path(std::path::Path::new("src/testdata/tunic.webp"));
|
||||||
assert!(res.is_err());
|
// assert!(res.is_err());
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,16 +57,11 @@ impl MyEguiApp {
|
|||||||
ui.heading(&user.path.to_string_lossy().to_string());
|
ui.heading(&user.path.to_string_lossy().to_string());
|
||||||
}
|
}
|
||||||
for shortcut in user.shortcuts.iter() {
|
for shortcut in user.shortcuts.iter() {
|
||||||
if shortcut.is_boilr_shortcut()
|
if shortcut.is_boilr_shortcut() && ui.button(&shortcut.app_name).clicked() && disconnect_shortcut(&self.settings, shortcut.app_id).is_ok() {
|
||||||
&& ui.button(&shortcut.app_name).clicked()
|
|
||||||
{
|
|
||||||
if disconnect_shortcut(&self.settings, shortcut.app_id).is_ok()
|
|
||||||
{
|
|
||||||
redraw = shortcut.app_id;
|
redraw = shortcut.app_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if redraw != 0 {
|
if redraw != 0 {
|
||||||
self.disconect_state.connected_shortcuts = None;
|
self.disconect_state.connected_shortcuts = None;
|
||||||
|
|||||||
@@ -353,7 +353,6 @@ impl MyEguiApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn render_ui_images(&mut self, ui: &mut egui::Ui) {
|
pub(crate) fn render_ui_images(&mut self, ui: &mut egui::Ui) {
|
||||||
self.ensure_games_loaded();
|
|
||||||
self.ensure_steam_users_loaded();
|
self.ensure_steam_users_loaded();
|
||||||
|
|
||||||
if let Some(error_message) = &self.image_selected_state.settings_error {
|
if let Some(error_message) = &self.image_selected_state.settings_error {
|
||||||
|
|||||||
+85
-53
@@ -2,15 +2,18 @@ use eframe::egui;
|
|||||||
use egui::ScrollArea;
|
use egui::ScrollArea;
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
|
|
||||||
|
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
||||||
use crate::config::get_renames_file;
|
use crate::config::get_renames_file;
|
||||||
use crate::settings::Settings;
|
use crate::platforms::ShortcutToImport;
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
use crate::steam::setup_proton_games;
|
||||||
use crate::sync;
|
use crate::sync;
|
||||||
|
|
||||||
use crate::sync::{download_images, SyncProgress};
|
use crate::sync::{download_images, SyncProgress};
|
||||||
|
|
||||||
use super::{backup_shortcuts, ImageSelectState};
|
use super::{backup_shortcuts, all_ready, get_all_games};
|
||||||
use super::{
|
use super::{
|
||||||
ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR},
|
ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR},
|
||||||
MyEguiApp,
|
MyEguiApp,
|
||||||
@@ -32,22 +35,12 @@ impl<T> FetcStatus<T> {
|
|||||||
FetcStatus::Fetched(_) => true,
|
FetcStatus::Fetched(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn needs_fetching(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
FetcStatus::NeedsFetched => true,
|
|
||||||
FetcStatus::Fetching => false,
|
|
||||||
FetcStatus::Fetched(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyEguiApp {
|
impl MyEguiApp {
|
||||||
pub(crate) fn render_import_games(&mut self, ui: &mut egui::Ui) {
|
pub(crate) fn render_import_games(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.heading("Import Games");
|
ui.heading("Import Games");
|
||||||
|
|
||||||
self.ensure_games_loaded();
|
|
||||||
|
|
||||||
let mut scroll_style = ui.style_mut();
|
let mut scroll_style = ui.style_mut();
|
||||||
scroll_style.visuals.extreme_bg_color = BACKGROUND_COLOR;
|
scroll_style.visuals.extreme_bg_color = BACKGROUND_COLOR;
|
||||||
scroll_style.visuals.widgets.inactive.bg_fill = EXTRA_BACKGROUND_COLOR;
|
scroll_style.visuals.widgets.inactive.bg_fill = EXTRA_BACKGROUND_COLOR;
|
||||||
@@ -60,15 +53,25 @@ impl MyEguiApp {
|
|||||||
.auto_shrink([false,true])
|
.auto_shrink([false,true])
|
||||||
.show(ui,|ui| {
|
.show(ui,|ui| {
|
||||||
ui.reset_style();
|
ui.reset_style();
|
||||||
|
|
||||||
let borrowed_games = &*self.games_to_sync.borrow();
|
|
||||||
match borrowed_games{
|
|
||||||
FetcStatus::Fetched(games_to_sync) => {
|
|
||||||
ui.label("Select the games you want to import into steam");
|
ui.label("Select the games you want to import into steam");
|
||||||
for (platform_name, shortcuts) in games_to_sync{
|
for (name,status) in &self.games_to_sync{
|
||||||
ui.heading(platform_name);
|
ui.heading(name);
|
||||||
|
match &*status.borrow(){
|
||||||
for shortcut in shortcuts {
|
FetcStatus::NeedsFetched => {ui.label("Need to find games");},
|
||||||
|
FetcStatus::Fetching => {
|
||||||
|
ui.horizontal(|ui|{
|
||||||
|
ui.spinner();
|
||||||
|
ui.label("Finding installed games");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
FetcStatus::Fetched(shortcuts) => {
|
||||||
|
match shortcuts{
|
||||||
|
Ok(shortcuts) => {
|
||||||
|
if shortcuts.is_empty(){
|
||||||
|
ui.label("Did not find any games");
|
||||||
|
}
|
||||||
|
for shortcut_to_import in shortcuts {
|
||||||
|
let shortcut = &shortcut_to_import.shortcut;
|
||||||
let mut import_game = !self.settings.blacklisted_games.contains(&shortcut.app_id);
|
let mut import_game = !self.settings.blacklisted_games.contains(&shortcut.app_id);
|
||||||
ui.horizontal(|ui|{
|
ui.horizontal(|ui|{
|
||||||
if self.current_edit == Option::Some(shortcut.app_id){
|
if self.current_edit == Option::Some(shortcut.app_id){
|
||||||
@@ -92,9 +95,7 @@ impl MyEguiApp {
|
|||||||
let checkbox = egui::Checkbox::new(&mut import_game,name);
|
let checkbox = egui::Checkbox::new(&mut import_game,name);
|
||||||
let response = ui.add(checkbox);
|
let response = ui.add(checkbox);
|
||||||
if response.double_clicked(){
|
if response.double_clicked(){
|
||||||
if !self.rename_map.contains_key(&shortcut.app_id){
|
self.rename_map.entry(shortcut.app_id).or_insert_with(|| shortcut.app_name.to_owned());
|
||||||
self.rename_map.insert(shortcut.app_id,shortcut.app_name.to_owned());
|
|
||||||
}
|
|
||||||
self.current_edit = Option::Some(shortcut.app_id);
|
self.current_edit = Option::Some(shortcut.app_id);
|
||||||
}
|
}
|
||||||
if response.clicked(){
|
if response.clicked(){
|
||||||
@@ -108,34 +109,22 @@ impl MyEguiApp {
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
ui.label("Check the settings if BoilR didn't find the game you where looking for");
|
|
||||||
},
|
},
|
||||||
_=> {
|
Err(err) => {
|
||||||
ui.ctx().request_repaint();
|
ui.label("Failed finding games").on_hover_text(format!("Error message: {err}"));
|
||||||
ui.horizontal(|ui|{
|
|
||||||
ui.spinner();
|
|
||||||
ui.label("Finding installed games");
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
|
||||||
|
ui.label("Check the settings if BoilR didn't find the game you where looking for");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_games_loaded(&mut self) {
|
|
||||||
if self.games_to_sync.borrow().needs_fetching() {
|
|
||||||
self.image_selected_state = ImageSelectState::default();
|
|
||||||
let (tx, rx) = watch::channel(FetcStatus::NeedsFetched);
|
|
||||||
self.games_to_sync = rx;
|
|
||||||
let settings = self.settings.clone();
|
|
||||||
self.rt.spawn_blocking(move || {
|
|
||||||
let _ = tx.send(FetcStatus::Fetching);
|
|
||||||
let games_to_sync = sync::get_platform_shortcuts(&settings);
|
|
||||||
let _ = tx.send(FetcStatus::Fetched(games_to_sync));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_sync(&mut self, wait: bool ) {
|
pub fn run_sync(&mut self, wait: bool ) {
|
||||||
let (sender, reciever) = watch::channel(SyncProgress::NotStarted);
|
let (sender, reciever) = watch::channel(SyncProgress::NotStarted);
|
||||||
@@ -144,18 +133,28 @@ impl MyEguiApp {
|
|||||||
crate::steam::ensure_steam_stopped();
|
crate::steam::ensure_steam_stopped();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO This might break cli sync, test it
|
||||||
|
|
||||||
self.status_reciever = reciever;
|
self.status_reciever = reciever;
|
||||||
let renames = self.rename_map.clone();
|
let renames = self.rename_map.clone();
|
||||||
|
let all_ready= all_ready(&self.games_to_sync);
|
||||||
|
let _ = sender.send(SyncProgress::Starting);
|
||||||
|
if all_ready{
|
||||||
|
let shortcuts_to_import = get_all_games(&self.games_to_sync);
|
||||||
let handle = self.rt.spawn_blocking(move || {
|
let handle = self.rt.spawn_blocking(move || {
|
||||||
MyEguiApp::save_settings_to_file(&settings);
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
setup_proton(shortcuts_to_import.iter());
|
||||||
|
|
||||||
|
let import_games = to_shortcut_owned(shortcuts_to_import);
|
||||||
|
|
||||||
let mut some_sender = Some(sender);
|
let mut some_sender = Some(sender);
|
||||||
backup_shortcuts(&settings.steam);
|
backup_shortcuts(&settings.steam);
|
||||||
let usersinfo = sync::run_sync(&settings, &mut some_sender,&renames).unwrap();
|
let usersinfo = sync::sync_shortcuts(&settings, &import_games, &mut some_sender,&renames).unwrap();
|
||||||
let task = download_images(&settings, &usersinfo, &mut some_sender);
|
let task = download_images(&settings, &usersinfo, &mut some_sender);
|
||||||
block_on(task);
|
block_on(task);
|
||||||
|
|
||||||
//Run a second time to fix up shortcuts after images are downloaded
|
//Run a second time to fix up shortcuts after images are downloaded
|
||||||
sync::run_sync(&settings, &mut some_sender,&renames).unwrap();
|
sync::sync_shortcuts(&settings, &import_games, &mut some_sender,&renames).unwrap();
|
||||||
|
|
||||||
if let Some(sender) = some_sender {
|
if let Some(sender) = some_sender {
|
||||||
let _ = sender.send(SyncProgress::Done);
|
let _ = sender.send(SyncProgress::Done);
|
||||||
@@ -168,10 +167,43 @@ impl MyEguiApp {
|
|||||||
self.rt.block_on(handle).unwrap();
|
self.rt.block_on(handle).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_settings_to_file(settings: &Settings) {
|
|
||||||
let toml = toml::to_string(&settings).unwrap();
|
}
|
||||||
let config_path = crate::config::get_config_file();
|
|
||||||
std::fs::write(config_path, toml).unwrap();
|
fn to_shortcut_owned(shortcuts_to_import: Vec<(String, Vec<ShortcutToImport>)>) -> Vec<(String, Vec<ShortcutOwned>)> {
|
||||||
|
let mut import_games = vec![];
|
||||||
|
for(name,infos) in shortcuts_to_import{
|
||||||
|
let mut shortcuts = vec![];
|
||||||
|
for info in infos{
|
||||||
|
shortcuts.push(info.shortcut);
|
||||||
|
}
|
||||||
|
import_games.push((name,shortcuts));
|
||||||
|
}
|
||||||
|
import_games
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
fn setup_proton<'a, I>(shortcut_infos: I)
|
||||||
|
where I: IntoIterator<Item = &'a (String,Vec<ShortcutToImport>)>
|
||||||
|
{
|
||||||
|
let mut shortcuts_to_proton = vec![];
|
||||||
|
|
||||||
|
for (name,shortcuts) in shortcut_infos {
|
||||||
|
for shortcut_info in shortcuts{
|
||||||
|
if shortcut_info.needs_proton {
|
||||||
|
crate::sync::symlinks::ensure_links_folder_created(&name);
|
||||||
|
}
|
||||||
|
if shortcut_info.needs_proton {
|
||||||
|
shortcuts_to_proton.push(format!("{}", shortcut_info.shortcut.app_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if shortcut_info.needs_symlinks {
|
||||||
|
crate::sync::symlinks::create_sym_links(&shortcut_info.shortcut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setup_proton_games(&shortcuts_to_proton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-262
@@ -2,10 +2,6 @@ use copypasta::ClipboardProvider;
|
|||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui::ScrollArea;
|
use egui::ScrollArea;
|
||||||
|
|
||||||
use crate::{egs::EpicPlatform};
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
use crate::heroic::HeroicPlatform;
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR},
|
ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR},
|
||||||
MyEguiApp,
|
MyEguiApp,
|
||||||
@@ -34,205 +30,14 @@ impl MyEguiApp {
|
|||||||
|
|
||||||
self.render_steam_settings(ui);
|
self.render_steam_settings(ui);
|
||||||
|
|
||||||
self.render_epic_settings(ui);
|
for platform in &mut self.platforms{
|
||||||
|
platform.render_ui(ui);
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
self.render_heroic_settings(ui);
|
|
||||||
self.render_bottles_settings(ui);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.render_legendary_settings(ui);
|
|
||||||
self.render_itch_settings(ui);
|
|
||||||
self.render_origin_settings(ui);
|
|
||||||
self.render_gog_settings(ui);
|
|
||||||
self.render_uplay_settings(ui);
|
|
||||||
self.render_lutris_settings(ui);
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
self.render_amazon_settings(ui);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
self.render_flatpak_settings(ui);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ui.add_space(SECTION_SPACING);
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
ui.label(format!("Version: {}", VERSION));
|
ui.label(format!("Version: {}", VERSION));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn render_flatpak_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Flatpak");
|
|
||||||
ui.checkbox(&mut self.settings.flatpak.enabled, "Import from Flatpak");
|
|
||||||
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn render_bottles_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Bottles");
|
|
||||||
ui.checkbox(&mut self.settings.bottles.enabled, "Import from Bottles");
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn render_heroic_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Heroic");
|
|
||||||
ui.checkbox(&mut self.settings.heroic.enabled, "Import from Heroic");
|
|
||||||
ui.checkbox(&mut self.settings.heroic.default_launch_through_heroic, "Always launch games through Heroic");
|
|
||||||
let safe_mode_header = match (self.settings.heroic.default_launch_through_heroic,self.settings.heroic.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.heroic.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.");
|
|
||||||
|
|
||||||
}
|
|
||||||
#[cfg(target_family = "unix")]{
|
|
||||||
let manifests =self.heroic_games.get_or_insert_with(||{
|
|
||||||
let heroic_setting = self.settings.heroic.clone();
|
|
||||||
|
|
||||||
let heroic_platform =HeroicPlatform{
|
|
||||||
settings:heroic_setting
|
|
||||||
};
|
|
||||||
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.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) ;
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_lutris_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Lutris");
|
|
||||||
ui.checkbox(&mut self.settings.lutris.enabled, "Import from Lutris");
|
|
||||||
if self.settings.lutris.enabled {
|
|
||||||
ui.checkbox(&mut self.settings.lutris.flatpak, "Flatpak version");
|
|
||||||
if !self.settings.lutris.flatpak {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let lutris_location = &mut self.settings.lutris.executable;
|
|
||||||
ui.label("Lutris Location: ");
|
|
||||||
ui.text_edit_singleline(lutris_location);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let flatpak_image = &mut self.settings.lutris.flatpak_image;
|
|
||||||
ui.label("Flatpak image");
|
|
||||||
ui.text_edit_singleline(flatpak_image);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn render_amazon_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Amazon");
|
|
||||||
ui.checkbox(&mut self.settings.amazon.enabled, "Import from Amazon");
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_uplay_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Uplay");
|
|
||||||
ui.checkbox(&mut self.settings.uplay.enabled, "Import from Uplay");
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_gog_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("GoG Galaxy");
|
|
||||||
ui.checkbox(&mut self.settings.gog.enabled, "Import from GoG Galaxy");
|
|
||||||
if self.settings.gog.enabled {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut empty_string = "".to_string();
|
|
||||||
let itch_location = self
|
|
||||||
.settings
|
|
||||||
.gog
|
|
||||||
.location
|
|
||||||
.as_mut()
|
|
||||||
.unwrap_or(&mut empty_string);
|
|
||||||
ui.label("GoG Galaxy Folder: ");
|
|
||||||
if ui.text_edit_singleline(itch_location).changed() {
|
|
||||||
self.settings.gog.location = Some(itch_location.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_origin_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Origin");
|
|
||||||
ui.checkbox(&mut self.settings.origin.enabled, "Import from Origin");
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_itch_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Itch.io");
|
|
||||||
ui.checkbox(&mut self.settings.itch.enabled, "Import from Itch.io");
|
|
||||||
if self.settings.itch.enabled {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut empty_string = "".to_string();
|
|
||||||
let itch_location = self
|
|
||||||
.settings
|
|
||||||
.itch
|
|
||||||
.location
|
|
||||||
.as_mut()
|
|
||||||
.unwrap_or(&mut empty_string);
|
|
||||||
ui.label("Itch.io Folder: ");
|
|
||||||
if ui.text_edit_singleline(itch_location).changed() {
|
|
||||||
self.settings.itch.location = if itch_location.is_empty(){
|
|
||||||
None
|
|
||||||
}else {
|
|
||||||
Some(itch_location.to_string())
|
|
||||||
};
|
|
||||||
}else{
|
|
||||||
if !itch_location.is_empty(){
|
|
||||||
if ui.button("Reset").on_hover_text("Reset the itch path, let BoilR guess again").clicked(){
|
|
||||||
self.settings.itch.location = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
ui.checkbox(&mut self.settings.itch.create_symlinks, "Create symlinks");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_steam_settings(&mut self, ui: &mut egui::Ui) {
|
fn render_steam_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.heading("Steam");
|
ui.heading("Steam");
|
||||||
@@ -317,68 +122,4 @@ self.settings.heroic.default_launch_through_heroic{
|
|||||||
}
|
}
|
||||||
ui.add_space(SECTION_SPACING);
|
ui.add_space(SECTION_SPACING);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_legendary_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Legendary & Rare");
|
|
||||||
ui.checkbox(
|
|
||||||
&mut self.settings.legendary.enabled,
|
|
||||||
"Import from Legendary & Rare",
|
|
||||||
);
|
|
||||||
if self.settings.legendary.enabled {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut empty_string = "".to_string();
|
|
||||||
let legendary_location = self
|
|
||||||
.settings
|
|
||||||
.legendary
|
|
||||||
.executable
|
|
||||||
.as_mut()
|
|
||||||
.unwrap_or(&mut empty_string);
|
|
||||||
ui.label("Legendary Executable: ")
|
|
||||||
.on_hover_text("The location of the legendary executable to use");
|
|
||||||
if ui.text_edit_singleline(legendary_location).changed() {
|
|
||||||
self.settings.legendary.executable = Some(legendary_location.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_epic_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
let epic_settings = &mut self.settings.epic_games;
|
|
||||||
ui.heading("Epic Games");
|
|
||||||
ui.checkbox(&mut epic_settings.enabled, "Import from Epic Games");
|
|
||||||
if epic_settings.enabled {
|
|
||||||
let safe_mode_header = match epic_settings.safe_launch.len() {
|
|
||||||
0 => "Force games to launch through Epic Launcher".to_string(),
|
|
||||||
1 => "One game forced to launch through Epic Launcher".to_string(),
|
|
||||||
x => format!("{} games forced to launch through Epic Launcher", x),
|
|
||||||
};
|
|
||||||
|
|
||||||
egui::CollapsingHeader::new(safe_mode_header)
|
|
||||||
.id_source("Epic_Launcher_safe_launch")
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.label("Some games must be started from the Epic Launcher, select those games below and BoilR will create shortcuts that opens the games through the Epic Launcher.");
|
|
||||||
let manifests =self.epic_manifests.get_or_insert_with(||{
|
|
||||||
let epic_platform = EpicPlatform::new(epic_settings);
|
|
||||||
let manifests = crate::platform::Platform::get_shortcuts(&epic_platform);
|
|
||||||
manifests.unwrap_or_default()
|
|
||||||
});
|
|
||||||
let mut safe_open_games = epic_settings.safe_launch.clone();
|
|
||||||
for manifest in manifests{
|
|
||||||
let key = manifest.get_key();
|
|
||||||
let display_name = &manifest.display_name;
|
|
||||||
let mut safe_open = safe_open_games.contains(display_name) || safe_open_games.contains(&key);
|
|
||||||
if ui.checkbox(&mut safe_open, display_name).clicked(){
|
|
||||||
if safe_open{
|
|
||||||
safe_open_games.push(key);
|
|
||||||
}else{
|
|
||||||
safe_open_games.retain(|m| m!= display_name && m!= &key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
epic_settings.safe_launch = safe_open_games;
|
|
||||||
}) ;
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-23
@@ -1,17 +1,18 @@
|
|||||||
use std::{collections::HashMap, error::Error};
|
use std::{collections::HashMap, error::Error, time::Duration};
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
use crate::heroic::HeroicGameType;
|
|
||||||
|
|
||||||
use eframe::{egui, App, Frame};
|
use eframe::{egui, App, Frame};
|
||||||
use egui::{ImageButton, Rounding, Stroke, TextureHandle};
|
use egui::{ImageButton, Rounding, Stroke, TextureHandle};
|
||||||
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
runtime::Runtime,
|
runtime::Runtime,
|
||||||
sync::watch::{self, Receiver},
|
sync::watch::{self, Receiver},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{egs::ManifestItem, settings::Settings, sync::SyncProgress, config::get_renames_file};
|
use crate::{
|
||||||
|
config::get_renames_file,
|
||||||
|
platforms::{get_platforms, GamesPlatform, Platforms, ShortcutToImport},
|
||||||
|
settings::{save_settings, Settings},
|
||||||
|
sync::{self, SyncProgress},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ui_colors::{
|
ui_colors::{
|
||||||
@@ -31,42 +32,62 @@ struct UiImages {
|
|||||||
save_button: Option<egui::TextureHandle>,
|
save_button: Option<egui::TextureHandle>,
|
||||||
logo_32: Option<egui::TextureHandle>,
|
logo_32: Option<egui::TextureHandle>,
|
||||||
}
|
}
|
||||||
|
type GamesToSync = Vec<(
|
||||||
|
String,
|
||||||
|
Receiver<FetcStatus<eyre::Result<Vec<ShortcutToImport>>>>,
|
||||||
|
)>;
|
||||||
|
|
||||||
|
pub(crate) fn all_ready(games: &GamesToSync) -> bool {
|
||||||
|
games.iter().all(|(_name, rx)| rx.borrow().is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_all_games(games: &GamesToSync) -> Vec<(String, Vec<ShortcutToImport>)> {
|
||||||
|
games
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(name, rx)| {
|
||||||
|
if let FetcStatus::Fetched(Ok(data)) = &*rx.borrow() {
|
||||||
|
Some((name.to_owned(), data.to_owned()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MyEguiApp {
|
pub struct MyEguiApp {
|
||||||
selected_menu: Menues,
|
selected_menu: Menues,
|
||||||
pub(crate) settings: Settings,
|
pub(crate) settings: Settings,
|
||||||
pub(crate) rt: Runtime,
|
pub(crate) rt: Runtime,
|
||||||
ui_images: UiImages,
|
ui_images: UiImages,
|
||||||
pub(crate) games_to_sync: Receiver<FetcStatus<Vec<(String, Vec<ShortcutOwned>)>>>,
|
pub(crate) games_to_sync: GamesToSync,
|
||||||
pub(crate) status_reciever: Receiver<SyncProgress>,
|
pub(crate) status_reciever: Receiver<SyncProgress>,
|
||||||
pub(crate) epic_manifests: Option<Vec<ManifestItem>>,
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
pub(crate) heroic_games: Option<Vec<HeroicGameType>>,
|
|
||||||
pub(crate) image_selected_state: ImageSelectState,
|
pub(crate) image_selected_state: ImageSelectState,
|
||||||
pub(crate) backup_state: BackupState,
|
pub(crate) backup_state: BackupState,
|
||||||
pub(crate) disconect_state: DiconnectState,
|
pub(crate) disconect_state: DiconnectState,
|
||||||
pub(crate) rename_map: HashMap<u32, String>,
|
pub(crate) rename_map: HashMap<u32, String>,
|
||||||
pub(crate) current_edit: Option<u32>,
|
pub(crate) current_edit: Option<u32>,
|
||||||
|
pub(crate) platforms: Platforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyEguiApp {
|
impl MyEguiApp {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let runtime = Runtime::new().unwrap();
|
let mut runtime = Runtime::new().unwrap();
|
||||||
|
let settings = Settings::new().expect("We must be able to load our settings");
|
||||||
|
let platforms = get_platforms();
|
||||||
|
let games_to_sync = create_games_to_sync(&mut runtime, &platforms);
|
||||||
Self {
|
Self {
|
||||||
selected_menu: Menues::Import,
|
selected_menu: Menues::Import,
|
||||||
settings: Settings::new().expect("We must be able to load our settings"),
|
settings,
|
||||||
rt: runtime,
|
rt: runtime,
|
||||||
games_to_sync: watch::channel(FetcStatus::NeedsFetched).1,
|
games_to_sync,
|
||||||
ui_images: UiImages::default(),
|
ui_images: UiImages::default(),
|
||||||
status_reciever: watch::channel(SyncProgress::NotStarted).1,
|
status_reciever: watch::channel(SyncProgress::NotStarted).1,
|
||||||
epic_manifests: None,
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
heroic_games: None,
|
|
||||||
image_selected_state: ImageSelectState::default(),
|
image_selected_state: ImageSelectState::default(),
|
||||||
backup_state: BackupState::default(),
|
backup_state: BackupState::default(),
|
||||||
disconect_state: DiconnectState::default(),
|
disconect_state: DiconnectState::default(),
|
||||||
rename_map: get_rename_map(),
|
rename_map: get_rename_map(),
|
||||||
current_edit: Option::None
|
current_edit: Option::None,
|
||||||
|
platforms,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,7 +103,7 @@ fn try_get_rename_map() -> Result<HashMap<u32,String>,Box<dyn Error>>{
|
|||||||
Ok(deserialized)
|
Ok(deserialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Clone)]
|
||||||
enum Menues {
|
enum Menues {
|
||||||
Import,
|
Import,
|
||||||
Settings,
|
Settings,
|
||||||
@@ -97,6 +118,23 @@ impl Default for Menues {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_games_to_sync(rt: &mut Runtime, platforms: &[Box<dyn GamesPlatform>]) -> GamesToSync {
|
||||||
|
let mut to_sync = vec![];
|
||||||
|
for platform in platforms {
|
||||||
|
if platform.enabled() {
|
||||||
|
let (tx, rx) = watch::channel(FetcStatus::NeedsFetched);
|
||||||
|
to_sync.push((platform.name().to_string(), rx));
|
||||||
|
let platform = platform.clone();
|
||||||
|
rt.spawn_blocking(move || {
|
||||||
|
let _ = tx.send(FetcStatus::Fetching);
|
||||||
|
let games_to_sync = sync::get_platform_shortcuts(platform);
|
||||||
|
let _ = tx.send(FetcStatus::Fetched(games_to_sync));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
to_sync
|
||||||
|
}
|
||||||
|
|
||||||
impl App for MyEguiApp {
|
impl App for MyEguiApp {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut Frame) {
|
||||||
let frame = egui::Frame::default()
|
let frame = egui::Frame::default()
|
||||||
@@ -111,6 +149,8 @@ impl App for MyEguiApp {
|
|||||||
ui.image(texture, size);
|
ui.image(texture, size);
|
||||||
ui.add_space(SECTION_SPACING);
|
ui.add_space(SECTION_SPACING);
|
||||||
|
|
||||||
|
let menu_before = self.selected_menu.clone();
|
||||||
|
|
||||||
let mut changed = ui
|
let mut changed = ui
|
||||||
.selectable_value(&mut self.selected_menu, Menues::Import, "Import Games")
|
.selectable_value(&mut self.selected_menu, Menues::Import, "Import Games")
|
||||||
.changed();
|
.changed();
|
||||||
@@ -138,9 +178,12 @@ impl App for MyEguiApp {
|
|||||||
if changed {
|
if changed {
|
||||||
self.backup_state.available_backups = None;
|
self.backup_state.available_backups = None;
|
||||||
}
|
}
|
||||||
if changed && self.selected_menu == Menues::Settings {
|
if changed
|
||||||
|
&& menu_before == Menues::Settings
|
||||||
|
&& self.selected_menu == Menues::Import
|
||||||
|
{
|
||||||
//We reset games here, since user might change settings
|
//We reset games here, since user might change settings
|
||||||
self.games_to_sync = watch::channel(FetcStatus::NeedsFetched).1;
|
self.games_to_sync = create_games_to_sync(&mut self.rt, &self.platforms);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -153,11 +196,11 @@ impl App for MyEguiApp {
|
|||||||
let save_button = ImageButton::new(texture, size * 0.5);
|
let save_button = ImageButton::new(texture, size * 0.5);
|
||||||
|
|
||||||
if ui.add(save_button).on_hover_text("Save settings").clicked() {
|
if ui.add(save_button).on_hover_text("Save settings").clicked() {
|
||||||
MyEguiApp::save_settings_to_file(&self.settings.clone());
|
save_settings(&self.settings, &self.platforms);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if self.games_to_sync.borrow().is_some() {
|
if self.selected_menu == Menues::Import {
|
||||||
egui::TopBottomPanel::new(egui::panel::TopBottomSide::Bottom, "Bottom Panel")
|
egui::TopBottomPanel::new(egui::panel::TopBottomSide::Bottom, "Bottom Panel")
|
||||||
.frame(frame)
|
.frame(frame)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
@@ -186,11 +229,13 @@ impl App for MyEguiApp {
|
|||||||
ui.label(&status_string);
|
ui.label(&status_string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let all_ready = all_ready(&self.games_to_sync);
|
||||||
|
|
||||||
let texture = self.get_import_image(ui);
|
let texture = self.get_import_image(ui);
|
||||||
let size = texture.size_vec2();
|
let size = texture.size_vec2();
|
||||||
let image_button = ImageButton::new(texture, size * 0.5);
|
let image_button = ImageButton::new(texture, size * 0.5);
|
||||||
if ui
|
if all_ready
|
||||||
|
&& ui
|
||||||
.add(image_button)
|
.add(image_button)
|
||||||
.on_hover_text("Import your games into steam")
|
.on_hover_text("Import your games into steam")
|
||||||
.clicked()
|
.clicked()
|
||||||
@@ -293,6 +338,10 @@ fn setup(ctx: &egui::Context) {
|
|||||||
}
|
}
|
||||||
pub fn run_sync() {
|
pub fn run_sync() {
|
||||||
let mut app = MyEguiApp::new();
|
let mut app = MyEguiApp::new();
|
||||||
|
while !all_ready(&app.games_to_sync) {
|
||||||
|
println!("Finding games, trying again in 500ms");
|
||||||
|
std::thread::sleep(Duration::from_secs_f32(0.5));
|
||||||
|
}
|
||||||
app.run_sync(true);
|
app.run_sync(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
mod game;
|
|
||||||
mod platform;
|
|
||||||
mod settings;
|
|
||||||
|
|
||||||
pub use platform::Uplay;
|
|
||||||
pub use settings::UplaySettings;
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
use crate::platform::Platform;
|
|
||||||
use std::error::Error;
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
use std::path::Path;
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use super::{game::Game, settings::UplaySettings};
|
|
||||||
|
|
||||||
pub struct Uplay {
|
|
||||||
pub settings: UplaySettings,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Platform<Game, Box<dyn Error>> for Uplay {
|
|
||||||
fn enabled(&self) -> bool {
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
false
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
self.settings.enabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Uplay"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
//Linux not supported yet
|
|
||||||
crate::platform::SettingsValidity::Invalid {
|
|
||||||
reason: "Linux not supported yet".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
if get_launcher_path().is_some() {
|
|
||||||
crate::platform::SettingsValidity::Valid
|
|
||||||
} else {
|
|
||||||
crate::platform::SettingsValidity::Invalid {
|
|
||||||
reason: "Could not find UPlay instalation".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<Game>, Box<dyn Error>> {
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
get_games_from_winreg()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn create_symlinks(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &Game) -> bool {
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
return false;
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
//TODO update this when uplay gets proton support on linux
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn get_launcher_path() -> Option<PathBuf> {
|
|
||||||
use winreg::enums::*;
|
|
||||||
use winreg::RegKey;
|
|
||||||
|
|
||||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
|
||||||
if let Ok(launcher_key) = hklm.open_subkey("SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher") {
|
|
||||||
let launcher_dir: Result<String, _> = launcher_key.get_value("InstallDir");
|
|
||||||
if let Ok(launcher_dir) = launcher_dir {
|
|
||||||
let path = Path::new(&launcher_dir).join("upc.exe");
|
|
||||||
if path.exists() {
|
|
||||||
return Some(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn get_games_from_winreg() -> Result<Vec<Game>, Box<dyn Error>> {
|
|
||||||
use winreg::enums::*;
|
|
||||||
use winreg::RegKey;
|
|
||||||
|
|
||||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
|
||||||
let mut games = vec![];
|
|
||||||
let mut installed_ids = vec![];
|
|
||||||
let launcher_path =
|
|
||||||
get_launcher_path().expect("This should only be called if launcher is found");
|
|
||||||
|
|
||||||
if let Ok(installs) = hklm.open_subkey("SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher\\Installs") {
|
|
||||||
for i in installs.enum_keys().filter_map(|i| i.ok()) {
|
|
||||||
if let Ok(install) = installs.open_subkey(&i) {
|
|
||||||
let install_dir: Result<String, _> = install.get_value("InstallDir");
|
|
||||||
if let Ok(folder) = install_dir {
|
|
||||||
let path = Path::new(&folder);
|
|
||||||
if path.exists() {
|
|
||||||
installed_ids.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in installed_ids {
|
|
||||||
let path = format!("SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Uplay Install {}",id);
|
|
||||||
let subkey = hklm.open_subkey(path);
|
|
||||||
if let Ok(subkey) = subkey {
|
|
||||||
let name: Result<String, _> = subkey.get_value("DisplayName");
|
|
||||||
if let Ok(name) = name {
|
|
||||||
let icon: String = subkey.get_value("DisplayIcon").unwrap_or_default();
|
|
||||||
games.push(Game {
|
|
||||||
name,
|
|
||||||
icon,
|
|
||||||
id,
|
|
||||||
launcher: launcher_path.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(games)
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
|
||||||
pub struct UplaySettings {
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user