From dbdf94a925c92eef6b52ef6aca3f78eae0bb364c Mon Sep 17 00:00:00 2001 From: Halgari Date: Sun, 23 Oct 2022 15:28:44 -0600 Subject: [PATCH] Verify hashes while installing --- Wabbajack.CLI/VerbRegistration.cs | 2 + Wabbajack.CLI/Verbs/VerifyModlistInstall.cs | 74 +++++++++++++++++++ Wabbajack.Common/AbsolutePathExtensions.cs | 15 ++++ Wabbajack.Installer/AInstaller.cs | 17 ++++- Wabbajack.Installer/StandardInstaller.cs | 3 +- .../Utilities/BinaryPatching.cs | 7 +- .../Wabbajack.Installer.csproj | 2 +- 7 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 Wabbajack.CLI/Verbs/VerifyModlistInstall.cs diff --git a/Wabbajack.CLI/VerbRegistration.cs b/Wabbajack.CLI/VerbRegistration.cs index 0e7154a7..b000d201 100644 --- a/Wabbajack.CLI/VerbRegistration.cs +++ b/Wabbajack.CLI/VerbRegistration.cs @@ -51,6 +51,8 @@ CommandLineBuilder.RegisterCommand(UploadToNexus.Definition, c => services.AddSingleton(); CommandLineBuilder.RegisterCommand(ValidateLists.Definition, c => ((ValidateLists)c).Run); services.AddSingleton(); +CommandLineBuilder.RegisterCommand(VerifyModlistInstall.Definition, c => ((VerifyModlistInstall)c).Run); +services.AddSingleton(); CommandLineBuilder.RegisterCommand(VFSIndex.Definition, c => ((VFSIndex)c).Run); services.AddSingleton(); } diff --git a/Wabbajack.CLI/Verbs/VerifyModlistInstall.cs b/Wabbajack.CLI/Verbs/VerifyModlistInstall.cs new file mode 100644 index 00000000..c0e033ba --- /dev/null +++ b/Wabbajack.CLI/Verbs/VerifyModlistInstall.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Wabbajack.CLI.Builder; +using Wabbajack.DTOs.Directives; +using Wabbajack.DTOs.JsonConverters; +using Wabbajack.Installer; +using Wabbajack.Paths; +using Wabbajack.Paths.IO; + +namespace Wabbajack.CLI.Verbs; + +public class VerifyModlistInstall +{ + private readonly DTOSerializer _dtos; + private readonly ILogger _logger; + + public VerifyModlistInstall(ILogger logger, DTOSerializer dtos) + { + _logger = logger; + _dtos = dtos; + } + + public static VerbDefinition Definition = new("verify-modlist-install", "Verify a modlist installed correctly", + new[] + { + new OptionDefinition(typeof(AbsolutePath), "m", "modlistLocation", + "The .wabbajack file used to install the modlist"), + new OptionDefinition(typeof(AbsolutePath), "i", "installFolder", "The installation folder of the modlist") + }); + + + + public async Task Run(AbsolutePath modlistLocation, AbsolutePath installFolder) + { + _logger.LogInformation("Loading modlist {ModList}", modlistLocation); + var list = await StandardInstaller.LoadFromFile(_dtos, modlistLocation); + + var errors = new List(); + + _logger.LogInformation("Scanning files"); + foreach (var directive in list.Directives) + { + var dest = directive.To.RelativeTo(installFolder); + if (dest.Size() != directive.Size) + { + errors.Add(new Result() + { + Path = directive.To, + Message = $"Sizes do not match got {dest.Size()} expected {directive.Size}" + }); + } + + } + + _logger.LogInformation("Found {Count} errors", errors.Count); + + + foreach (var error in errors) + { + _logger.LogError("{File} | {Message}", error.Path, error.Message); + } + + + return 0; + } + + public class Result + { + public RelativePath Path { get; set; } + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/Wabbajack.Common/AbsolutePathExtensions.cs b/Wabbajack.Common/AbsolutePathExtensions.cs index c2b4ff36..91ed4e2a 100644 --- a/Wabbajack.Common/AbsolutePathExtensions.cs +++ b/Wabbajack.Common/AbsolutePathExtensions.cs @@ -21,4 +21,19 @@ public static class AbsolutePathExtensions await using var fs = path.Open(FileMode.Open, FileAccess.Read, FileShare.Read); return await fs.FromJson(dtos); } + + public static async Task WriteAllHashedAsync(this AbsolutePath file, Stream srcStream, CancellationToken token, + bool closeWhenDone = true) + { + try + { + await using var dest = file.Open(FileMode.Create, FileAccess.Write, FileShare.None); + return await srcStream.HashingCopy(dest, token); + } + finally + { + if (closeWhenDone) + srcStream.Close(); + } + } } \ No newline at end of file diff --git a/Wabbajack.Installer/AInstaller.cs b/Wabbajack.Installer/AInstaller.cs index 1d20a863..73468a93 100644 --- a/Wabbajack.Installer/AInstaller.cs +++ b/Wabbajack.Installer/AInstaller.cs @@ -243,7 +243,8 @@ public abstract class AInstaller await using var patchDataStream = await InlinedFileStream(pfa.PatchID); { await using var os = destPath.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.None); - await BinaryPatching.ApplyPatch(s, patchDataStream, os); + var hash = await BinaryPatching.ApplyPatch(s, patchDataStream, os); + ThrowOnNonMatchingHash(file, hash); } } break; @@ -268,7 +269,8 @@ public abstract class AInstaller else { await using var s = await sf.GetStream(); - await destPath.WriteAllAsync(s, token, false); + var hash = await destPath.WriteAllHashedAsync(s, token, false); + ThrowOnNonMatchingHash(file, hash); } break; @@ -282,6 +284,17 @@ public abstract class AInstaller }, token); } + protected void ThrowOnNonMatchingHash(Directive file, Hash gotHash) + { + if (file.Hash != gotHash) + ThrowNonMatchingError(file, gotHash); + } + private void ThrowNonMatchingError(Directive file, Hash gotHash) + { + _logger.LogError("Hashes for {Path} did not match, expected {Expected} got {Got}", file.To, file.Hash, gotHash); + throw new Exception($"Hashes for {file.To} did not match, expected {file.Hash} got {gotHash}"); + } + public async Task DownloadArchives(CancellationToken token) { var missing = ModList.Archives.Where(a => !HashedArchives.ContainsKey(a.Hash)).ToList(); diff --git a/Wabbajack.Installer/StandardInstaller.cs b/Wabbajack.Installer/StandardInstaller.cs index e6a9d7cc..638708c1 100644 --- a/Wabbajack.Installer/StandardInstaller.cs +++ b/Wabbajack.Installer/StandardInstaller.cs @@ -481,7 +481,8 @@ public class StandardInstaller : AInstaller .Open(FileMode.Create, FileAccess.ReadWrite, FileShare.None); try { - await BinaryPatching.ApplyPatch(new MemoryStream(srcData), new MemoryStream(patchData), fs); + var hash = await BinaryPatching.ApplyPatch(new MemoryStream(srcData), new MemoryStream(patchData), fs); + ThrowOnNonMatchingHash(m, hash); } catch (Exception ex) { diff --git a/Wabbajack.Installer/Utilities/BinaryPatching.cs b/Wabbajack.Installer/Utilities/BinaryPatching.cs index 59e65cb9..03cd46a1 100644 --- a/Wabbajack.Installer/Utilities/BinaryPatching.cs +++ b/Wabbajack.Installer/Utilities/BinaryPatching.cs @@ -1,16 +1,19 @@ using System.IO; +using System.Threading; using System.Threading.Tasks; using Octodiff.Core; using Octodiff.Diagnostics; +using Wabbajack.Hashing.xxHash64; namespace Wabbajack.Installer.Utilities; public class BinaryPatching { - public static ValueTask ApplyPatch(Stream input, Stream deltaStream, Stream output) + public static async ValueTask ApplyPatch(Stream input, Stream deltaStream, Stream output, CancellationToken? token = null) { var deltaApplier = new DeltaApplier(); deltaApplier.Apply(input, new BinaryDeltaReader(deltaStream, new NullProgressReporter()), output); - return ValueTask.CompletedTask; + output.Position = 0; + return await output.Hash(token ?? CancellationToken.None); } } \ No newline at end of file diff --git a/Wabbajack.Installer/Wabbajack.Installer.csproj b/Wabbajack.Installer/Wabbajack.Installer.csproj index 23356d4d..7b3417ae 100644 --- a/Wabbajack.Installer/Wabbajack.Installer.csproj +++ b/Wabbajack.Installer/Wabbajack.Installer.csproj @@ -24,7 +24,7 @@ - +