diff --git a/ArcFormats/KiriKiri/ArcXP3.cs b/ArcFormats/KiriKiri/ArcXP3.cs index fcf08e06..cb59d513 100644 --- a/ArcFormats/KiriKiri/ArcXP3.cs +++ b/ArcFormats/KiriKiri/ArcXP3.cs @@ -354,6 +354,10 @@ NextEntry: // 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); @@ -389,6 +393,84 @@ NextEntry: 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))