diff --git a/DisplayMagician/DisplayMagician.csproj b/DisplayMagician/DisplayMagician.csproj index d8f73a6..e108dd8 100644 --- a/DisplayMagician/DisplayMagician.csproj +++ b/DisplayMagician/DisplayMagician.csproj @@ -99,6 +99,8 @@ + + diff --git a/DisplayMagician/GameLibraries/EpicGame.cs b/DisplayMagician/GameLibraries/EpicGame.cs new file mode 100644 index 0000000..b64a103 --- /dev/null +++ b/DisplayMagician/GameLibraries/EpicGame.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using DisplayMagician.Resources; +using System.Diagnostics; + +namespace DisplayMagician.GameLibraries +{ + public class EpicGame : Game + { + private string _epicGameId; + private string _epicGameName; + private string _epicGameExePath; + private string _epicGameDir; + private string _epicGameExe; + private string _epicGameProcessName; + private string _epicGameIconPath; + //private string _epicURI; + private static readonly EpicLibrary _epicGameLibrary = EpicLibrary.GetLibrary(); + private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + + static EpicGame() + { + ServicePointManager.ServerCertificateValidationCallback += + (send, certificate, chain, sslPolicyErrors) => true; + } + + + public EpicGame(string epicGameId, string epicGameName, string epicGameExePath, string epicGameIconPath) + { + + //_gameRegistryKey = $@"{EpicLibrary.registryEpicInstallsKey}\\{EpicGameId}"; + _epicGameId = epicGameId; + _epicGameName = epicGameName; + _epicGameExePath = epicGameExePath; + _epicGameDir = Path.GetDirectoryName(epicGameExePath); + _epicGameExe = Path.GetFileName(_epicGameExePath); + _epicGameProcessName = Path.GetFileNameWithoutExtension(_epicGameExePath); + _epicGameIconPath = epicGameIconPath; + + } + + public override string Id + { + get => _epicGameId; + set => _epicGameId = value; + } + + public override string Name + { + get => _epicGameName; + set => _epicGameName = value; + } + + public override SupportedGameLibraryType GameLibrary + { + get => SupportedGameLibraryType.Epic; + } + + public override string IconPath + { + get => _epicGameIconPath; + set => _epicGameIconPath = value; + } + + public override string ExePath + { + get => _epicGameExePath; + set => _epicGameExePath = value; + } + + public override string Directory + { + get => _epicGameDir; + set => _epicGameDir = value; + } + + public override bool IsRunning + { + get + { + int numGameProcesses = 0; + List gameProcesses = Process.GetProcessesByName(_epicGameProcessName).ToList(); + foreach (Process gameProcess in gameProcesses) + { + try + { + if (gameProcess.ProcessName.Equals(_epicGameProcessName)) + numGameProcesses++; + } + catch (Exception ex) + { + logger.Debug(ex, $"EpicGame/IsRunning: Accessing Process.ProcessName caused exception. Trying GameUtils.GetMainModuleFilepath instead"); + // If there is a race condition where MainModule isn't available, then we + // instead try the much slower GetMainModuleFilepath (which does the same thing) + string filePath = GameUtils.GetMainModuleFilepath(gameProcess.Id); + if (filePath == null) + { + // if we hit this bit then GameUtils.GetMainModuleFilepath failed, + // so we just assume that the process is a game process + // as it matched the epical process search + numGameProcesses++; + continue; + } + else + { + if (filePath.StartsWith(_epicGameExePath)) + numGameProcesses++; + } + + } + } + if (numGameProcesses > 0) + return true; + else + return false; + } + } + + // Have to do much more research to figure out how to detect when Epic is updating a game + public override bool IsUpdating + { + get + { + return false; + } + } + + public override GameStartMode StartMode + { + get => GameStartMode.URI; + } + + public override string GetStartURI(string gameArguments = "") + { + string address = $"epic2://game/launch?offerIds={_epicGameId}"; + if (String.IsNullOrWhiteSpace(gameArguments)) + { + address += "/" + gameArguments; + } + return address; + } + + public bool CopyTo(EpicGame EpicGame) + { + if (!(EpicGame is EpicGame)) + return false; + + // Copy all the game data over to the other game + EpicGame.IconPath = IconPath; + EpicGame.Id = Id; + EpicGame.Name = Name; + EpicGame.ExePath = ExePath; + EpicGame.Directory = Directory; + return true; + } + + public override string ToString() + { + var name = _epicGameName; + + if (string.IsNullOrWhiteSpace(name)) + { + name = Language.Unknown; + } + + if (IsRunning) + { + return name + " " + Language.Running; + } + + /*if (IsUpdating) + { + return name + " " + Language.Updating; + }*/ + + return name; + } + + } +} \ No newline at end of file diff --git a/DisplayMagician/GameLibraries/EpicLibrary.cs b/DisplayMagician/GameLibraries/EpicLibrary.cs new file mode 100644 index 0000000..9de256d --- /dev/null +++ b/DisplayMagician/GameLibraries/EpicLibrary.cs @@ -0,0 +1,753 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.Win32; +using System.IO; +using System.Security; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using System.Web; +using System.Diagnostics; + +namespace DisplayMagician.GameLibraries +{ + public sealed class EpicLibrary : GameLibrary + { + #region Class Variables + // Static members are 'eagerly initialized', that is, + // immediately when class is loaded for the first time. + // .NET guarantees thread safety for static initialization + private static readonly EpicLibrary _instance = new EpicLibrary(); + + + // Common items to the class + private List _allEpicGames = new List(); + private string EpicAppIdRegex = @"/^[0-9A-F]{1,10}$"; + private string _epicExe; + private string _epicPath; + private string _epicLocalContent = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Epic"); + private bool _isEpicInstalled = false; + private List _epicProcessList = new List(){ "epic" }; + + //private string _epicConfigVdfFile; + internal string registryEpicLauncherKey = @"SOFTWARE\WOW6432Node\Epic"; + //internal string registryEpicInstallsKey = @"SOFTWARE\WOW6432Node\Ubisoft\Launcher\Installs"; + //internal string registryEpicOpenCmdKey = @"SOFTWARE\Classes\Epic\Shell\Open\Command"; + private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + + + // Other constants that are useful + #endregion + + #region Class Constructors + static EpicLibrary() { } + + private EpicLibrary() + { + try + { + logger.Trace($"EpicLibrary/EpicLibrary: Epic launcher registry key = HKLM\\{registryEpicLauncherKey}"); + // Find the EpicExe location, and the EpicPath for later + RegistryKey EpicInstallKey = Registry.LocalMachine.OpenSubKey(registryEpicLauncherKey, RegistryKeyPermissionCheck.ReadSubTree); + if (EpicInstallKey == null) + return; + _epicExe = EpicInstallKey.GetValue("ClientPath", @"C:\Program Files (x86)\Epic\Epic.exe").ToString(); + _epicPath = _epicExe; + _epicPath = _epicPath.Replace(@"\Epic.exe", ""); + if (File.Exists(_epicExe)) + { + logger.Info($"EpicLibrary/EpicLibrary: Epic library is installed in {_epicPath}. Found {_epicExe}"); + _isEpicInstalled = true; + } + else + { + logger.Info($"EpicLibrary/EpicLibrary: Epic library is not installed!"); + } + + } + catch (SecurityException ex) + { + logger.Warn(ex, "EpicLibrary/EpicLibrary: The user does not have the permissions required to read the Epic ClientPath registry key."); + } + catch(ObjectDisposedException ex) + { + logger.Warn(ex, "EpicLibrary/EpicLibrary: The Microsoft.Win32.RegistryKey is closed when trying to access the Epic ClientPath registry key (closed keys cannot be accessed)."); + } + catch (IOException ex) + { + logger.Warn(ex, "EpicLibrary/EpicLibrary: The Epic ClientPath registry key has been marked for deletion so we cannot access the value dueing the EpicLibrary check."); + } + catch (UnauthorizedAccessException ex) + { + logger.Warn(ex, "EpicLibrary/EpicLibrary: The user does not have the necessary registry rights to check whether Epic is installed."); + } + } + #endregion + + #region Class Properties + public override List AllInstalledGames + { + get + { + // Load the Epic Games from Epic Client if needed + if (_allEpicGames.Count == 0) + LoadInstalledGames(); + return _allEpicGames; + } + } + + + public override int InstalledGameCount + { + get + { + return _allEpicGames.Count; + } + } + + public override string GameLibraryName + { + get + { + return "Epic"; + } + } + + public override SupportedGameLibraryType GameLibraryType + { + get + { + return SupportedGameLibraryType.Epic; + } + } + + public override string GameLibraryExe + { + get + { + return _epicExe; + } + } + + public override string GameLibraryPath + { + get + { + return _epicPath; + } + } + + public override bool IsGameLibraryInstalled + { + get + { + return _isEpicInstalled; + } + + } + + public override bool IsRunning + { + get + { + List epicLibraryProcesses = new List(); + + foreach (string epicLibraryProcessName in _epicProcessList) + { + // Look for the processes with the ProcessName we sorted out earlier + epicLibraryProcesses.AddRange(Process.GetProcessesByName(epicLibraryProcessName)); + } + + // If we have found one or more processes then we should be good to go + // so let's break, and get to the next step.... + if (epicLibraryProcesses.Count > 0) + return true; + else + return false; + } + + } + + public override bool IsUpdating + { + get + { + // Not implemeted at present + // so we just return a false + // TODO Implement Epic specific detection for updating the game client + return false; + } + + } + + public override List GameLibraryProcesses + { + get + { + return _epicProcessList; + } + } + + + #endregion + + #region Class Methods + public static EpicLibrary GetLibrary() + { + return _instance; + } + + + public override bool AddGame(Game epicGame) + { + if (!(epicGame is EpicGame)) + return false; + + // Doublecheck if it already exists + // Because then we just update the one that already exists + if (ContainsGame(epicGame)) + { + logger.Debug($"EpicLibrary/AddEpicGame: Updating Epic game {epicGame.Name} in our Epic library"); + // We update the existing Shortcut with the data over + EpicGame epicGameToUpdate = (EpicGame)GetGame(epicGame.Id.ToString()); + epicGame.CopyTo(epicGameToUpdate); + } + else + { + logger.Debug($"EpicLibrary/AddEpicGame: Adding Epic game {epicGame.Name} to our Epic library"); + // Add the EpicGame to the list of EpicGames + _allEpicGames.Add(epicGame); + } + + //Doublecheck it's been added + if (ContainsGame(epicGame)) + { + return true; + } + else + return false; + + } + + public override bool RemoveGame(Game epicGame) + { + if (!(epicGame is EpicGame)) + return false; + + logger.Debug($"EpicLibrary/RemoveEpicGame: Removing Epic game {epicGame.Name} from our Epic library"); + + // Remove the EpicGame from the list. + int numRemoved = _allEpicGames.RemoveAll(item => item.Id.Equals(epicGame.Id)); + + if (numRemoved == 1) + { + logger.Debug($"EpicLibrary/RemoveEpicGame: Removed Epic game with name {epicGame.Name}"); + return true; + } + else if (numRemoved == 0) + { + logger.Debug($"EpicLibrary/RemoveEpicGame: Didn't remove Epic game with ID {epicGame.Name} from the Epic Library"); + return false; + } + else + throw new EpicLibraryException(); + } + + public override bool RemoveGameById(string epicGameId) + { + if (epicGameId.Equals(0)) + return false; + + logger.Debug($"EpicLibrary/RemoveEpicGame2: Removing Epic game with ID {epicGameId} from the Epic library"); + + // Remove the EpicGame from the list. + int numRemoved = _allEpicGames.RemoveAll(item => item.Id.Equals(epicGameId)); + + if (numRemoved == 1) + { + logger.Debug($"EpicLibrary/RemoveEpicGame2: Removed Epic game with ID {epicGameId}"); + return true; + } + else if (numRemoved == 0) + { + logger.Debug($"EpicLibrary/RemoveEpicGame2: Didn't remove Epic game with ID {epicGameId} from the Epic Library"); + return false; + } + else + throw new EpicLibraryException(); + } + + public override bool RemoveGame(string epicGameNameOrId) + { + if (String.IsNullOrWhiteSpace(epicGameNameOrId)) + return false; + + logger.Debug($"EpicLibrary/RemoveEpicGame3: Removing Epic game with Name or ID {epicGameNameOrId} from the Epic library"); + + int numRemoved; + Match match = Regex.Match(epicGameNameOrId, EpicAppIdRegex, RegexOptions.IgnoreCase); + if (match.Success) + numRemoved = _allEpicGames.RemoveAll(item => epicGameNameOrId.Equals(item.Id)); + else + numRemoved = _allEpicGames.RemoveAll(item => epicGameNameOrId.Equals(item.Name)); + + if (numRemoved == 1) + { + logger.Debug($"EpicLibrary/RemoveEpicGame3: Removed Epic game with Name or UUID {epicGameNameOrId} "); + return true; + } + else if (numRemoved == 0) + { + logger.Debug($"EpicLibrary/RemoveEpicGame3: Didn't remove Epic game with Name or UUID {epicGameNameOrId} from the Epic Library"); + return false; + } + else + throw new EpicLibraryException(); + + } + + public override bool ContainsGame(Game epicGame) + { + if (!(epicGame is EpicGame)) + return false; + + foreach (EpicGame testEpicGame in _allEpicGames) + { + if (testEpicGame.Id.Equals(epicGame.Id)) + return true; + } + + return false; + } + + public override bool ContainsGameById(string epicGameId) + { + foreach (EpicGame testEpicGame in _allEpicGames) + { + if (epicGameId == testEpicGame.Id) + return true; + } + + + return false; + + } + + public override bool ContainsGame(string epicGameNameOrId) + { + if (String.IsNullOrWhiteSpace(epicGameNameOrId)) + return false; + + + Match match = Regex.Match(epicGameNameOrId, EpicAppIdRegex, RegexOptions.IgnoreCase); + if (match.Success) + { + foreach (EpicGame testEpicGame in _allEpicGames) + { + if (epicGameNameOrId.Equals(Convert.ToInt32(testEpicGame.Id))) + return true; + } + + } + else + { + foreach (EpicGame testEpicGame in _allEpicGames) + { + if (epicGameNameOrId.Equals(testEpicGame.Name)) + return true; + } + + } + + return false; + + } + + + public override Game GetGame(string epicGameNameOrId) + { + if (String.IsNullOrWhiteSpace(epicGameNameOrId)) + return null; + + Match match = Regex.Match(epicGameNameOrId, EpicAppIdRegex, RegexOptions.IgnoreCase); + if (match.Success) + { + foreach (EpicGame testEpicGame in _allEpicGames) + { + if (epicGameNameOrId.Equals(Convert.ToInt32(testEpicGame.Id))) + return testEpicGame; + } + + } + else + { + foreach (EpicGame testEpicGame in _allEpicGames) + { + if (epicGameNameOrId.Equals(testEpicGame.Name)) + return testEpicGame; + } + + } + + return null; + + } + + public override Game GetGameById(string epicGameId) + { + foreach (EpicGame testEpicGame in _allEpicGames) + { + if (epicGameId == testEpicGame.Id) + return testEpicGame; + } + + return null; + + } + + private Dictionary ParseEpicManifest(string path) + { + string encodedContents = File.ReadAllText(path); + Dictionary parameters = Regex.Matches(encodedContents, "([^?=&]+)(=([^&]*))?").Cast().ToDictionary(x => x.Groups[1].Value, x => x.Groups[3].Value); + return parameters; + } + + + public override bool LoadInstalledGames() + { + try + { + + if (!_isEpicInstalled) + { + // Epic isn't installed, so we return an empty list. + logger.Info($"EpicLibrary/LoadInstalledGames: Epic library is not installed"); + return false; + } + + var localContentPath = Path.Combine(_epicLocalContent, "LocalContent"); + logger.Trace($"EpicLibrary/LoadInstalledGames: Looking for Local Content in {localContentPath}"); + + if (Directory.Exists(localContentPath)) + { + logger.Trace($"EpicLibrary/LoadInstalledGames: Local Content Directory {localContentPath} exists!"); + string[] packages = Directory.GetFiles(localContentPath, "*.mfst", SearchOption.AllDirectories); + logger.Trace($"EpicLibrary/LoadInstalledGames: Found .mfst files in Local Content Directory {localContentPath}: {packages.ToString()}"); + foreach (string package in packages) + { + logger.Trace($"EpicLibrary/LoadInstalledGames: Parsing {package} name to find GameID"); + try + { + GameAppInfo epicGame = new GameAppInfo(); + epicGame.GameID = Path.GetFileNameWithoutExtension(package); + logger.Trace($"EpicLibrary/LoadInstalledGames: Got GameID of {epicGame.GameID } from file {package}"); + if (!epicGame.GameID.StartsWith("Epic")) + { + // If the gameId doesn't start with epic, then we need to find it! + // Get game id by fixing file via adding : before integer part of the name + // for example OFB-EAST52017 converts to OFB-EAST:52017 + Match match = Regex.Match(epicGame.GameID, @"^(.*?)(\d+)$"); + if (!match.Success) + { + logger.Warn($"EpicLibrary/LoadInstalledGames: Failed to match game id from file {package} name so ignoring game"); + continue; + } + + epicGame.GameID = match.Groups[1].Value + ":" + match.Groups[2].Value; + logger.Trace($"EpicLibrary/LoadInstalledGames: GameID doesn't start with 'Epic' so using different pattern to find {epicGame.GameID} GameID"); + } + + // Now we get the rest of the game information out of the manifest file + Dictionary manifestInfo = ParseEpicManifest(package); + + logger.Trace($"EpicLibrary/LoadInstalledGames: Looking whether Epic is still downloading the game to install it"); + if (manifestInfo.ContainsKey("ddinitialdownload") && manifestInfo["ddinitialdownload"] == "1") + { + // Epic is downloading and installing the game so we skip it + logger.Warn($"EpicLibrary/LoadInstalledGames: Epic is still downloading the game with Game ID {epicGame.GameID} to install it"); + continue; + } + logger.Trace($"EpicLibrary/LoadInstalledGames: Looking whether Epic is downloading game updates"); + if (manifestInfo.ContainsKey("downloading") && manifestInfo["downloading"] == "1") + { + // Epic is downloading some new content so we can't play it at the moment + logger.Warn($"EpicLibrary/LoadInstalledGames: Epic is downloading game updates for the game with Game ID {epicGame.GameID}"); + continue; + } + + epicGame.GameInstallDir = null; + logger.Trace($"EpicLibrary/LoadInstalledGames: Looking where the game with Game ID {epicGame.GameID} is installed"); + if (manifestInfo.ContainsKey("dipinstallpath")) + { + // This is where Epic has installed this game + epicGame.GameInstallDir = HttpUtility.UrlDecode(manifestInfo["dipinstallpath"]); + if (String.IsNullOrEmpty(epicGame.GameInstallDir) || !Directory.Exists(epicGame.GameInstallDir)) + { + logger.Warn($"EpicLibrary/LoadInstalledGames: Epic game with ID {epicGame.GameID} found but no valid directory found at {epicGame.GameInstallDir}"); + continue; + } + } + else + { + logger.Warn($"EpicLibrary/LoadInstalledGames: Couldn't figure out where Game ID {epicGame.GameID} is installed. Skipping game."); + } + + string gameInstallerData = Path.Combine(epicGame.GameInstallDir, @"__Installer", @"installerdata.xml"); + logger.Trace($"EpicLibrary/LoadInstalledGames: Parsing the Game Installer Data at {gameInstallerData}"); + + if (File.Exists(gameInstallerData)) + { + logger.Trace($"EpicLibrary/LoadInstalledGames: Game Installer Data file was found at {gameInstallerData}"); + logger.Trace($"EpicLibrary/LoadInstalledGames: Attempting to parse XML Game Installer Data file at {gameInstallerData}"); + // Now we parse the XML + XDocument xdoc = XDocument.Load(gameInstallerData); + epicGame.GameName = xdoc.XPathSelectElement("/DiPManifest/gameTitles/gameTitle[@locale='en_US']").Value; + logger.Trace($"EpicLibrary/LoadInstalledGames: Game Name {epicGame.GameName} found in Game Installer Data file {gameInstallerData}"); + string gameFilePath = xdoc.XPathSelectElement("/DiPManifest/runtime/launcher/filePath").Value; + logger.Trace($"EpicLibrary/LoadInstalledGames: Game File Path is {gameFilePath } found in Game Installer Data file {gameInstallerData}"); + + string epicGameInstallLocation = ""; + // Check whether gameFilePath contains a registry key! Cause if it does we need to lookup the path there instead + if (gameFilePath.StartsWith("[HKEY_LOCAL_MACHINE")) + { + logger.Trace($"EpicLibrary/LoadInstalledGames: Game File Path starts with a registery key so needs to be translated"); + // The filePath contains a registry key lookup that we need to execute and replace + string epicGameInstallKeyNameAndValue = ""; + string epicGameRestOfFile = ""; + MatchCollection mc = Regex.Matches(gameFilePath, @"\[HKEY_LOCAL_MACHINE\\(.*)\](.*)"); + if (mc.Count > 0) + { + // Split the Reg key bit from the File Path bit + + epicGameInstallKeyNameAndValue = mc[0].Groups[1].ToString(); + logger.Trace($"EpicLibrary/LoadInstalledGames: epicGameInstallKeyNameAndValue = {epicGameInstallKeyNameAndValue}"); + epicGameRestOfFile = mc[0].Groups[2].ToString(); + logger.Trace($"EpicLibrary/LoadInstalledGames: epicGameRestOfFile = {epicGameRestOfFile}"); + if (epicGameInstallKeyNameAndValue == null || epicGameInstallKeyNameAndValue == "") + { + // then we have a problem and we need to continue and ignore this game + logger.Warn($"EpicLibrary/LoadInstalledGames: Epic game with ID {epicGame.GameID} has registry key but we can't extract it! gameFilePath is {gameFilePath}."); + continue; + } + + // Split the reg key from the value name + + string epicGameInstallKeyName = ""; + string epicGameInstallKeyValue = ""; + mc = Regex.Matches(epicGameInstallKeyNameAndValue, @"(.*)\\([^\\]*)"); + if (mc.Count > 0) + { + epicGameInstallKeyName = mc[0].Groups[1].ToString(); + logger.Trace($"EpicLibrary/LoadInstalledGames: epicGameInstallKeyName = {epicGameInstallKeyName }"); + epicGameInstallKeyValue = mc[0].Groups[2].ToString(); + logger.Trace($"EpicLibrary/LoadInstalledGames: epicGameInstallKeyValue = {epicGameInstallKeyValue }"); + } + + // Lookup the reg key to figure out where the game is installed + try + { + RegistryKey epicGameInstallKey = Registry.LocalMachine.OpenSubKey(epicGameInstallKeyName, RegistryKeyPermissionCheck.ReadSubTree); + if (epicGameInstallKey == null) + { + // then we have a problem as we cannot find the game exe location! + logger.Warn($"EpicLibrary/LoadInstalledGames: Epic game with ID {epicGame.GameID} has a install reg key we cannot find! epicGameInstallKey is {gameFilePath} and epicGameInstallKeyValue is {epicGameInstallKeyValue}."); + continue; + } + epicGameInstallLocation = Path.Combine(epicGameInstallKey.GetValue(epicGameInstallKeyValue).ToString(), epicGameRestOfFile); + if (!File.Exists(epicGameInstallLocation)) + { + // then we have a problem as we cannot locate the game exe file to start! + logger.Warn($"EpicLibrary/LoadInstalledGames: Epic game with ID {epicGame.GameID} has gameexe we cannot find! epicGameInstallLocation is {epicGameInstallLocation}."); + continue; + } + epicGame.GameExePath = epicGameInstallLocation; + } + catch (SecurityException ex) + { + logger.Warn(ex, $"EpicLibrary/LoadInstalledGames: The user does not have the permissions required to read the Epic Game location registry key {epicGameInstallKeyName}, so skipping game"); + continue; + } + catch (ObjectDisposedException ex) + { + logger.Warn(ex, "EpicLibrary/LoadInstalledGames: The Microsoft.Win32.RegistryKey is closed when trying to access the Epic ClientPath registry key (closed keys cannot be accessed), so skipping game"); + continue; + } + catch (IOException ex) + { + logger.Warn(ex, "EpicLibrary/LoadInstalledGames: The Epic ClientPath registry key has been marked for deletion so we cannot access the value dueing the EpicLibrary check, so skipping game"); + continue; + } + catch (UnauthorizedAccessException ex) + { + logger.Warn(ex, "EpicLibrary/LoadInstalledGames: The user does not have the necessary registry rights to check whether Epic is installed, so skipping game"); + continue; + } + } + else + { + logger.Warn($"EpicLibrary/LoadInstalledGames: Game File Path {gameFilePath} starts with '[HEKY_LOCAL_MACHINE' but didn't match the regex when it should have"); + continue; + } + + } + else if (gameFilePath.StartsWith("[HKEY_CURRENT_USER")) + { + // The filePath contains a registry key lookup that we need to execute and replace + MatchCollection mc = Regex.Matches(gameFilePath, @"\[HKEY_CURRENT_USER\\(.*)\](.*)"); + if (mc.Count > 0) + { + string epicGameInstallKeyNameAndValue = mc[0].Groups[1].ToString(); + string epicGameRestOfFile = mc[0].Groups[2].ToString(); + if (epicGameInstallKeyNameAndValue == null) + { + // then we have a problem and we need to continue and ignore this game + logger.Warn($"EpicLibrary/LoadInstalledGames: Epic game with ID {epicGame.GameID} has registry but we can't match it! gameFilePath is {gameFilePath}."); + continue; + } + + mc = Regex.Matches(epicGameInstallKeyNameAndValue, @"(.*)\\([^\\]*)"); + string epicGameInstallKeyName = mc[0].Groups[1].ToString(); + string epicGameInstallKeyValue = mc[0].Groups[2].ToString(); + + try + { + RegistryKey epicGameInstallKey = Registry.LocalMachine.OpenSubKey(epicGameInstallKeyName, RegistryKeyPermissionCheck.ReadSubTree); + if (epicGameInstallKey == null) + { + // then we have a problem as we cannot find the game exe location! + logger.Warn($"EpicLibrary/LoadInstalledGames: Epic game with ID {epicGame.GameID} has a install reg key we cannot find! epicGameInstallKey is {gameFilePath} and epicGameInstallKeyValue is {epicGameInstallKeyValue}."); + continue; + } + epicGameInstallLocation = Path.Combine(epicGameInstallKey.GetValue(epicGameInstallKeyValue).ToString(), epicGameRestOfFile); + if (!File.Exists(epicGameInstallLocation)) + { + // then we have a problem as we cannot locate the game exe file to start! + logger.Warn($"EpicLibrary/LoadInstalledGames: Epic game with ID {epicGame.GameID} has gameexe we cannot find! epicGameInstallLocation is {epicGameInstallLocation}."); + continue; + } + epicGame.GameExePath = epicGameInstallLocation; + + } + catch (SecurityException ex) + { + logger.Warn(ex, $"EpicLibrary/LoadInstalledGames: The user does not have the permissions required to read the Epic Game location registry key {epicGameInstallKeyName}, so skipping game"); + continue; + } + catch (ObjectDisposedException ex) + { + logger.Warn(ex, "EpicLibrary/LoadInstalledGames: The Microsoft.Win32.RegistryKey is closed when trying to access the Epic ClientPath registry key (closed keys cannot be accessed), so skipping game"); + continue; + } + catch (IOException ex) + { + logger.Warn(ex, "EpicLibrary/LoadInstalledGames: The Epic ClientPath registry key has been marked for deletion so we cannot access the value dueing the EpicLibrary check, so skipping game"); + continue; + } + catch (UnauthorizedAccessException ex) + { + logger.Warn(ex, "EpicLibrary/LoadInstalledGames: The user does not have the necessary registry rights to check whether Epic is installed, so skipping game"); + continue; + } + } + else + { + logger.Warn($"EpicLibrary/LoadInstalledGames: Game File Path {gameFilePath} starts with '[HKEY_CURRENT_USER' but didn't match the regex when it should have, so skipping game"); + continue; + } + } + else + { + // If we get here, then the gameFilepath is the actual filepath! So we just copy it. + logger.Trace($"EpicLibrary/LoadInstalledGames: Game File Path {gameFilePath} doesn't start with '[HKEY_LOCAL_MACHINE' or '[HKEY_CURRENT_USER' so it must be aplain file path"); + epicGame.GameExePath = gameFilePath; + } + + + if (!File.Exists(epicGame.GameExePath)) + { + logger.Warn($"EpicLibrary/LoadInstalledGames: Epic game with ID {epicGame.GameID} found but no game exe found at epicGame.GameExePath {epicGame.GameExePath} so skipping game"); + continue; + } + + // TODO check for icon! For now we will just use the exe one + epicGame.GameIconPath = epicGame.GameExePath; + logger.Trace($"EpicLibrary/LoadInstalledGames: Epic gameIconPath = {epicGame.GameIconPath} (currently just taking it from the file exe!"); + + // If we reach here we add the Game to the list of games we have! + _allEpicGames.Add(new EpicGame(epicGame.GameID, epicGame.GameName, epicGame.GameExePath, epicGame.GameIconPath)); + } + else + { + // If we can't find the __Installer\installerdata.xml file then we ignore this game + logger.Trace($"EpicLibrary/LoadInstalledGames: Couldn't find Game Installer Data file at {gameInstallerData} for game with GameID {epicGame.GameID} so skipping this game"); + continue; + } + + + } + catch (Exception ex) + { + logger.Error(ex, $"EpicLibrary/LoadInstalledGames: Failed to import installed Epic game {package}."); + } + } + } + else + { + logger.Warn($"EpicLibrary/LoadInstalledGames: No Epic games installed in the Epic library"); + return false; + } + + logger.Info($"EpicLibrary/LoadInstalledGames: Found {_allEpicGames.Count} installed Epic games"); + + } + + catch (ArgumentNullException ex) + { + logger.Warn(ex, "EpicLibrary/GetAllInstalledGames: An argument supplied to the function is null."); + } + catch (NotSupportedException ex) + { + logger.Warn(ex, "EpicLibrary/GetAllInstalledGames: The invoked method is not supported or reading, seeking or writing tp a stream that isn't supported."); + } + catch (PathTooLongException ex) + { + logger.Warn(ex, "EpicLibrary/GetAllInstalledGames: The path is longer than the maximum allowed by the operating system."); + } + catch (SecurityException ex) + { + logger.Warn(ex, "EpicLibrary/GetAllInstalledGames: The user does not have the permissions required to read the Epic InstallDir registry key."); + } + catch (ObjectDisposedException ex) + { + logger.Warn(ex, "EpicLibrary/GetAllInstalledGames: The Microsoft.Win32.RegistryKey is closed when trying to access the Epic InstallDir registry key (closed keys cannot be accessed)."); + } + catch (IOException ex) + { + logger.Warn(ex, "EpicLibrary/GetAllInstalledGames: The Epic InstallDir registry key has been marked for deletion so we cannot access the value dueing the EpicLibrary check."); + } + catch (UnauthorizedAccessException ex) + { + logger.Warn(ex, "EpicLibrary/GetAllInstalledGames: The user does not have the necessary registry rights to check whether Epic is installed."); + } + + return true; + } + + #endregion + + } + + [global::System.Serializable] + public class EpicLibraryException : GameLibraryException + { + public EpicLibraryException() { } + public EpicLibraryException(string message) : base(message) { } + public EpicLibraryException(string message, Exception inner) : base(message, inner) { } + protected EpicLibraryException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } + +}