mirror of
https://github.com/djibux/BoilR.git
synced 2026-09-01 05:53:41 +02:00
Add Ubisoft connect (Linux Proton) support (#346)
* Added Ubisoft connect (Linux Proton) support * Update src/platforms/uplay/platform.rs Co-authored-by: Philip Kristoffersen <philipkristoffersen@gmail.com> * Update src/platforms/uplay/platform.rs Co-authored-by: Philip Kristoffersen <philipkristoffersen@gmail.com> * Fixed needing "find_subsequence". * Update platform.rs * Update platform.rs * Fix trying to use 'None' as a function. --------- Co-authored-by: Ofacy <git@ofacy.com> Co-authored-by: Philip Kristoffersen <philipkristoffersen@gmail.com>
This commit is contained in:
co-authored by
Philip Kristoffersen
Ofacy
parent
7df543f586
commit
c092ffba88
@@ -8,11 +8,18 @@ pub(crate) struct UplayGame {
|
|||||||
pub(crate) icon: String,
|
pub(crate) icon: String,
|
||||||
pub(crate) id: String,
|
pub(crate) id: String,
|
||||||
pub(crate) launcher: PathBuf,
|
pub(crate) launcher: PathBuf,
|
||||||
|
pub(crate) launcher_compat_folder: Option<PathBuf>,
|
||||||
|
pub(crate) launch_id: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<UplayGame> for ShortcutOwned {
|
impl From<UplayGame> for ShortcutOwned {
|
||||||
fn from(game: UplayGame) -> Self {
|
fn from(game: UplayGame) -> Self {
|
||||||
let launch = format!("\"uplay://launch/{}/0\"", game.id);
|
let launch = match game.launcher_compat_folder{
|
||||||
|
Some(compat_folder) => format!
|
||||||
|
("STEAM_COMPAT_DATA_PATH=\"{}\" %command% \"uplay://launch/{}/{}\"",
|
||||||
|
compat_folder.to_string_lossy(), game.id, game.launch_id),
|
||||||
|
None => format!("\"uplay://launch/{}/{}\"", game.id, game.launch_id)
|
||||||
|
};
|
||||||
let start_dir = game
|
let start_dir = game
|
||||||
.launcher
|
.launcher
|
||||||
.parent()
|
.parent()
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
#[cfg(target_os = "windows")]
|
//All of this is technically related to Ubisoft Connnect, not Ubisoft Play.
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use std::io::Read;
|
||||||
|
use std::io::BufReader;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
use crate::platforms::load_settings;
|
use crate::platforms::load_settings;
|
||||||
use crate::platforms::to_shortcuts_simple;
|
use crate::platforms::to_shortcuts_simple;
|
||||||
use crate::platforms::FromSettingsString;
|
use crate::platforms::FromSettingsString;
|
||||||
use crate::platforms::GamesPlatform;
|
use crate::platforms::GamesPlatform;
|
||||||
use crate::platforms::ShortcutToImport;
|
use crate::platforms::ShortcutToImport;
|
||||||
|
use crate::platforms::NeedsPorton;
|
||||||
|
|
||||||
use super::{game::UplayGame, settings::UplaySettings};
|
use super::{game::UplayGame, settings::UplaySettings};
|
||||||
|
|
||||||
@@ -16,10 +21,27 @@ pub struct UplayPlatform {
|
|||||||
pub settings: UplaySettings,
|
pub settings: UplaySettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NeedsPorton<UplayPlatform> for UplayGame {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn needs_proton(&self, _platform: &UplayPlatform) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
fn needs_proton(&self, _platform: &UplayPlatform) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_symlinks(&self, _platform: &UplayPlatform) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn get_uplay_games() -> eyre::Result<Vec<UplayGame>> {
|
fn get_uplay_games() -> eyre::Result<Vec<UplayGame>> {
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
{
|
{
|
||||||
Err(eyre::format_err!("Uplay is not supported on Linux"))
|
get_games_from_proton()
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
@@ -27,8 +49,64 @@ fn get_uplay_games() -> eyre::Result<Vec<UplayGame>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct UplayPathData {
|
||||||
|
//~/.steam/steam/steamapps/compatdata/X/pfx/drive_c/Program Files (x86)/Ubisoft/Ubisoft Game Launcher/upc.exe
|
||||||
|
exe_path: PathBuf,
|
||||||
|
//~/.steam/steam/steamapps/compatdata/X/pfx/drive_c/Program Files (x86)/Ubisoft/Ubisoft Game Launcher/games/
|
||||||
|
games_path: PathBuf,
|
||||||
|
//~/.steam/steam/steamapps/compatdata/X
|
||||||
|
compat_folder: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
fn get_launcher_path() -> eyre::Result<UplayPathData> {
|
||||||
|
let mut res = UplayPathData::default();
|
||||||
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
|
let compat_folder_path = Path::new(&home)
|
||||||
|
.join(".steam")
|
||||||
|
.join("steam")
|
||||||
|
.join("steamapps")
|
||||||
|
.join("compatdata");
|
||||||
|
|
||||||
|
if let Ok(compat_folder) = std::fs::read_dir(compat_folder_path) {
|
||||||
|
for dir in compat_folder.flatten() {
|
||||||
|
let uplay_exe_path = dir
|
||||||
|
.path()
|
||||||
|
.join("pfx")
|
||||||
|
.join("drive_c")
|
||||||
|
.join("Program Files (x86)")
|
||||||
|
.join("Ubisoft")
|
||||||
|
.join("Ubisoft Game Launcher")
|
||||||
|
.join("upc.exe");
|
||||||
|
|
||||||
|
let uplay_games = dir
|
||||||
|
.path()
|
||||||
|
.join("pfx")
|
||||||
|
.join("drive_c")
|
||||||
|
.join("Program Files (x86)")
|
||||||
|
.join("Ubisoft")
|
||||||
|
.join("Ubisoft Game Launcher")
|
||||||
|
.join("games");
|
||||||
|
|
||||||
|
if uplay_exe_path.exists() && uplay_games.exists() {
|
||||||
|
res.exe_path = uplay_exe_path;
|
||||||
|
res.games_path = uplay_games;
|
||||||
|
res.compat_folder = Some(dir.path());
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(eyre::eyre!(
|
||||||
|
"Could not find uplay launcher"))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn get_launcher_path() -> eyre::Result<PathBuf> {
|
fn get_launcher_path() -> eyre::Result<UplayPathData> {
|
||||||
|
let mut res = UplayPathData::default();
|
||||||
use winreg::enums::*;
|
use winreg::enums::*;
|
||||||
use winreg::RegKey;
|
use winreg::RegKey;
|
||||||
|
|
||||||
@@ -37,7 +115,8 @@ fn get_launcher_path() -> eyre::Result<PathBuf> {
|
|||||||
let launcher_dir: String = launcher_key.get_value("InstallDir")?;
|
let launcher_dir: String = launcher_key.get_value("InstallDir")?;
|
||||||
let path = Path::new(&launcher_dir).join("upc.exe");
|
let path = Path::new(&launcher_dir).join("upc.exe");
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
Ok(path)
|
res.exe_path = path;
|
||||||
|
Ok(res)
|
||||||
} else {
|
} else {
|
||||||
Err(eyre::eyre!(
|
Err(eyre::eyre!(
|
||||||
"Could not find uplay launcher at path {:?}",
|
"Could not find uplay launcher at path {:?}",
|
||||||
@@ -54,7 +133,7 @@ fn get_games_from_winreg() -> eyre::Result<Vec<UplayGame>> {
|
|||||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||||
let mut games = vec![];
|
let mut games = vec![];
|
||||||
let mut installed_ids = vec![];
|
let mut installed_ids = vec![];
|
||||||
let launcher_path = get_launcher_path()?;
|
let launcher_path = get_launcher_path()?.exe_path;
|
||||||
|
|
||||||
if let Ok(installs) = hklm.open_subkey("SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher\\Installs") {
|
if let Ok(installs) = hklm.open_subkey("SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher\\Installs") {
|
||||||
for i in installs.enum_keys().filter_map(|i| i.ok()) {
|
for i in installs.enum_keys().filter_map(|i| i.ok()) {
|
||||||
@@ -82,6 +161,8 @@ fn get_games_from_winreg() -> eyre::Result<Vec<UplayGame>> {
|
|||||||
icon,
|
icon,
|
||||||
id,
|
id,
|
||||||
launcher: launcher_path.clone(),
|
launcher: launcher_path.clone(),
|
||||||
|
launcher_compat_folder: None,
|
||||||
|
launch_id: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +171,99 @@ fn get_games_from_winreg() -> eyre::Result<Vec<UplayGame>> {
|
|||||||
Ok(games)
|
Ok(games)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
fn get_games_from_proton() -> eyre::Result<Vec<UplayGame>> {
|
||||||
|
let mut games = vec![];
|
||||||
|
|
||||||
|
let launcher_path = get_launcher_path()?;
|
||||||
|
let parent = launcher_path.exe_path.parent().unwrap_or_else(|| Path::new("/"));
|
||||||
|
let file = File::open(parent
|
||||||
|
.join("cache")
|
||||||
|
.join("configuration")
|
||||||
|
.join("configurations"))?;
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
// Read file into vector.
|
||||||
|
reader.read_to_end(&mut buffer)?;
|
||||||
|
|
||||||
|
let mut splits: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
while !buffer.is_empty() {
|
||||||
|
let game_header = b"version: 2.0";
|
||||||
|
let foundindex: usize = match buffer.windows(game_header.len()).position(|window| window == game_header) {
|
||||||
|
Some(index) => {index},
|
||||||
|
None => {break;},
|
||||||
|
};
|
||||||
|
let (mut first, second) = buffer.split_at(foundindex);
|
||||||
|
if first.len() >= 14usize {
|
||||||
|
first = first.split_at(first.len()-14).0;
|
||||||
|
}
|
||||||
|
splits.push(unsafe {std::str::from_utf8_unchecked(first).to_string()});
|
||||||
|
buffer = second.split_at("version: 2.0".len()).1.to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for gameconfig in splits {
|
||||||
|
if !gameconfig.contains("executables:") {continue};
|
||||||
|
if !gameconfig.contains("online:") {continue};
|
||||||
|
if !gameconfig.contains("shortcut_name:") {continue};
|
||||||
|
if !gameconfig.contains("register:") {continue};
|
||||||
|
|
||||||
|
let mut inonline = false;
|
||||||
|
let mut shortcut_name: String = "".to_string();
|
||||||
|
let mut game_id: String = "".to_string();
|
||||||
|
let mut icon_image: PathBuf = "".into();
|
||||||
|
let mut launch_id = 0;
|
||||||
|
for line in gameconfig.split('\n') {
|
||||||
|
let trimed = line.trim();
|
||||||
|
if trimed.starts_with("online:") {
|
||||||
|
inonline = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if trimed.starts_with("offline:") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if trimed.starts_with("icon_image: ") {
|
||||||
|
let split = trimed.split_at("icon_image: ".len()).1;
|
||||||
|
if split.is_empty() {break}; // invalid config.
|
||||||
|
icon_image = parent.join("data").join("games").join(split);
|
||||||
|
}
|
||||||
|
if !inonline {continue};
|
||||||
|
if trimed.starts_with("- shortcut_name:") {
|
||||||
|
let split = trimed.split_at("- shortcut_name:".len()).1;
|
||||||
|
if split.is_empty() {break}; // invalid config.
|
||||||
|
shortcut_name = split.to_string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if trimed.starts_with("register: ") {
|
||||||
|
let split = trimed.split_at("register: ".len()).1;
|
||||||
|
if split.is_empty() {break}; // invalid config.
|
||||||
|
game_id = split
|
||||||
|
.strip_prefix("HKEY_LOCAL_MACHINE\\SOFTWARE\\Ubisoft\\Launcher\\Installs\\").unwrap_or_default()
|
||||||
|
.strip_suffix("\\InstallDir").unwrap_or_default().to_string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if trimed == "denuvo: yes" {
|
||||||
|
games.push(UplayGame {
|
||||||
|
name: shortcut_name.clone(),
|
||||||
|
icon: icon_image.to_string_lossy().to_string(),
|
||||||
|
id: game_id.clone(),
|
||||||
|
launcher: launcher_path.exe_path.clone(),
|
||||||
|
launcher_compat_folder: launcher_path.compat_folder.clone(),
|
||||||
|
launch_id,
|
||||||
|
});
|
||||||
|
launch_id += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(games)
|
||||||
|
}
|
||||||
|
|
||||||
impl FromSettingsString for UplayPlatform {
|
impl FromSettingsString for UplayPlatform {
|
||||||
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
|
||||||
UplayPlatform {
|
UplayPlatform {
|
||||||
|
|||||||
Reference in New Issue
Block a user