Skip to content

Commit

Permalink
Initial playnite support (#310)
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilipK authored Jan 5, 2023
1 parent 4ed9490 commit ee76886
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 19 deletions.
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,
}

0 comments on commit ee76886

Please sign in to comment.