diff --git a/Maple2.File.Generator/Maple2.File.Generator.csproj b/Maple2.File.Generator/Maple2.File.Generator.csproj index c7c3adc..7884773 100644 --- a/Maple2.File.Generator/Maple2.File.Generator.csproj +++ b/Maple2.File.Generator/Maple2.File.Generator.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/Maple2.File.IO/Crypto/CryptoManager.cs b/Maple2.File.IO/Crypto/CryptoManager.cs index c63b62d..e79c0d9 100644 --- a/Maple2.File.IO/Crypto/CryptoManager.cs +++ b/Maple2.File.IO/Crypto/CryptoManager.cs @@ -1,144 +1,183 @@ -using System.IO.MemoryMappedFiles; +using System.IO.Compression; +using System.IO.MemoryMappedFiles; using System.Text; -using Ionic.Zlib; using Maple2.File.IO.Crypto.Common; using Maple2.File.IO.Crypto.Keys; using Maple2.File.IO.Crypto.Stream; -namespace Maple2.File.IO.Crypto { - public static class CryptoManager { - public static byte[] DecryptFileString(IPackStream stream, System.IO.Stream buffer) { - if (stream.CompressedHeaderSize > 0 && stream.EncodedHeaderSize > 0 && - stream.HeaderSize > 0) { - byte[] src = new byte[stream.EncodedHeaderSize]; - - if ((ulong) buffer.Read(src, 0, (int) stream.EncodedHeaderSize) == - stream.EncodedHeaderSize) { - return Decrypt(stream.Version, (uint) stream.EncodedHeaderSize, - (uint) stream.CompressedHeaderSize, Encryption.Aes | Encryption.Zlib, src); - } - } +namespace Maple2.File.IO.Crypto; + +public static class CryptoManager { + public static byte[] DecryptFileString(IPackStream stream, System.IO.Stream buffer) { + if (stream.CompressedHeaderSize > 0 && stream.EncodedHeaderSize > 0 && stream.HeaderSize > 0) { + byte[] src = new byte[stream.EncodedHeaderSize]; - throw new Exception("ERROR decrypting file list: the size of the list is invalid."); + if ((ulong) buffer.Read(src, 0, (int) stream.EncodedHeaderSize) == stream.EncodedHeaderSize) { + return Decrypt(stream.Version, (uint) stream.EncodedHeaderSize, (uint) stream.CompressedHeaderSize, Encryption.Aes | Encryption.Zlib, src); + } } - public static byte[] DecryptFileTable(IPackStream stream, System.IO.Stream buffer) { - if (stream.CompressedDataSize > 0 && stream.EncodedDataSize > 0 && stream.DataSize > 0) { - byte[] src = new byte[stream.EncodedDataSize]; + throw new Exception("ERROR decrypting file list: the size of the list is invalid."); + } + + public static byte[] DecryptFileTable(IPackStream stream, System.IO.Stream buffer) { + if (stream.CompressedDataSize > 0 && stream.EncodedDataSize > 0 && stream.DataSize > 0) { + byte[] src = new byte[stream.EncodedDataSize]; - if ((ulong) buffer.Read(src, 0, (int) stream.EncodedDataSize) == stream.EncodedDataSize) { - return Decrypt(stream.Version, (uint) stream.EncodedDataSize, - (uint) stream.CompressedDataSize, Encryption.Aes | Encryption.Zlib, src); - } + if ((ulong) buffer.Read(src, 0, (int) stream.EncodedDataSize) == stream.EncodedDataSize) { + return Decrypt(stream.Version, (uint) stream.EncodedDataSize, (uint) stream.CompressedDataSize, Encryption.Aes | Encryption.Zlib, src); } - - throw new Exception("ERROR decrypting file table: the size of the table is invalid."); } - public static byte[] DecryptData(IPackFileHeader pHeader, MemoryMappedFile data) { - if (pHeader.CompressedFileSize > 0 && pHeader.EncodedFileSize > 0 && pHeader.FileSize > 0) { - using MemoryMappedViewStream buffer = - data.CreateViewStream((long) pHeader.Offset, pHeader.EncodedFileSize); - byte[] src = new byte[pHeader.EncodedFileSize]; + throw new Exception("ERROR decrypting file table: the size of the table is invalid."); + } + + public static byte[] DecryptData(IPackFileHeader pHeader, MemoryMappedFile data) { + if (pHeader.CompressedFileSize > 0 && pHeader.EncodedFileSize > 0 && pHeader.FileSize > 0) { + using MemoryMappedViewStream buffer = data.CreateViewStream((long) pHeader.Offset, pHeader.EncodedFileSize); + byte[] src = new byte[pHeader.EncodedFileSize]; - if (buffer.Read(src, 0, (int) pHeader.EncodedFileSize) == pHeader.EncodedFileSize) { - return Decrypt(pHeader.Version, pHeader.EncodedFileSize, - (uint) pHeader.CompressedFileSize, pHeader.BufferFlag, src); - } + if (buffer.Read(src, 0, (int) pHeader.EncodedFileSize) == pHeader.EncodedFileSize) { + return Decrypt(pHeader.Version, pHeader.EncodedFileSize, (uint) pHeader.CompressedFileSize, pHeader.BufferFlag, src); } + } + + throw new Exception("ERROR decrypting data file segment: the size of the block is invalid."); + } - throw new Exception("ERROR decrypting data file segment: the size of the block is invalid."); + // Decryption Routine: Base64 -> AES -> Zlib + private static byte[] Decrypt(PackVersion version, uint size, uint sizeCompressed, Encryption flag, byte[] src) { + if (flag.HasFlag(Encryption.Aes)) { + // Get the AES Key/IV for transformation + CipherKeys.GetKeyAndIV(version, sizeCompressed, out byte[] key, out byte[] iv); + + // Decode the base64 encoded string + src = Convert.FromBase64String(Encoding.UTF8.GetString(src)); + + // Decrypt the AES encrypted block + var pCipher = new AESCipher(key, iv); + pCipher.TransformBlock(src, 0, size, src, 0); + } else if (flag.HasFlag(Encryption.Xor)) { + // Decrypt the XOR encrypted block + src = EncryptXor(version, src, size, sizeCompressed); } - // Decryption Routine: Base64 -> AES -> Zlib - private static byte[] Decrypt(PackVersion version, uint size, uint sizeCompressed, Encryption flag, - byte[] src) { - if (flag.HasFlag(Encryption.Aes)) { - // Get the AES Key/IV for transformation - CipherKeys.GetKeyAndIV(version, sizeCompressed, out byte[] key, out byte[] iv); - - // Decode the base64 encoded string - src = Convert.FromBase64String(Encoding.UTF8.GetString(src)); - - // Decrypt the AES encrypted block - var pCipher = new AESCipher(key, iv); - pCipher.TransformBlock(src, 0, size, src, 0); - } else if (flag.HasFlag(Encryption.Xor)) { - // Decrypt the XOR encrypted block - src = EncryptXor(version, src, size, sizeCompressed); - } + return flag.HasFlag(Encryption.Zlib) ? UncompressBuffer(src) : src; + } - return flag.HasFlag(Encryption.Zlib) ? ZlibStream.UncompressBuffer(src) : src; + // Encryption Routine: Zlib -> AES -> Base64 + public static byte[] Encrypt(PackVersion version, byte[] src, Encryption flag, out uint size, out uint sizeCompressed, out uint sizeEncoded) { + byte[] pEncrypted; + if (flag.HasFlag(Encryption.Zlib)) { + pEncrypted = CompressBuffer(src); + } else { + pEncrypted = new byte[src.Length]; + Buffer.BlockCopy(src, 0, pEncrypted, 0, src.Length); } - // Encryption Routine: Zlib -> AES -> Base64 - public static byte[] Encrypt(PackVersion version, byte[] src, Encryption flag, out uint size, - out uint sizeCompressed, out uint sizeEncoded) { - byte[] pEncrypted; - if (flag.HasFlag(Encryption.Zlib)) { - pEncrypted = ZlibStream.CompressBuffer(src); - } else { - pEncrypted = new byte[src.Length]; - Buffer.BlockCopy(src, 0, pEncrypted, 0, src.Length); - } + size = (uint) src.Length; + sizeCompressed = (uint) pEncrypted.Length; - size = (uint) src.Length; - sizeCompressed = (uint) pEncrypted.Length; + if (flag.HasFlag(Encryption.Aes)) { + // Get the AES Key/IV for transformation + CipherKeys.GetKeyAndIV(version, sizeCompressed, out byte[] key, out byte[] iv); - if (flag.HasFlag(Encryption.Aes)) { - // Get the AES Key/IV for transformation - CipherKeys.GetKeyAndIV(version, sizeCompressed, out byte[] key, out byte[] iv); + // Perform AES block encryption + var pCipher = new AESCipher(key, iv); + pCipher.TransformBlock(pEncrypted, 0, size, pEncrypted, 0); - // Perform AES block encryption - var pCipher = new AESCipher(key, iv); - pCipher.TransformBlock(pEncrypted, 0, size, pEncrypted, 0); + // Encode the encrypted data into a base64 encoded string + pEncrypted = Encoding.UTF8.GetBytes(Convert.ToBase64String(pEncrypted)); + } else if (flag.HasFlag(Encryption.Xor)) { + // Perform XOR block encryption + pEncrypted = EncryptXor(version, pEncrypted, size, sizeCompressed); + } - // Encode the encrypted data into a base64 encoded string - pEncrypted = Encoding.UTF8.GetBytes(Convert.ToBase64String(pEncrypted)); - } else if (flag.HasFlag(Encryption.Xor)) { - // Perform XOR block encryption - pEncrypted = EncryptXor(version, pEncrypted, size, sizeCompressed); - } + sizeEncoded = (uint) pEncrypted.Length; - sizeEncoded = (uint) pEncrypted.Length; + return pEncrypted; + } + + private static byte[] EncryptXor(PackVersion version, byte[] src, uint size, uint sizeCompressed) { + CipherKeys.GetXorKey(version, out byte[] key); + + uint uBlock = size >> 2; + uint uBlockOffset = 0; + int nKeyOffset = 0; + + if (uBlock != 0) { + while (uBlockOffset < uBlock) { + uint pBlockData = BitConverter.ToUInt32(src, (int) (4 * uBlockOffset)) ^ + BitConverter.ToUInt32(key, 4 * nKeyOffset); + Buffer.BlockCopy(BitConverter.GetBytes(pBlockData), 0, src, (int) (4 * uBlockOffset), + sizeof(uint)); - return pEncrypted; + nKeyOffset = ((ushort) nKeyOffset + 1) & 0x1FF; + uBlockOffset++; + } } - private static byte[] EncryptXor(PackVersion version, byte[] src, uint size, uint sizeCompressed) { - CipherKeys.GetXorKey(version, out byte[] key); + uBlock = (size & 3); + if (uBlock != 0) { + int nStart = (int) (4 * uBlockOffset); - uint uBlock = size >> 2; - uint uBlockOffset = 0; - int nKeyOffset = 0; + uBlockOffset = 0; + nKeyOffset = 0; - if (uBlock != 0) { - while (uBlockOffset < uBlock) { - uint pBlockData = BitConverter.ToUInt32(src, (int) (4 * uBlockOffset)) ^ - BitConverter.ToUInt32(key, 4 * nKeyOffset); - Buffer.BlockCopy(BitConverter.GetBytes(pBlockData), 0, src, (int) (4 * uBlockOffset), - sizeof(uint)); + while (uBlockOffset < uBlock) { + src[nStart + uBlockOffset++] ^= (byte) (key[nKeyOffset]); - nKeyOffset = ((ushort) nKeyOffset + 1) & 0x1FF; - uBlockOffset++; - } + nKeyOffset = ((ushort) nKeyOffset + 1) & 0x7FF; } + } + + return src; + } - uBlock = (size & 3); - if (uBlock != 0) { - int nStart = (int) (4 * uBlockOffset); + private static byte[] UncompressBuffer(byte[] src) { + using var compressedStream = new MemoryStream(src, 2, src.Length - 6); + using var decompressStream = new DeflateStream(compressedStream, CompressionMode.Decompress); + using var resultStream = new MemoryStream(); - uBlockOffset = 0; - nKeyOffset = 0; + decompressStream.CopyTo(resultStream); + return resultStream.ToArray(); + } - while (uBlockOffset < uBlock) { - src[nStart + uBlockOffset++] ^= (byte) (key[nKeyOffset]); + private static byte[] CompressBuffer(byte[] src) { + using var outputStream = new MemoryStream(); - nKeyOffset = ((ushort) nKeyOffset + 1) & 0x7FF; - } - } + // Add Zlib header bytes + outputStream.WriteByte(0x78); // CMF byte (compression method and flags) + outputStream.WriteByte(0x9C); // Additional flags byte (default compression) + + using (var compressionStream = new DeflateStream(outputStream, CompressionMode.Compress, true)) { + compressionStream.Write(src, 0, src.Length); + } - return src; + // Calculate and append Adler-32 checksum + uint adler32 = CalculateAdler32(src); + byte[] checksumBytes = [ + (byte) ((adler32 >> 24) & 0xFF), + (byte) ((adler32 >> 16) & 0xFF), + (byte) ((adler32 >> 8) & 0xFF), + (byte) (adler32 & 0xFF), + ]; + outputStream.Write(checksumBytes, 0, 4); + + return outputStream.ToArray(); + } + + // Adler-32 checksum calculation (needed for Zlib format) + private static uint CalculateAdler32(byte[] data) { + // ReSharper disable once InconsistentNaming + const uint BASE = 65521; + uint a = 1, b = 0; + + foreach (byte byteData in data) { + a = (a + byteData) % BASE; + b = (b + a) % BASE; } + + return (b << 16) | a; } } diff --git a/Maple2.File.IO/Maple2.File.IO.csproj b/Maple2.File.IO/Maple2.File.IO.csproj index de68901..88686d4 100644 --- a/Maple2.File.IO/Maple2.File.IO.csproj +++ b/Maple2.File.IO/Maple2.File.IO.csproj @@ -8,8 +8,4 @@ enable - - - - diff --git a/Maple2.File.Parser/Maple2.File.Parser.csproj b/Maple2.File.Parser/Maple2.File.Parser.csproj index 0008299..54e2c09 100644 --- a/Maple2.File.Parser/Maple2.File.Parser.csproj +++ b/Maple2.File.Parser/Maple2.File.Parser.csproj @@ -13,7 +13,7 @@ MapleStory2, File, Parser, m2d, xml true - 2.1.33 + 2.2.0 net8.0 README.md enable @@ -36,7 +36,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Maple2.File.Tests/Maple2.File.Tests.csproj b/Maple2.File.Tests/Maple2.File.Tests.csproj index 746c634..48425f0 100644 --- a/Maple2.File.Tests/Maple2.File.Tests.csproj +++ b/Maple2.File.Tests/Maple2.File.Tests.csproj @@ -15,9 +15,9 @@ - - - + + +