Added robust Uplay config file game parsing

Used Josef Nemec's AMAZING code as a starter at https://github.com/JosefNemec/PlayniteExtensions/blob/master/source/Libraries/UplayLibrary/Models/ProductSchema.cs to build a proper Uplay Game parser! Uplay use protobuf encoded files that contain YAML embedded within them, just to annoy developers like me.

Uplay processing should be much more robust now.
This commit is contained in:
Terry MacDonald 2021-10-15 21:51:53 +13:00
parent 4a8acd4b86
commit b02155d9b1
5 changed files with 470 additions and 298 deletions

View File

@ -108,7 +108,7 @@
<Compile Include="GameLibraries\SteamAppInfoParser\Package.cs" /> <Compile Include="GameLibraries\SteamAppInfoParser\Package.cs" />
<Compile Include="GameLibraries\SteamAppInfoParser\PackageInfo.cs" /> <Compile Include="GameLibraries\SteamAppInfoParser\PackageInfo.cs" />
<Compile Include="GameLibraries\SteamAppInfoParser\App.cs" /> <Compile Include="GameLibraries\SteamAppInfoParser\App.cs" />
<Compile Include="GameLibraries\UplayConfigurationParser\UplayConfigurationParser.cs" /> <Compile Include="GameLibraries\UplayFileStructure.cs" />
<Compile Include="GameLibraries\OriginGame.cs" /> <Compile Include="GameLibraries\OriginGame.cs" />
<Compile Include="GameLibraries\OriginLibrary.cs" /> <Compile Include="GameLibraries\OriginLibrary.cs" />
<Compile Include="GameLibraries\UplayLibrary.cs" /> <Compile Include="GameLibraries\UplayLibrary.cs" />
@ -312,6 +312,9 @@
<PackageReference Include="NLog"> <PackageReference Include="NLog">
<Version>4.7.11</Version> <Version>4.7.11</Version>
</PackageReference> </PackageReference>
<PackageReference Include="protobuf-net">
<Version>3.0.101</Version>
</PackageReference>
<PackageReference Include="QueryString.NET"> <PackageReference Include="QueryString.NET">
<Version>1.0.0</Version> <Version>1.0.0</Version>
</PackageReference> </PackageReference>
@ -324,6 +327,9 @@
<PackageReference Include="WinFormAnimation"> <PackageReference Include="WinFormAnimation">
<Version>1.6.0.4</Version> <Version>1.6.0.4</Version>
</PackageReference> </PackageReference>
<PackageReference Include="YamlDotNet">
<Version>11.2.1</Version>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">

View File

@ -1,104 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// This configuration parses logic is kept here for possible future use
// It was really difficult to find this logic in some obscure webpage
// so I'm keeping it in case I need it later.
namespace DisplayMagician.GameLibraries.UplayConfigurationParser
{
class UplayConfigurationParser
{
/*def _convert_data(self, data):
# calculate object size (konrad's formula)
if data > 256 * 256:
data = data - (128 * 256 * math.ceil(data / (256 * 256)))
data = data - (128 * math.ceil(data / 256))
else:
if data > 256:
data = data - (128 * math.ceil(data / 256))
return data*/
internal static decimal ConvertData (decimal data)
{
if (data > 65536)
{
data = data - (128 * 256 * Math.Ceiling(data / 65536));
}
else if (data > 256)
{
data = data - (128 * Math.Ceiling(data / 256));
}
return data;
}
/*def _parse_configuration_header(self, header, second_eight= False):
try:
offset = 1
multiplier = 1
record_size = 0
tmp_size = 0
if second_eight:
while header[offset] != 0x08 or(header[offset] == 0x08 and header[offset + 1] == 0x08) :
record_size += header[offset] * multiplier
multiplier *= 256
offset += 1
tmp_size += 1
else:
while header[offset] != 0x08 or record_size == 0:
record_size += header[offset] * multiplier
multiplier *= 256
offset += 1
tmp_size += 1
record_size = self._convert_data(record_size)
offset += 1 # skip 0x08
# look for launch_id
multiplier = 1
launch_id = 0
while header[offset] != 0x10 or header[offset + 1] == 0x10:
launch_id += header[offset] * multiplier
multiplier *= 256
offset += 1
launch_id = self._convert_data(launch_id)
offset += 1 # skip 0x10
multiplier = 1
launch_id_2 = 0
while header[offset] != 0x1A or(header[offset] == 0x1A and header[offset + 1] == 0x1A) :
launch_id_2 += header[offset] * multiplier
multiplier *= 256
offset += 1
launch_id_2 = self._convert_data(launch_id_2)
#if object size is smaller than 128b, there might be a chance that secondary size will not occupy 2b
if record_size - offset < 128 <= record_size:
tmp_size -= 1
record_size += 1
# we end up in the middle of header, return values normalized
# to end of record as well real yaml size and game launch_id
return record_size - offset, launch_id, launch_id_2, offset + tmp_size + 1
except:
# something went horribly wrong, do not crash it,
# just return 0s, this way it will be handled later in the code
# 10 is to step a little in configuration file in order to find next game
return 0, 0, 0, 10*/
//internal static decimal ParseConfigurationHeader(decimal data);
}
}

