Skip to content
Draft
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
1 change: 1 addition & 0 deletions AssettoServer.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<Project Path="AssettoServer.Tests\AssettoServer.Tests.csproj" Type="Classic C#" />
<Project Path="AssettoServer\AssettoServer.csproj" Type="Classic C#" />
<Project Path="AutoModerationPlugin\AutoModerationPlugin.csproj" Type="Classic C#" />
<Project Path="ChecksumUtils\ChecksumUtils.csproj" Type="Classic C#" />
<Project Path="CustomCommandPlugin\CustomCommandPlugin.csproj" Type="Classic C#" />
<Project Path="DiscordAuditPlugin\DiscordAuditPlugin.csproj" Type="Classic C#" />
<Project Path="FastLaneUtils\FastLaneUtils.csproj" Type="Classic C#" />
Expand Down
20,312 changes: 20,312 additions & 0 deletions AssettoServer/Assets/data_checksums.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions AssettoServer/Network/Tcp/ACTcpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using AssettoServer.Network.Udp;
using AssettoServer.Server;
using AssettoServer.Server.Blacklist;
using AssettoServer.Server.Checksum;
using AssettoServer.Server.Configuration;
using AssettoServer.Server.OpenSlotFilters;
using AssettoServer.Server.Weather;
Expand Down
3 changes: 2 additions & 1 deletion AssettoServer/Server/ACServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using AssettoServer.Server.Configuration;
using AssettoServer.Server.Ai.Splines;
using AssettoServer.Server.Blacklist;
using AssettoServer.Server.Checksum;
using AssettoServer.Server.GeoParams;
using AssettoServer.Server.Whitelist;
using AssettoServer.Shared.Network.Packets.Outgoing;
Expand Down Expand Up @@ -305,7 +306,7 @@ public Task StartedAsync(CancellationToken cancellationToken)
public async Task StartingAsync(CancellationToken cancellationToken)
{
_entryCarManager.Initialize();
_checksumManager.Initialize();
await _checksumManager.Initialize();
await _geoParamsManager.InitializeAsync();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using AssettoServer.Server.Configuration;
using Serilog;

namespace AssettoServer.Server;
namespace AssettoServer.Server.Checksum;

public class ChecksumManager
{
Expand All @@ -17,15 +18,20 @@ public class ChecksumManager

private readonly ACServerConfiguration _configuration;
private readonly EntryCarManager _entryCarManager;

public ChecksumManager(ACServerConfiguration configuration, EntryCarManager entryCarManager)
private readonly ChecksumProvider _checksumProvider;

public ChecksumManager(ACServerConfiguration configuration,
EntryCarManager entryCarManager,
ChecksumProvider checksumProvider)
{
_configuration = configuration;
_entryCarManager = entryCarManager;
_checksumProvider = checksumProvider;
}

public void Initialize()
public async Task Initialize()
{
await _checksumProvider.InitializeAsync();
CalculateTrackChecksums(_configuration.Server.Track, _configuration.Server.TrackConfig);
Log.Information("Initialized {Count} track checksums", TrackChecksums.Count);

Expand Down Expand Up @@ -64,27 +70,32 @@ private void CalculateTrackChecksums(string track, string trackConfig)
var dict = new Dictionary<string, byte[]>();
var surfaceFix = _configuration.CSPTrackOptions.MinimumCSPVersion.HasValue;

AddChecksum(dict, "system/data/surfaces.ini");
const string systemSurfaces = "system/data/surfaces.ini";
var systemSurfacesChecksum = _checksumProvider.GetChecksumsForOther(systemSurfaces);
AddChecksum(dict, systemSurfaces, defaultValue: systemSurfacesChecksum?.MD5);

var realTrackPath = $"content/tracks/{track}";
var virtualTrackPath = realTrackPath;
if (!Directory.Exists(realTrackPath))
{
realTrackPath = $"content/tracks/{_configuration.CSPTrackOptions.Track}";
}

var trackChecksums = _checksumProvider.GetChecksumsForTrack(_configuration.CSPTrackOptions.Track, trackConfig);
var defaultChecksums = surfaceFix ? trackChecksums?.CSP : trackChecksums?.Vanilla;

if (string.IsNullOrEmpty(trackConfig))
{
AddChecksumVirtualPath(dict, realTrackPath, virtualTrackPath, "data/surfaces.ini", surfaceFix);
AddChecksumVirtualPath(dict, realTrackPath, virtualTrackPath, "models.ini");
AddChecksumVirtualPath(dict, realTrackPath, virtualTrackPath, "data/surfaces.ini", surfaceFix, defaultChecksums);
AddChecksumVirtualPath(dict, realTrackPath, virtualTrackPath, "models.ini", defaultSums: defaultChecksums);
}
else
{
AddChecksumVirtualPath(dict, realTrackPath, virtualTrackPath, $"{trackConfig}/data/surfaces.ini", surfaceFix);
AddChecksumVirtualPath(dict, realTrackPath, virtualTrackPath, $"models_{trackConfig}.ini", surfaceFix);
AddChecksumVirtualPath(dict, realTrackPath, virtualTrackPath, $"{trackConfig}/data/surfaces.ini", surfaceFix, defaultChecksums);
AddChecksumVirtualPath(dict, realTrackPath, virtualTrackPath, $"models_{trackConfig}.ini", surfaceFix, defaultChecksums);
}

ChecksumDirectory(dict, realTrackPath, virtualTrackPath);
ChecksumDirectory(dict, realTrackPath, virtualTrackPath, defaultChecksums);

TrackChecksums = dict;
}
Expand All @@ -96,31 +107,32 @@ private void CalculateCarChecksums(IEnumerable<string> cars, bool allowAlternati

foreach (string car in cars)
{
var defaultSums = _checksumProvider.GetChecksumsForCar(car);

string carFolder = $"content/cars/{car}";

AddChecksum(additionalChecksums, $"{carFolder}/collider.kn5");
var colliderFile = $"{carFolder}/collider.kn5";
AddChecksum(additionalChecksums, colliderFile, defaultValue: defaultSums?.GetValueOrDefault(colliderFile)?.MD5);

var checksums = new Dictionary<string, byte[]>();
if (allowAlternatives && Directory.Exists(carFolder))
{
foreach (string file in Directory.EnumerateFiles(carFolder, "data*.acd"))
{
if (TryCreateChecksum(file, out byte[]? checksum))
{
checksums.Add(file, checksum);
Log.Debug("Added checksum for {Path}", file);
}
AddChecksum(checksums, file);
}
}
else
else if (allowAlternatives && defaultSums != null)
{
var acdPath = Path.Join(carFolder, "data.acd");
if (TryCreateChecksum(acdPath, out byte[]? checksum))
foreach (var file in defaultSums.Where(x => x.Key.Split('/').Last().StartsWith("data") || x.Key.EndsWith(".acd")))
{
checksums.Add(acdPath, checksum);
Log.Debug("Added checksum for {Path}", car);
AddDefaultChecksum(checksums, file.Key, file.Value.MD5);
}
}
else
{
var acdPath = $"{carFolder}/data.acd";
AddChecksum(checksums, acdPath, defaultValue: defaultSums?.GetValueOrDefault(acdPath)?.MD5);
}

carDataChecksums.Add(car, checksums);
}
Expand Down Expand Up @@ -157,26 +169,51 @@ private static bool TryCreateChecksum(string filePath, [MaybeNullWhen(false)] ou
return false;
}

private static void AddChecksumVirtualPath(Dictionary<string, byte[]> dict, string path, string virtualPath, string file, bool surfaceFix = false)
=> AddChecksum(dict, $"{path}/{file}", $"{virtualPath}/{file}", surfaceFix);
private static void AddChecksumVirtualPath(Dictionary<string, byte[]> dict, string path, string virtualPath, string file, bool surfaceFix = false, ChecksumFileList? defaultSums = null)
{
var physicalFile = $"{path}/{file}";
var defaultSum = defaultSums?.GetValueOrDefault(physicalFile);
AddChecksum(dict, physicalFile, $"{virtualPath}/{file}", surfaceFix, defaultSum?.MD5);
}

private static void AddChecksum(Dictionary<string, byte[]> dict, string filePath, string? name = null, bool surfaceFix = false)
private static void AddChecksum(Dictionary<string, byte[]> dict, string filePath, string? name = null, bool surfaceFix = false, string? defaultValue = null)
{
if (TryCreateChecksum(filePath, out byte[]? checksum, surfaceFix))
{
dict.Add(name ?? filePath, checksum);
Log.Debug("Added checksum for {Path}", name ?? filePath);
Log.Debug("Added checksum ({Checksum}) for {Path}", Convert.ToHexStringLower(checksum), name ?? filePath);
}
else if (defaultValue != null)
{
AddDefaultChecksum(dict, name ?? filePath, defaultValue);
}
}

private static void AddDefaultChecksum(Dictionary<string, byte[]> dict, string virtualPath, string defaultValue)
{
var defaultChecksum = Convert.FromHexString(defaultValue);
dict.Add(virtualPath, defaultChecksum);
Log.Debug("Added checksum ({Checksum}) for {Path} from Default List", defaultValue, virtualPath);
}

private static void ChecksumDirectory(Dictionary<string, byte[]> dict, string path, string? virtualPath = null)
private static void ChecksumDirectory(Dictionary<string, byte[]> dict, string path, string? virtualPath = null, ChecksumFileList? defaultSums = null)
{
virtualPath ??= path;

if (!Directory.Exists(path))
{
DefaultChecksumDirectory(dict, path, defaultSums);
return;
}

virtualPath ??= path;
var files = Directory.GetFiles(path);
if (files.Length == 0)
{
DefaultChecksumDirectory(dict, path, defaultSums);
return;
}

foreach (var file in Directory.GetFiles(path))
foreach (var file in files)
{
var name = Path.GetFileName(file);
var virtualName = $"{virtualPath}/{name.Replace("\\", "/")}";
Expand All @@ -187,4 +224,15 @@ private static void ChecksumDirectory(Dictionary<string, byte[]> dict, string pa
}
}
}

private static void DefaultChecksumDirectory(Dictionary<string, byte[]> dict, string path, ChecksumFileList? defaultSums = null)
{
if (defaultSums == null)
return;

foreach (var file in defaultSums.Where(x => x.Key.Split('/').Last() == "surfaces.ini" || x.Key.EndsWith(".kn5")))
{
AddDefaultChecksum(dict, file.Key, file.Value.MD5);
}
}
}
96 changes: 96 additions & 0 deletions AssettoServer/Server/Checksum/ChecksumProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Serilog;

namespace AssettoServer.Server.Checksum;

public class ChecksumProvider
{
private const string ChecksumsPath = "cfg/data_checksums.yaml";

private const string RemoteChecksumsUrl =
"https://raw.githubusercontent.com/compujuckel/AssettoServer/master/AssettoServer/Assets/data_checksums.yaml";

private readonly HttpClient _httpClient;
private ChecksumsFile? _checksums;

public ChecksumProvider()
{
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(10);
}

public async Task InitializeAsync()
{
if (!File.Exists(ChecksumsPath))
{
Log.Information("{Path} not found, downloading from GitHub...", ChecksumsPath);

try
{
var response = await _httpClient.GetAsync(RemoteChecksumsUrl);

if (!response.IsSuccessStatusCode)
{
Log.Error("Could not get checksums from {TrackParamsUrl} ({StatusCode})", RemoteChecksumsUrl, response.StatusCode);
}
else
{
await using var file = File.Create(ChecksumsPath);
var responseStream = await response.Content.ReadAsStreamAsync();
await responseStream.CopyToAsync(file);
}
}
catch (Exception ex)
{
Log.Error(ex, "Could not get checksums from {TrackParamsUrl}", RemoteChecksumsUrl);
}
}

_checksums = ChecksumsFile.FromFile(ChecksumsPath);
}

public TrackLayoutChecksum? GetChecksumsForTrack(string track, string? layout)
{
if (_checksums == null) return null;

if (_checksums.Tracks.TryGetValue(track, out var checksum))
{
if (string.IsNullOrEmpty(layout))
{
return checksum.Default;
}

return checksum.Layouts?.GetValueOrDefault(layout);
}

return null;
}

public ChecksumFileList? GetChecksumsForCar(string car)
{
if (_checksums == null) return null;

if (_checksums.Cars.TryGetValue(car, out var checksum))
{
return checksum;
}

return null;
}

public ChecksumItem? GetChecksumsForOther(string path)
{
if (_checksums == null) return null;

if (_checksums.Other.TryGetValue(path, out var checksum))
{
return checksum;
}

return null;
}
}
42 changes: 42 additions & 0 deletions AssettoServer/Server/Checksum/Checksums.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.IO;
using YamlDotNet.Serialization;

namespace AssettoServer.Server.Checksum;

public class ChecksumsFile
{
public Dictionary<string, TrackChecksum> Tracks { get; set; } = [];
public Dictionary<string, ChecksumFileList> Cars { get; set; } = [];
public ChecksumFileList Other { get; set; } = [];

public static ChecksumsFile FromFile(string path)
{
var deserializer = new DeserializerBuilder().Build();
using var checksumsFile = File.OpenText(path);
return deserializer.Deserialize<ChecksumsFile>(checksumsFile);
}
}

/// <summary>
/// Tracks use the real path without /csp
/// </summary>
public class TrackChecksum
{
public Dictionary<string, TrackLayoutChecksum> Layouts { get; set; } = [];
public TrackLayoutChecksum Default { get; set; } = new();
}

public class TrackLayoutChecksum
{
public ChecksumFileList CSP { get; set; } = new();
public ChecksumFileList Vanilla { get; set; } = new();
}

public class ChecksumFileList : Dictionary<string, ChecksumItem>;

public class ChecksumItem
{
public string MD5 { get; set; }
public string SHA256 { get; set; }
}
2 changes: 2 additions & 0 deletions AssettoServer/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using AssettoServer.Server.Admin;
using AssettoServer.Server.Ai;
using AssettoServer.Server.Blacklist;
using AssettoServer.Server.Checksum;
using AssettoServer.Server.CMContentProviders;
using AssettoServer.Server.Configuration;
using AssettoServer.Server.Configuration.Serialization;
Expand Down Expand Up @@ -103,6 +104,7 @@ public void ConfigureContainer(ContainerBuilder builder)
builder.RegisterType<EntryCarManager>().AsSelf().SingleInstance();
builder.RegisterType<IpApiGeoParamsProvider>().As<IGeoParamsProvider>();
builder.RegisterType<GeoParamsManager>().AsSelf().SingleInstance();
builder.RegisterType<ChecksumProvider>().AsSelf().SingleInstance();
builder.RegisterType<ChecksumManager>().AsSelf().SingleInstance();
builder.RegisterType<CSPServerExtraOptions>().AsSelf().SingleInstance();
builder.RegisterType<OpenSlotFilterChain>().AsSelf().SingleInstance();
Expand Down
Loading