mirror of
https://github.com/djibux/BoilR.git
synced 2026-09-01 05:53:41 +02:00
Initial playnite support (#310)
This commit is contained in:
@@ -58,23 +58,24 @@ There is also an [AUR package](https://aur.archlinux.org/packages/steam-boilr-gu
|
|||||||
|
|
||||||
BoilR can import games from many platforms, but there are limits based
|
BoilR can import games from many platforms, but there are limits based
|
||||||
|
|
||||||
| Platforms | Windows | Linux (executable) | Linux (flatpak) |
|
| Platforms | Windows | Linux (executable) | Linux (flatpak) |
|
||||||
| ------------------------------------------------------------------------------- | ------- | --------------------------- | ------------------------------------------------------------------------- |
|
| ------------------------------------------------------------------------------- | ------- | ----------------------------- | ----------------------------------------- |
|
||||||
| [Epic Games Store](https://www.epicgames.com/) | Yes | Yes, install through proton | Yes, install through proton |
|
| [Epic Games Store](https://www.epicgames.com/) | Yes | Yes, install through proton | Yes, install through proton |
|
||||||
| [Itch.io](https://itch.io/app) (Windows Games) | Yes | No | No |
|
| [Itch.io](https://itch.io/app) (Windows Games) | Yes | No | No |
|
||||||
| [Itch.io](https://itch.io/app) (Linux Games) | No | Yes | Yes |
|
| [Itch.io](https://itch.io/app) (Linux Games) | No | Yes | Yes |
|
||||||
| [Origin](https://www.origin.com) | Yes | Yes, install through proton | Yes, install through proton |
|
| [Origin](https://www.origin.com) | Yes | Yes, install through proton | Yes, install through proton |
|
||||||
| [GOG](https://www.gog.com/galaxy) | Yes | No (Use Heroic or MiniGalaxy) | No (Use Heroic or MiniGalaxy) |
|
| [GOG](https://www.gog.com/galaxy) | Yes | No (Use Heroic or MiniGalaxy) | No (Use Heroic or MiniGalaxy) |
|
||||||
| [UPlay](https://ubisoftconnect.com) | Yes | No | No |
|
| [UPlay](https://ubisoftconnect.com) | Yes | No | No |
|
||||||
| [Lutris](https://github.com/lutris/lutris) (Flatpak) | No | Yes | Yes (make sure Lutris is shut down first) |
|
| [Lutris](https://github.com/lutris/lutris) (Flatpak) | No | Yes | Yes (make sure Lutris is shut down first) |
|
||||||
| [Lutris](https://github.com/lutris/lutris) (Non-Flatpak) | No | Yes | Yes |
|
| [Lutris](https://github.com/lutris/lutris) (Non-Flatpak) | No | Yes | Yes |
|
||||||
| [Legendary](https://github.com/derrod/legendary) | No | Yes | Yes |
|
| [Legendary](https://github.com/derrod/legendary) | No | Yes | Yes |
|
||||||
| [Rare](https://github.com/Dummerle/Rare/releases) | No | Yes | Yes |
|
| [Rare](https://github.com/Dummerle/Rare/releases) | No | Yes | Yes |
|
||||||
| [Heroic Launcher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher) | No | Yes | Yes |
|
| [Heroic Launcher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher) | No | Yes | Yes |
|
||||||
| [Amazon Games](https://gaming.amazon.com) | Yes | No | No |
|
| [Amazon Games](https://gaming.amazon.com) | Yes | No | No |
|
||||||
| [Flatpaks](https://flathub.org/) | No | Yes | Yes |
|
| [Flatpaks](https://flathub.org/) | No | Yes | Yes |
|
||||||
| [Bottles](https://usebottles.com/) | No | Yes | Yes |
|
| [Bottles](https://usebottles.com/) | No | Yes | Yes |
|
||||||
| [MiniGalaxy](https://sharkwouter.github.io/minigalaxy/) | No | Yes | Yes |
|
| [MiniGalaxy](https://sharkwouter.github.io/minigalaxy/) | No | Yes | Yes |
|
||||||
|
| [Playnite](https://playnite.link/) | Yes | No | No |
|
||||||
|
|
||||||
## Getting cover art for your shortcuts
|
## Getting cover art for your shortcuts
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ mod minigalaxy;
|
|||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
mod amazon;
|
mod amazon;
|
||||||
|
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
mod playnite;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
mod gog;
|
mod gog;
|
||||||
mod itch;
|
mod itch;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::collections::HashMap;
|
|||||||
use super::GamesPlatform;
|
use super::GamesPlatform;
|
||||||
|
|
||||||
use crate::settings::load_setting_sections;
|
use crate::settings::load_setting_sections;
|
||||||
const PLATFORM_NAMES: [&str; 12] = [
|
const PLATFORM_NAMES: [&str; 13] = [
|
||||||
"amazon",
|
"amazon",
|
||||||
"bottles",
|
"bottles",
|
||||||
"epic_games",
|
"epic_games",
|
||||||
@@ -17,6 +17,7 @@ const PLATFORM_NAMES: [&str; 12] = [
|
|||||||
"origin",
|
"origin",
|
||||||
"uplay",
|
"uplay",
|
||||||
"minigalaxy",
|
"minigalaxy",
|
||||||
|
"playnite",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub type Platforms = Vec<Box<dyn GamesPlatform>>;
|
pub type Platforms = Vec<Box<dyn GamesPlatform>>;
|
||||||
@@ -32,8 +33,10 @@ pub fn load_platform<A: AsRef<str>, B: AsRef<str>>(
|
|||||||
{
|
{
|
||||||
//Windows only platforms
|
//Windows only platforms
|
||||||
use super::amazon::AmazonPlatform;
|
use super::amazon::AmazonPlatform;
|
||||||
|
use super::playnite::PlaynitePlatform;
|
||||||
match name {
|
match name {
|
||||||
"amazon" => return load::<AmazonPlatform>(s),
|
"amazon" => return load::<AmazonPlatform>(s),
|
||||||
|
"playnite" => return load::<PlaynitePlatform>(s),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +61,7 @@ pub fn load_platform<A: AsRef<str>, B: AsRef<str>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Common platforms
|
//Common platforms
|
||||||
use super::egs::EpicPlatform;
|
use super::egs::EpicPlatform;
|
||||||
use super::gog::GogPlatform;
|
use super::gog::GogPlatform;
|
||||||
use super::itch::ItchPlatform;
|
use super::itch::ItchPlatform;
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mod playnite_platform;
|
||||||
|
mod playnite_parser;
|
||||||
|
pub use playnite_platform::PlaynitePlatform;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use nom::{
|
||||||
|
bytes::{
|
||||||
|
complete::{take_until, take_while},
|
||||||
|
streaming::take,
|
||||||
|
},
|
||||||
|
character::is_alphanumeric,
|
||||||
|
multi::many0,
|
||||||
|
IResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) struct NamesAndId {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_db<'a>(content: &'a [u8]) -> nom::IResult<&'a [u8], Vec<NamesAndId>> {
|
||||||
|
many0(parse_game)(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_game(i: &[u8]) -> nom::IResult<&[u8], NamesAndId> {
|
||||||
|
let (i, _taken) = take_until("_id")(i)?;
|
||||||
|
let (i, _taken) = take_until("Image")(i)?;
|
||||||
|
let (i, prefix_and_id) = take_until("\\")(i)?;
|
||||||
|
let id_bytes = prefix_and_id
|
||||||
|
.split(|b| *b == 0 as u8)
|
||||||
|
.last()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let id = String::from_utf8_lossy(id_bytes).to_string();
|
||||||
|
|
||||||
|
let (i, _taken) = take_until("InstallSizeGroup")(i)?;
|
||||||
|
let (i, _taken) = take_until("Name")(i)?;
|
||||||
|
let (i, _taken) = take(4usize)(i)?;
|
||||||
|
let (i, _taken) = take_while(|b| !is_alphanumeric(b))(i)?;
|
||||||
|
let (i, name_bytes) = take_while(|b| b != 0)(i)?;
|
||||||
|
let name = String::from_utf8_lossy(name_bytes).to_string();
|
||||||
|
IResult::Ok((i, NamesAndId { id, name }))
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
||||||
|
|
||||||
|
use crate::platforms::{load_settings, to_shortcuts_simple, FromSettingsString, GamesPlatform};
|
||||||
|
|
||||||
|
use super::playnite_parser::parse_db;
|
||||||
|
|
||||||
|
impl FromSettingsString for PlaynitePlatform {
|
||||||
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
|
PlaynitePlatform {
|
||||||
|
settings: load_settings(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PlaynitePlatform {
|
||||||
|
pub settings: PlayniteSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct PlayniteSettings {
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PlayniteSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { enabled: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GamesPlatform for PlaynitePlatform {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"Playnite"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_name(&self) -> &str {
|
||||||
|
"playnite"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.settings.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_info(&self) -> eyre::Result<Vec<crate::platforms::ShortcutToImport>> {
|
||||||
|
to_shortcuts_simple(self.get_playnite_games())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings_serilizable(&self) -> String {
|
||||||
|
toml::to_string(&self.settings).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Playnite");
|
||||||
|
ui.checkbox(&mut self.settings.enabled, "Import from Playnite");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlaynitePlatform {
|
||||||
|
fn get_playnite_games(&self) -> eyre::Result<Vec<PlayniteGame>> {
|
||||||
|
let mut res = vec![];
|
||||||
|
let app_data_path = env::var("APPDATA")?;
|
||||||
|
let app_data_local_path = env::var("LOCALAPPDATA")?;
|
||||||
|
let launcher_path = Path::new(&app_data_local_path)
|
||||||
|
.join("Playnite")
|
||||||
|
.join("Playnite.DesktopApp.exe");
|
||||||
|
if !launcher_path.exists() {
|
||||||
|
return Err(eyre::eyre!("Did not find Playnite installation"));
|
||||||
|
}
|
||||||
|
let launcher_path = launcher_path.to_string_lossy().to_string();
|
||||||
|
let playnite_folder = Path::new(&app_data_path).join("Playnite");
|
||||||
|
let games_file_path = playnite_folder.join("library").join("games.db");
|
||||||
|
if games_file_path.exists() {
|
||||||
|
let games_bytes = std::fs::read(&games_file_path).unwrap();
|
||||||
|
let (_, games) = parse_db(&games_bytes).map_err(|e| eyre::eyre!(e.to_string()))?;
|
||||||
|
for game in games {
|
||||||
|
res.push(PlayniteGame {
|
||||||
|
id: game.id,
|
||||||
|
launcher_path: launcher_path.clone().into(),
|
||||||
|
name: game.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PlayniteGame> for ShortcutOwned {
|
||||||
|
fn from(game: PlayniteGame) -> Self {
|
||||||
|
let launch = format!("--hidesplashscreen --start {}", 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.name.as_str(),
|
||||||
|
exe.as_str(),
|
||||||
|
start_dir.as_str(),
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
launch.as_str(),
|
||||||
|
)
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PlayniteGame {
|
||||||
|
pub name: String,
|
||||||
|
pub id: String,
|
||||||
|
pub launcher_path: PathBuf,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user