From 7ef900a252316d414bef4d5cb547eb860b55768b Mon Sep 17 00:00:00 2001 From: Atvaark Date: Fri, 2 Mar 2018 20:34:32 +0100 Subject: [PATCH] Fix repacking large QAR files - Rewrote QarEntry to use streams - Still uses a temporary file for the 1/2 of the steps --- GzsTool.Core/Common/FileSystemDirectory.cs | 14 +- GzsTool.Core/Common/Interfaces/IDirectory.cs | 1 + .../Common/VirtualFileSystemDirectory.cs | 13 +- GzsTool.Core/Crypto/Cryptography.cs | 141 ------------------ GzsTool.Core/Crypto/Decrypt1Stream.cs | 29 ++-- GzsTool.Core/Crypto/Decrypt2Stream.cs | 20 ++- GzsTool.Core/Crypto/Md5Stream.cs | 88 +++++++++++ GzsTool.Core/Crypto/StreamMode.cs | 8 + GzsTool.Core/GzsTool.Core.csproj | 2 + GzsTool.Core/Qar/QarEntry.cs | 131 +++++++++------- GzsTool.Core/Utility/Compression.cs | 13 +- GzsTool.Core/Utility/Hashing.cs | 5 + 12 files changed, 233 insertions(+), 232 deletions(-) create mode 100644 GzsTool.Core/Crypto/Md5Stream.cs create mode 100644 GzsTool.Core/Crypto/StreamMode.cs diff --git a/GzsTool.Core/Common/FileSystemDirectory.cs b/GzsTool.Core/Common/FileSystemDirectory.cs index 5fd0b53..9a613b2 100644 --- a/GzsTool.Core/Common/FileSystemDirectory.cs +++ b/GzsTool.Core/Common/FileSystemDirectory.cs @@ -19,15 +19,19 @@ public FileSystemDirectory(string baseDirectoryPath) public byte[] ReadFile(string filePath) { - string inputFilePath = Path.Combine(_baseDirectoryPath, filePath); - using (FileStream input = new FileStream(inputFilePath, FileMode.Open)) + using (var stream = ReadFileStream(filePath)) { - byte[] data = new byte[input.Length]; - input.Read(data, 0, data.Length); - return data; + return stream.ToArray(); } } + public Stream ReadFileStream(string filePath) + { + string inputFilePath = Path.Combine(_baseDirectoryPath, filePath); + FileStream stream = new FileStream(inputFilePath, FileMode.Open); + return stream; + } + public void WriteFile(string filePath, Func fileContentStream) { string outputFilePath = Path.Combine(_baseDirectoryPath, filePath); diff --git a/GzsTool.Core/Common/Interfaces/IDirectory.cs b/GzsTool.Core/Common/Interfaces/IDirectory.cs index f061a21..c5f66b1 100644 --- a/GzsTool.Core/Common/Interfaces/IDirectory.cs +++ b/GzsTool.Core/Common/Interfaces/IDirectory.cs @@ -8,6 +8,7 @@ public interface IDirectory : IFileSystemEntry { IEnumerable Entries { get; } byte[] ReadFile(string filePath); + Stream ReadFileStream(string filePath); void WriteFile(string filePath, Func fileContentStream); } } diff --git a/GzsTool.Core/Common/VirtualFileSystemDirectory.cs b/GzsTool.Core/Common/VirtualFileSystemDirectory.cs index 810807c..551cce3 100644 --- a/GzsTool.Core/Common/VirtualFileSystemDirectory.cs +++ b/GzsTool.Core/Common/VirtualFileSystemDirectory.cs @@ -26,16 +26,25 @@ public string Name } public byte[] ReadFile(string filePath) + { + using (var stream = ReadFileStream(filePath)) + { + byte[] content = stream.ToArray(); + return content; + } + } + + public Stream ReadFileStream(string filePath) { int index = filePath.IndexOf(DirectorySeparator, StringComparison.Ordinal); if (index == -1) { - return _files.Single(f => f.Name == filePath).Content; + return _files.Single(f => f.Name == filePath).ContentStream; } string subDirectory = filePath.Substring(0, index); string subDirectoryFilePath = filePath.Substring(index + DirectorySeparator.Length, filePath.Length - index - DirectorySeparator.Length); - return _directories.Single(d => d.Name == subDirectory).ReadFile(subDirectoryFilePath); + return _directories.Single(d => d.Name == subDirectory).ReadFileStream(subDirectoryFilePath); } public void WriteFile(string filePath, Func fileContentStream) diff --git a/GzsTool.Core/Crypto/Cryptography.cs b/GzsTool.Core/Crypto/Cryptography.cs index a0cab54..b0a9bd8 100644 --- a/GzsTool.Core/Crypto/Cryptography.cs +++ b/GzsTool.Core/Crypto/Cryptography.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace GzsTool.Core.Crypto { internal class Cryptography @@ -23,143 +20,5 @@ public static int GetHeaderSize(uint encryption) return headerSize; } - - public static void Decrypt1(byte[] sectionData, uint hashLow, uint version, byte[] dataHash) - { - // TODO: Use a ulong array instead. - uint[] decryptionTable = - { - 0xBB8ADEDB, - 0x65229958, - 0x08453206, - 0x88121302, - 0x4C344955, - 0x2C02F10C, - 0x4887F823, - 0xF3818583, - //0x40C90FDB, - //0x3FC90FDB, - //0x3F490FDB, - //0x3EA2F983, - //0x3C8EFA35, - //0x42652EE0, - //0x40C90FDB, - //0x3FC90FDB, - //0x3F490FDB, - //0x3EA2F983, - //0x3C8EFA35, - //0x42652EE0 - }; - - int blocks = sectionData.Length / sizeof(ulong); - if (version != 2) - { - for (int i = 0; i < blocks; i++) - { - int offset1 = i * sizeof(ulong); - int offset2 = i * sizeof(ulong) + sizeof(uint); - int index = (int)(2 * ((hashLow + offset1 / 11) % 4)); - uint u1 = BitConverter.ToUInt32(sectionData, offset1) ^ decryptionTable[index]; - uint u2 = BitConverter.ToUInt32(sectionData, offset2) ^ decryptionTable[index + 1]; - Buffer.BlockCopy(BitConverter.GetBytes(u1), 0, sectionData, offset1, sizeof(uint)); - Buffer.BlockCopy(BitConverter.GetBytes(u2), 0, sectionData, offset2, sizeof(uint)); - } - - int remaining = sectionData.Length % sizeof(ulong); - for (int i = 0; i < remaining; i++) - { - int offset = blocks * sizeof(long) + i * sizeof(byte); - int index = (int)(2 * ((hashLow + (offset - (offset % sizeof(long))) / 11) % 4)); - int decryptionIndex = offset % sizeof(long); - uint xorMask = decryptionIndex < 4 ? decryptionTable[index] : decryptionTable[index + 1]; - byte xorMaskByte = (byte)((xorMask >> (8 * decryptionIndex)) & 0xff); - byte b1 = (byte)(sectionData[offset] ^ xorMaskByte); - sectionData[offset] = b1; - } - } - else - { - ulong seed = BitConverter.ToUInt64(dataHash, (int)(hashLow % 2) * 8); - uint seedLow = (uint)seed & 0xFFFFFFFF; - uint seedHigh = (uint)(seed >> 32); - for (int i = 0; i < blocks; i++) - { - int offset1 = i * sizeof(ulong); - int offset2 = i * sizeof(ulong) + sizeof(uint); - int index = 2 * (int)((hashLow + seed + (ulong)(offset1 / 11)) % 4); - uint u1 = BitConverter.ToUInt32(sectionData, offset1) ^ decryptionTable[index] ^ seedLow; - uint u2 = BitConverter.ToUInt32(sectionData, offset2) ^ decryptionTable[index + 1] ^ seedHigh; - Buffer.BlockCopy(BitConverter.GetBytes(u1), 0, sectionData, offset1, sizeof(uint)); - Buffer.BlockCopy(BitConverter.GetBytes(u2), 0, sectionData, offset2, sizeof(uint)); - } - - int remaining = sectionData.Length % sizeof(ulong); - for (int i = 0; i < remaining; i++) - { - int offset = blocks * sizeof(long) + i * sizeof(byte); - int offsetBlock = offset - (offset % sizeof(long)); - int index = 2 * (int)((hashLow + seed + (ulong)(offsetBlock / 11)) % 4); - int decryptionIndex = offset % sizeof(long); - uint xorMask = decryptionIndex < 4 ? decryptionTable[index] : decryptionTable[index + 1]; - byte xorMaskByte = (byte)((xorMask >> (8 * (decryptionIndex % 4))) & 0xff); - uint seedMask = decryptionIndex < 4 ? seedLow : seedHigh; - byte seedByte = (byte)((seedMask >> (8 * (decryptionIndex % 4))) & 0xff); - sectionData[offset] = (byte)(sectionData[offset] ^ (byte)(xorMaskByte ^ seedByte)); - } - } - } - - public static unsafe void Decrypt2(byte[] input, uint key) - { - int size = input.Length; - uint currentKey = key | ((key ^ 25974) << 16); - - byte[] output = input.ToArray(); - fixed (byte* pDestBase = output, pSrcBase = input) - { - uint* pDest = (uint*) pDestBase; - uint* pSrc = (uint*) pSrcBase; - uint i = 278 * key; - for (; size >= 64; size -= 64) - { - uint j = 16; - do - { - *pDest = currentKey ^ *pSrc; - currentKey = i + 48828125 * currentKey; - - --j; - pDest++; - pSrc++; - } while (j > 0); - } - - for (; size >= 16; pSrc += 4) - { - *pDest = currentKey ^ *pSrc; - uint v7 = i + 48828125 * currentKey; - *(pDest + 1) = v7 ^ *(pSrc + 1); - uint v8 = i + 48828125 * v7; - *(pDest + 2) = v8 ^ *(pSrc + 2); - uint v9 = i + 48828125 * v8; - *(pDest + 3) = v9 ^ *(pSrc + 3); - - currentKey = i + 48828125 * v9; - size -= 16; - pDest += 4; - } - - for (; size >= 4; pSrc++) - { - *pDest = currentKey ^ *pSrc; - - currentKey = i + 48828125 * currentKey; - size -= 4; - pDest++; - } - } - - Buffer.BlockCopy(output, 0, input, 0, input.Length); - } } } \ No newline at end of file diff --git a/GzsTool.Core/Crypto/Decrypt1Stream.cs b/GzsTool.Core/Crypto/Decrypt1Stream.cs index 1daf440..1513833 100644 --- a/GzsTool.Core/Crypto/Decrypt1Stream.cs +++ b/GzsTool.Core/Crypto/Decrypt1Stream.cs @@ -6,30 +6,30 @@ namespace GzsTool.Core.Crypto public class Decrypt1Stream : Stream { private readonly Stream _input; - - private int _position; - + private readonly int _size; private readonly int _version; private readonly uint _hashLow; - + + private readonly StreamMode _streamMode; + private readonly ulong _seed; private readonly uint _seedLow; private readonly uint _seedHigh; - public Decrypt1Stream(Stream input, int version, int size, byte[] dataHash, uint hashLow) + private int _position; + + public Decrypt1Stream(Stream input, int version, int size, byte[] dataHash, uint hashLow, StreamMode streamMode) { _input = input; - _version = version; - _size = size; - _hashLow = hashLow; + _streamMode = streamMode; _seed = BitConverter.ToUInt64(dataHash, (int)(hashLow % 2) * 8); _seedLow = (uint)_seed & 0xFFFFFFFF; _seedHigh = (uint)(_seed >> 32); @@ -74,14 +74,21 @@ public override int Read(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count) { - throw new NotSupportedException(); + if (offset != 0) + { + throw new NotSupportedException(); + } + + Decrypt1(buffer); + _input.Write(buffer, offset, count); + _position += count; } public override bool CanRead { get { - return true; + return _streamMode == StreamMode.Read; } } @@ -97,7 +104,7 @@ public override bool CanWrite { get { - return false; + return _streamMode == StreamMode.Write; } } diff --git a/GzsTool.Core/Crypto/Decrypt2Stream.cs b/GzsTool.Core/Crypto/Decrypt2Stream.cs index addb344..06a9b70 100644 --- a/GzsTool.Core/Crypto/Decrypt2Stream.cs +++ b/GzsTool.Core/Crypto/Decrypt2Stream.cs @@ -9,17 +9,20 @@ public class Decrypt2Stream : Stream private readonly Stream _input; private readonly int _size; - + private readonly uint _key; + private readonly StreamMode _streamMode; + private int _position; private uint _blockKey; - public Decrypt2Stream(Stream input, int size, uint key) + public Decrypt2Stream(Stream input, int size, uint key, StreamMode streamMode) { _input = input; _size = size; + _streamMode = streamMode; _key = 278 * key; _blockKey = key | ((key ^ 25974) << 16); } @@ -63,14 +66,21 @@ public override int Read(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count) { - throw new NotSupportedException(); + if (offset != 0) + { + throw new NotSupportedException(); + } + + Decrypt2(buffer, count); + _input.Write(buffer, offset, count); + _position += count; } public override bool CanRead { get { - return true; + return _streamMode == StreamMode.Read; } } @@ -86,7 +96,7 @@ public override bool CanWrite { get { - return false; + return _streamMode == StreamMode.Write; } } diff --git a/GzsTool.Core/Crypto/Md5Stream.cs b/GzsTool.Core/Crypto/Md5Stream.cs new file mode 100644 index 0000000..ea3c6e2 --- /dev/null +++ b/GzsTool.Core/Crypto/Md5Stream.cs @@ -0,0 +1,88 @@ +using System.IO; +using System.Security.Cryptography; + +namespace GzsTool.Core.Crypto +{ + public class Md5Stream : Stream + { + private readonly CryptoStream _stream; + + private readonly MD5 _md5; + + public Md5Stream(Stream stream) + { + _md5 = MD5.Create(); + _stream = new CryptoStream( + stream, + _md5, + CryptoStreamMode.Write); + } + + public override void Flush() + { + _stream.Flush(); + _stream.FlushFinalBlock(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _stream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _stream.SetLength(value); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _stream.Read(buffer, offset, count); + } + + public override void Write(byte[] buffer, int offset, int count) + { + _stream.Write(buffer, offset, count); + } + + public override bool CanRead + { + get { return _stream.CanRead; } + } + + public override bool CanSeek + { + get { return _stream.CanSeek; } + } + + public override bool CanWrite + { + get { return _stream.CanWrite; } + } + + public override long Length + { + get { return _stream.Length; } + } + + public override long Position + { + get { return _stream.Position; } + set { _stream.Position = value; } + } + + public byte[] Hash + { + get { return _md5.Hash; } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _md5.Dispose(); + } + + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/GzsTool.Core/Crypto/StreamMode.cs b/GzsTool.Core/Crypto/StreamMode.cs new file mode 100644 index 0000000..a7b56f9 --- /dev/null +++ b/GzsTool.Core/Crypto/StreamMode.cs @@ -0,0 +1,8 @@ +namespace GzsTool.Core.Crypto +{ + public enum StreamMode + { + Read, + Write + } +} \ No newline at end of file diff --git a/GzsTool.Core/GzsTool.Core.csproj b/GzsTool.Core/GzsTool.Core.csproj index 94d5a82..41d712d 100644 --- a/GzsTool.Core/GzsTool.Core.csproj +++ b/GzsTool.Core/GzsTool.Core.csproj @@ -60,6 +60,8 @@ + + diff --git a/GzsTool.Core/Qar/QarEntry.cs b/GzsTool.Core/Qar/QarEntry.cs index d92df02..69ce8bd 100644 --- a/GzsTool.Core/Qar/QarEntry.cs +++ b/GzsTool.Core/Qar/QarEntry.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Linq; using System.Text; using System.Xml.Serialization; using GzsTool.Core.Common; @@ -137,7 +136,7 @@ public void Read(BinaryReader reader, uint version) DataOffset = reader.BaseStream.Position; byte[] header = new byte[8]; - using (Stream headerStream = new Decrypt1Stream(reader.BaseStream, (int)Version, header.Length, DataHash, hashLow: (uint)(Hash & 0xFFFFFFFF))) + using (Stream headerStream = new Decrypt1Stream(reader.BaseStream, (int)Version, header.Length, DataHash, hashLow: (uint)(Hash & 0xFFFFFFFF), streamMode: StreamMode.Read)) { headerStream.Read(header, 0, header.Length); Encryption = BitConverter.ToUInt32(header, 0); @@ -178,88 +177,104 @@ private Stream ReadData(Stream input) { input.Position = DataOffset; int dataSize = (int)CompressedSize; - Stream stream = new Decrypt1Stream(input, (int)Version, dataSize, DataHash, hashLow: (uint)(Hash & 0xFFFFFFFF)); - + Stream stream = new Decrypt1Stream(input, (int)Version, dataSize, DataHash, hashLow: (uint)(Hash & 0xFFFFFFFF), streamMode: StreamMode.Read); + if (Encryption == Cryptography.Magic1 || Encryption == Cryptography.Magic2) { int headerSize = Cryptography.GetHeaderSize(Encryption); stream.Read(new byte[headerSize], 0, headerSize); dataSize -= headerSize; - stream = new Decrypt2Stream(stream, dataSize, Key); + stream = new Decrypt2Stream(stream, dataSize, Key, StreamMode.Read); } - + if (Compressed) { stream = Compression.UncompressStream(stream); } - + return stream; } public void Write(Stream output, IDirectory inputDirectory) { - const ulong xorMask1Long = 0x4144104341441043; - const uint xorMask1 = 0x41441043; - const uint xorMask2 = 0x11C22050; - const uint xorMask3 = 0xD05608C3; - const uint xorMask4 = 0x532C7319; - - byte[] data = inputDirectory.ReadFile(Hashing.NormalizeFilePath(FilePath)); - uint uncompressedSize = (uint) data.Length; - uint compressedSize; - if (Compressed) - { - data = Compression.Compress(data); - compressedSize = (uint) data.Length; - } - else + string tempFileName = Path.GetTempFileName(); + using (Stream inputStream = inputDirectory.ReadFileStream(Hashing.NormalizeFilePath(FilePath))) + using (FileStream tempOutputFileStream = File.Create(tempFileName, 4096, FileOptions.DeleteOnClose)) + using (Md5Stream md5OutputStream = new Md5Stream(tempOutputFileStream)) { - compressedSize = uncompressedSize; - } + uint uncompressedSize = (uint)inputStream.Length; - if (Encryption != 0) - { - Cryptography.Decrypt2(data, Key); + Stream outputDataStream = md5OutputStream; + if (Compressed) + { + outputDataStream = Compression.CompressStream(outputDataStream); + } - int headerSize = Cryptography.GetHeaderSize(Encryption); - if (headerSize >= 8) + if (Encryption != 0) { - byte[] header = new byte[headerSize]; - Buffer.BlockCopy(BitConverter.GetBytes(Encryption), 0, header, 0, sizeof(uint)); - Buffer.BlockCopy(BitConverter.GetBytes(Key), 0, header, 4, sizeof(uint)); - if (headerSize == 16) + int encryptionHeaderSize = Cryptography.GetHeaderSize(Encryption); + if (encryptionHeaderSize >= 8) { - Buffer.BlockCopy(BitConverter.GetBytes(uncompressedSize), 0, header, 8, sizeof(uint)); - Buffer.BlockCopy(BitConverter.GetBytes(uncompressedSize), 0, header, 12, sizeof(uint)); + byte[] header = new byte[encryptionHeaderSize]; + Buffer.BlockCopy(BitConverter.GetBytes(Encryption), 0, header, 0, sizeof(uint)); + Buffer.BlockCopy(BitConverter.GetBytes(Key), 0, header, 4, sizeof(uint)); + if (encryptionHeaderSize == 16) + { + Buffer.BlockCopy(BitConverter.GetBytes(uncompressedSize), 0, header, 8, sizeof(uint)); + Buffer.BlockCopy(BitConverter.GetBytes(uncompressedSize), 0, header, 12, sizeof(uint)); + } + + using (var headerStream = new MemoryStream(header)) + { + headerStream.CopyTo(outputDataStream); + } } - byte[] encryptedData = new byte[data.Length + headerSize]; - Buffer.BlockCopy(header, 0, encryptedData, 0, header.Length); - Buffer.BlockCopy(data, 0, encryptedData, headerSize, data.Length); - data = encryptedData; - compressedSize = (uint) encryptedData.Length; - uncompressedSize = Compressed ? uncompressedSize : (uint) encryptedData.Length; + outputDataStream = new Decrypt2Stream(outputDataStream, (int)uncompressedSize, Key, StreamMode.Write); } - } - - // TODO: HACK to support repacked files - if (DataHash == null) - { - DataHash = Hashing.Md5Hash(data); - } - Cryptography.Decrypt1(data, hashLow: (uint) (Hash & 0xFFFFFFFF), version: Version, dataHash: DataHash); - BinaryWriter writer = new BinaryWriter(output, Encoding.Default, true); - writer.Write(Hash ^ xorMask1Long); - writer.Write((Version != 2 ? uncompressedSize : compressedSize) ^ xorMask2); - writer.Write((Version != 2 ? compressedSize : uncompressedSize) ^ xorMask3); + inputStream.CopyTo(outputDataStream); + outputDataStream.Close(); + + long headerPosition = output.Position; + const int entryHeaderSize = 32; + long dataStartPosition = headerPosition + entryHeaderSize; + output.Position = dataStartPosition; - writer.Write(BitConverter.ToUInt32(DataHash, 0) ^ xorMask4); - writer.Write(BitConverter.ToUInt32(DataHash, 4) ^ xorMask1); - writer.Write(BitConverter.ToUInt32(DataHash, 8) ^ xorMask1); - writer.Write(BitConverter.ToUInt32(DataHash, 12) ^ xorMask2); + // TODO: HACK to support repacked files + if (DataHash == null) + { + md5OutputStream.Flush(); + DataHash = md5OutputStream.Hash; + } - writer.Write(data); + uint compressedSize = (uint)tempOutputFileStream.Length; + uncompressedSize = Compressed ? uncompressedSize : compressedSize; + using (var decrypt1Stream = new Decrypt1Stream(output, (int)Version, (int)compressedSize, DataHash, hashLow: (uint)(Hash & 0xFFFFFFFF), streamMode: StreamMode.Write)) + { + tempOutputFileStream.Position = 0; + tempOutputFileStream.CopyTo(decrypt1Stream); // TODO: Encrypt directly in output file if possible + } + + long dataEndPosition = output.Position; + output.Position = headerPosition; + + const ulong xorMask1Long = 0x4144104341441043; + const uint xorMask1 = 0x41441043; + const uint xorMask2 = 0x11C22050; + const uint xorMask3 = 0xD05608C3; + const uint xorMask4 = 0x532C7319; + BinaryWriter writer = new BinaryWriter(output, Encoding.ASCII, true); + writer.Write(Hash ^ xorMask1Long); + writer.Write((Version != 2 ? uncompressedSize : compressedSize) ^ xorMask2); + writer.Write((Version != 2 ? compressedSize : uncompressedSize) ^ xorMask3); + writer.Write(BitConverter.ToUInt32(DataHash, 0) ^ xorMask4); + writer.Write(BitConverter.ToUInt32(DataHash, 4) ^ xorMask1); + writer.Write(BitConverter.ToUInt32(DataHash, 8) ^ xorMask1); + writer.Write(BitConverter.ToUInt32(DataHash, 12) ^ xorMask2); + + output.Position = dataEndPosition; + } } } } \ No newline at end of file diff --git a/GzsTool.Core/Utility/Compression.cs b/GzsTool.Core/Utility/Compression.cs index 683f8af..3010916 100644 --- a/GzsTool.Core/Utility/Compression.cs +++ b/GzsTool.Core/Utility/Compression.cs @@ -9,17 +9,10 @@ internal static Stream UncompressStream(Stream stream) { return new ZlibStream(stream, CompressionMode.Decompress, false); } - - internal static byte[] Compress(byte[] buffer) + + internal static Stream CompressStream(Stream stream) { - using (var output = new MemoryStream()) - { - using (Stream compressor = new ZlibStream(output, CompressionMode.Compress, CompressionLevel.BestCompression)) - { - compressor.Write(buffer, 0, buffer.Length); - } - return output.ToArray(); - } + return new ZlibStream(stream, CompressionMode.Compress, CompressionLevel.BestCompression, true); } } } \ No newline at end of file diff --git a/GzsTool.Core/Utility/Hashing.cs b/GzsTool.Core/Utility/Hashing.cs index ea2f6a2..b49fe5c 100644 --- a/GzsTool.Core/Utility/Hashing.cs +++ b/GzsTool.Core/Utility/Hashing.cs @@ -325,6 +325,11 @@ internal static byte[] Md5Hash(byte[] buffer) return Md5.ComputeHash(buffer); } + internal static byte[] Md5Hash(Stream stream) + { + return Md5.ComputeHash(stream); + } + internal static byte[] Md5HashText(string text) { return Md5.ComputeHash(Encoding.Default.GetBytes(text));