View File

@ -0,0 +1,144 @@
using ProtoBuf;
using System.Collections.Generic;
namespace DisplayMagician.GameLibraries
{
// #####################################################################################################
// # This set of classes are used for deserialising Uplay protobuf files
// #####################################################################################################
[ProtoContract]
public class UplayCachedGame
{
[ProtoMember(1)]
public uint UplayId { get; set; }
[ProtoMember(2)]
public uint InstallId { get; set; }
[ProtoMember(3)]
public string GameInfo { get; set; }
}
[ProtoContract]
public class UplayCachedGameCollection
{
[ProtoMember(1)]
public List<UplayCachedGame> Games { get; set; }
}
// #####################################################################################################
// # This set of classes are used for deserialising Uplay YAML enbedded within the protobuf file format
// #####################################################################################################
public class ProductInformation
{
public class Executable
{
public class Path
{
public string relative;
}
public class WorkingDirectory
{
public string register;
public string append;
}
public Path path;
public WorkingDirectory working_directory;
public string internal_name;
public string description;
public string shortcut_name;
public string icon_image;
}
public class StartGameItem
{
public bool after_game_report_enabled;
public bool overlay_supported;
public bool overlay_product_activation_enabled;
public bool overlay_required;
public bool overlay_shop_enabled;
public bool legacy_ticket_enabled;
public List<Executable> executables;
}
public class StartGame
{
public StartGameItem online;
public StartGameItem offline;
}
public class DigitalDistribution
{
public int version;
}
public class Localization
{
public string l1;
}
public class Club
{
public bool enabled;
}
public class Addon
{
public uint id;
public bool is_visible;
public string name;
public string description;
public string thumb_image;
}
public class Uplay
{
public string game_code;
public string achievements;
public string achievements_sync_id;
}
public class ThirdPartyPlatform
{
public string name;
}
public class Product
{
public string name;
public string background_image;
public string thumb_image;
public string logo_image;
public string dialog_image;
public string icon_image;
public ThirdPartyPlatform third_party_platform;
public string sort_string;
public bool cloud_saves;
public string forum_url;
public string homepage_url;
public string facebook_url;
public string help_url;
public bool after_game_report_ad;
public bool force_safe_mode;
public bool uplay_pipe_required;
public bool show_properties;
public bool game_streaming_enabled;
public Uplay uplay;
public List<Addon> addons;
public Club club;
public DigitalDistribution digital_distribution;
public bool is_ulc;
public bool is_visible;
public StartGame start_game;
}
public string version;
public Product root;
public Dictionary<string,Localization> localizations;
public uint uplay_id;
public uint install_id;
}
}

View File

