mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Add GameFinder lib
This commit is contained in:
parent
fbb6769d3b
commit
03b6e6b0d1
@ -1,213 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using Microsoft.Win32;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Wabbajack.Common.StoreHandlers
|
||||
{
|
||||
public class BethNetGame : AStoreGame
|
||||
{
|
||||
public override Game Game { get; internal set; }
|
||||
public override StoreType Type { get; internal set; } = StoreType.BethNet;
|
||||
|
||||
public AbsolutePath InstallPath;
|
||||
|
||||
}
|
||||
|
||||
public class BethNetHandler : AStoreHandler
|
||||
{
|
||||
public override StoreType Type { get; internal set; } = StoreType.BethNet;
|
||||
|
||||
private const string RegKey = @"SOFTWARE\WOW6432Node\bethesda softworks\Bethesda.net";
|
||||
|
||||
public AbsolutePath BethPath { get; set; }
|
||||
private AbsolutePath Launcher => new RelativePath("BethesdaNetLauncher.exe").RelativeTo(BethPath);
|
||||
private AbsolutePath GamesFolder => new RelativePath("games").RelativeTo(BethPath);
|
||||
|
||||
public override bool Init()
|
||||
{
|
||||
try
|
||||
{
|
||||
var key = Registry.LocalMachine.OpenSubKey(RegKey);
|
||||
|
||||
var pathKey = key?.GetValue("installLocation");
|
||||
if (pathKey == null)
|
||||
{
|
||||
Utils.Error(new StoreException("Could not open the BethNetPath registry key!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var bethPath = pathKey.ToString() ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(bethPath))
|
||||
{
|
||||
Utils.Error(new StoreException("Path to the BethNet Directory from registry is Null or Empty!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
BethPath = (AbsolutePath)bethPath;
|
||||
|
||||
if (!BethPath.IsDirectory || !BethPath.Exists)
|
||||
{
|
||||
Utils.Error(new StoreException($"Path to the BethNet Directory from registry does not exists: {BethPath}"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Launcher.Exists && Launcher.IsFile)
|
||||
return true;
|
||||
|
||||
Utils.Error(new StoreException($"The BethNet Launcher could not be located: {Launcher}"));
|
||||
return false;
|
||||
}
|
||||
catch (SecurityException se)
|
||||
{
|
||||
Utils.Error(se, "BethNetHandler could not read from registry!");
|
||||
}
|
||||
catch (UnauthorizedAccessException uae)
|
||||
{
|
||||
Utils.Error(uae, "BethNetHandler could not read from registry!");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool LoadAllGames()
|
||||
{
|
||||
List<BethNetGame> possibleGames = new List<BethNetGame>();
|
||||
// games folder
|
||||
|
||||
if (!GamesFolder.Exists)
|
||||
{
|
||||
Utils.Error(new StoreException($"The GamesFolder for BethNet at {GamesFolder} does not exist!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GamesFolder.Exists && GamesFolder.IsDirectory)
|
||||
{
|
||||
GamesFolder.EnumerateDirectories(false).Do(d =>
|
||||
{
|
||||
var files = d.EnumerateFiles();
|
||||
var game = GameRegistry.Games.Values
|
||||
.FirstOrDefault(g => g.RequiredFiles?.All(f =>
|
||||
{
|
||||
var absPath = new RelativePath(f).RelativeTo(d);
|
||||
return files.Contains(absPath);
|
||||
}) ?? true);
|
||||
|
||||
if (game != null)
|
||||
{
|
||||
possibleGames.Add(new BethNetGame
|
||||
{
|
||||
Game = game.Game,
|
||||
ID = game.BethNetID,
|
||||
Name = game.Game.ToString(),
|
||||
Path = d,
|
||||
Type = StoreType.BethNet
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.Log($"BethNet Game at {d} is not supported!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
possibleGames.Do(g =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var regString = g.Game.MetaData().RegString;
|
||||
var regKey = Registry.LocalMachine.OpenSubKey(regString);
|
||||
regString = @"HKEY_LOCAL_MACHINE\" + regString;
|
||||
if (regKey == null)
|
||||
{
|
||||
Utils.Error(new StoreException($@"Could not open registry key at {regString}"));
|
||||
return;
|
||||
}
|
||||
|
||||
var pathValue = regKey.GetValue("Path");
|
||||
var uninstallStringValue = regKey.GetValue("UninstallString");
|
||||
|
||||
if (pathValue == null || uninstallStringValue == null)
|
||||
{
|
||||
Utils.Error(new StoreException($@"Could not get Value from either {regString}\Path or UninstallString"));
|
||||
return;
|
||||
}
|
||||
|
||||
var path = pathValue.ToString() ?? string.Empty;
|
||||
var uninstallString = uninstallStringValue.ToString() ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path) || string.IsNullOrWhiteSpace(uninstallString))
|
||||
{
|
||||
Utils.Error(new StoreException($@"Path or UninstallString is null or empty for {regString}!"));
|
||||
return;
|
||||
}
|
||||
|
||||
path = FixRegistryValue(path);
|
||||
|
||||
if ((AbsolutePath)path != g.Path)
|
||||
{
|
||||
Utils.Error(new StoreException($"Path from registry does not equal game path: {path} != {g.Path} at {regString}"));
|
||||
return;
|
||||
}
|
||||
|
||||
var split = uninstallString.Split("\"");
|
||||
if (split.Length != 3)
|
||||
{
|
||||
Utils.Error(new StoreException($"UninstallString at {regString} can not be split into 3 parts!"));
|
||||
return;
|
||||
}
|
||||
|
||||
var updaterPath = (AbsolutePath)split[1];
|
||||
var args = split[2].Trim();
|
||||
|
||||
if (!updaterPath.Exists)
|
||||
{
|
||||
Utils.Error(new StoreException($"UpdaterPath from {regString} does not exist at {updaterPath}"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (updaterPath.Parent != BethPath)
|
||||
{
|
||||
Utils.Error(new StoreException($"Parent of UpdatePath from {regString} is not BethPath: {updaterPath.Parent} != {BethPath}"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.Equals($"bethesdanet://uninstall/{g.ID}", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
Utils.Error(new StoreException($"Uninstall arguments from {regString} is not valid: {args}"));
|
||||
return;
|
||||
}
|
||||
|
||||
Utils.Log($"Found BethNet game \"{g.Game}\" ({g.ID}) at {g.Path}");
|
||||
|
||||
Games.Add(g);
|
||||
}
|
||||
catch (SecurityException se)
|
||||
{
|
||||
Utils.Error(se, "BethNetHandler could not read from registry!");
|
||||
}
|
||||
catch (UnauthorizedAccessException uae)
|
||||
{
|
||||
Utils.Error(uae, "BethNetHandler could not read from registry!");
|
||||
}
|
||||
});
|
||||
|
||||
Utils.Log($"Total number of BethNet Games found: {Games.Count}");
|
||||
|
||||
return Games.Count != 0;
|
||||
}
|
||||
|
||||
private static string FixRegistryValue(string value)
|
||||
{
|
||||
var s = value;
|
||||
if (s.StartsWith("\""))
|
||||
s = s[1..];
|
||||
if (s.EndsWith("\""))
|
||||
s = s[..^1];
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Wabbajack.Common.StoreHandlers
|
||||
{
|
||||
public class EpicGameStoreHandler : AStoreHandler
|
||||
{
|
||||
public override StoreType Type { get; internal set; }
|
||||
|
||||
public string BaseRegKey = @"SOFTWARE\Epic Games\EOS";
|
||||
public override bool Init()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool LoadAllGames()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var eosKey = Registry.CurrentUser.OpenSubKey(BaseRegKey);
|
||||
if (eosKey == null)
|
||||
{
|
||||
Utils.Log("Epic Game Store is not installed");
|
||||
return false;
|
||||
}
|
||||
|
||||
var name = eosKey.GetValue("ModSdkMetadataDir");
|
||||
if (name == null)
|
||||
{
|
||||
Utils.Log("Registry key entry does not exist for Epic Game store");
|
||||
return false;
|
||||
}
|
||||
|
||||
var byID = GameRegistry.Games.SelectMany(g => g.Value.EpicGameStoreIDs
|
||||
.Select(id => (id, g.Value.Game)))
|
||||
.GroupBy(t => t.id)
|
||||
.ToDictionary(t => t.Key, t => t.First().Game);
|
||||
|
||||
foreach (var itm in ((AbsolutePath)(string)(name!)).EnumerateFiles(false, "*.item"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = itm.FromJson<EpicGameItem>();
|
||||
Utils.Log($"Found Epic Game Store Game: {item.DisplayName} at {item.InstallLocation}");
|
||||
|
||||
if (byID.TryGetValue(item.CatalogItemId, out var game))
|
||||
{
|
||||
Games.Add(new EpicStoreGame(game, item));
|
||||
}
|
||||
}
|
||||
catch (Newtonsoft.Json.JsonReaderException)
|
||||
{
|
||||
Utils.Log($"Failure parsing Epic Game Store manifest: {itm}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
Utils.Log("Epic Game Store is does not appear to be installed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public class EpicStoreGame : AStoreGame
|
||||
{
|
||||
public EpicStoreGame(Game game, EpicGameItem item)
|
||||
{
|
||||
Type = StoreType.EpicGameStore;
|
||||
Game = game;
|
||||
Path = (AbsolutePath)item.InstallLocation;
|
||||
Name = game.MetaData().HumanFriendlyGameName;
|
||||
|
||||
}
|
||||
|
||||
public override Game Game { get; internal set; }
|
||||
public override StoreType Type { get; internal set; }
|
||||
}
|
||||
|
||||
public class EpicGameItem
|
||||
{
|
||||
public string DisplayName { get; set; } = "";
|
||||
public string InstallationGuid { get; set; } = "";
|
||||
public string CatalogItemId { get; set; } = "";
|
||||
public string CatalogNamespace { get; set; } = "";
|
||||
public string InstallSessionId { get; set; } = "";
|
||||
public string InstallLocation { get; set; } = "";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
using System;
|
||||
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 StoreType Type { get; internal set; } = StoreType.GOG;
|
||||
}
|
||||
|
||||
public class GOGHandler : AStoreHandler
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (GOGKey == null)
|
||||
{
|
||||
Utils.Error("GOGHandler could not read from registry!");
|
||||
return false;
|
||||
}
|
||||
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() ?? string.Empty;
|
||||
|
||||
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() ?? string.Empty;
|
||||
|
||||
var game = new GOGGame
|
||||
{
|
||||
ID = gameID,
|
||||
Name = gameName,
|
||||
Path = (AbsolutePath)path
|
||||
};
|
||||
|
||||
var gameMeta = GameRegistry.Games.Values.FirstOrDefault(g => (g.GOGIDs?.Contains(gameID) ?? false));
|
||||
|
||||
if (gameMeta == null)
|
||||
{
|
||||
Utils.Log($"GOG Game \"{gameName}\" ({gameID}) is not supported, skipping");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,16 +7,16 @@ using Microsoft.Win32;
|
||||
|
||||
namespace Wabbajack.Common.StoreHandlers
|
||||
{
|
||||
public class OriginHandler : AStoreHandler
|
||||
public class OriginHandler
|
||||
{
|
||||
private AbsolutePath OriginDataPath = (AbsolutePath)@"C:\ProgramData\Origin\LocalContent";
|
||||
private Extension MFSTExtension = new Extension(".mfst");
|
||||
private HashSet<string> KnownMFSTs = new();
|
||||
|
||||
public override StoreType Type { get; internal set; } = StoreType.Origin;
|
||||
|
||||
public List<OriginGame> Games = new();
|
||||
|
||||
private static Regex SplitRegex = new Regex("(.*)([0-9]+)(@subscription)?", RegexOptions.RightToLeft);
|
||||
public override bool Init()
|
||||
public bool Init()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -66,7 +66,7 @@ namespace Wabbajack.Common.StoreHandlers
|
||||
}
|
||||
}
|
||||
|
||||
public override bool LoadAllGames()
|
||||
public bool LoadAllGames()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -90,7 +90,7 @@ namespace Wabbajack.Common.StoreHandlers
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class OriginGame : AStoreGame
|
||||
public sealed class OriginGame
|
||||
{
|
||||
private string _mfst;
|
||||
private GameMetaData _metaData;
|
||||
@ -101,10 +101,9 @@ namespace Wabbajack.Common.StoreHandlers
|
||||
Game = game;
|
||||
_metaData = metaData;
|
||||
}
|
||||
public override Game Game { get; internal set; }
|
||||
public override StoreType Type { get; internal set; } = StoreType.Origin;
|
||||
public Game Game { get; internal set; }
|
||||
|
||||
public override AbsolutePath Path
|
||||
public AbsolutePath Path
|
||||
{
|
||||
get
|
||||
{
|
||||
|
@ -1,319 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using Microsoft.Win32;
|
||||
#nullable enable
|
||||
|
||||
namespace Wabbajack.Common.StoreHandlers
|
||||
{
|
||||
public class SteamGame : AStoreGame
|
||||
{
|
||||
public override Game Game { get; internal set; }
|
||||
public override StoreType Type { get; internal set; } = StoreType.STEAM;
|
||||
|
||||
public AbsolutePath Universe;
|
||||
|
||||
public readonly List<SteamWorkshopItem> WorkshopItems = new List<SteamWorkshopItem>();
|
||||
public int WorkshopItemsSizeOnDisk;
|
||||
}
|
||||
|
||||
public class SteamWorkshopItem
|
||||
{
|
||||
public readonly SteamGame Game;
|
||||
public int ItemID;
|
||||
public long Size;
|
||||
|
||||
public SteamWorkshopItem(SteamGame game)
|
||||
{
|
||||
Game = game;
|
||||
}
|
||||
}
|
||||
|
||||
public class SteamHandler : AStoreHandler
|
||||
{
|
||||
public override StoreType Type { get; internal set; } = StoreType.STEAM;
|
||||
|
||||
private const string SteamRegKey = @"Software\Valve\Steam";
|
||||
|
||||
public AbsolutePath SteamPath { get; set; }
|
||||
private AbsolutePath SteamConfig => new RelativePath("config//config.vdf").RelativeTo(SteamPath);
|
||||
private List<AbsolutePath>? SteamUniverses { get; set; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
var steamPath = steamPathKey.ToString() ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(steamPath))
|
||||
{
|
||||
Utils.Error(new StoreException("Path to the Steam Directory from registry is Null or Empty!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
SteamPath = new AbsolutePath(steamPath);
|
||||
|
||||
if (!SteamPath.Exists)
|
||||
{
|
||||
Utils.Error(new StoreException($"Path to the Steam Directory from registry does not exists: {SteamPath}"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SteamConfig.Exists)
|
||||
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 List<AbsolutePath> LoadUniverses()
|
||||
{
|
||||
var ret = new List<AbsolutePath>();
|
||||
|
||||
SteamConfig.ReadAllLines().Do(l =>
|
||||
{
|
||||
if (!l.ContainsCaseInsensitive("BaseInstallFolder_")) return;
|
||||
var s = new AbsolutePath(GetVdfValue(l));
|
||||
var path = new RelativePath("steamapps").RelativeTo(s);
|
||||
|
||||
if (!path.Exists)
|
||||
{
|
||||
Utils.Log($"Directory {path} does not exist, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
ret.Add(path);
|
||||
Utils.Log($"Steam Library found at {path}");
|
||||
});
|
||||
|
||||
Utils.Log($"Total number of Steam Libraries found: {ret.Count}");
|
||||
|
||||
// Default path in the Steam folder isn't in the configs
|
||||
var defaultPath = new RelativePath("steamapps").RelativeTo(SteamPath);
|
||||
if(defaultPath.Exists)
|
||||
ret.Add(defaultPath);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public override bool LoadAllGames()
|
||||
{
|
||||
SteamUniverses ??= LoadUniverses();
|
||||
|
||||
if (SteamUniverses.Count == 0)
|
||||
{
|
||||
Utils.Log("Could not find any Steam Libraries");
|
||||
return false;
|
||||
}
|
||||
|
||||
SteamUniverses.Do(u =>
|
||||
{
|
||||
Utils.Log($"Searching for Steam Games in {u}");
|
||||
|
||||
u.EnumerateFiles(false, "*.acf")
|
||||
.Where(a => a.Exists)
|
||||
.Where(a => a.IsFile)
|
||||
.Do(f =>
|
||||
{
|
||||
var game = new SteamGame();
|
||||
var gotID = false;
|
||||
|
||||
f.ReadAllLines().Do(l =>
|
||||
{
|
||||
if (l.ContainsCaseInsensitive("\"appid\""))
|
||||
{
|
||||
if (!int.TryParse(GetVdfValue(l), out var id))
|
||||
return;
|
||||
game.ID = id;
|
||||
gotID = true;
|
||||
}
|
||||
|
||||
if (l.ContainsCaseInsensitive("\"name\""))
|
||||
game.Name = GetVdfValue(l);
|
||||
|
||||
if (!l.ContainsCaseInsensitive("\"installdir\""))
|
||||
return;
|
||||
|
||||
var value = GetVdfValue(l);
|
||||
AbsolutePath absPath;
|
||||
|
||||
if (Path.IsPathRooted(value))
|
||||
{
|
||||
absPath = (AbsolutePath)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
absPath = new RelativePath("common").Combine(GetVdfValue(l)).RelativeTo(u);
|
||||
}
|
||||
|
||||
if (absPath.Exists)
|
||||
game.Path = absPath;
|
||||
|
||||
});
|
||||
|
||||
if (!gotID || !game.Path.IsDirectory) return;
|
||||
|
||||
var gameMeta = GameRegistry.Games.Values.FirstOrDefault(g => g.SteamIDs?.Contains(game.ID) ?? false);
|
||||
|
||||
if (gameMeta == null)
|
||||
{
|
||||
Utils.Log($"Steam Game \"{game.Name}\" ({game.ID}) is not supported, skipping");
|
||||
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 static void LoadWorkshopItems(SteamGame game)
|
||||
{
|
||||
var workshop = new RelativePath("workshop").RelativeTo(game.Universe);
|
||||
if (!workshop.Exists)
|
||||
return;
|
||||
|
||||
workshop.EnumerateFiles(false, "*.acf")
|
||||
.Where(f => f.Exists)
|
||||
.Where(f => f.IsFile)
|
||||
.Do(f =>
|
||||
{
|
||||
if (f.FileName.ToString() != $"appworkshop_{game.ID}.acf")
|
||||
return;
|
||||
|
||||
Utils.Log($"Found Steam Workshop item file {f} for \"{game.Name}\"");
|
||||
|
||||
var lines = f.ReadAllLines().ToList();
|
||||
var end = false;
|
||||
var foundAppID = false;
|
||||
var workshopItemsInstalled = 0;
|
||||
var workshopItemDetails = 0;
|
||||
var bracketStart = 0;
|
||||
var bracketEnd = 0;
|
||||
|
||||
SteamWorkshopItem? currentItem = new SteamWorkshopItem(game);
|
||||
|
||||
lines.Do(l =>
|
||||
{
|
||||
if (end)
|
||||
return;
|
||||
|
||||
currentItem ??= new SteamWorkshopItem(game);
|
||||
|
||||
var currentLine = lines.IndexOf(l);
|
||||
if (l.ContainsCaseInsensitive("\"appid\"") && !foundAppID)
|
||||
{
|
||||
if (!int.TryParse(GetVdfValue(l), out var appID))
|
||||
return;
|
||||
|
||||
foundAppID = true;
|
||||
|
||||
if (appID != game.ID)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!foundAppID)
|
||||
return;
|
||||
|
||||
if (l.ContainsCaseInsensitive("\"SizeOnDisk\""))
|
||||
{
|
||||
if (!int.TryParse(GetVdfValue(l), out var sizeOnDisk))
|
||||
return;
|
||||
|
||||
game.WorkshopItemsSizeOnDisk += sizeOnDisk;
|
||||
}
|
||||
|
||||
if (l.ContainsCaseInsensitive("\"WorkshopItemsInstalled\""))
|
||||
workshopItemsInstalled = currentLine;
|
||||
|
||||
if (l.ContainsCaseInsensitive("\"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 (!long.TryParse(GetVdfValue(l), out currentItem.Size))
|
||||
return;
|
||||
|
||||
if (bracketStart == 0 || bracketEnd == 0 || currentItem.ItemID == 0 || currentItem.Size == 0)
|
||||
return;
|
||||
|
||||
bracketStart = 0;
|
||||
bracketEnd = 0;
|
||||
game.WorkshopItems.Add(currentItem);
|
||||
|
||||
Utils.Log($"Found Steam Workshop item {currentItem.ItemID}");
|
||||
|
||||
currentItem = null;
|
||||
end = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static string GetVdfValue(string line)
|
||||
{
|
||||
var trim = line.Trim('\t').Replace("\t", "");
|
||||
var split = trim.Split('\"');
|
||||
return split.Length >= 4 ? split[3].Replace("\\\\", "\\") : string.Empty;
|
||||
}
|
||||
|
||||
private static string GetSingleVdfValue(string line)
|
||||
{
|
||||
var trim = line.Trim('\t').Replace("\t", "");
|
||||
var split = trim.Split('\"');
|
||||
return split.Length >= 2 ? split[1] : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,108 +2,101 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using GameFinder;
|
||||
using GameFinder.StoreHandlers.BethNet;
|
||||
using GameFinder.StoreHandlers.EGS;
|
||||
using GameFinder.StoreHandlers.GOG;
|
||||
using GameFinder.StoreHandlers.Steam;
|
||||
|
||||
namespace Wabbajack.Common.StoreHandlers
|
||||
{
|
||||
public enum StoreType
|
||||
{
|
||||
STEAM,
|
||||
GOG,
|
||||
BethNet,
|
||||
EpicGameStore,
|
||||
Origin
|
||||
}
|
||||
|
||||
public class StoreHandler
|
||||
{
|
||||
private static readonly Lazy<StoreHandler> _instance = new Lazy<StoreHandler>(() => new StoreHandler(), isThreadSafe: true);
|
||||
private static readonly Lazy<StoreHandler> _instance = new(() => new StoreHandler(), isThreadSafe: true);
|
||||
public static StoreHandler Instance => _instance.Value;
|
||||
|
||||
private static readonly Lazy<SteamHandler> _steamHandler = new Lazy<SteamHandler>(() => new SteamHandler());
|
||||
private static readonly Lazy<SteamHandler> _steamHandler = new(() => new SteamHandler());
|
||||
public SteamHandler SteamHandler = _steamHandler.Value;
|
||||
|
||||
private static readonly Lazy<GOGHandler> _gogHandler = new Lazy<GOGHandler>(() => new GOGHandler());
|
||||
private static readonly Lazy<GOGHandler> _gogHandler = new(() => new GOGHandler());
|
||||
public GOGHandler GOGHandler = _gogHandler.Value;
|
||||
|
||||
private static readonly Lazy<BethNetHandler> _bethNetHandler = new Lazy<BethNetHandler>(() => new BethNetHandler());
|
||||
private static readonly Lazy<BethNetHandler> _bethNetHandler = new(() => new BethNetHandler());
|
||||
public BethNetHandler BethNetHandler = _bethNetHandler.Value;
|
||||
|
||||
private static readonly Lazy<EpicGameStoreHandler> _epicGameStoreHandler = new Lazy<EpicGameStoreHandler>(() => new EpicGameStoreHandler());
|
||||
public EpicGameStoreHandler EpicGameStoreHandler = _epicGameStoreHandler.Value;
|
||||
private static readonly Lazy<EGSHandler> _epicGameStoreHandler = new(() => new EGSHandler());
|
||||
public EGSHandler EpicGameStoreHandler = _epicGameStoreHandler.Value;
|
||||
|
||||
private static readonly Lazy<OriginHandler> _originHandler = new Lazy<OriginHandler>(() => new OriginHandler());
|
||||
private static readonly Lazy<OriginHandler> _originHandler = new(() => new OriginHandler());
|
||||
public OriginHandler OriginHandler = _originHandler.Value;
|
||||
|
||||
public List<AStoreGame> StoreGames;
|
||||
|
||||
public Dictionary<Game, AStoreGame> Games = new();
|
||||
|
||||
private void FindGames<THandler, TGame>(THandler handler, string name)
|
||||
where THandler : AStoreHandler<TGame>
|
||||
where TGame : AStoreGame
|
||||
{
|
||||
try
|
||||
{
|
||||
handler.FindAllGames();
|
||||
foreach (var game in handler.Games)
|
||||
{
|
||||
Utils.Log($"{handler.StoreType}: Found game {game}");
|
||||
StoreGames.Add(game);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Utils.Error(e, $"Could not load all Games from the {name}");
|
||||
}
|
||||
}
|
||||
|
||||
public StoreHandler()
|
||||
{
|
||||
StoreGames = new List<AStoreGame>();
|
||||
|
||||
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!"));
|
||||
}
|
||||
|
||||
if (BethNetHandler.Init())
|
||||
{
|
||||
if (BethNetHandler.LoadAllGames())
|
||||
StoreGames.AddRange(BethNetHandler.Games);
|
||||
else
|
||||
Utils.Error(new StoreException("Could not load all Games from the BethNetHandler, check previous error messages!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.Error(new StoreException("Could not Init the BethNetHandler, check previous error messages!"));
|
||||
}
|
||||
|
||||
if (EpicGameStoreHandler.Init())
|
||||
{
|
||||
if (EpicGameStoreHandler.LoadAllGames())
|
||||
StoreGames.AddRange(EpicGameStoreHandler.Games);
|
||||
else
|
||||
Utils.Error(new StoreException("Could not load all Games from the EpicGameStoreHandler, check previous error messages!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.Error(new StoreException("Could not Init the EpicGameStoreHandler, check previous error messages!"));
|
||||
}
|
||||
FindGames<SteamHandler, SteamGame>(SteamHandler, "SteamHandler");
|
||||
FindGames<GOGHandler, GOGGame>(GOGHandler, "GOGHandler");
|
||||
FindGames<BethNetHandler, BethNetGame>(BethNetHandler, "BethNetHandler");
|
||||
FindGames<EGSHandler, EGSGame>(EpicGameStoreHandler, "EpicGameStoreHandler");
|
||||
|
||||
if (OriginHandler.Init())
|
||||
{
|
||||
if (OriginHandler.LoadAllGames())
|
||||
StoreGames.AddRange(OriginHandler.Games);
|
||||
else
|
||||
if (!OriginHandler.LoadAllGames())
|
||||
Utils.Error(new StoreException("Could not load all Games from the OriginHandler, check previous error messages!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.Error(new StoreException("Could not Init the OriginHandler, check previous error messages!"));
|
||||
}
|
||||
|
||||
foreach (var storeGame in StoreGames)
|
||||
{
|
||||
IEnumerable<KeyValuePair<Game, GameMetaData>>? enumerable = storeGame switch
|
||||
{
|
||||
SteamGame steamGame => GameRegistry.Games.Where(y => y.Value.SteamIDs?.Contains(steamGame.ID) ?? false),
|
||||
GOGGame gogGame => GameRegistry.Games.Where(y => y.Value.GOGIDs?.Contains(gogGame.GameID) ?? false),
|
||||
BethNetGame bethNetGame => GameRegistry.Games.Where(y => y.Value.BethNetID.Equals((int)bethNetGame.ID)),
|
||||
EGSGame egsGame => GameRegistry.Games.Where(y => y.Value.EpicGameStoreIDs.Contains(egsGame.CatalogItemId ?? string.Empty)),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (enumerable == null) continue;
|
||||
|
||||
var list = enumerable.ToList();
|
||||
if (list.Count == 0) continue;
|
||||
|
||||
Games.Add(list.First().Key, storeGame);
|
||||
}
|
||||
}
|
||||
|
||||
public AbsolutePath? TryGetGamePath(Game game)
|
||||
{
|
||||
return StoreGames.FirstOrDefault(g => g.Game == game)?.Path;
|
||||
if (Games.TryGetValue(game, out var storeGame))
|
||||
return (AbsolutePath) storeGame.Path;
|
||||
return OriginHandler.Games.FirstOrDefault(x => x.Game == game)?.Path;
|
||||
}
|
||||
|
||||
public static void Warmup()
|
||||
@ -111,27 +104,7 @@ namespace Wabbajack.Common.StoreHandlers
|
||||
Task.Run(() => _instance.Value).FireAndForget();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AStoreGame
|
||||
{
|
||||
public abstract Game Game { get; internal set; }
|
||||
public virtual string Name { get; internal set; } = string.Empty;
|
||||
public virtual AbsolutePath Path { get; internal set; }
|
||||
public virtual int ID { get; internal set; }
|
||||
public abstract StoreType Type { get; internal set; }
|
||||
}
|
||||
|
||||
public abstract class AStoreHandler
|
||||
{
|
||||
public List<AStoreGame> Games { get; } = new List<AStoreGame>();
|
||||
|
||||
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)
|
||||
|
@ -48,6 +48,10 @@
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="GameFinder.StoreHandlers.BethNet" Version="1.3.1" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.EGS" Version="1.3.1" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.GOG" Version="1.3.1" />
|
||||
<PackageReference Include="GameFinder.StoreHandlers.Steam" Version="1.3.1" />
|
||||
<PackageReference Include="Genbox.AlphaFS" Version="2.2.2.1" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
|
||||
<PackageReference Include="ini-parser-netstandard" Version="2.5.2" />
|
||||
|
@ -1,77 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Common.StoreHandlers;
|
||||
|
||||
namespace Wabbajack.Lib.CompilationSteps
|
||||
{
|
||||
public class IncludeSteamWorkshopItems : ACompilationStep
|
||||
{
|
||||
private readonly Regex _regex = new Regex("steamWorkshopItem_\\d*\\.meta$");
|
||||
private readonly bool _isGenericGame;
|
||||
private readonly SteamGame? _game;
|
||||
|
||||
public IncludeSteamWorkshopItems(ACompiler compiler) : base(compiler)
|
||||
{
|
||||
var mo2Compiler = (MO2Compiler)compiler;
|
||||
_isGenericGame = mo2Compiler.CompilingGame.IsGenericMO2Plugin;
|
||||
_game = (SteamGame?)StoreHandler.Instance.SteamHandler.Games.FirstOrDefault(x =>
|
||||
mo2Compiler.CompilingGame.SteamIDs!.Contains(x.ID));
|
||||
}
|
||||
|
||||
public override async ValueTask<Directive?> Run(RawSourceFile source)
|
||||
{
|
||||
if (!_isGenericGame)
|
||||
return null;
|
||||
|
||||
if (_game == null)
|
||||
return null;
|
||||
|
||||
if (!_regex.IsMatch((string)source.Path))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var lines = await source.AbsolutePath.ReadAllLinesAsync();
|
||||
var sID = lines.FirstOrDefault(l => l.StartsWith("itemID="))?.Replace("itemID=", "");
|
||||
if (string.IsNullOrEmpty(sID))
|
||||
{
|
||||
Utils.Error($"Found no itemID= in file {source.AbsolutePath}!");
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!int.TryParse(sID, out var id))
|
||||
{
|
||||
Utils.Error($"Unable to parse int {sID} in {source.AbsolutePath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
//Get-ChildItem -Name -Directory | ForEach-Object -Process {Out-File -FilePath .\steamWorkshopItem_$_.meta -InputObject "itemID=$($_)" -Encoding utf8}
|
||||
if (id == 0)
|
||||
return null;
|
||||
|
||||
SteamWorkshopItem? item = _game.WorkshopItems.FirstOrDefault(x => x.ItemID == id);
|
||||
if (item == null)
|
||||
{
|
||||
Utils.Error($"Unable to find workshop item with ID {id} in loaded workshop item list!");
|
||||
return null;
|
||||
}
|
||||
|
||||
var fromSteam = source.EvolveTo<SteamMeta>();
|
||||
fromSteam.SourceDataID = await _compiler.IncludeFile(source.AbsolutePath);
|
||||
fromSteam.ItemID = item.ItemID;
|
||||
fromSteam.Size = item.Size;
|
||||
return fromSteam;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Utils.Error(e, $"Exception while trying to evolve source to FromSteam");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -35,7 +35,6 @@ namespace Wabbajack.Lib.Downloaders
|
||||
typeof(MegaDownloader.State),
|
||||
typeof(ModDBDownloader.State),
|
||||
typeof(NexusDownloader.State),
|
||||
typeof(SteamWorkshopDownloader.State),
|
||||
typeof(VectorPlexusDownloader.State),
|
||||
typeof(DeadlyStreamDownloader.State),
|
||||
typeof(TESAllianceDownloader.State),
|
||||
|
@ -1,118 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Common.StoreHandlers;
|
||||
using Wabbajack.Lib.Validation;
|
||||
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public class SteamWorkshopDownloader : IUrlDownloader
|
||||
{
|
||||
public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
|
||||
{
|
||||
var id = archiveINI?.General?.itemID;
|
||||
var steamID = archiveINI?.General?.steamID;
|
||||
var size = archiveINI?.General?.itemSize;
|
||||
if (steamID == null)
|
||||
{
|
||||
throw new ArgumentException("Steam workshop item had no steam ID.");
|
||||
}
|
||||
var item = new SteamWorkshopItem(GameRegistry.GetBySteamID(int.Parse(steamID)))
|
||||
{
|
||||
ItemID = id != null ? int.Parse(id) : 0,
|
||||
Size = size != null ? int.Parse(size) : 0,
|
||||
};
|
||||
return new State(item);
|
||||
}
|
||||
|
||||
public async Task Prepare()
|
||||
{
|
||||
}
|
||||
|
||||
public AbstractDownloadState GetDownloaderState(string url)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public class State : AbstractDownloadState
|
||||
{
|
||||
public SteamWorkshopItem Item { get; }
|
||||
|
||||
public override object[] PrimaryKey => new object[] { Item.Game, Item.ItemID };
|
||||
|
||||
public State(SteamWorkshopItem item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
public override bool IsWhitelisted(ServerWhitelist whitelist)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async Task<bool> Download(Archive a, AbsolutePath destination)
|
||||
{
|
||||
var currentLib = Item.Game.Universe;
|
||||
|
||||
var downloadFolder = new RelativePath($"workshop//downloads//{Item.Game.ID}").RelativeTo(currentLib);
|
||||
var contentFolder = new RelativePath($"workshop//content//{Item.Game.ID}").RelativeTo(currentLib);
|
||||
var p = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = new RelativePath("steam.exe").RelativeTo(StoreHandler.Instance.SteamHandler.SteamPath).ToString(),
|
||||
CreateNoWindow = true,
|
||||
Arguments = $"console +workshop_download_item {Item.Game.ID} {Item.ItemID}"
|
||||
}
|
||||
};
|
||||
|
||||
p.Start();
|
||||
|
||||
//TODO: async
|
||||
var finished = false;
|
||||
var itemDownloadPath = new RelativePath(Item.ItemID.ToString()).RelativeTo(downloadFolder);
|
||||
var itemContentPath = new RelativePath(Item.ItemID.ToString()).RelativeTo(contentFolder);
|
||||
while (!finished)
|
||||
{
|
||||
if(!itemDownloadPath.Exists)
|
||||
if(itemContentPath.Exists)
|
||||
finished = true;
|
||||
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async Task<bool> Verify(Archive a, CancellationToken? token)
|
||||
{
|
||||
//TODO: find a way to verify steam workshop items
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IDownloader GetDownloader()
|
||||
{
|
||||
return DownloadDispatcher.GetInstance<SteamWorkshopDownloader>();
|
||||
}
|
||||
|
||||
public override string GetManifestURL(Archive a)
|
||||
{
|
||||
return $"https://steamcommunity.com/sharedfiles/filedetails/?id={Item.ItemID}";
|
||||
}
|
||||
|
||||
public override string[] GetMetaIni()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
"[General]",
|
||||
$"itemID={Item.ItemID}",
|
||||
$"steamID={Item.Game.Game.MetaData().SteamIDs!.First()}",
|
||||
$"itemSize={Item.Size}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user