Skip to content

Commit

Permalink
Add Ubisoft connect (Linux Proton) support (#346)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people committed May 29, 2023
1 parent 7df543f commit c092ffb
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 8 deletions.
9 changes: 8 additions & 1 deletion src/platforms/uplay/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ pub(crate) struct UplayGame {
pub(crate) icon: String,
pub(crate) id: String,
pub(crate) launcher: PathBuf,
pub(crate) launcher_compat_folder: Option<PathBuf>,
pub(crate) launch_id: u8,
}

impl From<UplayGame> for ShortcutOwned {
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
.launcher
.parent()
Expand Down
188 changes: 181 additions & 7 deletions src/platforms/uplay/platform.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
#[cfg(target_os = "windows")]
//All of this is technically related to Ubisoft Connnect, not Ubisoft Play.

use std::path::Path;
#[cfg(target_os = "windows")]
use std::path::PathBuf;

use std::io::Read;
use std::io::BufReader;
use std::fs::File;

use crate::platforms::load_settings;
use crate::platforms::to_shortcuts_simple;
use crate::platforms::FromSettingsString;
use crate::platforms::GamesPlatform;
use crate::platforms::ShortcutToImport;
use crate::platforms::NeedsPorton;

use super::{game::UplayGame, settings::UplaySettings};

Expand All @@ -16,19 +21,92 @@ pub struct UplayPlatform {
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>> {
#[cfg(target_family = "unix")]
{
Err(eyre::format_err!("Uplay is not supported on Linux"))
get_games_from_proton()
}
#[cfg(target_os = "windows")]
{
get_games_from_winreg()
}
}

#[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")]
fn get_launcher_path() -> eyre::Result<PathBuf> {
fn get_launcher_path() -> eyre::Result<UplayPathData> {
let mut res = UplayPathData::default();
use winreg::enums::*;
use winreg::RegKey;

Expand All @@ -37,7 +115,8 @@ fn get_launcher_path() -> eyre::Result<PathBuf> {
let launcher_dir: String = launcher_key.get_value("InstallDir")?;
let path = Path::new(&launcher_dir).join("upc.exe");
if path.exists() {
Ok(path)
res.exe_path = path;
Ok(res)
} else {
Err(eyre::eyre!(
"Could not find uplay launcher at path {:?}",
Expand All @@ -54,7 +133,7 @@ fn get_games_from_winreg() -> eyre::Result<Vec<UplayGame>> {
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
let mut games = 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") {
for i in installs.enum_keys().filter_map(|i| i.ok()) {
Expand Down Expand Up @@ -82,6 +161,8 @@ fn get_games_from_winreg() -> eyre::Result<Vec<UplayGame>> {
icon,
id,
launcher: launcher_path.clone(),
launcher_compat_folder: None,
launch_id: 0
})
}
}
Expand All @@ -90,6 +171,99 @@ fn get_games_from_winreg() -> eyre::Result<Vec<UplayGame>> {
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 {
fn from_settings_string<S: AsRef<str>>(s: S) -> Self {
UplayPlatform {
Expand Down Expand Up @@ -123,4 +297,4 @@ impl GamesPlatform for UplayPlatform {
fn code_name(&self) -> &str {
"uplay"
}
}
}

0 comments on commit c092ffb

Please sign in to comment.