Merge pull request #1242 from wabbajack-tools/origin-support

Add support for Origin
This commit is contained in:
Timothy Baldridge 2021-01-04 22:54:32 -07:00 committed by GitHub
commit a709d534d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 288 additions and 3 deletions

View File

@ -1,5 +1,10 @@
### Changelog
#### Version - 2.4.0.0 - ???
* Wabbajack is now based on .NET 5.0 (does not require a runtime download by users)
* Origin is now supported as a game source
* Basic (mostly untested) support for Dragon Age : Origins
#### Version - 2.3.6.2 - 12/31/2020
* HOTFIX: Also apply the IPS4 changes to LL Meta lookups

View File

@ -41,7 +41,8 @@ namespace Wabbajack.Common
StardewValley,
KingdomComeDeliverance,
MechWarrior5Mercenaries,
NoMansSky
NoMansSky,
DragonAgeOrigins
}
public static class GameExtensions
@ -70,6 +71,11 @@ namespace Wabbajack.Common
// to get gog ids: https://www.gogdb.org
public List<int>? 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<string> OriginIDs { get; set; } = new();
public List<string> EpicGameStoreIDs { get; internal set; } = new List<string>();
@ -548,7 +554,7 @@ namespace Wabbajack.Common
Game = Game.NoMansSky,
NexusName = "nomanssky",
NexusGameId = 1634,
MO2Name = "Mo Man's Sky",
MO2Name = "No Man's Sky",
SteamIDs = new List<int>{275850},
GOGIDs = new List<int>{1446213994},
RequiredFiles = new List<string>
@ -557,6 +563,22 @@ namespace Wabbajack.Common
},
MainExecutable = @"Binaries\NMS.exe"
}
},
{
Game.DragonAgeOrigins, new GameMetaData
{
Game = Game.DragonAgeOrigins,
NexusName = "dragonage",
NexusGameId = 140,
MO2Name = "Dragon Age: Origins", // Probably wrong
SteamIDs = new List<int>{17450},
OriginIDs = new List<string>{"DR:208591800"},
RequiredFiles = new List<string>
{
@"bin_ship\daorigins.exe"
},
MainExecutable = @"bin_ship\daorigins.exe"
}
}
};

View File

@ -12,6 +12,9 @@ namespace Wabbajack.Common.StoreHandlers
{
public override Game Game { get; internal set; }
public override StoreType Type { get; internal set; } = StoreType.BethNet;
public AbsolutePath InstallPath;
}
public class BethNetHandler : AStoreHandler

View File

