diff --git a/CHANGELIST.md b/CHANGELIST.md
index 4c8e05e0..f27e9cae 100644
--- a/CHANGELIST.md
+++ b/CHANGELIST.md
@@ -10,6 +10,7 @@
- Hide size if value is 0 (Deterous)
- Fix title normalization (Deterous)
- Ensure no labels are empty
+- Use SabreTools.Hashing
### 3.1.2 (2024-02-27)
diff --git a/MPF.Core/Hashing/Hasher.cs b/MPF.Core/Hashing/Hasher.cs
deleted file mode 100644
index 5ec59503..00000000
--- a/MPF.Core/Hashing/Hasher.cs
+++ /dev/null
@@ -1,350 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-#if NET6_0_OR_GREATER
-using System.IO.Hashing;
-#endif
-using System.Linq;
-using System.Security.Cryptography;
-using System.Threading.Tasks;
-using MPF.Core.Data;
-
-namespace MPF.Core.Hashing
-{
- public sealed class Hasher : IDisposable
- {
- #region Properties
-
- ///
- /// Hash type associated with the current state
- ///
-#if NETFRAMEWORK || NETCOREAPP3_1
- public Hash HashType { get; private set; }
-#else
- public Hash HashType { get; init; }
-#endif
-
- ///
- /// Current hash in bytes
- ///
- public byte[]? CurrentHashBytes
- {
- get
- {
- return (_hasher) switch
- {
- HashAlgorithm ha => ha.Hash,
- NonCryptographicHashAlgorithm ncha => ncha.GetCurrentHash().Reverse().ToArray(),
- _ => null,
- };
- }
- }
-
- ///
- /// Current hash as a string
- ///
- public string? CurrentHashString => ByteArrayToString(CurrentHashBytes);
-
- #endregion
-
- #region Private Fields
-
- ///
- /// Internal hasher being used for processing
- ///
- /// May be either a HashAlgorithm or NonCryptographicHashAlgorithm
- private object? _hasher;
-
- #endregion
-
- #region Constructors
-
- ///
- /// Constructor
- ///
- /// Hash type to instantiate
- public Hasher(Hash hashType)
- {
- this.HashType = hashType;
- GetHasher();
- }
-
- ///
- /// Generate the correct hashing class based on the hash type
- ///
- private void GetHasher()
- {
- _hasher = HashType switch
- {
- Hash.CRC32 => new Crc32(),
-#if NET6_0_OR_GREATER
- Hash.CRC64 => new Crc64(),
-#endif
- Hash.MD5 => MD5.Create(),
- Hash.SHA1 => SHA1.Create(),
- Hash.SHA256 => SHA256.Create(),
- Hash.SHA384 => SHA384.Create(),
- Hash.SHA512 => SHA512.Create(),
-#if NET6_0_OR_GREATER
- Hash.XxHash32 => new XxHash32(),
- Hash.XxHash64 => new XxHash64(),
-#endif
- _ => null,
- };
- }
-
- ///
- public void Dispose()
- {
- if (_hasher is IDisposable disposable)
- disposable.Dispose();
- }
-
- #endregion
-
- #region Static Hashing
-
- ///
- /// Get hashes from an input file path
- ///
- /// Path to the input file
- /// True if hashing was successful, false otherwise
- public static bool GetFileHashes(string filename, out long size, out string? crc32, out string? md5, out string? sha1)
- {
- // Set all initial values
- crc32 = null; md5 = null; sha1 = null;
-
- // Get all file hashes
- var fileHashes = GetFileHashes(filename, out size);
- if (fileHashes == null)
- return false;
-
- // Assign the file hashes and return
- crc32 = fileHashes[Hash.CRC32];
- md5 = fileHashes[Hash.MD5];
- sha1 = fileHashes[Hash.SHA1];
- return true;
- }
-
- ///
- /// Get hashes from an input file path
- ///
- /// Path to the input file
- /// Dictionary containing hashes on success, null on error
- public static Dictionary? GetFileHashes(string filename, out long size)
- {
- // If the file doesn't exist, we can't do anything
- if (!File.Exists(filename))
- {
- size = -1;
- return null;
- }
-
- // Set the file size
- size = new FileInfo(filename).Length;
-
- // Open the input file
- var input = File.OpenRead(filename);
-
- // Return the hashes from the stream
- return GetStreamHashes(input);
- }
-
- ///
- /// Get hashes from an input Stream
- ///
- /// Stream to hash
- /// Dictionary containing hashes on success, null on error
- public static Dictionary? GetStreamHashes(Stream input)
- {
- // Create the output dictionary
- var hashDict = new Dictionary();
-
- try
- {
- // Get a list of hashers to run over the buffer
- var hashers = new Dictionary
- {
- { Hash.CRC32, new Hasher(Hash.CRC32) },
-#if NET6_0_OR_GREATER
- { Hash.CRC64, new Hasher(Hash.CRC64) },
-#endif
- { Hash.MD5, new Hasher(Hash.MD5) },
- { Hash.SHA1, new Hasher(Hash.SHA1) },
- { Hash.SHA256, new Hasher(Hash.SHA256) },
- { Hash.SHA384, new Hasher(Hash.SHA384) },
- { Hash.SHA512, new Hasher(Hash.SHA512) },
-#if NET6_0_OR_GREATER
- { Hash.XxHash32, new Hasher(Hash.XxHash32) },
- { Hash.XxHash64, new Hasher(Hash.XxHash64) },
-#endif
- };
-
- // Initialize the hashing helpers
- var loadBuffer = new ThreadLoadBuffer(input);
- int buffersize = 3 * 1024 * 1024;
- byte[] buffer0 = new byte[buffersize];
- byte[] buffer1 = new byte[buffersize];
-
- /*
- Please note that some of the following code is adapted from
- RomVault. This is a modified version of how RomVault does
- threaded hashing. As such, some of the terminology and code
- is the same, though variable names and comments may have
- been tweaked to better fit this code base.
- */
-
- // Pre load the first buffer
- long refsize = input.Length;
- int next = refsize > buffersize ? buffersize : (int)refsize;
- input.Read(buffer0, 0, next);
- int current = next;
- refsize -= next;
- bool bufferSelect = true;
-
- while (current > 0)
- {
- // Trigger the buffer load on the second buffer
- next = refsize > buffersize ? buffersize : (int)refsize;
- if (next > 0)
- loadBuffer.Trigger(bufferSelect ? buffer1 : buffer0, next);
-
- byte[] buffer = bufferSelect ? buffer0 : buffer1;
-
-#if NET20 || NET35
- // Run hashers sequentially on each chunk
- foreach (var h in hashers)
- {
- h.Value.Process(buffer, current);
- }
-#else
- // Run hashers in parallel on each chunk
- Parallel.ForEach(hashers, h => h.Value.Process(buffer, current));
-#endif
-
- // Wait for the load buffer worker, if needed
- if (next > 0)
- loadBuffer.Wait();
-
- // Setup for the next hashing step
- current = next;
- refsize -= next;
- bufferSelect = !bufferSelect;
- }
-
- // Finalize all hashing helpers
- loadBuffer.Finish();
-#if NET20 || NET35
- foreach (var h in hashers)
- {
- h.Value.Terminate();
- }
-#else
- Parallel.ForEach(hashers, h => h.Value.Terminate());
-#endif
-
- // Get the results
- hashDict[Hash.CRC32] = hashers[Hash.CRC32].CurrentHashString;
-#if NET6_0_OR_GREATER
- hashDict[Hash.CRC64] = hashers[Hash.CRC64].CurrentHashString;
-#endif
- hashDict[Hash.MD5] = hashers[Hash.MD5].CurrentHashString;
- hashDict[Hash.SHA1] = hashers[Hash.SHA1].CurrentHashString;
- hashDict[Hash.SHA256] = hashers[Hash.SHA256].CurrentHashString;
- hashDict[Hash.SHA384] = hashers[Hash.SHA384].CurrentHashString;
- hashDict[Hash.SHA512] = hashers[Hash.SHA512].CurrentHashString;
-#if NET6_0_OR_GREATER
- hashDict[Hash.XxHash32] = hashers[Hash.XxHash32].CurrentHashString;
- hashDict[Hash.XxHash64] = hashers[Hash.XxHash64].CurrentHashString;
-#endif
-
- // Dispose of the hashers
- loadBuffer.Dispose();
- foreach (var hasher in hashers.Values)
- {
- hasher.Dispose();
- }
-
- return hashDict;
- }
- catch (IOException)
- {
- return null;
- }
- finally
- {
- input.Dispose();
- }
- }
-
- #endregion
-
- #region Hashing
-
- ///
- /// Process a buffer of some length with the internal hash algorithm
- ///
- public void Process(byte[] buffer, int size)
- {
- switch (_hasher)
- {
- case HashAlgorithm ha:
- ha.TransformBlock(buffer, 0, size, null, 0);
- break;
- case NonCryptographicHashAlgorithm ncha:
-#if NET20 || NET35 || NET40
- byte[] bufferSpan = new byte[size];
- Array.Copy(buffer, bufferSpan, size);
-#else
- var bufferSpan = new ReadOnlySpan(buffer, 0, size);
-#endif
- ncha.Append(bufferSpan);
- break;
- }
- }
-
- ///
- /// Finalize the internal hash algorigthm
- ///
- /// NonCryptographicHashAlgorithm implementations do not need finalization
- public void Terminate()
- {
- byte[] emptyBuffer = [];
- switch (_hasher)
- {
- case HashAlgorithm ha:
- ha.TransformFinalBlock(emptyBuffer, 0, 0);
- break;
- }
- }
-
- #endregion
-
- #region Helpers
-
- ///
- /// Convert a byte array to a hex string
- ///
- /// Byte array to convert
- /// Hex string representing the byte array
- /// http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa
- private static string? ByteArrayToString(byte[]? bytes)
- {
- // If we get null in, we send null out
- if (bytes == null)
- return null;
-
- try
- {
- string hex = BitConverter.ToString(bytes);
- return hex.Replace("-", string.Empty).ToLowerInvariant();
- }
- catch
- {
- return null;
- }
- }
-
- #endregion
- }
-}
diff --git a/MPF.Core/Hashing/OptimizedCRC.cs b/MPF.Core/Hashing/OptimizedCRC.cs
deleted file mode 100644
index cffeed7d..00000000
--- a/MPF.Core/Hashing/OptimizedCRC.cs
+++ /dev/null
@@ -1,184 +0,0 @@
-#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0
-
-/*
-
- Copyright (c) 2012-2015 Eugene Larchenko (spct@mail.ru)
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
-
-*/
-
-using System;
-
-//namespace OptimizedCRC
-namespace MPF.Core.Hashing
-{
- ///
- /// Shell class to trick older versions into using CRC-32 properly
- ///
- internal abstract class NonCryptographicHashAlgorithm
- {
-#if NET20 || NET35 || NET40
- ///
- /// When overridden in a derived class, appends the contents of source to
- /// the data already processed for the current hash computation.
- ///
- /// The data to process.
- public abstract void Append(byte[] source);
-#else
- ///
- /// When overridden in a derived class, appends the contents of source to
- /// the data already processed for the current hash computation.
- ///
- /// The data to process.
- public abstract void Append(ReadOnlySpan source);
-#endif
-
- ///
- /// Gets the current computed hash value without modifying accumulated state.
- ///
- /// The hash value for the data already provided.
- public abstract byte[] GetCurrentHash();
- }
-
- ///
- /// Some changes have been made to this code to make it more similar to the System.IO.Hashing implementations
- ///
- internal class Crc32 : NonCryptographicHashAlgorithm, IDisposable
- {
- private const uint kCrcPoly = 0xEDB88320;
- private const uint kInitial = 0xFFFFFFFF;
- private const int CRC_NUM_TABLES = 8;
- private static readonly uint[] Table;
-
- static Crc32()
- {
- unchecked
- {
- Table = new uint[256 * CRC_NUM_TABLES];
- int i;
- for (i = 0; i < 256; i++)
- {
- uint r = (uint)i;
- for (int j = 0; j < 8; j++)
- {
- r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1));
- }
- Table[i] = r;
- }
- for (; i < 256 * CRC_NUM_TABLES; i++)
- {
- uint r = Table[i - 256];
- Table[i] = Table[r & 0xFF] ^ (r >> 8);
- }
- }
- }
-
- public uint UnsignedValue;
-
- public Crc32()
- {
- Init();
- }
-
- ///
- /// Reset CRC
- ///
- public void Init()
- {
- UnsignedValue = kInitial;
- }
-
- ///
- public override byte[] GetCurrentHash()
- {
- return BitConverter.GetBytes(~UnsignedValue);
- }
-
- ///
-#if NET20 || NET35 || NET40
- public override void Append(byte[] source)
- {
- Update(source, 0, source.Length);
- }
-#else
- public override void Append(ReadOnlySpan source)
- {
- byte[] sourceBytes = source.ToArray();
- Update(sourceBytes, 0, sourceBytes.Length);
- }
-#endif
-
- private void Update(byte[] data, int offset, int count)
- {
- _ = new ArraySegment(data, offset, count); // check arguments
- if (count == 0)
- {
- return;
- }
-
- var table = Table;
-
- uint crc = UnsignedValue;
-
- for (; (offset & 7) != 0 && count != 0; count--)
- {
- crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
- }
-
- if (count >= 8)
- {
- /*
- * Idea from 7-zip project sources (http://7-zip.org/sdk.html)
- */
-
- int end = (count - 8) & ~7;
- count -= end;
- end += offset;
-
- while (offset != end)
- {
- crc ^= (uint)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24));
- uint high = (uint)(data[offset + 4] + (data[offset + 5] << 8) + (data[offset + 6] << 16) + (data[offset + 7] << 24));
- offset += 8;
-
- crc = table[(byte)crc + 0x700]
- ^ table[(byte)(crc >>= 8) + 0x600]
- ^ table[(byte)(crc >>= 8) + 0x500]
- ^ table[/*(byte)*/(crc >> 8) + 0x400]
- ^ table[(byte)(high) + 0x300]
- ^ table[(byte)(high >>= 8) + 0x200]
- ^ table[(byte)(high >>= 8) + 0x100]
- ^ table[/*(byte)*/(high >> 8) + 0x000];
- }
- }
-
- while (count-- != 0)
- {
- crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
- }
-
- UnsignedValue = crc;
- }
-
- public void Dispose()
- {
- UnsignedValue = 0;
- }
- }
-}
-
-#endif
diff --git a/MPF.Core/Hashing/ThreadLoadBuffer.cs b/MPF.Core/Hashing/ThreadLoadBuffer.cs
deleted file mode 100644
index 02443564..00000000
--- a/MPF.Core/Hashing/ThreadLoadBuffer.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System;
-using System.IO;
-using System.Threading;
-
-//namespace Compress.ThreadReaders
-namespace MPF.Core.Hashing
-{
- public sealed class ThreadLoadBuffer : IDisposable
- {
- private readonly AutoResetEvent _waitEvent;
- private readonly AutoResetEvent _outEvent;
- private readonly Thread _tWorker;
-
- private byte[]? _buffer;
- private int _size;
- private readonly Stream _ds;
- private bool _finished;
- public bool errorState;
-
- public int SizeRead;
-
- public ThreadLoadBuffer(Stream ds)
- {
- _waitEvent = new AutoResetEvent(false);
- _outEvent = new AutoResetEvent(false);
- _finished = false;
- _ds = ds;
- errorState = false;
-
- _tWorker = new Thread(MainLoop);
- _tWorker.Start();
- }
-
- public void Dispose()
- {
- _waitEvent.Close();
- _outEvent.Close();
- }
-
- private void MainLoop()
- {
- while (true)
- {
- _waitEvent.WaitOne();
- if (_finished)
- {
- break;
- }
- try
- {
- if (_buffer != null)
- SizeRead = _ds.Read(_buffer, 0, _size);
- }
- catch (Exception)
- {
- errorState = true;
- }
- _outEvent.Set();
- }
- }
-
- public void Trigger(byte[] buffer, int size)
- {
- _buffer = buffer;
- _size = size;
- _waitEvent.Set();
- }
-
- public void Wait()
- {
- _outEvent.WaitOne();
- }
-
- public void Finish()
- {
- _finished = true;
- _waitEvent.Set();
- _tWorker.Join();
- }
- }
-}
\ No newline at end of file
diff --git a/MPF.Core/MPF.Core.csproj b/MPF.Core/MPF.Core.csproj
index b76aa5af..14657540 100644
--- a/MPF.Core/MPF.Core.csproj
+++ b/MPF.Core/MPF.Core.csproj
@@ -56,6 +56,7 @@
+
diff --git a/MPF.Core/Modules/PS3CFW/Parameters.cs b/MPF.Core/Modules/PS3CFW/Parameters.cs
index 928261b4..b063387e 100644
--- a/MPF.Core/Modules/PS3CFW/Parameters.cs
+++ b/MPF.Core/Modules/PS3CFW/Parameters.cs
@@ -5,6 +5,7 @@ using System.Text;
using System.Text.RegularExpressions;
using MPF.Core.Converters;
using MPF.Core.Data;
+using SabreTools.Hashing;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
@@ -171,7 +172,7 @@ namespace MPF.Core.Modules.PS3CFW
try
{
- if (Hashing.Hasher.GetFileHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
+ if (HashTool.GetStandardHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
{
return new Datafile
{
@@ -254,7 +255,7 @@ namespace MPF.Core.Modules.PS3CFW
try
{
- if (Hashing.Hasher.GetFileHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
+ if (HashTool.GetStandardHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
return $"";
return null;
}
diff --git a/MPF.Core/Modules/UmdImageCreator/Parameters.cs b/MPF.Core/Modules/UmdImageCreator/Parameters.cs
index e4e25d41..43cdb268 100644
--- a/MPF.Core/Modules/UmdImageCreator/Parameters.cs
+++ b/MPF.Core/Modules/UmdImageCreator/Parameters.cs
@@ -4,7 +4,7 @@ using System.IO;
using System.Linq;
using MPF.Core.Converters;
using MPF.Core.Data;
-using MPF.Core.Hashing;
+using SabreTools.Hashing;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
@@ -78,7 +78,7 @@ namespace MPF.Core.Modules.UmdImageCreator
case MediaType.UMD:
info.Extras!.PVD = GetPVD(basePath + "_mainInfo.txt") ?? string.Empty;
- if (Hasher.GetFileHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
+ if (HashTool.GetStandardHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
{
// Get the Datafile information
var datafile = new Datafile