4b1cad1c90
Speeds up SPT server initialization and reduces allocations when a lot of bundle mods are active
122 lines
4.0 KiB
C#
122 lines
4.0 KiB
C#
using System.Buffers;
|
|
using System.IO.Hashing;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using SPTarkov.DI.Annotations;
|
|
|
|
namespace SPTarkov.Server.Core.Utils;
|
|
|
|
[Injectable(InjectionType.Singleton)]
|
|
public class HashUtil(RandomUtil _randomUtil)
|
|
{
|
|
public uint GenerateCrc32ForData(string data)
|
|
{
|
|
return Crc32.HashToUInt32(new ArraySegment<byte>(Encoding.UTF8.GetBytes(data)));
|
|
}
|
|
|
|
public uint GenerateCrc32ForData(ReadOnlySpan<byte> data)
|
|
{
|
|
return Crc32.HashToUInt32(data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a CRC32 hash for a file, reading in chunks using a pooled buffer to reduce allocations.
|
|
/// </summary>
|
|
/// <param name="filePath">The path to the file</param>
|
|
/// <returns>The CRC32 hash as a uint</returns>
|
|
public async Task<uint> GenerateCrc32ForFileAsync(string filePath)
|
|
{
|
|
var crc = new Crc32();
|
|
await using var stream = File.OpenRead(filePath);
|
|
|
|
// Rent from pool to avoid repeated allocations for each file read
|
|
var buffer = ArrayPool<byte>.Shared.Rent(81920);
|
|
try
|
|
{
|
|
int bytesRead;
|
|
while ((bytesRead = await stream.ReadAsync(buffer)) > 0)
|
|
{
|
|
crc.Append(buffer.AsSpan(0, bytesRead));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<byte>.Shared.Return(buffer);
|
|
}
|
|
|
|
return crc.GetCurrentHashAsUInt32();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a hash for the data parameter
|
|
/// </summary>
|
|
/// <param name="algorithm">algorithm to use to hash</param>
|
|
/// <param name="data">data to be hashed</param>
|
|
/// <returns>hash value</returns>
|
|
/// <exception cref="NotImplementedException">thrown if the provided algorithm is not implemented</exception>
|
|
/// >
|
|
public string GenerateHashForData(HashingAlgorithm algorithm, string data)
|
|
{
|
|
switch (algorithm)
|
|
{
|
|
case HashingAlgorithm.MD5:
|
|
var md5HashData = MD5.HashData(Encoding.UTF8.GetBytes(data));
|
|
return Convert.ToHexString(md5HashData).Replace("-", string.Empty);
|
|
|
|
case HashingAlgorithm.SHA1:
|
|
var sha1HashData = SHA1.HashData(Encoding.UTF8.GetBytes(data));
|
|
return Convert.ToHexString(sha1HashData).Replace("-", string.Empty);
|
|
}
|
|
|
|
throw new NotImplementedException($"Provided hash algorithm: {algorithm} is not supported.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a hash for the data parameter asynchronously
|
|
/// </summary>
|
|
/// <param name="algorithm">algorithm to use to hash</param>
|
|
/// <param name="data">data to be hashed</param>
|
|
/// <returns>A task which contains the hash value</returns>
|
|
/// <exception cref="NotImplementedException">thrown if the provided algorithm is not implemented</exception>
|
|
/// >
|
|
public async Task<string> GenerateHashForDataAsync(HashingAlgorithm algorithm, string data)
|
|
{
|
|
switch (algorithm)
|
|
{
|
|
case HashingAlgorithm.MD5:
|
|
{
|
|
await using var ms = new MemoryStream(Encoding.UTF8.GetBytes(data));
|
|
var md5HashData = await MD5.HashDataAsync(ms);
|
|
return Convert.ToHexString(md5HashData).Replace("-", string.Empty);
|
|
}
|
|
|
|
case HashingAlgorithm.SHA1:
|
|
{
|
|
await using var ms = new MemoryStream(Encoding.UTF8.GetBytes(data));
|
|
var sha1HashData = await SHA1.HashDataAsync(ms);
|
|
return Convert.ToHexString(sha1HashData).Replace("-", string.Empty);
|
|
}
|
|
}
|
|
|
|
throw new NotImplementedException($"Provided hash algorithm: {algorithm} is not supported.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates an account ID for a profile
|
|
/// </summary>
|
|
/// <returns>Generated account ID</returns>
|
|
public int GenerateAccountId()
|
|
{
|
|
const int min = 1000000;
|
|
const int max = 1999999;
|
|
|
|
return _randomUtil.Random.Next(min, max + 1);
|
|
}
|
|
}
|
|
|
|
public enum HashingAlgorithm
|
|
{
|
|
MD5,
|
|
SHA1,
|
|
}
|