@ -6,6 +6,10 @@ using Microsoft.Win32;
using System.IO; using System.IO;
using System.Security; using System.Security;
using System.Diagnostics; using System.Diagnostics;
using ProtoBuf;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using System.Globalization;
namespace DisplayMagician.GameLibraries namespace DisplayMagician.GameLibraries
{ {
@ -404,6 +408,52 @@ namespace DisplayMagician.GameLibraries
} }
public bool GetInstallDirFromRegKey(string regKeyPath, out string filePath)
{
filePath = "";
RegistryKey uplayGameInstallKey;
if (regKeyPath.StartsWith("HKEY_LOCAL_MACHINE"))
{
logger.Trace($"UplayLibrary/GetInstallDirFromRegKey: Accessing HKLM reg key {regKeyPath}");
string regKeyText = regKeyPath.Replace(@"HKEY_LOCAL_MACHINE\", "");
uplayGameInstallKey = Registry.LocalMachine.OpenSubKey(regKeyText, RegistryKeyPermissionCheck.ReadSubTree);
}
else if (regKeyPath.StartsWith("HKEY_CURRENT_USER"))
{
logger.Trace($"UplayLibrary/GetInstallDirFromRegKey: Accessing HKCU reg key {regKeyPath}");
string regKeyText = regKeyPath.Replace(@"HKEY_CURRENT_USER\", "");
uplayGameInstallKey = Registry.LocalMachine.OpenSubKey(regKeyText, RegistryKeyPermissionCheck.ReadSubTree);
}
else
{
logger.Trace($"UplayLibrary/GetInstallDirFromRegKey: Skipping processing as regkey supplied was odd: {regKeyPath}");
return false;
}
// If the key doesn't exist we skip it as the game isn't installed any longer!
if (uplayGameInstallKey == null)
{
logger.Trace($"UplayLibrary/GetInstallDirFromRegKey: Skipping Uplay Game as it isn't installed at the moment (it was uninstalled at some point)");
return false;
}
// From that we lookup the actual game path
string gameInstallDir = uplayGameInstallKey.GetValue("InstallDir", "").ToString();
logger.Trace($"UplayLibrary/GetInstallDirFromRegKey: gameInstallDir found = {gameInstallDir}");
if (!String.IsNullOrWhiteSpace(gameInstallDir))
{
filePath = Path.GetFullPath(gameInstallDir).TrimEnd('\\');
return true;
}
else
{
logger.Warn($"UplayLibrary/GetInstallDirFromRegKey: gameInstallDir is null or all whitespace!");
return false;
}
}
public override bool LoadInstalledGames() public override bool LoadInstalledGames()
{ {
try try
@ -475,211 +525,287 @@ namespace DisplayMagician.GameLibraries
// Access {installdir}\\cache\\configuration\\configurations file // Access {installdir}\\cache\\configuration\\configurations file
string uplayConfigFilePath = _uplayPath + @"cache\configuration\configurations"; string uplayConfigFilePath = _uplayPath + @"cache\configuration\configurations";
logger.Trace($"UplayLibrary/LoadInstalledGames: Uplay Config File Path = {uplayConfigFilePath }"); logger.Trace($"UplayLibrary/LoadInstalledGames: Uplay Config File Path = {uplayConfigFilePath }");
string uplayConfigFileString = File.ReadAllText(uplayConfigFilePath);
uplayConfigFileString = uplayConfigFileString.Remove(0, 12);
string[] dividingText = { "version: 2.0" };
List<string> uplayConfigFile = uplayConfigFileString.Split(dividingText,StringSplitOptions.RemoveEmptyEntries).ToList();
// Split the file into records at the SOH unicode character
//List<string> uplayConfigFile = uplayConfigFileString.Split((Char)1).ToList();
// Go through every record and attempt to parse it var deserializer = new DeserializerBuilder()
foreach (string uplayEntry in uplayConfigFile) { .IgnoreUnmatchedProperties()
// Skip any Uplay entry records that don't start with 'version:' .Build();
//if (!uplayEntry.StartsWith("version:",StringComparison.OrdinalIgnoreCase))
// continue;
logger.Trace($"UplayLibrary/LoadInstalledGames: Uplay Entry that starts with 'version: 2.0') = {uplayEntry}"); using (var file = File.OpenRead(uplayConfigFilePath))
{
//Split the record into entrylines try
string[] delimeters = { "\r\n" };
List<string> uplayEntryLines = uplayEntry.Split(delimeters, System.StringSplitOptions.RemoveEmptyEntries).ToList();
// Skip any records NOT containing an entryline with ' start_game:' (note 2 leading spaces)
// All games contain a start_game entry
if (!uplayEntryLines.Exists(a => a.StartsWith(" start_game:")))
continue;
// Skip any records containing an entryline with ' third_party_platform:' (note 2 leading spaces)
// We only want the native uplay games....
if (uplayEntryLines.Exists(a => a.StartsWith(" third_party_platform:")))
continue;
// if we get here then we have a real game to parse!
// Yay us :).
// First we want to know the index of the start_game entry to use later
//int startGameIndex = uplayEntryLines.FindIndex(a => a.StartsWith(" start_game:"));
MatchCollection mc;
// First we check if there are any localization CONSTANTS that we will need to map later.
Dictionary<string, string> localizations = new Dictionary<string, string>();
int localizationsIndex = uplayEntryLines.FindIndex(a => a == "localizations:");
// If there are localizations, then we need to store them for later
if (localizationsIndex != -1)
{ {
// grab the localizations: -> default: entries to use as a lookup table for the info we need var gameCollection = ProtoBuf.Serializer.Deserialize<UplayCachedGameCollection>(file).Games;
int defaultIndex = localizationsIndex + 1; foreach (var item in gameCollection)
int currentIndex = defaultIndex + 1;
// Grab all EntryLines with 4 leading spaces (these are all the localizations)
while (uplayEntryLines[currentIndex].StartsWith(" ")){
string[] split = uplayEntryLines[currentIndex].Split(':');
localizations.Add(split[0].Trim(), split[1].Trim());
currentIndex++;
}
}
// for each game record grab:
GameAppInfo uplayGameAppInfo = new GameAppInfo();
// find the exe name looking at root: -> start_game: -> online: -> executables: -> path: -> relative: (get ACU.exe)
// Lookup the Game registry key from looking at root: -> start_game: -> online: -> executables: -> working_directory: -> register: (get HKEY_LOCAL_MACHINE\SOFTWARE\Ubisoft\Launcher\Installs\720\InstallDir)
// Extract the GameAppID from the number in the working directory (e.g. 720)
// Lookup the Game install path by reading the game registry key: D:/Ubisoft Game Launcher/Assassin's Creed Unity/
// join the Game install path and the exe name to get the full game exe path: D:/Ubisoft Game Launcher/Assassin's Creed Unity/ACU.exe
//if (uplayEntryLines.Find (a => a.StartsWith(" icon_image:", StringComparison.InvariantCultureIgnoreCase)))
bool gotGameIconPath = false;
bool gotGameName = false;
string gameFileName = "";
bool gotGameFileName = false;
string gameId = "";
bool gotGameId = false;
string gameRegistryKey = "";
bool gotGameRegistryKey = false;
for (int i = 0; i <= 50; i++)
{
// Stop this loop once we have both filname and gameid
if (gotGameFileName && gotGameId && gotGameIconPath && gotGameName && gotGameRegistryKey)
{ {
logger.Trace($"UplayLibrary/LoadInstalledGames: We got all the entries: gameFileName = {gameFileName } && gameId = {gameId } && gameIconPath = {uplayGameAppInfo.GameIconPath} && gameName = {uplayGameAppInfo.GameName}"); if (!String.IsNullOrEmpty(item.GameInfo))
break;
}
// This line contains the Game Name
if (uplayEntryLines[i].StartsWith(" name:", StringComparison.OrdinalIgnoreCase) && !gotGameName)
{
mc = Regex.Matches(uplayEntryLines[i], @" name\: (.*)");
if (mc.Count > 0)
{ {
uplayGameAppInfo.GameName = mc[0].Groups[1].ToString(); ProductInformation productInfo;
// if the name contains a localization reference, then dereference it try
if (localizations.ContainsKey(uplayGameAppInfo.GameName))
{ {
uplayGameAppInfo.GameName = localizations[uplayGameAppInfo.GameName]; productInfo = deserializer.Deserialize<ProductInformation>(item.GameInfo);
var root = productInfo.root;
string gameName = "";
string gameExePath = "";
string gameIconPath = "";
// Try finding the Game Name using the localisation currently in use as a first step
logger.Trace($"UplayLibrary/LoadInstalledGames: Looking for the Uplay game name.");
string currentLang = CultureInfo.CurrentCulture.Name;
foreach (var lang in productInfo.localizations)
{
// If we find the same language as the user is using, then let's use that!
if (lang.Key.Equals(currentLang))
{
gameName = lang.Value.l1;
logger.Trace($"UplayLibrary/LoadInstalledGames: We found the Uplay game name '{gameName}' in the user's language of {currentLang}.");
break;
}
}
// If the gameName isn't available in the users language, then we go for default
if (String.IsNullOrEmpty(gameName) && productInfo.localizations.ContainsKey("default"))
{
gameName = productInfo.localizations["default"].l1;
if (!String.IsNullOrEmpty(gameName))
{
logger.Trace($"UplayLibrary/LoadInstalledGames: Looking for the Uplay game name with the en language as the local language didn't work. We found game name '{gameName}'. ");
}
else
{
logger.Trace($"UplayLibrary/LoadInstalledGames: Looking for the Uplay game name with the en language as the local language didn't work. We found no en language. ");
}
}
// Now we'll try to sort out the rest of the game data!
// We first look for the online executable information
if (root.start_game.online.executables.Count > 0)
{
logger.Trace($"UplayLibrary/LoadInstalledGames: Uplay game {gameName} has some online executables to process! ");
// First up we look at the online games, cause they're just better!
foreach (var executable in root.start_game.online.executables)
{
string exePath = "";
// Check if its a full path or a relative path
if (!String.IsNullOrEmpty(executable.path.relative))
{
if (executable.working_directory.register.StartsWith("HKEY_LOCAL_MACHINE"))
{
// This copes with relative files using a HKEY_LOCAL_MACHINE registry
string regKeyText = executable.working_directory.register;
regKeyText = regKeyText.Replace(@"\InstallDir", "");
regKeyText = regKeyText.Replace(@"Ubisoft", @"WOW6432Node\Ubisoft");
logger.Trace($"UplayLibrary/GetInstallDirFromRegKey: Accessing HKLM reg key {regKeyText}");
if (this.GetInstallDirFromRegKey(regKeyText, out exePath))
{
gameExePath = Path.Combine(exePath, executable.path.relative);
logger.Trace($"UplayLibrary/LoadInstalledGames: Relative executable uses local machine registry key: {executable.working_directory.register} ");
}
}
/*else if (executable.working_directory.register.StartsWith("HKEY_CURRENT_USER"))
{
// This copes with relative files using a HKEY_CURRENT_USER registry
string regKeyText = executable.working_directory.register;
regKeyText = regKeyText.Replace(@"\InstallDir", "");
regKeyText = regKeyText.Replace(@"Ubisoft", @"WOW6432Node\Ubisoft");
logger.Trace($"UplayLibrary/GetInstallDirFromRegKey: Accessing HKLM reg key {regKeyText}");
if (this.GetInstallDirFromRegKey(executable.working_directory.register, out exePath))
{
gameExePath = Path.Combine(exePath, executable.path.relative);
logger.Trace($"UplayLibrary/LoadInstalledGames: Relative executable uses current user registry key: {executable.working_directory.register} ");
}
}*/
else if (!String.IsNullOrEmpty(executable.working_directory.append))
{
// This copes with relative files using an appended path
gameExePath = Path.Combine(executable.working_directory.append, executable.path.relative);
gameIconPath = Path.Combine(executable.working_directory.append, executable.icon_image);
logger.Trace($"UplayLibrary/LoadInstalledGames: Relative executable uses appended file path: {executable.working_directory.append} ");
}
else
{
// Problem!
logger.Error($"UplayLibrary/LoadInstalledGames: Found relative GameExePath {executable.path.relative} for Uplay game {gameName} but no registry key or appended file path! Skipping this game.");
continue;
}
}
else
{
// This should cope with full pathed files, but we have no examples to test! So log it
logger.Error($"UplayLibrary/LoadInstalledGames: Found non-relative GameExePath {executable.path} for Uplay game {gameName} but we've not seen it before so no idea how to handle it! Skipping this game.");
logger.Error($"UplayLibrary/LoadInstalledGames: executable.path for troubleshooting: {executable.path}");
continue;
}
// We should check the exe file exists, and if it doesn't then we need to do the next exe
if (!File.Exists(gameExePath))
{
logger.Error($"UplayLibrary/LoadInstalledGames: Couldn't find the GameExePath {gameExePath} for Uplay game {gameName} so skipping this exe, and trying the next one.");
continue;
}
// Now try to get the Uplay game icon
if (!String.IsNullOrEmpty(executable.icon_image))
{
gameIconPath = Path.Combine(_uplayPath, "data", "games", executable.icon_image);
// If the icon file isn't actually there, then use the game exe instead.
if (!File.Exists(gameIconPath))
{
gameIconPath = gameExePath;
}
}
logger.Trace($"UplayLibrary/LoadInstalledGames: Found GameExePath {exePath} and Icon Path {gameIconPath} for Uplay game {gameName}.");
// We do a final check to make sure that we do have a GameName, and if not we use the shortcut
if (String.IsNullOrEmpty(gameName) && !String.IsNullOrEmpty(executable.shortcut_name))
{
gameName = executable.shortcut_name;
logger.Trace($"UplayLibrary/LoadInstalledGames: Game Name was still empty, so we're using the shortcut name as a last resort: {executable.shortcut_name} ");
}
// Now we need to save the game name, cause if we're here then we're good enough to save
// Then we have the gameID, the thumbimage, the icon, the name, the exe path
// And we add the Game to the list of games we have!
_allGames.Add(new UplayGame(productInfo.uplay_id.ToString(), gameName, gameExePath, gameIconPath));
logger.Trace($"UplayLibrary/LoadInstalledGames: Adding Uplay Game with game id {productInfo.uplay_id}, name {gameName}, game exe {gameExePath} and icon path {gameIconPath}");
break;
}
}
// This is the offline exes
else if (root.start_game.offline.executables.Count > 0)
{
logger.Trace($"UplayLibrary/LoadInstalledGames: Uplay game {gameName} has some offline executables to process! ");
// we look at the offline games, cause there weren't any online ones
foreach (var executable in root.start_game.offline.executables)
{
string exePath = "";
// Check if its a full path or a relative path
if (!String.IsNullOrEmpty(executable.path.relative))
{
if (executable.working_directory.register.StartsWith("HKEY_LOCAL_MACHINE"))
{
// This copes with relative files using a HKEY_LOCAL_MACHINE registry
string regKeyText = executable.working_directory.register;
regKeyText = regKeyText.Replace(@"\InstallDir", "");
regKeyText = regKeyText.Replace(@"Ubisoft", @"WOW6432Node\Ubisoft");
logger.Trace($"UplayLibrary/GetInstallDirFromRegKey: Accessing HKLM reg key {regKeyText}");
if (this.GetInstallDirFromRegKey(regKeyText, out exePath))
{
gameExePath = Path.Combine(exePath, executable.path.relative);
logger.Trace($"UplayLibrary/LoadInstalledGames: Relative executable uses local machine registry key: {executable.working_directory.register} ");
}
}
/*else if (executable.working_directory.register.StartsWith("HKEY_CURRENT_USER"))
{
// This copes with relative files using a HKEY_CURRENT_USER registry
string regKeyText = executable.working_directory.register;
regKeyText = regKeyText.Replace(@"\InstallDir", "");
regKeyText = regKeyText.Replace(@"Ubisoft", @"WOW6432Node\Ubisoft");
logger.Trace($"UplayLibrary/GetInstallDirFromRegKey: Accessing HKLM reg key {regKeyText}");
if (this.GetInstallDirFromRegKey(executable.working_directory.register, out exePath))
{
gameExePath = Path.Combine(exePath, executable.path.relative);
logger.Trace($"UplayLibrary/LoadInstalledGames: Relative executable uses current user registry key: {executable.working_directory.register} ");
}
}*/
else if (!String.IsNullOrEmpty(executable.working_directory.append))
{
// This copes with relative files using an appended path
gameExePath = Path.Combine(executable.working_directory.append, executable.path.relative);
gameIconPath = Path.Combine(executable.working_directory.append, executable.icon_image);
logger.Trace($"UplayLibrary/LoadInstalledGames: Relative executable uses appended file path: {executable.working_directory.append} ");
}
else
{
// Problem!
logger.Error($"UplayLibrary/LoadInstalledGames: Found relative GameExePath {executable.path.relative} for Uplay game {gameName} but no registry key or appended file path! Skipping this game.");
continue;
}
}
else
{
// This should cope with full pathed files, but we have no examples to test! So log it
logger.Error($"UplayLibrary/LoadInstalledGames: Found non-relative GameExePath {executable.path} for Uplay game {gameName} but we've not seen it before so no idea how to handle it! Skipping this game.");
logger.Error($"UplayLibrary/LoadInstalledGames: executable.path for troubleshooting: {executable.path}");
continue;
}
// We should check the exe file exists, and if it doesn't then we need to do the next exe
if (!File.Exists(gameExePath))
{
logger.Error($"UplayLibrary/LoadInstalledGames: Couldn't find the GameExePath {gameExePath} for Uplay game {gameName} so skipping this exe, and trying the next one.");
continue;
}
// Now try to get the Uplay game icon
if (!String.IsNullOrEmpty(executable.icon_image))
{
gameIconPath = Path.Combine(_uplayPath, "data", "games", executable.icon_image);
// If the icon file isn't actually there, then use the game exe instead.
if (!File.Exists(gameIconPath))
{
gameIconPath = gameExePath;
}
}
logger.Trace($"UplayLibrary/LoadInstalledGames: Found GameExePath {exePath} and Icon Path {gameIconPath} for Uplay game {gameName}.");
// We do a final check to make sure that we do have a GameName, and if not we use the shortcut
if (String.IsNullOrEmpty(gameName) && !String.IsNullOrEmpty(executable.shortcut_name))
{
gameName = executable.shortcut_name;
logger.Trace($"UplayLibrary/LoadInstalledGames: Game Name was still empty, so we're using the shortcut name as a last resort: {executable.shortcut_name} ");
}
// Now we need to save the game name, cause if we're here then we're good enough to save
// Then we have the gameID, the thumbimage, the icon, the name, the exe path
// And we add the Game to the list of games we have!
_allGames.Add(new UplayGame(productInfo.uplay_id.ToString(), gameName, gameExePath, gameIconPath));
logger.Trace($"UplayLibrary/LoadInstalledGames: Adding Uplay Game with game id {productInfo.uplay_id}, name {gameName}, game exe {gameExePath} and icon path {gameIconPath}");
break;
}
}
else
{
logger.Trace($"UplayLibrary/LoadInstalledGames: Uplay Entry {gameName} doesn't have any executables associated with it! We have to skip adding this game.");
continue;
}
} }
logger.Trace($"UplayLibrary/LoadInstalledGames: Found uplayGameAppInfo.GameName = {uplayGameAppInfo.GameName}"); catch (Exception ex)
gotGameName = true;
}
}
else if (uplayEntryLines[i].StartsWith(" icon_image:", StringComparison.OrdinalIgnoreCase) && !gotGameIconPath)
{
mc = Regex.Matches(uplayEntryLines[i], @"icon_image: (.*)");
if (mc.Count > 0)
{
string iconImageFileName = mc[0].Groups[1].ToString();
// if the icon_image contains a localization reference, then dereference it
if (localizations.ContainsKey(iconImageFileName))
{ {
iconImageFileName = localizations[iconImageFileName]; // If we get an error processing the game YAML, lets try and skip this game and try the next one. It might work!
logger.Trace($"UplayLibrary/LoadInstalledGames: Found iconImageFile = {iconImageFileName }"); logger.Error($"UplayLibrary/LoadInstalledGames: Problem deserialising the YAML embedded in the Uplay configuration file {uplayConfigFilePath}. Cannot process this games!");
continue;
} }
//61fdd16f06ae08158d0a6d476f1c6bd5.ico
string uplayGameIconPath = _uplayPath + @"data\games\" + iconImageFileName;
if (File.Exists(uplayGameIconPath) && uplayGameIconPath.EndsWith(".ico"))
{
uplayGameAppInfo.GameIconPath = uplayGameIconPath;
logger.Trace($"UplayLibrary/LoadInstalledGames: Found uplayGameAppInfo.GameUplayIconPath = {uplayGameAppInfo.GameIconPath }");
}
gotGameIconPath = true;
}
}
// This line contains the filename
else if (uplayEntryLines[i].StartsWith(" relative:") && !gotGameFileName)
{
mc = Regex.Matches(uplayEntryLines[i], @"relative: (.*)");
if (mc.Count > 0)
{
gameFileName = mc[0].Groups[1].ToString();
gotGameFileName = true;
logger.Trace($"UplayLibrary/LoadInstalledGames: Found gameFileName = {gameFileName}");
}
}
// This line contains the registryKey
else if (uplayEntryLines[i].StartsWith(" register: HKEY_LOCAL_MACHINE") && !gotGameId)
{
// Lookup the GameId within the registry key
mc = Regex.Matches(uplayEntryLines[i], @"Installs\\(\d+)\\InstallDir");
if (mc.Count > 0)
{
gameId = mc[0].Groups[1].ToString();
gotGameId = true;
logger.Trace($"UplayLibrary/LoadInstalledGames: Found gameId = {gameId}");
} }
mc = Regex.Matches(uplayEntryLines[i], @"HKEY_LOCAL_MACHINE\\(.*?)\\InstallDir");
if (mc.Count > 0)
{
gameRegistryKey = mc[0].Groups[1].ToString();
gameRegistryKey = gameRegistryKey.Replace(@"Ubisoft", @"WOW6432Node\Ubisoft");
gotGameRegistryKey = true;
logger.Trace($"UplayLibrary/LoadInstalledGames: Found gameRegistryKey = {gameRegistryKey}");
}
} }
} }
catch (Exception ex)
logger.Trace($"UplayLibrary/LoadInstalledGames: gameId = {gameId}");
logger.Trace($"UplayLibrary/LoadInstalledGames: gameFileName = {gameFileName}");
logger.Trace($"UplayLibrary/LoadInstalledGames: gameGameIconPath = {uplayGameAppInfo.GameIconPath}");
logger.Trace($"UplayLibrary/LoadInstalledGames: gameRegistryKey = {gameRegistryKey}");
if (gotGameRegistryKey)
{ {
// Now we need to lookup the game install path in registry using the game reg we got above // We can't do anything if we hit here.
// We assume its 64-bit OS too (not 32bit) logger.Error($"UplayLibrary/LoadInstalledGames: Problem deserialising the protobuf Uplay configuration file {uplayConfigFilePath}. Cannot process any games!");
using (RegistryKey uplayGameInstallKey = Registry.LocalMachine.OpenSubKey(gameRegistryKey, RegistryKeyPermissionCheck.ReadSubTree)) return false;
{
// If the key doesn't exist we skip it as the game isn't installed any longer!
if (uplayGameInstallKey == null)
{
logger.Trace($"UplayLibrary/LoadInstalledGames: Skipping Uplay Game {uplayGameAppInfo.GameName} as it isn't installed at the moment (it was uninstalled at some point)");
continue;
}
// If we get here, then we have a real game.
foreach (string regKeyName in uplayGameInstallKey.GetValueNames())
{
logger.Trace($"UplayLibrary/LoadInstalledGames: uplayGameInstallKey[{regKeyName}] = {uplayGameInstallKey.GetValue(regKeyName)}");
}
// From that we lookup the actual game path
string gameInstallDir = uplayGameInstallKey.GetValue("InstallDir", "").ToString();
logger.Trace($"UplayLibrary/LoadInstalledGames: gameInstallDir found = {gameInstallDir}");
if (!String.IsNullOrWhiteSpace(gameInstallDir))
{
uplayGameAppInfo.GameInstallDir = Path.GetFullPath(gameInstallDir).TrimEnd('\\');
logger.Trace($"UplayLibrary/LoadInstalledGames: uplayGameAppInfo.GameInstallDir = {uplayGameAppInfo.GameInstallDir }");
uplayGameAppInfo.GameExePath = Path.Combine(uplayGameAppInfo.GameInstallDir, gameFileName);
logger.Trace($"UplayLibrary/LoadInstalledGames: uplayGameAppInfo.GameExe = {uplayGameAppInfo.GameExePath}");
uplayGameAppInfo.GameID = gameId;
logger.Trace($"UplayLibrary/LoadInstalledGames: uplayGameAppInfo.GameID = {uplayGameAppInfo.GameID }");
}
else
{
logger.Warn($"UplayLibrary/LoadInstalledGames: gameInstallDir is null or all whitespace!");
}
// Then we have the gameID, the thumbimage, the icon, the name, the exe path
// And we add the Game to the list of games we have!
_allGames.Add(new UplayGame(uplayGameAppInfo.GameID, uplayGameAppInfo.GameName, uplayGameAppInfo.GameExePath, uplayGameAppInfo.GameIconPath));
logger.Debug($"UplayLibrary/LoadInstalledGames: Adding Uplay Game with game id {uplayGameAppInfo.GameID}, name {uplayGameAppInfo.GameName}, game exe {uplayGameAppInfo.GameExePath} and icon path {uplayGameAppInfo.GameIconPath}");
}
} }
}
}
logger.Info($"UplayLibrary/LoadInstalledGames: Found {_allGames.Count} installed Uplay games"); logger.Info($"UplayLibrary/LoadInstalledGames: Found {_allGames.Count} installed Uplay games");

View File

@ -26,8 +26,8 @@ using System.Resources;
[assembly: Guid("e4ceaf5e-ad01-4695-b179-31168eb74c48")] [assembly: Guid("e4ceaf5e-ad01-4695-b179-31168eb74c48")]
// Version information // Version information
[assembly: AssemblyVersion("2.0.1.95")] [assembly: AssemblyVersion("2.0.1.123")]
[assembly: AssemblyFileVersion("2.0.1.95")] [assembly: AssemblyFileVersion("2.0.1.123")]
[assembly: NeutralResourcesLanguageAttribute( "en" )] [assembly: NeutralResourcesLanguageAttribute( "en" )]
[assembly: CLSCompliant(true)] [assembly: CLSCompliant(true)]