From d7d49c010b58a49636ee97d39f42c5643084fdd4 Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 27 Sep 2017 18:04:42 +0400 Subject: [PATCH] (KiriKiri.ICrypt): added virtual method EntryReadFilter. --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/KiriKiri/ArcXP3.cs | 169 +------------------------ ArcFormats/KiriKiri/CryptAlgorithms.cs | 96 +++++++++++++- ArcFormats/KiriKiri/CzCrypt.cs | 133 +++++++++++++++++++ 4 files changed, 230 insertions(+), 169 deletions(-) create mode 100644 ArcFormats/KiriKiri/CzCrypt.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 79cc7206..e920c4cf 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -112,6 +112,7 @@ + diff --git a/ArcFormats/KiriKiri/ArcXP3.cs b/ArcFormats/KiriKiri/ArcXP3.cs index cb59d513..1c016ac9 100644 --- a/ArcFormats/KiriKiri/ArcXP3.cs +++ b/ArcFormats/KiriKiri/ArcXP3.cs @@ -2,7 +2,7 @@ //! \date Wed Jul 16 13:58:17 2014 //! \brief KiriKiri engine archive implementation. // -// Copyright (C) 2014-2016 by morkt +// Copyright (C) 2014-2017 by morkt // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -344,172 +344,7 @@ NextEntry: else input = new Xp3Stream (arc.File, xp3_entry); - if (xp3_entry.UnpackedSize <= 5 || "audio" == entry.Type) - return input; - - var header = new byte[5]; - input.Read (header, 0, 5); - if (0x184D2204 == header.ToInt32 (0)) // LZ4 magic - { - // assume no scripts are compressed using LZ4, return decompressed stream right away - return DecompressLz4 (xp3_entry, header, input); - } - else if (0xA590D7FD == header.ToUInt32 (0)) // cZLIB magic - { - return DecompressCz (xp3_entry, header, input); - } - - if (0xFE == header[0] && 0xFE == header[1] && header[2] < 3 && 0xFF == header[3] && 0xFE == header[4]) - return DecryptScript (header[2], input, xp3_entry.UnpackedSize); - - if (!input.CanSeek) - return new PrefixStream (header, input); - input.Position = 0; - return input; - } - - Stream DecompressLz4 (Xp3Entry entry, byte[] header, Stream input) - { - if (header.Length != 5) - throw new ArgumentException ("Invalid header length for DecompressLz4", "header"); - var info = new Lz4FrameInfo (header[4]); - info.SetBlockSize (input.ReadByte()); - if (info.HasContentLength) - { - input.Read (header, 0, 4); - long length = header.ToUInt32 (0); - input.Read (header, 0, 4); - length |= (long)header.ToUInt32 (0) << 32; - info.OriginalLength = length; - entry.UnpackedSize = (uint)length; - entry.IsPacked = true; - } - if (info.HasDictionary) - { - input.Read (header, 0, 4); - info.DictionaryId = header.ToInt32 (0); - } - input.ReadByte(); // skip descriptor checksum - return new Lz4Stream (input, info); - } - - Stream DecompressCz (Xp3Entry entry, byte[] src_header, Stream input) - { - var header = new byte[15]; - Buffer.BlockCopy (src_header, 0, header, 0, Math.Min (header.Length, src_header.Length)); - if (header.Length > src_header.Length) - input.Read (header, src_header.Length, header.Length - src_header.Length); - header[4] ^= 0x11; - header[5] ^= 0x7F; - header[6] ^= 0x9A; - byte key = header[4]; - int unpacked_size = CzDecryptInt (header, 7, key); - int packed_size = CzDecryptInt (header, 11, key); - var data = new byte[packed_size]; - input.Read (data, 0, packed_size); - input.Dispose(); - data = CzDecryptData (data); - input = new BinMemoryStream (data); - if ('C' == header[4]) - input = new ZLibStream (input, CompressionMode.Decompress); - return input; - } - - static int CzDecryptInt (byte[] data, int offset, byte key) - { - for (int i = 0; i < 4; ++i) - { - data[offset+i] ^= (byte)(key ^ CzHeaderKey[i]); - } - return data.ToInt32 (offset); - } - - static byte[] CzDecryptData (byte[] data) - { - int padded_size = data.Length - 5; - int original_size = padded_size - (data[padded_size+1] ^ data[padded_size]); - uint iv_seed = data.ToUInt32 (padded_size+1) ^ 0xBFBFBFBFu; - using (var aes = Aes.Create()) - { - aes.Mode = CipherMode.CBC; - aes.Padding = PaddingMode.Zeros; - aes.Key = CzDefaultKey; - aes.IV = CzCreateIV (iv_seed); - using (var enc = new MemoryStream (data, 0, padded_size)) - using (var dec = new InputCryptoStream (enc, aes.CreateDecryptor())) - { - var original = new byte[original_size]; - dec.Read (original, 0, original_size); - return original; - } - } - } - - static byte[] CzCreateIV (uint seed) - { - var state = new uint[4]; - state[0] = 123456789; // field_0 - state[1] = 972436830; // field_4 - state[2] = 524018621; // field_8 - state[3] = seed; // field_C - var iv = new byte[16]; - for (int i = 0; i < 16; ++i) - { - uint a = state[3]; - uint b = state[0] ^ (state[0] << 11); - state[0] = state[1]; - state[1] = state[2]; - state[2] = a; - state[3] = b ^ a ^ ((b ^ (a >> 11)) >> 8); - iv[i] = (byte)state[3]; - } - return iv; - } - - static readonly byte[] CzHeaderKey = { 0x9D, 0x1D, 0x9A, 0xF2 }; - static readonly byte[] CzDefaultKey = { - 0x91, 0x10, 0xFC, 0x75, 0x45, 0x8F, 0xB5, 0xE6, 0xFE, 0xAC, 0xBA, 0x44, 0x76, 0x58, 0xC2, 0x1A - }; - - Stream DecryptScript (int enc_type, Stream input, uint unpacked_size) - { - using (var reader = new BinaryReader (input, Encoding.Unicode, true)) - { - if (2 == enc_type) - { - reader.ReadInt64(); // packed_size - reader.ReadInt64(); // unpacked_size - return new ZLibStream (input, CompressionMode.Decompress); - } - var output = new MemoryStream ((int)unpacked_size+2); - using (var writer = new BinaryWriter (output, Encoding.Unicode, true)) - { - writer.Write ('\xFEFF'); // BOM - int c; - if (1 == enc_type) - { - while ((c = reader.Read()) != -1) - { - c = (c & 0xAAAA) >> 1 | (c & 0x5555) << 1; - writer.Write ((char)c); - } - } - else - { - while ((c = reader.Read()) != -1) - { - if (c >= 0x20) - { - c = c ^ (((c & 0xFE) << 8) ^ 1); - writer.Write ((char)c); - } - } - } - } - output.Position = 0; - input.Dispose(); - return output; - } + return xp3_entry.Cipher.EntryReadFilter (xp3_entry, input); } public override ResourceOptions GetDefaultOptions () diff --git a/ArcFormats/KiriKiri/CryptAlgorithms.cs b/ArcFormats/KiriKiri/CryptAlgorithms.cs index 8de0bda6..69f697f6 100644 --- a/ArcFormats/KiriKiri/CryptAlgorithms.cs +++ b/ArcFormats/KiriKiri/CryptAlgorithms.cs @@ -2,7 +2,7 @@ //! \date Thu Feb 04 12:08:40 2016 //! \brief KiriKiri engine encryption algorithms. // -// Copyright (C) 2016 by morkt +// Copyright (C) 2016-2017 by morkt // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -27,6 +27,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; +using GameRes.Compression; using GameRes.Utility; namespace GameRes.Formats.KiriKiri @@ -83,6 +85,96 @@ namespace GameRes.Formats.KiriKiri else return null; } + + /// + /// Post-process entry stream. + /// + public virtual Stream EntryReadFilter (Xp3Entry entry, Stream input) + { + if (entry.UnpackedSize <= 5 || "audio" == entry.Type) + return input; + + var header = new byte[5]; + input.Read (header, 0, 5); + if (0x184D2204 == header.ToInt32 (0)) // LZ4 magic + { + // assume no scripts are compressed using LZ4, return decompressed stream right away + return DecompressLz4 (entry, header, input); + } + if (0xFE == header[0] && 0xFE == header[1] && header[2] < 3 && 0xFF == header[3] && 0xFE == header[4]) + return DecryptScript (header[2], input, entry.UnpackedSize); + + if (!input.CanSeek) + return new PrefixStream (header, input); + input.Position = 0; + return input; + } + + internal Stream DecompressLz4 (Xp3Entry entry, byte[] header, Stream input) + { + if (header.Length != 5) + throw new ArgumentException ("Invalid header length for DecompressLz4", "header"); + var info = new Lz4FrameInfo (header[4]); + info.SetBlockSize (input.ReadByte()); + if (info.HasContentLength) + { + input.Read (header, 0, 4); + long length = header.ToUInt32 (0); + input.Read (header, 0, 4); + length |= (long)header.ToUInt32 (0) << 32; + info.OriginalLength = length; + entry.UnpackedSize = (uint)length; + entry.IsPacked = true; + } + if (info.HasDictionary) + { + input.Read (header, 0, 4); + info.DictionaryId = header.ToInt32 (0); + } + input.ReadByte(); // skip descriptor checksum + return new Lz4Stream (input, info); + } + + internal Stream DecryptScript (int enc_type, Stream input, uint unpacked_size) + { + using (var reader = new BinaryReader (input, Encoding.Unicode, true)) + { + if (2 == enc_type) + { + reader.ReadInt64(); // packed_size + reader.ReadInt64(); // unpacked_size + return new ZLibStream (input, CompressionMode.Decompress); + } + var output = new MemoryStream ((int)unpacked_size+2); + using (var writer = new BinaryWriter (output, Encoding.Unicode, true)) + { + writer.Write ('\xFEFF'); // BOM + int c; + if (1 == enc_type) + { + while ((c = reader.Read()) != -1) + { + c = (c & 0xAAAA) >> 1 | (c & 0x5555) << 1; + writer.Write ((char)c); + } + } + else + { + while ((c = reader.Read()) != -1) + { + if (c >= 0x20) + { + c = c ^ (((c & 0xFE) << 8) ^ 1); + writer.Write ((char)c); + } + } + } + } + output.Position = 0; + input.Dispose(); + return output; + } + } } [Serializable] @@ -984,7 +1076,7 @@ namespace GameRes.Formats.KiriKiri } [Serializable] - public class KissCrypt : ICrypt + public class KissCrypt : CzCrypt { public override void Decrypt (Xp3Entry entry, long offset, byte[] data, int pos, int count) { diff --git a/ArcFormats/KiriKiri/CzCrypt.cs b/ArcFormats/KiriKiri/CzCrypt.cs new file mode 100644 index 00000000..46782f1c --- /dev/null +++ b/ArcFormats/KiriKiri/CzCrypt.cs @@ -0,0 +1,133 @@ +//! \file CzCrypt.cs +//! \date 2017 Sep 27 +//! \brief implementation of cZLIB extraction filter for KiriKiri engine. +// +// Copyright (C) 2017 by morkt +// +// 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; +using System.IO; +using System.Security.Cryptography; +using GameRes.Compression; + +namespace GameRes.Formats.KiriKiri +{ + /// + /// cZLIB entry filter. + /// + [Serializable] + public abstract class CzCrypt : ICrypt + { + const uint CzMagic = 0xA590D7FDu; + const uint CzIvSeed = 0xBFBFBFBFu; + + static readonly byte[] CzHeaderKey = { 0x9D, 0x1D, 0x9A, 0xF2 }; + static readonly byte[] CzDefaultKey = { + 0x91, 0x10, 0xFC, 0x75, 0x45, 0x8F, 0xB5, 0xE6, 0xFE, 0xAC, 0xBA, 0x44, 0x76, 0x58, 0xC2, 0x1A + }; + + public override Stream EntryReadFilter (Xp3Entry entry, Stream input) + { + if (entry.UnpackedSize <= 15 || "audio" == entry.Type) + return input; + + var header = new byte[15]; + input.Read (header, 0, 15); + if (CzMagic == header.ToUInt32 (0)) + { + var type = new char[3] { + (char)(header[4] ^ 0x11), + (char)(header[5] ^ 0x7F), + (char)(header[6] ^ 0x9A) + }; + byte key = (byte)type[0]; + int unpacked_size = CzDecryptInt (header, 7, key); + int packed_size = CzDecryptInt (header, 11, key); + if (packed_size < entry.UnpackedSize && 0 == ((packed_size-5) & 0xF)) + { + var data = new byte[packed_size]; + input.Read (data, 0, packed_size); + input.Dispose(); + data = CzDecryptData (data); + input = new BinMemoryStream (data); + if ('C' == type[0]) + input = new ZLibStream (input, CompressionMode.Decompress); + return input; + } + } + if (!input.CanSeek) + return new PrefixStream (header, input); + input.Position = 0; + return input; + } + + static int CzDecryptInt (byte[] data, int offset, byte key) + { + for (int i = 0; i < 4; ++i) + { + data[offset+i] ^= (byte)(key ^ CzHeaderKey[i]); + } + return data.ToInt32 (offset); + } + + static byte[] CzDecryptData (byte[] data) + { + int padded_size = data.Length - 5; + int original_size = padded_size - (data[padded_size+1] ^ data[padded_size]); + uint iv_seed = data.ToUInt32 (padded_size+1) ^ CzIvSeed; + using (var aes = Aes.Create()) + { + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.Zeros; + aes.Key = CzDefaultKey; + aes.IV = CzCreateIV (iv_seed); + using (var enc = new MemoryStream (data, 0, padded_size)) + using (var dec = new InputCryptoStream (enc, aes.CreateDecryptor())) + { + var original = new byte[original_size]; + dec.Read (original, 0, original_size); + return original; + } + } + } + + static byte[] CzCreateIV (uint seed) + { + var state = new uint[4]; + state[0] = 123456789; // field_0 + state[1] = 972436830; // field_4 + state[2] = 524018621; // field_8 + state[3] = seed; // field_C + var iv = new byte[16]; + for (int i = 0; i < 16; ++i) + { + uint a = state[3]; + uint b = state[0] ^ (state[0] << 11); + state[0] = state[1]; + state[1] = state[2]; + state[2] = a; + state[3] = b ^ a ^ ((b ^ (a >> 11)) >> 8); + iv[i] = (byte)state[3]; + } + return iv; + } + } +}