173 lines
5.8 KiB
C#
173 lines
5.8 KiB
C#
#nullable enable
|
|
|
|
using Barotrauma.IO;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
public class Md5Hash
|
|
{
|
|
public static readonly Md5Hash Blank = new Md5Hash(new string('0', 32));
|
|
|
|
private static string RemoveWhitespace(string s)
|
|
{
|
|
StringBuilder sb = new StringBuilder(s.Length / 2); // Reserve half the size of the original string because
|
|
// that's probably close enough to the size of the result
|
|
for (int i = 0; i < s.Length; i++)
|
|
{
|
|
if (char.IsWhiteSpace(s[i])) { continue; }
|
|
sb.Append(s[i]);
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
//thanks to Jlobblet for this regex
|
|
private static readonly Regex stringHashRegex = new Regex(@"^[0-9a-fA-F]{7,32}$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
|
|
|
public readonly byte[] ByteRepresentation;
|
|
public readonly string StringRepresentation;
|
|
public readonly string ShortRepresentation;
|
|
|
|
private static void CalculateHash(byte[] bytes, out string stringRepresentation, out byte[] byteRepresentation)
|
|
{
|
|
using (MD5 md5 = MD5.Create())
|
|
{
|
|
byte[] byteHash = md5.ComputeHash(bytes);
|
|
|
|
byteRepresentation = byteHash;
|
|
stringRepresentation = ByteRepresentationToStringRepresentation(byteHash);
|
|
}
|
|
}
|
|
|
|
private static string ByteRepresentationToStringRepresentation(byte[] byteHash)
|
|
=> ToolBoxCore.ByteArrayToHexString(byteHash);
|
|
|
|
private static byte[] StringRepresentationToByteRepresentation(string strHash)
|
|
=> ToolBoxCore.HexStringToByteArray(strHash);
|
|
|
|
public static string GetShortHash(string fullHash)
|
|
{
|
|
return fullHash.Length < 7 ? fullHash : fullHash.Substring(0, 7);
|
|
}
|
|
|
|
private Md5Hash(string md5Hash)
|
|
{
|
|
StringRepresentation = md5Hash;
|
|
ByteRepresentation = StringRepresentationToByteRepresentation(StringRepresentation);
|
|
|
|
ShortRepresentation = GetShortHash(md5Hash);
|
|
}
|
|
|
|
private Md5Hash(byte[] bytes, bool calculate)
|
|
{
|
|
if (calculate)
|
|
{
|
|
CalculateHash(bytes, out StringRepresentation, out ByteRepresentation);
|
|
}
|
|
else
|
|
{
|
|
StringRepresentation = ByteRepresentationToStringRepresentation(bytes);
|
|
ByteRepresentation = bytes;
|
|
}
|
|
|
|
ShortRepresentation = GetShortHash(StringRepresentation);
|
|
}
|
|
|
|
public static Md5Hash StringAsHash(string hash)
|
|
{
|
|
if (!stringHashRegex.IsMatch(hash)) { throw new ArgumentException($"{hash} is not a valid hash"); }
|
|
return new Md5Hash(hash);
|
|
}
|
|
|
|
public static Md5Hash MergeHashes(IEnumerable<Md5Hash> hashes)
|
|
{
|
|
using IncrementalHash incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.MD5);
|
|
foreach (var hash in hashes)
|
|
{
|
|
incrementalHash.AppendData(hash.ByteRepresentation);
|
|
}
|
|
return BytesAsHash(incrementalHash.GetHashAndReset());
|
|
}
|
|
|
|
public static Md5Hash CalculateForBytes(byte[] bytes)
|
|
{
|
|
return new Md5Hash(bytes, calculate: true);
|
|
}
|
|
|
|
public static Md5Hash BytesAsHash(byte[] bytes)
|
|
{
|
|
return new Md5Hash(bytes, calculate: false);
|
|
}
|
|
|
|
[Flags]
|
|
public enum StringHashOptions
|
|
{
|
|
BytePerfect = 0,
|
|
IgnoreCase = 0x1,
|
|
IgnoreWhitespace = 0x2
|
|
}
|
|
|
|
public static Md5Hash CalculateForFile(string path, StringHashOptions options)
|
|
{
|
|
if (options.HasFlag(StringHashOptions.IgnoreWhitespace) || options.HasFlag(StringHashOptions.IgnoreCase))
|
|
{
|
|
string str = File.ReadAllText(path, Encoding.UTF8);
|
|
return CalculateForString(str, options);
|
|
}
|
|
else
|
|
{
|
|
byte[] bytes = File.ReadAllBytes(path);
|
|
return CalculateForBytes(bytes);
|
|
}
|
|
}
|
|
|
|
public static Md5Hash CalculateForString(string str, StringHashOptions options)
|
|
{
|
|
if (options.HasFlag(StringHashOptions.IgnoreCase))
|
|
{
|
|
str = str.ToLowerInvariant();
|
|
}
|
|
if (options.HasFlag(StringHashOptions.IgnoreWhitespace))
|
|
{
|
|
str = RemoveWhitespace(str);
|
|
}
|
|
byte[] bytes = Encoding.UTF8.GetBytes(str);
|
|
return CalculateForBytes(bytes);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return StringRepresentation;
|
|
}
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
if (obj is Md5Hash { StringRepresentation: { } otherStr })
|
|
{
|
|
string selfStr = otherStr.Length < StringRepresentation.Length
|
|
? StringRepresentation[..otherStr.Length]
|
|
: StringRepresentation;
|
|
otherStr = StringRepresentation.Length < otherStr.Length
|
|
? otherStr[..StringRepresentation.Length]
|
|
: otherStr;
|
|
return selfStr.Equals(otherStr, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return ShortRepresentation.GetHashCode(StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public static bool operator ==(Md5Hash? a, Md5Hash? b)
|
|
=> Equals(a, b);
|
|
|
|
public static bool operator !=(Md5Hash? a, Md5Hash? b) => !(a == b);
|
|
}
|
|
}
|