From 01521e52fad3368689d79bbb46093542c46b0fc1 Mon Sep 17 00:00:00 2001 From: Anten Skrabec Date: Fri, 31 Jul 2026 15:04:16 -0600 Subject: [PATCH 1/2] feat: switch hash verification from SHA-256 to xxHash3-64 Matches Quartermaster PR #372 which changed server-side hashing. Adds System.IO.Hashing NuGet package (zero transitive deps). Catalog now sends 16-char xxHash3-64 hex; VerifyHashes updated to match. Co-Authored-By: Claude Opus 4.6 (1M context) --- Convoy/Convoy.csproj | 6 ++++++ Convoy/SyncEngine.cs | 26 +++++++++++--------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Convoy/Convoy.csproj b/Convoy/Convoy.csproj index 4f00e22..d434c5c 100644 --- a/Convoy/Convoy.csproj +++ b/Convoy/Convoy.csproj @@ -36,6 +36,10 @@ + + + + @@ -59,6 +63,8 @@ + diff --git a/Convoy/SyncEngine.cs b/Convoy/SyncEngine.cs index 873cc99..4bb6949 100644 --- a/Convoy/SyncEngine.cs +++ b/Convoy/SyncEngine.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.IO.Hashing; using System.Linq; using System.Net; -using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Logging; @@ -563,22 +563,18 @@ private List ExtractZip(byte[] zipBytes, string targetDir) private bool VerifyHashes(List extractedPaths, Dictionary expected, string baseDir) { - using (var sha = SHA256.Create()) + foreach (var relPath in extractedPaths) { - foreach (var relPath in extractedPaths) + if (!expected.TryGetValue(relPath, out var want)) continue; + var fullPath = Path.Combine(baseDir, relPath.Replace('/', Path.DirectorySeparatorChar)); + var hasher = new XxHash3(); + using (var stream = File.OpenRead(fullPath)) + hasher.Append(stream); + var got = hasher.GetCurrentHashAsUInt64().ToString("x16"); + if (got != want.ToLowerInvariant()) { - if (!expected.TryGetValue(relPath, out var want)) continue; - var fullPath = Path.Combine(baseDir, relPath.Replace('/', Path.DirectorySeparatorChar)); - using (var stream = File.OpenRead(fullPath)) - { - var got = BitConverter.ToString(sha.ComputeHash(stream)) - .Replace("-", "").ToLowerInvariant(); - if (got != want.ToLowerInvariant()) - { - _log.LogError($"Hash mismatch: {relPath} (expected {want}, got {got})"); - return false; - } - } + _log.LogError($"Hash mismatch: {relPath} (expected {want}, got {got})"); + return false; } } return true; From 1a1442c713ecf4df2f28f5ffb68cd6464f17e22d Mon Sep 17 00:00:00 2001 From: Anten Skrabec Date: Fri, 31 Jul 2026 15:09:35 -0600 Subject: [PATCH 2/2] docs: update README for xxHash3 migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hash algorithm reference: SHA-256 → xxHash3 - Installation: mention System.IO.Hashing.dll alongside Convoy.dll Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index df8205b..69b66d4 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ BepInEx 5 plugin for [SPT](https://www.sp-tarkov.com/) that syncs client-side mo 1. Fetches the mod catalog from your Quartermaster server (with ETag caching) 2. Diffs against local state to determine what needs to be installed, updated, or removed -3. Downloads changed mods as a batch ZIP, extracts, and verifies SHA-256 hashes +3. Downloads changed mods as a batch ZIP, extracts, and verifies xxHash3 hashes 4. Prompts for a restart if any BepInEx plugin files changed (they're already loaded by the time Convoy runs) Optional mod groups are toggled via BepInEx Configuration Manager (F12 in-game). ## Installation -Copy `Convoy.dll` to `BepInEx/plugins/Convoy/` in your SPT install directory. +Copy the contents of `Build/BepInEx/plugins/Convoy/` (includes `Convoy.dll` and `System.IO.Hashing.dll`) to `BepInEx/plugins/Convoy/` in your SPT install directory. On first run, Convoy creates its config at `BepInEx/config/io.cebarks.convoy.cfg` — set `ServerUrl` to your Quartermaster instance (e.g. `http://192.168.1.50:9190`).