@ -0,0 +1,210 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using Microsoft.Win32;
namespace Wabbajack.Common.StoreHandlers
{
public class OriginHandler : AStoreHandler
{
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 override bool Init()
{
try
{
if (!OriginDataPath.Exists)
return false;
KnownMFSTs = OriginDataPath.EnumerateFiles()
.Where(f => f.Extension == MFSTExtension)
.Select(f => f.FileNameWithoutExtension.ToString())
.ToHashSet();
Utils.Log($"Found MFSTs from Origin: {string.Join(", ", KnownMFSTs)}");
return true;
}
catch (Exception)
{
return false;
}
}
public override bool LoadAllGames()
{
try
{
foreach (var game in GameRegistry.Games)
{
var mfst = game.Value.OriginIDs.FirstOrDefault(g => KnownMFSTs.Contains(g.Replace(":", "")));
if (mfst == null)
continue;
var ogame = new OriginGame(mfst, game.Key, game.Value);
ogame.GetAndCacheManifestResponse();
Games.Add(ogame);
}
return true;
}
catch (Exception ex)
{
Utils.Log(ex.ToString());
return false;
}
}
}
public sealed class OriginGame : AStoreGame
{
private string _mfst;
private GameMetaData _metaData;
public OriginGame(string mfst, Game game, GameMetaData metaData)
{
_mfst = mfst;
Game = game;
_metaData = metaData;
}
public override Game Game { get; internal set; }
public override StoreType Type { get; internal set; } = StoreType.Origin;
public override AbsolutePath Path
{
get
{
return GetGamePath();
}
internal set
{
throw new NotImplementedException();
}
}
private AbsolutePath GetGamePath()
{
var manifestData = GetAndCacheManifestResponse().FromJsonString<GameLocalDataResponse>();
var platform = manifestData!.publishing!.softwareList!.software!.FirstOrDefault(a => a.softwarePlatform == "PCWIN");
var installPath = GetPathFromPlatformPath(platform!.fulfillmentAttributes!.installCheckOverride!);
return installPath;
}
internal AbsolutePath GetPathFromPlatformPath(string path, RegistryView platformView)
{
if (!path.StartsWith("["))
{
return (AbsolutePath)path;
}
var matchPath = Regex.Match(path, @"\[(.*?)\\(.*)\\(.*)\](.*)");
if (!matchPath.Success)
{
Utils.Log("Unknown path format " + path);
return default;
}
var root = matchPath.Groups[1].Value;
RegistryKey rootKey = root switch
{
"HKEY_LOCAL_MACHINE" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, platformView),
"HKEY_CURRENT_USER" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, platformView),
_ => throw new Exception("Unknown registry root entry " + root)
};
var subPath = matchPath.Groups[2].Value.Trim('\\');
var key = matchPath.Groups[3].Value;
var executable = matchPath.Groups[4].Value.Trim('\\');
var subKey = rootKey.OpenSubKey(subPath);
if (subKey == null)
{
return default;
}
var keyValue = rootKey!.OpenSubKey(subPath)!.GetValue(key);
if (keyValue == null)
{
return default;
}
return (AbsolutePath)keyValue!.ToString()!;
}
internal AbsolutePath GetPathFromPlatformPath(string path)
{
var resultPath = GetPathFromPlatformPath(path, RegistryView.Registry64);
if (resultPath == default)
{
resultPath = GetPathFromPlatformPath(path, RegistryView.Registry32);
}
return resultPath;
}
private AbsolutePath ManifestCacheLocation =>
Consts.LocalAppDataPath.Combine("OriginManifestCache", _mfst.Replace(":", ""));
internal string GetAndCacheManifestResponse()
{
if (ManifestCacheLocation.Exists)
{
return ManifestCacheLocation.ReadAllText();
}
Utils.Log($"Getting Origin Manifest info for {_mfst}");
var client = new HttpClient();
var data = client.GetStringAsync($"https://api1.origin.com/ecommerce2/public/{_mfst}/en_US").Result;
ManifestCacheLocation.Parent.CreateDirectory();
ManifestCacheLocation.WriteAllTextAsync(data).Wait();
return data;
}
public class GameLocalDataResponse
{
public class LocalizableAttributes
{
public string? longDescription;
public string? displayName;
}
public class Publishing
{
public class Software
{
public class FulfillmentAttributes
{
public string? executePathOverride;
public string? installationDirectory;
public string? installCheckOverride;
}
public string? softwareId;
public string? softwarePlatform;
public FulfillmentAttributes? fulfillmentAttributes;
}
public class SoftwareList
{
public List<Software>? software;
}
public SoftwareList? softwareList;
}
public string? offerId;
public string? offerType;
public Publishing? publishing;
public LocalizableAttributes? localizableAttributes;
}
}
}

View File

@ -10,7 +10,8 @@ namespace Wabbajack.Common.StoreHandlers
STEAM,
GOG,
BethNet,
EpicGameStore
EpicGameStore,
Origin
}
public class StoreHandler
@ -29,6 +30,9 @@ namespace Wabbajack.Common.StoreHandlers
private static readonly Lazy<EpicGameStoreHandler> _epicGameStoreHandler = new Lazy<EpicGameStoreHandler>(() => new EpicGameStoreHandler());
public EpicGameStoreHandler EpicGameStoreHandler = _epicGameStoreHandler.Value;
private static readonly Lazy<OriginHandler> _originHandler = new Lazy<OriginHandler>(() => new OriginHandler());
public OriginHandler OriginHandler = _originHandler.Value;
public List<AStoreGame> StoreGames;
@ -83,6 +87,18 @@ namespace Wabbajack.Common.StoreHandlers
{
Utils.Error(new StoreException("Could not Init the EpicGameStoreHandler, check previous error messages!"));
}
if (OriginHandler.Init())
{
if (OriginHandler.LoadAllGames())
StoreGames.AddRange(OriginHandler.Games);
else
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!"));
}
}
public AbsolutePath? TryGetGamePath(Game game)

View File

@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Wabbajack.Common;
using Xunit;
using Xunit.Abstractions;
namespace Wabbajack.Test
{
public class GameStoreTests : ATestBase
{
public GameStoreTests(ITestOutputHelper output) : base(output)
{
}
/// <summary>
/// Comment out this [Fact] when not testing by hand. It's too hard to have all games installed at all times
/// so we only run this test as neede
/// </summary>
/// <returns></returns>
//[Fact]
public async Task OriginGameStoreTest()
{
Assert.True(Game.DragonAgeOrigins.MetaData().TryGetGameLocation(out var loc));
Assert.NotEqual(default, loc);
Assert.Equal((AbsolutePath)@"c:\Games\Dragon Age", loc);
Assert.True(Game.DragonAgeOrigins.MetaData().IsInstalled);
}
}
}