From a7790a271fd4bb2370ced2637535294ebee396e3 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 4 Mar 2024 21:23:45 -0500 Subject: [PATCH] Use SabreTools.Hashing --- CHANGELIST.md | 1 + MPF.Core/Hashing/Hasher.cs | 350 ------------------ MPF.Core/Hashing/OptimizedCRC.cs | 184 --------- MPF.Core/Hashing/ThreadLoadBuffer.cs | 81 ---- MPF.Core/MPF.Core.csproj | 1 + MPF.Core/Modules/PS3CFW/Parameters.cs | 5 +- .../Modules/UmdImageCreator/Parameters.cs | 4 +- 7 files changed, 7 insertions(+), 619 deletions(-) delete mode 100644 MPF.Core/Hashing/Hasher.cs delete mode 100644 MPF.Core/Hashing/OptimizedCRC.cs delete mode 100644 MPF.Core/Hashing/ThreadLoadBuffer.cs 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