using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net.Http.Headers; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Wabbajack.Common.StoreHandlers; namespace Wabbajack.Common { [JsonConverter(typeof(StringEnumConverter))] public enum Game { //MO2 GAMES Morrowind, Oblivion, [Description("Fallout 3")] Fallout3, [Description("Fallout New Vegas")] FalloutNewVegas, [Description("Skyrim Legendary Edition")] Skyrim, Enderal, [Description("Skyrim Special Edition")] SkyrimSpecialEdition, [Description("Fallout 4")] Fallout4, [Description("Skyrim VR")] SkyrimVR, [Description("Fallout 4 VR")] Fallout4VR, //MO2 Non-BGS Games [Description("Darkest Dungeon")] DarkestDungeon, Dishonored, Witcher3, [Description("Stardew Valley")] StardewValley, KingdomComeDeliverance, MechWarrior5Mercenaries, NoMansSky, DragonAgeOrigins } public static class GameExtensions { public static GameMetaData MetaData(this Game game) { return GameRegistry.Games[game]; } } public class GameMetaData { public Game Game { get; internal set; } public ModManager SupportedModManager { get; internal set; } public bool IsGenericMO2Plugin { get; internal set; } public string? MO2ArchiveName { get; internal set; } public string? NexusName { get; internal set; } // Nexus DB id for the game, used in some specific situations public long NexusGameId { get; internal set; } public string? MO2Name { get; internal set; } // to get steam ids: https://steamdb.info public List? SteamIDs { get; internal set; } // to get gog ids: https://www.gogdb.org public List? GOGIDs { get; internal set; } // to get these ids, split the numbers from the letters in file names found in // C:\ProgramData\Origin\LocalContent\{game name)\*.mfst // So for DA:O this is "DR208591800.mfst" -> "DR:208591800" public List OriginIDs { get; set; } = new(); public List EpicGameStoreIDs { get; internal set; } = new List(); // to get BethNet IDs: check the registry public int BethNetID { get; internal set; } //for BethNet games only! public string RegString { get; internal set; } = string.Empty; // file to check if the game is present, useful when steamIds and gogIds dont help public List? RequiredFiles { get; internal set; } public string? MainExecutable { get; internal set; } // Games that this game are commonly confused with, for example Skyrim SE vs Skyrim LE public Game[] CommonlyConfusedWith { get; set; } = Array.Empty(); /// /// Other games this game can pull source files from (if the game is installed on the user's machine) /// public Game[] CanSourceFrom { get; set; } = Array.Empty(); public string HumanFriendlyGameName => Game.GetDescription(); private AbsolutePath _cachedPath = default; public string InstalledVersion { get { if (!TryGetGameLocation(out var gameLoc)) throw new GameNotInstalledException(this); if (MainExecutable == null) throw new NotImplementedException(); var info = FileVersionInfo.GetVersionInfo((string)gameLoc.Combine(MainExecutable)); var version = info.ProductVersion; if (string.IsNullOrWhiteSpace(version)) { version = $"{info.ProductMajorPart}.{info.ProductMinorPart}.{info.ProductBuildPart}.{info.ProductPrivatePart}"; return version; } return version; } } public bool IsInstalled => TryGetGameLocation() != null; public AbsolutePath? TryGetGameLocation() { return StoreHandler.Instance.TryGetGamePath(Game); } public bool TryGetGameLocation(out AbsolutePath path) { if (_cachedPath != default) { path = _cachedPath; return true; } var ret = TryGetGameLocation(); if (ret != null) { _cachedPath = ret.Value; path = ret.Value; return true; } path = default; return false; } public AbsolutePath GameLocation() { var ret = TryGetGameLocation(); if (ret == null) throw new ArgumentNullException(); return ret.Value; } } public class GameNotInstalledException : Exception { public GameNotInstalledException(GameMetaData gameMetaData) : base($"Game {gameMetaData.Game} does not appear to be installed.") { } } public static class EnumExtensions { public static string GetDescription(this T enumerationValue) where T : Enum { var type = enumerationValue.GetType(); if(!type.IsEnum) { throw new ArgumentException($"{nameof(enumerationValue)} must be of Enum type", nameof(enumerationValue)); } var memberInfo = type.GetMember(enumerationValue.ToString()!); if (memberInfo.Length <= 0) return enumerationValue.ToString()!; var attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); return attrs.Length > 0 ? ((DescriptionAttribute)attrs[0]).Description : enumerationValue.ToString(); } public static IEnumerable GetAllItems() where T : struct { return Enum.GetValues(typeof(T)).Cast(); } } public class GameRegistry { public static GameMetaData? GetByMO2ArchiveName(string gameName) { gameName = gameName.ToLower(); return Games.Values.FirstOrDefault(g => g.MO2ArchiveName?.ToLower() == gameName); } public static GameMetaData? GetByNexusName(string gameName) { return Games.Values.FirstOrDefault(g => g.NexusName == gameName.ToLower()); } public static GameMetaData? GetBySteamID(int id) { return Games.Values .FirstOrDefault(g => g.SteamIDs != null && g.SteamIDs.Count > 0 && g.SteamIDs.Any(i => i == id)); } /// /// Parse game data from an arbitrary string. Tries first via parsing as a game Enum, then by Nexus name. /// Name to query /// GameMetaData found /// If string could not be translated to a game /// public static GameMetaData GetByFuzzyName(string someName) { return TryGetByFuzzyName(someName) ?? throw new ArgumentNullException(nameof(someName), $"\"{someName}\" could not be translated to a game!"); } /// /// Tries to parse game data from an arbitrary string. Tries first via parsing as a game Enum, then by Nexus name. /// Name to query /// GameMetaData if found, otherwise null /// public static GameMetaData? TryGetByFuzzyName(string someName) { if (Enum.TryParse(typeof(Game), someName, true, out var metadata)) return ((Game)metadata!).MetaData(); GameMetaData? result = GetByNexusName(someName); if (result != null) return result; result = GetByMO2ArchiveName(someName); if (result != null) return result; result = GetByMO2Name(someName); if (result != null) return result; return int.TryParse(someName, out int id) ? GetBySteamID(id) : null; } private static GameMetaData? GetByMO2Name(string gameName) { gameName = gameName.ToLower(); return Games.Values.FirstOrDefault(g => g.MO2Name?.ToLower() == gameName); } public static bool TryGetByFuzzyName(string someName, [MaybeNullWhen(false)] out GameMetaData gameMetaData) { var result = TryGetByFuzzyName(someName); if (result == null) { gameMetaData = Games.Values.First(); return false; } gameMetaData = result; return true; } public static IReadOnlyDictionary Games = new Dictionary { { Game.Morrowind, new GameMetaData { SupportedModManager = ModManager.MO2, Game = Game.Morrowind, SteamIDs = new List{22320}, GOGIDs = new List{1440163901, 1435828767}, NexusName = "morrowind", NexusGameId = 100, MO2Name = "Morrowind", MO2ArchiveName = "morrowind", BethNetID = 31, RegString = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\The Elder Scrolls III: Morrowind Game of the Year Edition", RequiredFiles = new List { "Morrowind.exe" }, MainExecutable = "Morrowind.exe" } }, { Game.Oblivion, new GameMetaData { SupportedModManager = ModManager.MO2, Game = Game.Oblivion, NexusName = "oblivion", NexusGameId = 101, MO2Name = "Oblivion", MO2ArchiveName = "oblivion", SteamIDs = new List {22330}, GOGIDs = new List{1458058109}, RequiredFiles = new List { "oblivion.exe" }, MainExecutable = "Oblivion.exe" } }, { Game.Fallout3, new GameMetaData { SupportedModManager = ModManager.MO2, Game = Game.Fallout3, NexusName = "fallout3", NexusGameId = 120, MO2Name = "Fallout 3", MO2ArchiveName = "fallout3", SteamIDs = new List {22300, 22370}, // base game and GotY GOGIDs = new List{1454315831}, // GotY edition RequiredFiles = new List { "Fallout3.exe" }, MainExecutable = "Fallout3.exe" } }, { Game.FalloutNewVegas, new GameMetaData { SupportedModManager = ModManager.MO2, Game = Game.FalloutNewVegas, NexusName = "newvegas", NexusGameId = 130, MO2Name = "New Vegas", MO2ArchiveName = "falloutnv", SteamIDs = new List {22380, 22490}, // normal and RU version GOGIDs = new List{1454587428}, RequiredFiles = new List { "FalloutNV.exe" }, MainExecutable = "FalloutNV.exe" } }, { Game.Skyrim, new GameMetaData { SupportedModManager = ModManager.MO2, Game = Game.Skyrim, NexusName = "skyrim", NexusGameId = 110, MO2Name = "Skyrim", MO2ArchiveName = "skyrim", SteamIDs = new List {72850}, RequiredFiles = new List { "tesv.exe" }, MainExecutable = "TESV.exe", CommonlyConfusedWith = new [] {Game.SkyrimSpecialEdition, Game.SkyrimVR} } }, { Game.SkyrimSpecialEdition, new GameMetaData { SupportedModManager = ModManager.MO2, Game = Game.SkyrimSpecialEdition, NexusName = "skyrimspecialedition", NexusGameId = 1704, MO2Name = "Skyrim Special Edition", MO2ArchiveName = "skyrimse", SteamIDs = new List {489830}, RequiredFiles = new List { "SkyrimSE.exe" }, MainExecutable = "SkyrimSE.exe", CommonlyConfusedWith = new []{Game.Skyrim, Game.SkyrimVR}, } }, { Game.Fallout4, new GameMetaData { SupportedModManager = ModManager.MO2, Game = Game.Fallout4, NexusName = "fallout4", NexusGameId = 1151, MO2Name = "Fallout 4", MO2ArchiveName = "fallout4", SteamIDs = new List {377160}, RequiredFiles = new List { "Fallout4.exe" }, MainExecutable = "Fallout4.exe", CommonlyConfusedWith = new [] {Game.Fallout4VR}, } }, { Game.SkyrimVR, new GameMetaData { SupportedModManager = ModManager.MO2, Game = Game.SkyrimVR, NexusName = "skyrimspecialedition", NexusGameId = 1704, MO2Name = "Skyrim VR", MO2ArchiveName = "skyrimse", SteamIDs = new List {611670}, RequiredFiles = new List { "SkyrimVR.exe" }, MainExecutable = "SkyrimVR.exe", CommonlyConfusedWith = new []{Game.Skyrim, Game.SkyrimSpecialEdition}, CanSourceFrom = new [] {Game.SkyrimSpecialEdition} } }, { Game.Enderal, new GameMetaData { SupportedModManager = ModManager.MO2, Game = Game.Enderal, NexusName = "enderal", NexusGameId = 2736, MO2Name = "Enderal", MO2ArchiveName = "enderal", SteamIDs = new List{1027920, 933480}, RequiredFiles = new List { "TESV.exe" }, MainExecutable = "TESV.exe" } }, { Game.Fallout4VR, new GameMetaData { SupportedModManager = ModManager.MO2, Game = Game.Fallout4VR, NexusName = "fallout4", MO2Name = "Fallout 4 VR", MO2ArchiveName = "Fallout4", SteamIDs = new List{611660}, RequiredFiles = new List { "Fallout4VR.exe" }, MainExecutable = "Fallout4VR.exe", CommonlyConfusedWith = new [] {Game.Fallout4}, CanSourceFrom = new [] {Game.Fallout4} } }, { Game.DarkestDungeon, new GameMetaData { Game = Game.DarkestDungeon, NexusName = "darkestdungeon", MO2Name = "Darkest Dungeon", NexusGameId = 804, SteamIDs = new List {262060}, GOGIDs = new List{1450711444}, EpicGameStoreIDs = new List {"b4eecf70e3fe4e928b78df7855a3fc2d"}, IsGenericMO2Plugin = true, RequiredFiles = new List { "_windowsnosteam\\Darkest.exe" }, MainExecutable = "_windowsnosteam\\Darkest.exe" } }, { Game.Dishonored, new GameMetaData { Game = Game.Dishonored, NexusName = "dishonored", MO2Name = "Dishonored", NexusGameId = 802, SteamIDs = new List {205100}, GOGIDs = new List{1701063787}, RequiredFiles = new List { "Binaries\\Win32\\Dishonored.exe" }, MainExecutable = @"Binaries\Win32\Dishonored.exe" } }, { Game.Witcher3, new GameMetaData { Game = Game.Witcher3, NexusName = "witcher3", NexusGameId = 952, MO2Name = "The Witcher 3: Wild Hunt", SteamIDs = new List{292030, 499450}, // normal and GotY GOGIDs = new List{1207664643, 1495134320, 1207664663, 1640424747}, // normal, GotY and both in packages RequiredFiles = new List { "bin\\x64\\witcher3.exe" }, MainExecutable = @"bin\x64\witcher3.exe" } }, { Game.StardewValley, new GameMetaData { Game = Game.StardewValley, NexusName = "stardewvalley", MO2Name = "Stardew Valley", NexusGameId = 1303, SteamIDs = new List{413150}, GOGIDs = new List{1453375253}, IsGenericMO2Plugin = true, RequiredFiles = new List { "Stardew Valley.exe" }, MainExecutable = "Stardew Valley.exe" } }, { Game.KingdomComeDeliverance, new GameMetaData { Game = Game.KingdomComeDeliverance, NexusName = "kingdomcomedeliverance", MO2Name = "Kingdom Come: Deliverance", MO2ArchiveName = "kingdomcomedeliverance", NexusGameId = 2298, SteamIDs = new List{379430}, GOGIDs = new List{1719198803}, IsGenericMO2Plugin = true, RequiredFiles = new List { @"bin\Win64\KingdomCome.exe" }, MainExecutable = @"bin\Win64\KingdomCome.exe" } }, { Game.MechWarrior5Mercenaries, new GameMetaData { Game = Game.MechWarrior5Mercenaries, NexusName = "mechwarrior5mercenaries", MO2Name = "Mechwarrior 5: Mercenaries", MO2ArchiveName = "mechwarrior5mercenaries", NexusGameId = 3099, EpicGameStoreIDs = new List {"9fd39d8ac72946a2a10a887ce86e6c35"}, IsGenericMO2Plugin = true, RequiredFiles = new List { @"MW5Mercs\Binaries\Win64\MechWarrior-Win64-Shipping.exe" }, MainExecutable = @"MW5Mercs\Binaries\Win64\MechWarrior-Win64-Shipping.exe" } }, { Game.NoMansSky, new GameMetaData { Game = Game.NoMansSky, NexusName = "nomanssky", NexusGameId = 1634, MO2Name = "No Man's Sky", SteamIDs = new List{275850}, GOGIDs = new List{1446213994}, RequiredFiles = new List { @"Binaries\NMS.exe" }, MainExecutable = @"Binaries\NMS.exe" } }, { Game.DragonAgeOrigins, new GameMetaData { Game = Game.DragonAgeOrigins, NexusName = "dragonage", NexusGameId = 140, MO2Name = "Dragon Age: Origins", // Probably wrong SteamIDs = new List{47810}, OriginIDs = new List{"DR:208591800"}, RequiredFiles = new List { @"bin_ship\daorigins.exe" }, MainExecutable = @"bin_ship\daorigins.exe" } } }; public static Dictionary ByNexusID = Games.Values.Where(g => g.NexusGameId != 0) .GroupBy(g => g.NexusGameId) .Select(g => g.First()) .ToDictionary(d => d.NexusGameId, d => d.Game); } }