Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Convoy/Convoy.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
</Reference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.IO.Hashing" Version="9.0.13" />
</ItemGroup>

<Target Name="GitVersion" BeforeTargets="CoreCompile">
<Exec Command="git describe --tags --always 2>/dev/null || echo 0.0.0-dev"
ConsoleToMsBuild="true" StandardOutputImportance="low">
Expand All @@ -59,6 +63,8 @@
</PropertyGroup>
<MakeDir Directories="$(BuildOutputDir)"/>
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(BuildOutputDir)"/>
<Copy SourceFiles="$(OutDir)System.IO.Hashing.dll" DestinationFolder="$(BuildOutputDir)"
SkipUnchangedFiles="true" Condition="Exists('$(OutDir)System.IO.Hashing.dll')"/>
<Message Text="Build output copied to: $(BuildOutputDir)" Importance="high"/>
</Target>

Expand Down
26 changes: 11 additions & 15 deletions Convoy/SyncEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -563,22 +563,18 @@ private List<string> ExtractZip(byte[] zipBytes, string targetDir)

private bool VerifyHashes(List<string> extractedPaths, Dictionary<string, string> 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;
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).

Expand Down