mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Merge branch 'main' into dependabot/nuget/MegaApiClient-1.10.3
This commit is contained in:
commit
6aacab3885
7
.github/workflows/tests.yaml
vendored
7
.github/workflows/tests.yaml
vendored
@ -39,12 +39,7 @@ jobs:
|
|||||||
include-prerelease: true
|
include-prerelease: true
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:EnableWindowsTargeting=true --filter "Category!=FlakeyNetwork"
|
run: dotnet test /p:EnableWindowsTargeting=true --filter "Category!=FlakeyNetwork"
|
||||||
|
|
||||||
- uses: codecov/codecov-action@v3
|
|
||||||
with:
|
|
||||||
flags: unittests, ${{runner.os}}
|
|
||||||
verbose: true
|
|
||||||
|
|
||||||
#- name: Upload Test Folder on Failure
|
#- name: Upload Test Folder on Failure
|
||||||
# if: ${{ failure() }}
|
# if: ${{ failure() }}
|
||||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,5 +1,18 @@
|
|||||||
### Changelog
|
### Changelog
|
||||||
|
|
||||||
|
#### Version - 3.0.4.0 - TBD
|
||||||
|
* upgrade GameFinder to 2.2.1
|
||||||
|
|
||||||
|
#### Version - 3.0.3.1 - 10/30/2022
|
||||||
|
* Fix file verification issues for CreatedBSAs
|
||||||
|
* Fix files during verification where CreatedDate > LastModified
|
||||||
|
|
||||||
|
#### Version - 3.0.3.0 - 10/26/2022
|
||||||
|
* Verify hashes of all installed files
|
||||||
|
* Verify contents of BSAs during installation
|
||||||
|
* Provide a new CLI command for verifying a installed modlist
|
||||||
|
* When downloading from one Nexus CDN server fails, WJ will now try alternate Nexus servers
|
||||||
|
|
||||||
#### Version - 3.0.2.3 - 10/19/2022
|
#### Version - 3.0.2.3 - 10/19/2022
|
||||||
* HOTFIX: revert GameFinder library to 1.8 until it's a bit more forgiving of corrupt files
|
* HOTFIX: revert GameFinder library to 1.8 until it's a bit more forgiving of corrupt files
|
||||||
|
|
||||||
|
@ -66,10 +66,10 @@ namespace Wabbajack
|
|||||||
public ICommand OpenSettingsCommand { get; }
|
public ICommand OpenSettingsCommand { get; }
|
||||||
|
|
||||||
public string VersionDisplay { get; }
|
public string VersionDisplay { get; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string ResourceStatus { get; set; }
|
public string ResourceStatus { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string AppName { get; set; }
|
public string AppName { get; set; }
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ namespace Wabbajack
|
|||||||
MessageBus.Current.Listen<NavigateToGlobal>()
|
MessageBus.Current.Listen<NavigateToGlobal>()
|
||||||
.Subscribe(m => HandleNavigateTo(m.Screen))
|
.Subscribe(m => HandleNavigateTo(m.Screen))
|
||||||
.DisposeWith(CompositeDisposable);
|
.DisposeWith(CompositeDisposable);
|
||||||
|
|
||||||
MessageBus.Current.Listen<NavigateTo>()
|
MessageBus.Current.Listen<NavigateTo>()
|
||||||
.Subscribe(m => HandleNavigateTo(m.ViewModel))
|
.Subscribe(m => HandleNavigateTo(m.ViewModel))
|
||||||
.DisposeWith(CompositeDisposable);
|
.DisposeWith(CompositeDisposable);
|
||||||
@ -106,7 +106,7 @@ namespace Wabbajack
|
|||||||
MessageBus.Current.Listen<NavigateBack>()
|
MessageBus.Current.Listen<NavigateBack>()
|
||||||
.Subscribe(HandleNavigateBack)
|
.Subscribe(HandleNavigateBack)
|
||||||
.DisposeWith(CompositeDisposable);
|
.DisposeWith(CompositeDisposable);
|
||||||
|
|
||||||
MessageBus.Current.Listen<SpawnBrowserWindow>()
|
MessageBus.Current.Listen<SpawnBrowserWindow>()
|
||||||
.ObserveOnGuiThread()
|
.ObserveOnGuiThread()
|
||||||
.Subscribe(HandleSpawnBrowserWindow)
|
.Subscribe(HandleSpawnBrowserWindow)
|
||||||
@ -116,7 +116,7 @@ namespace Wabbajack
|
|||||||
.Select(r => string.Join(", ", r.Where(r => r.Throughput > 0)
|
.Select(r => string.Join(", ", r.Where(r => r.Throughput > 0)
|
||||||
.Select(s => $"{s.Name} - {s.Throughput.ToFileSizeString()}/sec")))
|
.Select(s => $"{s.Name} - {s.Throughput.ToFileSizeString()}/sec")))
|
||||||
.BindToStrict(this, view => view.ResourceStatus);
|
.BindToStrict(this, view => view.ResourceStatus);
|
||||||
|
|
||||||
|
|
||||||
if (IsStartingFromModlist(out var path))
|
if (IsStartingFromModlist(out var path))
|
||||||
{
|
{
|
||||||
@ -134,25 +134,24 @@ namespace Wabbajack
|
|||||||
var assembly = Assembly.GetExecutingAssembly();
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
var location = assembly.Location;
|
var location = assembly.Location;
|
||||||
if (string.IsNullOrWhiteSpace(location))
|
if (string.IsNullOrWhiteSpace(location))
|
||||||
location = Process.GetCurrentProcess().MainModule?.FileName ?? "";
|
location = Process.GetCurrentProcess().MainModule?.FileName ?? throw new Exception("Assembly location is unavailable!");
|
||||||
|
|
||||||
_logger.LogInformation("App Location: {Location}", assembly.Location);
|
_logger.LogInformation("App Location: {Location}", assembly.Location);
|
||||||
var fvi = FileVersionInfo.GetVersionInfo(location);
|
var fvi = FileVersionInfo.GetVersionInfo(location);
|
||||||
Consts.CurrentMinimumWabbajackVersion = Version.Parse(fvi.FileVersion);
|
Consts.CurrentMinimumWabbajackVersion = Version.Parse(fvi.FileVersion);
|
||||||
VersionDisplay = $"v{fvi.FileVersion}";
|
VersionDisplay = $"v{fvi.FileVersion}";
|
||||||
AppName = "WABBAJACK " + VersionDisplay;
|
AppName = "WABBAJACK " + VersionDisplay;
|
||||||
_logger.LogInformation("Wabbajack Version: {FileVersion}", fvi.FileVersion);
|
_logger.LogInformation("Wabbajack Version: {FileVersion}", fvi.FileVersion);
|
||||||
|
|
||||||
Task.Run(() => _wjClient.SendMetric("started_wabbajack", fvi.FileVersion)).FireAndForget();
|
Task.Run(() => _wjClient.SendMetric("started_wabbajack", fvi.FileVersion)).FireAndForget();
|
||||||
Task.Run(() => _wjClient.SendMetric("started_sha", ThisAssembly.Git.Sha));
|
Task.Run(() => _wjClient.SendMetric("started_sha", ThisAssembly.Git.Sha));
|
||||||
|
|
||||||
// setup file association
|
// setup file association
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var applicationRegistrationService =
|
var applicationRegistrationService = _serviceProvider.GetRequiredService<IApplicationRegistrationService>();
|
||||||
_serviceProvider.GetRequiredService<IApplicationRegistrationService>();
|
|
||||||
|
|
||||||
var applicationInfo = new ApplicationInfo(assembly);
|
var applicationInfo = new ApplicationInfo("Wabbajack", "Wabbajack", "Wabbajack", location);
|
||||||
applicationInfo.SupportedExtensions.Add("wabbajack");
|
applicationInfo.SupportedExtensions.Add("wabbajack");
|
||||||
applicationRegistrationService.RegisterApplication(applicationInfo);
|
applicationRegistrationService.RegisterApplication(applicationInfo);
|
||||||
}
|
}
|
||||||
@ -181,27 +180,27 @@ namespace Wabbajack
|
|||||||
|
|
||||||
ActivePane = objViewModel;
|
ActivePane = objViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleNavigateBack(NavigateBack navigateBack)
|
private void HandleNavigateBack(NavigateBack navigateBack)
|
||||||
{
|
{
|
||||||
ActivePane = PreviousPanes.Last();
|
ActivePane = PreviousPanes.Last();
|
||||||
PreviousPanes.RemoveAt(PreviousPanes.Count - 1);
|
PreviousPanes.RemoveAt(PreviousPanes.Count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleManualDownload(ManualDownload manualDownload)
|
private void HandleManualDownload(ManualDownload manualDownload)
|
||||||
{
|
{
|
||||||
var handler = _serviceProvider.GetRequiredService<ManualDownloadHandler>();
|
var handler = _serviceProvider.GetRequiredService<ManualDownloadHandler>();
|
||||||
handler.Intervention = manualDownload;
|
handler.Intervention = manualDownload;
|
||||||
//MessageBus.Current.SendMessage(new OpenBrowserTab(handler));
|
//MessageBus.Current.SendMessage(new OpenBrowserTab(handler));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleManualBlobDownload(ManualBlobDownload manualDownload)
|
private void HandleManualBlobDownload(ManualBlobDownload manualDownload)
|
||||||
{
|
{
|
||||||
var handler = _serviceProvider.GetRequiredService<ManualBlobDownloadHandler>();
|
var handler = _serviceProvider.GetRequiredService<ManualBlobDownloadHandler>();
|
||||||
handler.Intervention = manualDownload;
|
handler.Intervention = manualDownload;
|
||||||
//MessageBus.Current.SendMessage(new OpenBrowserTab(handler));
|
//MessageBus.Current.SendMessage(new OpenBrowserTab(handler));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleSpawnBrowserWindow(SpawnBrowserWindow msg)
|
private void HandleSpawnBrowserWindow(SpawnBrowserWindow msg)
|
||||||
{
|
{
|
||||||
var window = _serviceProvider.GetRequiredService<BrowserWindow>();
|
var window = _serviceProvider.GetRequiredService<BrowserWindow>();
|
||||||
@ -213,7 +212,7 @@ namespace Wabbajack
|
|||||||
{
|
{
|
||||||
if (s is NavigateToGlobal.ScreenType.Settings)
|
if (s is NavigateToGlobal.ScreenType.Settings)
|
||||||
PreviousPanes.Add(ActivePane);
|
PreviousPanes.Add(ActivePane);
|
||||||
|
|
||||||
ActivePane = s switch
|
ActivePane = s switch
|
||||||
{
|
{
|
||||||
NavigateToGlobal.ScreenType.ModeSelectionView => ModeSelectionVM,
|
NavigateToGlobal.ScreenType.ModeSelectionView => ModeSelectionVM,
|
||||||
|
@ -54,61 +54,58 @@ public class VerifyModlistInstall
|
|||||||
|
|
||||||
|
|
||||||
_logger.LogInformation("Scanning files");
|
_logger.LogInformation("Scanning files");
|
||||||
var errors = await list.Directives.PMapAllBatchedAsync(_limiter, async directive =>
|
var errors = await list.Directives.PMapAllBatchedAsync(_limiter, async directive =>
|
||||||
{
|
|
||||||
if (directive is ArchiveMeta)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (directive is RemappedInlineFile)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (directive.To.InFolder(Consts.BSACreationDir))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var dest = directive.To.RelativeTo(installFolder);
|
|
||||||
if (!dest.FileExists())
|
|
||||||
{
|
{
|
||||||
return new Result
|
if (!(directive is CreateBSA || directive.IsDeterministic))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (directive.To.InFolder(Consts.BSACreationDir))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var dest = directive.To.RelativeTo(installFolder);
|
||||||
|
if (!dest.FileExists())
|
||||||
{
|
{
|
||||||
Path = directive.To,
|
return new Result
|
||||||
Message = $"File does not exist directive {directive.GetType()}"
|
{
|
||||||
};
|
Path = directive.To,
|
||||||
}
|
Message = $"File does not exist directive {directive.GetType()}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Consts.KnownModifiedFiles.Contains(directive.To.FileName))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (directive is CreateBSA bsa)
|
||||||
|
{
|
||||||
|
return await VerifyBSA(dest, bsa, byTo, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dest.Size() != directive.Size)
|
||||||
|
{
|
||||||
|
return new Result
|
||||||
|
{
|
||||||
|
Path = directive.To,
|
||||||
|
Message = $"Sizes do not match got {dest.Size()} expected {directive.Size}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directive.Size > (1024 * 1024 * 128))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Hashing {Size} file at {Path}", directive.Size.ToFileSizeString(),
|
||||||
|
directive.To);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash = await AbsolutePathExtensions.Hash(dest, token);
|
||||||
|
if (hash != directive.Hash)
|
||||||
|
{
|
||||||
|
return new Result
|
||||||
|
{
|
||||||
|
Path = directive.To,
|
||||||
|
Message = $"Hashes do not match, got {hash} expected {directive.Hash}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (Consts.KnownModifiedFiles.Contains(directive.To.FileName))
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (directive is CreateBSA bsa)
|
|
||||||
{
|
|
||||||
return await VerifyBSA(dest, bsa, byTo, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dest.Size() != directive.Size)
|
|
||||||
{
|
|
||||||
return new Result
|
|
||||||
{
|
|
||||||
Path = directive.To,
|
|
||||||
Message = $"Sizes do not match got {dest.Size()} expected {directive.Size}"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (directive.Size > (1024 * 1024 * 128))
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Hashing {Size} file at {Path}", directive.Size.ToFileSizeString(),
|
|
||||||
directive.To);
|
|
||||||
}
|
|
||||||
|
|
||||||
var hash = await AbsolutePathExtensions.Hash(dest, token);
|
|
||||||
if (hash != directive.Hash)
|
|
||||||
{
|
|
||||||
return new Result
|
|
||||||
{
|
|
||||||
Path = directive.To,
|
|
||||||
Message = $"Hashes do not match, got {hash} expected {directive.Hash}"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}).Where(r => r != null)
|
}).Where(r => r != null)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@ -150,6 +147,9 @@ public class VerifyModlistInstall
|
|||||||
var astate = bsa.FileStates.First(f => f.Path == state.Path);
|
var astate = bsa.FileStates.First(f => f.Path == state.Path);
|
||||||
var srcDirective = byTo[Consts.BSACreationDir.Combine(bsa.TempID, astate.Path)];
|
var srcDirective = byTo[Consts.BSACreationDir.Combine(bsa.TempID, astate.Path)];
|
||||||
|
|
||||||
|
if (!srcDirective.IsDeterministic)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (srcDirective.Hash != hash)
|
if (srcDirective.Hash != hash)
|
||||||
{
|
{
|
||||||
return new Result
|
return new Result
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
using Wabbajack.Hashing.xxHash64;
|
using Wabbajack.Hashing.xxHash64;
|
||||||
using Wabbajack.Paths;
|
using Wabbajack.Paths;
|
||||||
|
|
||||||
@ -12,4 +13,6 @@ public abstract class Directive
|
|||||||
/// location the file will be copied to, relative to the install path.
|
/// location the file will be copied to, relative to the install path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RelativePath To { get; set; }
|
public RelativePath To { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore] public virtual bool IsDeterministic => true;
|
||||||
}
|
}
|
@ -8,4 +8,6 @@ namespace Wabbajack.DTOs.Directives;
|
|||||||
public class ArchiveMeta : Directive
|
public class ArchiveMeta : Directive
|
||||||
{
|
{
|
||||||
public RelativePath SourceDataID { get; set; }
|
public RelativePath SourceDataID { get; set; }
|
||||||
|
|
||||||
|
public override bool IsDeterministic => false;
|
||||||
}
|
}
|
@ -13,4 +13,6 @@ public class CreateBSA : Directive
|
|||||||
public RelativePath TempID { get; set; }
|
public RelativePath TempID { get; set; }
|
||||||
public IArchive State { get; set; }
|
public IArchive State { get; set; }
|
||||||
public AFile[] FileStates { get; set; } = Array.Empty<AFile>();
|
public AFile[] FileStates { get; set; } = Array.Empty<AFile>();
|
||||||
|
|
||||||
|
public override bool IsDeterministic => false;
|
||||||
}
|
}
|
@ -9,4 +9,5 @@ namespace Wabbajack.DTOs.Directives;
|
|||||||
[JsonAlias("RemappedInlineFile, Wabbajack.Lib")]
|
[JsonAlias("RemappedInlineFile, Wabbajack.Lib")]
|
||||||
public class RemappedInlineFile : InlineFile
|
public class RemappedInlineFile : InlineFile
|
||||||
{
|
{
|
||||||
|
public override bool IsDeterministic => false;
|
||||||
}
|
}
|
@ -11,4 +11,5 @@ public class TransformedTexture : FromArchive
|
|||||||
/// The file to apply to the source file to patch it
|
/// The file to apply to the source file to patch it
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ImageState ImageState { get; set; } = new();
|
public ImageState ImageState { get; set; } = new();
|
||||||
|
public override bool IsDeterministic => false;
|
||||||
}
|
}
|
@ -9,7 +9,6 @@ namespace Wabbajack.Downloaders.Dispatcher.Test;
|
|||||||
|
|
||||||
public class VerificationCacheTests
|
public class VerificationCacheTests
|
||||||
{
|
{
|
||||||
private readonly TemporaryFileManager _temp;
|
|
||||||
private readonly ILogger<VerificationCache.VerificationCache> _logger;
|
private readonly ILogger<VerificationCache.VerificationCache> _logger;
|
||||||
|
|
||||||
public VerificationCacheTests(ILogger<VerificationCache.VerificationCache> logger)
|
public VerificationCacheTests(ILogger<VerificationCache.VerificationCache> logger)
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using GameFinder.Common;
|
||||||
|
using GameFinder.RegistryUtils;
|
||||||
using GameFinder.StoreHandlers.EGS;
|
using GameFinder.StoreHandlers.EGS;
|
||||||
using GameFinder.StoreHandlers.GOG;
|
using GameFinder.StoreHandlers.GOG;
|
||||||
using GameFinder.StoreHandlers.Origin;
|
using GameFinder.StoreHandlers.Origin;
|
||||||
@ -5,67 +8,120 @@ using GameFinder.StoreHandlers.Steam;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Wabbajack.DTOs;
|
using Wabbajack.DTOs;
|
||||||
using Wabbajack.Paths;
|
using Wabbajack.Paths;
|
||||||
|
using Wabbajack.Paths.IO;
|
||||||
|
|
||||||
namespace Wabbajack.Downloaders.GameFile;
|
namespace Wabbajack.Downloaders.GameFile;
|
||||||
|
|
||||||
public class GameLocator : IGameLocator
|
public class GameLocator : IGameLocator
|
||||||
{
|
{
|
||||||
private readonly EGSHandler? _egs;
|
private readonly SteamHandler _steam;
|
||||||
private readonly GOGHandler? _gog;
|
private readonly GOGHandler? _gog;
|
||||||
|
private readonly EGSHandler? _egs;
|
||||||
|
private readonly OriginHandler? _origin;
|
||||||
|
|
||||||
|
private readonly Dictionary<int, AbsolutePath> _steamGames = new();
|
||||||
|
private readonly Dictionary<long, AbsolutePath> _gogGames = new();
|
||||||
|
private readonly Dictionary<string, AbsolutePath> _egsGames = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly Dictionary<string, AbsolutePath> _originGames = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
private readonly Dictionary<Game, AbsolutePath> _locationCache;
|
private readonly Dictionary<Game, AbsolutePath> _locationCache;
|
||||||
private readonly ILogger<GameLocator> _logger;
|
private readonly ILogger<GameLocator> _logger;
|
||||||
private readonly OriginHandler? _origin;
|
|
||||||
private readonly SteamHandler _steam;
|
|
||||||
|
|
||||||
public GameLocator(ILogger<GameLocator> logger)
|
public GameLocator(ILogger<GameLocator> logger)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_steam = new SteamHandler(logger);
|
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
_origin = new OriginHandler(true, false, logger);
|
var windowsRegistry = new WindowsRegistry();
|
||||||
_gog = new GOGHandler(logger);
|
|
||||||
|
|
||||||
_egs = new EGSHandler(logger);
|
_steam = new SteamHandler(windowsRegistry);
|
||||||
|
_gog = new GOGHandler(windowsRegistry);
|
||||||
|
_egs = new EGSHandler(windowsRegistry);
|
||||||
|
_origin = new OriginHandler();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_steam = new SteamHandler(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_locationCache = new Dictionary<Game, AbsolutePath>();
|
_locationCache = new Dictionary<Game, AbsolutePath>();
|
||||||
|
|
||||||
|
FindAllGames();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FindAllGames()
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_steam.FindAllGames();
|
FindStoreGames(_steam, _steamGames, game => game.Path);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "While finding Steam games");
|
_logger.LogError(e, "While finding games installed with Steam");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_origin?.FindAllGames();
|
FindStoreGames(_gog, _gogGames, game => game.Path);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(ex, "While finding Origin games");
|
_logger.LogError(e, "While finding games installed with GOG Galaxy");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_gog?.FindAllGames();
|
FindStoreGames(_egs, _egsGames, game => game.InstallLocation);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(ex, "While finding GoG games");
|
_logger.LogError(e, "While finding games installed with the Epic Games Store");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_egs?.FindAllGames();
|
FindStoreGames(_origin, _originGames, game => game.InstallPath);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(ex, "While finding Epic Games");
|
_logger.LogError(e, "While finding games installed with Origin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FindStoreGames<TGame, TId>(
|
||||||
|
AHandler<TGame, TId>? handler,
|
||||||
|
Dictionary<TId, AbsolutePath> paths,
|
||||||
|
Func<TGame, string> getPath)
|
||||||
|
where TGame : class
|
||||||
|
{
|
||||||
|
if (handler is null) return;
|
||||||
|
|
||||||
|
var games = handler.FindAllGamesById(out var errors);
|
||||||
|
|
||||||
|
foreach (var (id, game) in games)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = getPath(game).ToAbsolutePath();
|
||||||
|
if (!path.DirectoryExists())
|
||||||
|
{
|
||||||
|
_logger.LogError("Game does not exist: {Game}", game);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
paths[id] = path;
|
||||||
|
_logger.LogDebug("Found {Game}", game);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "While locating {Game}", game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var error in errors)
|
||||||
|
{
|
||||||
|
_logger.LogError("{Error}", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,71 +158,35 @@ public class GameLocator : IGameLocator
|
|||||||
{
|
{
|
||||||
var metaData = game.MetaData();
|
var metaData = game.MetaData();
|
||||||
|
|
||||||
try
|
foreach (var id in metaData.SteamIDs)
|
||||||
{
|
{
|
||||||
foreach (var steamGame in _steam.Games.Where(steamGame => metaData.SteamIDs.Contains(steamGame.ID)))
|
if (!_steamGames.TryGetValue(id, out var found)) continue;
|
||||||
{
|
path = found;
|
||||||
path = steamGame!.Path.ToAbsolutePath();
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(ex, "While finding {Game} from Steam", game);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
foreach (var id in metaData.GOGIDs)
|
||||||
{
|
{
|
||||||
if (_gog != null)
|
if (!_gogGames.TryGetValue(id, out var found)) continue;
|
||||||
{
|
path = found;
|
||||||
foreach (var gogGame in _gog.Games.Where(gogGame => metaData.GOGIDs.Contains(gogGame.GameID)))
|
return true;
|
||||||
{
|
|
||||||
path = gogGame!.Path.ToAbsolutePath();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(ex, "While finding {Game} from GoG", game);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
foreach (var id in metaData.EpicGameStoreIDs)
|
||||||
{
|
{
|
||||||
if (_egs != null)
|
if (!_egsGames.TryGetValue(id, out var found)) continue;
|
||||||
{
|
path = found;
|
||||||
foreach (var egsGame in _egs.Games.Where(egsGame =>
|
return true;
|
||||||
metaData.EpicGameStoreIDs.Contains(egsGame.CatalogItemId)))
|
|
||||||
{
|
|
||||||
path = egsGame!.Path.ToAbsolutePath();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(ex, "While finding {Game} from Epic", game);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var id in metaData.OriginIDs)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (_origin != null)
|
if (!_originGames.TryGetValue(id, out var found)) continue;
|
||||||
{
|
path = found;
|
||||||
foreach (var originGame in _origin.Games.Where(originGame =>
|
return true;
|
||||||
metaData.EpicGameStoreIDs.Contains(originGame.Id)))
|
|
||||||
{
|
|
||||||
path = originGame.Path.ToAbsolutePath();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(ex, "While finding {Game} from Origin", game);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
path = default;
|
path = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="GameFinder.StoreHandlers.EGS" Version="1.8.0" />
|
<PackageReference Include="GameFinder.StoreHandlers.EGS" Version="2.2.1" />
|
||||||
<PackageReference Include="GameFinder.StoreHandlers.GOG" Version="1.8.0" />
|
<PackageReference Include="GameFinder.StoreHandlers.GOG" Version="2.2.1" />
|
||||||
<PackageReference Include="GameFinder.StoreHandlers.Origin" Version="1.8.0" />
|
<PackageReference Include="GameFinder.StoreHandlers.Origin" Version="2.2.1" />
|
||||||
<PackageReference Include="GameFinder.StoreHandlers.Steam" Version="1.8.0" />
|
<PackageReference Include="GameFinder.StoreHandlers.Steam" Version="2.2.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -40,7 +40,8 @@ public class MediaFireDownloader : ADownloader<DTOs.DownloadStates.MediaFire>, I
|
|||||||
|
|
||||||
public override bool IsAllowed(ServerAllowList allowList, IDownloadState state)
|
public override bool IsAllowed(ServerAllowList allowList, IDownloadState state)
|
||||||
{
|
{
|
||||||
return true;
|
var mediaFireState = (DTOs.DownloadStates.MediaFire) state;
|
||||||
|
return allowList.AllowedPrefixes.Any(p => mediaFireState.Url.ToString().StartsWith(p, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IDownloadState? Resolve(IReadOnlyDictionary<string, string> iniData)
|
public override IDownloadState? Resolve(IReadOnlyDictionary<string, string> iniData)
|
||||||
@ -105,26 +106,26 @@ public class MediaFireDownloader : ADownloader<DTOs.DownloadStates.MediaFire>, I
|
|||||||
if (!result.IsSuccessStatusCode)
|
if (!result.IsSuccessStatusCode)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (job != null)
|
if (job != null)
|
||||||
job.Size = result.Content.Headers.ContentLength ?? 0;
|
job.Size = result.Content.Headers.ContentLength ?? 0;
|
||||||
|
|
||||||
if (result.Content.Headers.ContentType!.MediaType!.StartsWith("text/html",
|
if (result.Content.Headers.ContentType!.MediaType!.StartsWith("text/html",
|
||||||
StringComparison.OrdinalIgnoreCase))
|
StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var bodyData = await result.Content.ReadAsStringAsync((CancellationToken) token);
|
var bodyData = await result.Content.ReadAsStringAsync((CancellationToken) token);
|
||||||
if (job != null)
|
if (job != null)
|
||||||
await job.Report((int) (job.Size ?? 0), (CancellationToken) token);
|
await job.Report((int) (job.Size ?? 0), (CancellationToken) token);
|
||||||
var body = new HtmlDocument();
|
var body = new HtmlDocument();
|
||||||
body.LoadHtml(bodyData);
|
body.LoadHtml(bodyData);
|
||||||
var node = body.DocumentNode.DescendantsAndSelf().FirstOrDefault(d => d.HasClass("input") && d.HasClass("popsok") &&
|
var node = body.DocumentNode.DescendantsAndSelf().FirstOrDefault(d => d.HasClass("input") && d.HasClass("popsok") &&
|
||||||
d.GetAttributeValue("aria-label", "") ==
|
d.GetAttributeValue("aria-label", "") ==
|
||||||
"Download file");
|
"Download file");
|
||||||
if (node != null)
|
if (node != null)
|
||||||
return new Uri(node.GetAttributeValue("href", "not-found"));
|
return new Uri(node.GetAttributeValue("href", "not-found"));
|
||||||
|
|
||||||
var startText = "window.location.href = '";
|
var startText = "window.location.href = '";
|
||||||
var start = body.DocumentNode.InnerHtml.IndexOf(startText, StringComparison.CurrentCultureIgnoreCase);
|
var start = body.DocumentNode.InnerHtml.IndexOf(startText, StringComparison.CurrentCultureIgnoreCase);
|
||||||
|
|
||||||
if (start != -1)
|
if (start != -1)
|
||||||
{
|
{
|
||||||
var end = body.DocumentNode.InnerHtml.IndexOf("\'", start + startText.Length,
|
var end = body.DocumentNode.InnerHtml.IndexOf("\'", start + startText.Length,
|
||||||
@ -141,4 +142,4 @@ public class MediaFireDownloader : ADownloader<DTOs.DownloadStates.MediaFire>, I
|
|||||||
{
|
{
|
||||||
return new[] {$"directURL={state.Url}"};
|
return new[] {$"directURL={state.Url}"};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,7 +316,7 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
|||||||
var astate = bsa.FileStates.First(f => f.Path == state.Path);
|
var astate = bsa.FileStates.First(f => f.Path == state.Path);
|
||||||
var srcDirective = indexedByDestination[Consts.BSACreationDir.Combine(bsa.TempID, astate.Path)];
|
var srcDirective = indexedByDestination[Consts.BSACreationDir.Combine(bsa.TempID, astate.Path)];
|
||||||
//DX10Files are lossy
|
//DX10Files are lossy
|
||||||
if (astate is not BA2DX10File)
|
if (astate is not BA2DX10File && srcDirective.IsDeterministic)
|
||||||
ThrowOnNonMatchingHash(bsa, srcDirective, astate, hash);
|
ThrowOnNonMatchingHash(bsa, srcDirective, astate, hash);
|
||||||
return (srcDirective, hash);
|
return (srcDirective, hash);
|
||||||
}).ToHashSet();
|
}).ToHashSet();
|
||||||
|
@ -33,8 +33,7 @@ public class NexusApi
|
|||||||
private (ValidateInfo info, ResponseMetadata header) _lastValidatedInfo;
|
private (ValidateInfo info, ResponseMetadata header) _lastValidatedInfo;
|
||||||
|
|
||||||
public NexusApi(ITokenProvider<NexusApiState> apiKey, ILogger<NexusApi> logger, HttpClient client,
|
public NexusApi(ITokenProvider<NexusApiState> apiKey, ILogger<NexusApi> logger, HttpClient client,
|
||||||
IResource<HttpClient> limiter,
|
IResource<HttpClient> limiter, ApplicationInfo appInfo, JsonSerializerOptions jsonOptions)
|
||||||
ApplicationInfo appInfo, JsonSerializerOptions jsonOptions)
|
|
||||||
{
|
{
|
||||||
ApiKey = apiKey;
|
ApiKey = apiKey;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -55,6 +55,11 @@ public static class AbsolutePathExtensions
|
|||||||
{
|
{
|
||||||
return new FileInfo(file.ToNativePath()).LastWriteTimeUtc;
|
return new FileInfo(file.ToNativePath()).LastWriteTimeUtc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DateTime CreatedUtc(this AbsolutePath file)
|
||||||
|
{
|
||||||
|
return new FileInfo(file.ToNativePath()).CreationTimeUtc;
|
||||||
|
}
|
||||||
|
|
||||||
public static DateTime LastModified(this AbsolutePath file)
|
public static DateTime LastModified(this AbsolutePath file)
|
||||||
{
|
{
|
||||||
|
@ -91,6 +91,10 @@ public class FileHashCache
|
|||||||
var result = await Get(file);
|
var result = await Get(file);
|
||||||
if (result == default || result.Hash == default)
|
if (result == default || result.Hash == default)
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
|
// Fix for strange issue where dates are messed up on some systems
|
||||||
|
if (file.LastModifiedUtc() < file.CreatedUtc())
|
||||||
|
file.Touch();
|
||||||
|
|
||||||
if (result.LastModified == file.LastModifiedUtc().ToFileTimeUtc())
|
if (result.LastModified == file.LastModifiedUtc().ToFileTimeUtc())
|
||||||
{
|
{
|
||||||
|
@ -111,6 +111,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".solutionItems", ".solution
|
|||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
nuget.config = nuget.config
|
nuget.config = nuget.config
|
||||||
|
CHANGELOG.md = CHANGELOG.md
|
||||||
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wabbajack.Downloaders.GameFile", "Wabbajack.Downloaders.GameFile\Wabbajack.Downloaders.GameFile.csproj", "{4F252332-CA77-41DE-95A8-9DF38A81D675}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wabbajack.Downloaders.GameFile", "Wabbajack.Downloaders.GameFile\Wabbajack.Downloaders.GameFile.csproj", "{4F252332-CA77-41DE-95A8-9DF38A81D675}"
|
||||||
|
Loading…
Reference in New Issue
Block a user