mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Merge branch 'main' into dependabot/nuget/DynamicData-7.12.1
This commit is contained in:
commit
26e290d2a3
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
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ 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);
|
||||||
@ -149,10 +149,9 @@ namespace Wabbajack
|
|||||||
// 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);
|
||||||
}
|
}
|
||||||
|
@ -56,10 +56,7 @@ 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)
|
if (!(directive is CreateBSA || directive.IsDeterministic))
|
||||||
return null;
|
|
||||||
|
|
||||||
if (directive is RemappedInlineFile)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (directive.To.InFolder(Consts.BSACreationDir))
|
if (directive.To.InFolder(Consts.BSACreationDir))
|
||||||
@ -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,69 +158,33 @@ 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)))
|
|
||||||
{
|
|
||||||
path = gogGame!.Path.ToAbsolutePath();
|
|
||||||
return true;
|
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 =>
|
|
||||||
metaData.EpicGameStoreIDs.Contains(egsGame.CatalogItemId)))
|
|
||||||
{
|
|
||||||
path = egsGame!.Path.ToAbsolutePath();
|
|
||||||
return true;
|
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 =>
|
|
||||||
metaData.EpicGameStoreIDs.Contains(originGame.Id)))
|
|
||||||
{
|
|
||||||
path = originGame.Path.ToAbsolutePath();
|
|
||||||
return true;
|
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)
|
||||||
|
@ -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;
|
||||||
|
@ -56,6 +56,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)
|
||||||
{
|
{
|
||||||
return new FileInfo(file.ToNativePath()).LastWriteTime;
|
return new FileInfo(file.ToNativePath()).LastWriteTime;
|
||||||
|
@ -92,6 +92,10 @@ public class FileHashCache
|
|||||||
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())
|
||||||
{
|
{
|
||||||
return result.Hash;
|
return result.Hash;
|
||||||
|
@ -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