Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial playnite support #310

Merged
merged 1 commit into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

| Platforms | Windows | Linux (executable) | Linux (flatpak) |
| ------------------------------------------------------------------------------- | ------- | --------------------------- | ------------------------------------------------------------------------- |
| [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) (Linux Games) | No | Yes | Yes |
| [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) |
| [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) (Non-Flatpak) | No | Yes | Yes |
| [Legendary](https://github.com/derrod/legendary) | 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 |
| [Amazon Games](https://gaming.amazon.com) | Yes | No | No |
| [Flatpaks](https://flathub.org/) | No | Yes | Yes |
| [Bottles](https://usebottles.com/) | No | Yes | Yes |
| [MiniGalaxy](https://sharkwouter.github.io/minigalaxy/) | No | Yes | Yes |
| Platforms | Windows | Linux (executable) | Linux (flatpak) |
| ------------------------------------------------------------------------------- | ------- | ----------------------------- | ----------------------------------------- |
| [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) (Linux Games) | No | Yes | Yes |
| [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) |
| [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) (Non-Flatpak) | No | Yes | Yes |
| [Legendary](https://github.com/derrod/legendary) | 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 |
| [Amazon Games](https://gaming.amazon.com) | Yes | No | No |
| [Flatpaks](https://flathub.org/) | No | Yes | Yes |
| [Bottles](https://usebottles.com/) | 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

Expand Down
4 changes: 4 additions & 0 deletions src/platforms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ mod minigalaxy;
#[cfg(not(target_family = "unix"))]
mod amazon;

#[cfg(not(target_family = "unix"))]
mod playnite;



mod gog;
mod itch;
Expand Down
7 changes: 5 additions & 2 deletions src/platforms/platforms_load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::HashMap;
use super::GamesPlatform;

use crate::settings::load_setting_sections;
const PLATFORM_NAMES: [&str; 12] = [
const PLATFORM_NAMES: [&str; 13] = [
"amazon",
"bottles",
"epic_games",
Expand All @@ -17,6 +17,7 @@ const PLATFORM_NAMES: [&str; 12] = [
"origin",
"uplay",
"minigalaxy",
"playnite",
];

pub type Platforms = Vec<Box<dyn GamesPlatform>>;
Expand All @@ -32,8 +33,10 @@ pub fn load_platform<A: AsRef<str>, B: AsRef<str>>(
{
//Windows only platforms
use super::amazon::AmazonPlatform;
use super::playnite::PlaynitePlatform;
match name {
"amazon" => return load::<AmazonPlatform>(s),
"playnite" => return load::<PlaynitePlatform>(s),
_ => {}
}
}
Expand All @@ -58,7 +61,7 @@ pub fn load_platform<A: AsRef<str>, B: AsRef<str>>(
}
}

//Common platforms
//Common platforms
use super::egs::EpicPlatform;
use super::gog::GogPlatform;
use super::itch::ItchPlatform;
Expand Down
3 changes: 3 additions & 0 deletions src/platforms/playnite/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod playnite_platform;
mod playnite_parser;
pub use playnite_platform::PlaynitePlatform;
37 changes: 37 additions & 0 deletions src/platforms/playnite/playnite_parser.rs
Original file line number Diff line number Diff line change
@@ -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 }))
}
119 changes: 119 additions & 0 deletions src/platforms/playnite/playnite_platform.rs
Original file line number Diff line number Diff line change
@@ -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,
}