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;
+ }
+ }
+}