From 62c3178ff259bdf0dcc1c70e06f34a8d72ecc5f7 Mon Sep 17 00:00:00 2001 From: erri120 Date: Fri, 3 Jan 2020 18:03:09 +0100 Subject: [PATCH] Overhauled the StoreHandlers --- Wabbajack.Common/StoreHandlers/GOGHandler.cs | 133 ++++++++ .../StoreHandlers/SteamHandler.cs | 301 ++++++++++++++++++ .../StoreHandlers/StoreHandler.cs | 93 ++++++ 3 files changed, 527 insertions(+) create mode 100644 Wabbajack.Common/StoreHandlers/GOGHandler.cs create mode 100644 Wabbajack.Common/StoreHandlers/SteamHandler.cs create mode 100644 Wabbajack.Common/StoreHandlers/StoreHandler.cs diff --git a/Wabbajack.Common/StoreHandlers/GOGHandler.cs b/Wabbajack.Common/StoreHandlers/GOGHandler.cs new file mode 100644 index 00000000..70d9e9e2 --- /dev/null +++ b/Wabbajack.Common/StoreHandlers/GOGHandler.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security; +using Microsoft.Win32; + +namespace Wabbajack.Common.StoreHandlers +{ + public class GOGGame : AStoreGame + { + public override Game Game { get; internal set; } + public override string Name { get; internal set; } + public override string Path { get; internal set; } + public override int ID { get; internal set; } + public override StoreType Type { get; internal set; } = StoreType.GOG; + } + + public class GOGHandler : AStoreHandler + { + public override List Games { get; set; } + public override StoreType Type { get; internal set; } + + private const string GOGRegKey = @"Software\GOG.com\Games"; + private const string GOG64RegKey = @"Software\WOW6432Node\GOG.com\Games"; + + private RegistryKey GOGKey { get; set; } + + public override bool Init() + { + try + { + var gogKey = Registry.LocalMachine.OpenSubKey(GOGRegKey) ?? + Registry.LocalMachine.OpenSubKey(GOG64RegKey); + + if (gogKey == null) + { + Utils.Error(new StoreException("Could not open the GOG registry key!")); + return false; + } + + GOGKey = gogKey; + return true; + } + catch (SecurityException se) + { + Utils.Error(se, "GOGHandler could not read from registry!"); + } + catch (UnauthorizedAccessException uae) + { + Utils.Error(uae, "GOGHandler could not read from registry!"); + } + + return false; + } + + public override bool LoadAllGames() + { + try + { + string[] keys = GOGKey.GetSubKeyNames(); + Utils.Log($"Found {keys.Length} SubKeys for GOG"); + + keys.Do(key => + { + if (!int.TryParse(key, out var gameID)) + { + Utils.Error(new StoreException($"Could not read gameID for key {key}")); + return; + } + + var subKey = GOGKey.OpenSubKey(key); + if (subKey == null) + { + Utils.Error(new StoreException($"Could not open SubKey for {key}")); + return; + } + + var gameNameValue = subKey.GetValue("GAMENAME"); + if (gameNameValue == null) + { + Utils.Error(new StoreException($"Could not get GAMENAME for {gameID} at {key}")); + return; + } + + var gameName = gameNameValue.ToString(); + + var pathValue = subKey.GetValue("PATH"); + if (pathValue == null) + { + Utils.Error(new StoreException($"Could not get PATH for {gameID} at {key}")); + return; + } + + var path = pathValue.ToString(); + + var game = new GOGGame() {ID = gameID, Name = gameName, Path = path}; + + var gameMeta = GameRegistry.Games.Values.FirstOrDefault(g => + g.GOGIDs.Contains(game.ID) + && + g.RequiredFiles.TrueForAll(file => + File.Exists(Path.Combine(game.Path, file)))); + + if (gameMeta == null) + return; + + game.Game = gameMeta.Game; + + Utils.Log($"Found GOG Game: \"{game.Name}\"({game.ID}) at {game.Path}"); + + Games.Add(game); + }); + } + catch (SecurityException se) + { + Utils.Error(se, "GOGHandler could not read from registry!"); + } + catch (UnauthorizedAccessException uae) + { + Utils.Error(uae, "GOGHandler could not read from registry!"); + } + catch (Exception e) + { + Utils.ErrorThrow(e); + } + + Utils.Log($"Total number of GOG Games found: {Games.Count}"); + + return true; + } + } +} diff --git a/Wabbajack.Common/StoreHandlers/SteamHandler.cs b/Wabbajack.Common/StoreHandlers/SteamHandler.cs new file mode 100644 index 00000000..f7bbb2e5 --- /dev/null +++ b/Wabbajack.Common/StoreHandlers/SteamHandler.cs @@ -0,0 +1,301 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Security; +using DynamicData; +using Microsoft.Win32; + +namespace Wabbajack.Common.StoreHandlers +{ + public class SteamGame : AStoreGame + { + public override Game Game { get; internal set; } + public override string Name { get; internal set; } + public override string Path { get; internal set; } + public override int ID { get; internal set; } + public override StoreType Type { get; internal set; } = StoreType.STEAM; + + public string Universe; + + public List WorkshopItems; + public int WorkshopItemsSize; + } + + public class SteamWorkshopItem + { + public SteamGame Game; + public int ItemID; + public int Size; + } + + public class SteamHandler : AStoreHandler + { + public override List Games { get; set; } + public override StoreType Type { get; internal set; } = StoreType.STEAM; + + private const string SteamRegKey = @"Software\Valve\Steam"; + + private string SteamPath { get; set; } + private List SteamUniverses { get; set; } + + private string SteamConfig => Path.Combine(SteamPath, "config", "config.vdf"); + + public override bool Init() + { + try + { + var steamKey = Registry.CurrentUser.OpenSubKey(SteamRegKey); + + var steamPathKey = steamKey?.GetValue("SteamPath"); + if (steamPathKey == null) + { + Utils.Error(new StoreException("Could not open the SteamPath registry key!")); + return false; + } + + SteamPath = steamPathKey.ToString(); + if (string.IsNullOrWhiteSpace(SteamPath)) + { + Utils.Error(new StoreException("Path to the Steam Directory from registry is Null or Empty!")); + return false; + } + + if (!Directory.Exists(SteamPath)) + { + Utils.Error(new StoreException($"Path to the Steam Directory from registry does not exists: {SteamPath}")); + return false; + } + + if (File.Exists(SteamConfig)) + return true; + + Utils.Error(new StoreException($"The Steam config file could not be read: {SteamConfig}")); + return false; + + } + catch (SecurityException se) + { + Utils.Error(se, "SteamHandler could not read from registry!"); + } + catch (UnauthorizedAccessException uae) + { + Utils.Error(uae, "SteamHandler could not read from registry!"); + } + + return false; + } + + private void LoadUniverses() + { + SteamUniverses = new List(); + + File.ReadAllLines(SteamConfig).Do(l => + { + if (!l.Contains("BaseInstallFolder_")) return; + var s = GetVdfValue(l); + s = Path.Combine(s, "steamapps"); + if (!Directory.Exists(s)) + { + Utils.Log($"Directory {s} does not exist, skipping"); + return; + } + + SteamUniverses.Add(s); + Utils.Log($"Steam Library found at {s}"); + }); + + Utils.Log($"Total number of Steam Libraries found: {SteamUniverses.Count}"); + + // Default path in the Steam folder isn't in the configs + if(Directory.Exists(Path.Combine(SteamPath, "steamapps"))) + SteamUniverses.Add(Path.Combine(SteamPath, "steamapps")); + } + + public override bool LoadAllGames() + { + if(SteamUniverses == null) + LoadUniverses(); + + if (SteamUniverses.Count == 0) + { + Utils.Log("Could not find any Steam Libraries!"); + return false; + } + + SteamUniverses.Do(u => + { + Directory.EnumerateFiles(u, "*.acf", SearchOption.TopDirectoryOnly).Where(File.Exists).Do(f => + { + var game = new SteamGame(); + var valid = false; + + File.ReadAllLines(f).Do(l => + { + if (l.Contains("\"appid\"")) + { + if (!int.TryParse(GetVdfValue(l), out var id)) + return; + game.ID = id; + } + + if (l.Contains("\"name\"")) + game.Name = GetVdfValue(l); + + if (l.Contains("\"installdir\"")) + { + var path = Path.Combine(u, "common", GetVdfValue(l)); + if (Directory.Exists(path)) + game.Path = path; + else + return; + } + + valid = true; + }); + + if (!valid) return; + + var gameMeta = GameRegistry.Games.Values.FirstOrDefault(g => + g.SteamIDs.Contains(game.ID) + && + g.RequiredFiles.TrueForAll(file => + File.Exists(Path.Combine(game.Path, file)))); + + if (gameMeta == null) + return; + + game.Game = gameMeta.Game; + game.Universe = u; + + Utils.Log($"Found Steam Game: \"{game.Name}\"({game.ID}) at {game.Path}"); + + LoadWorkshopItems(game); + + Games.Add(game); + }); + }); + + Utils.Log($"Total number of Steam Games found: {Games.Count}"); + + return true; + } + + private void LoadWorkshopItems(SteamGame game) + { + if(game.WorkshopItems == null) + game.WorkshopItems = new List(); + + var workshop = Path.Combine(game.Universe, "workshop"); + if (!Directory.Exists(workshop)) + return; + + Directory.EnumerateFiles(workshop, "*.acf", SearchOption.TopDirectoryOnly).Where(File.Exists).Do(f => + { + if (Path.GetFileName(f) != $"appworkshop{game.ID}.acf") + return; + + Utils.Log($"Found workshop item file {f} for \"{game.Name}\""); + + var lines = File.ReadAllLines(f); + var end = false; + var foundAppID = false; + var workshopItemsInstalled = 0; + var workshopItemDetails = 0; + var bracketStart = 0; + var bracketEnd = 0; + + var currentItem = new SteamWorkshopItem(); + + lines.Do(l => + { + if (end) + return; + if(currentItem == null) + currentItem = new SteamWorkshopItem(); + + var currentLine = lines.IndexOf(l); + if (l.Contains("\"appid\"") && !foundAppID) + { + if (!int.TryParse(GetVdfValue(l), out var appID)) + return; + + foundAppID = true; + + if (appID != game.ID) + return; + } + + if (!foundAppID) + return; + + if (l.Contains("\"SizeOnDisk\"")) + { + if (!int.TryParse(GetVdfValue(l), out var sizeOnDisk)) + return; + + game.WorkshopItemsSize += sizeOnDisk; + } + + if (l.Contains("\"WorkshopItemsInstalled\"")) + workshopItemsInstalled = currentLine; + + if (l.Contains("\"WorkshopItemDetails\"")) + workshopItemDetails = currentLine; + + if (workshopItemsInstalled == 0) + return; + + if (currentLine <= workshopItemsInstalled + 1 && currentLine >= workshopItemDetails - 1) + return; + + if (currentItem.ItemID == 0) + if (!int.TryParse(GetSingleVdfValue(l), out currentItem.ItemID)) + return; + + if (currentItem.ItemID == 0) + return; + + if (bracketStart == 0 && l.Contains("{")) + bracketStart = currentLine; + + if (bracketEnd == 0 && l.Contains("}")) + bracketEnd = currentLine; + + if (bracketStart == 0) + return; + + if (currentLine == bracketStart + 1) + if (!int.TryParse(GetVdfValue(l), out currentItem.Size)) + return; + + if (bracketStart == 0 || bracketEnd == 0 || currentItem.ItemID == 0 || currentItem.Size == 0) + return; + + bracketStart = 0; + bracketEnd = 0; + currentItem.Game = game; + game.WorkshopItems.Add(currentItem); + + Utils.Log($"Found WorkshopItem {currentItem.ItemID}"); + + currentItem = null; + end = true; + }); + }); + } + + private static string GetVdfValue(string line) + { + var trim = line.Trim('\t').Replace("\t", ""); + string[] s = trim.Split('\"'); + return s[3].Replace("\\\\", "\\"); + } + + private static string GetSingleVdfValue(string line) + { + var trim = line.Trim('\t').Replace("\t", ""); + return trim.Split('\"')[1]; + } + } +} diff --git a/Wabbajack.Common/StoreHandlers/StoreHandler.cs b/Wabbajack.Common/StoreHandlers/StoreHandler.cs new file mode 100644 index 00000000..b303553e --- /dev/null +++ b/Wabbajack.Common/StoreHandlers/StoreHandler.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Wabbajack.Common.StoreHandlers +{ + public enum StoreType + { + STEAM, + GOG + } + + public class StoreHandler + { + private static readonly Lazy instance = new Lazy(() => new StoreHandler(), true); + public static StoreHandler Instance => instance.Value; + + private static readonly Lazy _steamHandler = new Lazy(() => new SteamHandler()); + public SteamHandler SteamHandler = _steamHandler.Value; + + private static readonly Lazy _gogHandler = new Lazy(() => new GOGHandler()); + public GOGHandler GOGHandler = _gogHandler.Value; + + public List StoreGames; + + public StoreHandler() + { + StoreGames = new List(); + + if (SteamHandler.Init()) + { + if(SteamHandler.LoadAllGames()) + StoreGames.AddRange(SteamHandler.Games); + else + Utils.Error(new StoreException("Could not load all Games from the SteamHandler, check previous error messages!")); + } + else + { + Utils.Error(new StoreException("Could not Init the SteamHandler, check previous error messages!")); + } + + if (GOGHandler.Init()) + { + if(GOGHandler.LoadAllGames()) + StoreGames.AddRange(GOGHandler.Games); + else + Utils.Error(new StoreException("Could not load all Games from the GOGHandler, check previous error messages!")); + } + else + { + Utils.Error(new StoreException("Could not Init the GOGHandler, check previous error messages!")); + } + } + + public string GetGamePath(Game game) + { + return StoreGames.FirstOrDefault(g => g.Game == game)?.Path; + } + + public string GetGamePath(int id) + { + return StoreGames.FirstOrDefault(g => g.ID == id)?.Path; + } + } + + public abstract class AStoreGame + { + public abstract Game Game { get; internal set; } + public abstract string Name { get; internal set; } + public abstract string Path { get; internal set; } + public abstract int ID { get; internal set; } + public abstract StoreType Type { get; internal set; } + } + + public abstract class AStoreHandler + { + public abstract List Games { get; set; } + + public abstract StoreType Type { get; internal set; } + + public abstract bool Init(); + + public abstract bool LoadAllGames(); + } + + public class StoreException : Exception + { + public StoreException(string msg) : base(msg) + { + + } + } +}