diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 94d6e08c..6294bfd6 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -232,6 +232,7 @@ + diff --git a/ArcFormats/KiriKiri/ArcXP3.cs b/ArcFormats/KiriKiri/ArcXP3.cs index b0971e68..d1a86199 100644 --- a/ArcFormats/KiriKiri/ArcXP3.cs +++ b/ArcFormats/KiriKiri/ArcXP3.cs @@ -56,6 +56,7 @@ namespace GameRes.Formats.KiriKiri public ICrypt Cipher { get; set; } public List Segments { get { return m_segments; } } public uint Hash { get; set; } + public object Extra { get; set; } } public class Xp3Options : ResourceOptions @@ -145,6 +146,7 @@ namespace GameRes.Formats.KiriKiri using (var header = new BinaryReader (header_stream, Encoding.Unicode)) using (var filename_map = new FilenameMap()) { + Dictionary hx_entry_info = null; while (-1 != header.PeekChar()) { uint entry_signature = header.ReadUInt32(); @@ -208,7 +210,6 @@ namespace GameRes.Formats.KiriKiri goto NextEntry; } entry.Name = name; - entry.Type = FormatCatalog.Instance.GetTypeFromName (name, ContainedFormats); entry.IsEncrypted = !(entry.Cipher is NoCrypt) && !(entry.Cipher.StartupTjsNotEncrypted && "startup.tjs" == name); break; @@ -253,6 +254,34 @@ namespace GameRes.Formats.KiriKiri { DeobfuscateEntry (entry); } + if (null != hx_entry_info) + { + if (hx_entry_info.TryGetValue (entry.Name, out HxEntry info)) + { + entry.Extra = info; + + var sb = new StringBuilder (); + if (!string.IsNullOrEmpty (info.Path)) + { + sb.Append (info.Path); + if (!info.Path.EndsWith ("/") && !info.Path.EndsWith ("\\")) + sb.Append ('/'); + } + if (!string.IsNullOrEmpty (info.Name)) + { + sb.Append (info.Name); + if (sb.Length > 0) + entry.Name = sb.ToString (); + } + else + { + sb.Append (entry.Name); + if (sb.Length > 0) + entry.Name = sb.ToString (); + } + } + } + entry.Type = FormatCatalog.Instance.GetTypeFromName(entry.Name, ContainedFormats); dir.Add (entry); } } @@ -271,6 +300,22 @@ namespace GameRes.Formats.KiriKiri } } } + else if (0x34767848 == entry_signature) // "Hxv4" + { + if (crypt_algorithm.Value is HxCrypt) + { + try + { + var offset = header.ReadInt64 () + base_offset; + var size = header.ReadUInt32 (); + var flags = header.ReadUInt16 (); + var hx = file.View.ReadBytes (offset, size); + var crypt = crypt_algorithm.Value as HxCrypt; + hx_entry_info = crypt.ReadIndex (hx); + } + catch (Exception) { /* ignore parse error */ } + } + } else if (entry_size > 7) { // 0x6E666E68 == entry_signature // "hnfn" diff --git a/ArcFormats/KiriKiri/HxCrypt.cs b/ArcFormats/KiriKiri/HxCrypt.cs new file mode 100644 index 00000000..af809815 --- /dev/null +++ b/ArcFormats/KiriKiri/HxCrypt.cs @@ -0,0 +1,984 @@ +//! \file HxCrypt.cs +//! \date 2022 +//! \brief Hx KiriKiri encryption schemes. +// +// Copyright (C) 2018 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 GameRes.Compression; +using GameRes.Utility; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +#pragma warning disable IDE0019 + +namespace GameRes.Formats.KiriKiri +{ + [Serializable] + public class HxCrypt : CxEncryption + { + public byte[] IndexKey1; // 32 bytes + public byte[] IndexKey2; // 16 bytes + public ulong FilterKey; + public int RandomType; + public string NamesFile; + + public HxCrypt(CxScheme scheme) : base(scheme) + { + } + + [NonSerialized] + uint[] _lookup32 = null; + + void CreateLookup32() + { + if (null != _lookup32) + return; + var result = new uint[256]; + for (int i = 0; i < result.Length; i++) + { + string s = i.ToString ("X2"); + result[i] = s[0] + ((uint)s[1] << 16); + } + _lookup32 = result; + } + + string BinaryToString(byte[] data) + { + if (data.Length == 0) + return string.Empty; + var lookup32 = _lookup32; + var result = new char[data.Length*2]; + for (int i = 0; i < data.Length; i++) + { + var val = lookup32[data[i]]; + result[2*i] = (char)val; + result[2*i+1] = (char)(val >> 16); + } + return new string(result); + } + + internal virtual Dictionary ReadIndex(byte[] data) + { + if (data.Length <= 20) // 16 + 4 + return null; + if (null == IndexKey1 || IndexKey1.Length != 32) + return null; + if (null == IndexKey2 || IndexKey2.Length != 16) + return null; + var seed = new uint[] { 1, 0 }; + var crypt = new HxChachaDecryptor (IndexKey1, IndexKey2, seed); + var buf = new byte[data.Length-16]; + crypt.Decrypt (data, 16, buf, 0, buf.Length); + Stream index_stream = null; + using (var stream = new MemoryStream (buf)) + { + stream.Position = 4; + index_stream = ZLibCompressor.DeCompress (stream); + } + if (null == index_stream) + return null; + object index_obj = HxIndexDeserializer.Deserialize (index_stream); + var root_obj = index_obj as object[]; + if (null == root_obj) + return null; + CreateLookup32 (); + var path_map = new Dictionary(); + var name_map = new Dictionary(); + try + { + FormatCatalog.Instance.ReadFileList (NamesFile, line => { + var name = line.Split (':'); // "hash:name" + if (name.Length != 2) + return; + if (name[0].Length == 16) + path_map[name[0]] = name[1]; + else if (name[0].Length == 64) + name_map[name[0]] = name[1]; + }); + } + catch (Exception) { } + var entry_info_map = new Dictionary(); + for (var i = 0; i < root_obj.Length; i += 2) + { + var path_hash = root_obj[i] as byte[]; + if (null == path_hash) + continue; + var dir_obj = root_obj[i+1] as object[]; + if (null == dir_obj) + continue; + var path_hash_str = BinaryToString (path_hash); + for (var j = 0; j < dir_obj.Length; j += 2) + { + var entry_hash = dir_obj[j] as byte[]; + if (null == entry_hash) + continue; + var entry_obj = dir_obj[j+1] as object[]; + if (null == entry_obj) + continue; + if (entry_obj.Length < 2) + continue; + var entry_id = entry_obj[0] as long?; + if (null == entry_id) + continue; + var entry_key = entry_obj[1] as long?; + if (null == entry_key) + continue; + var entry_info = new HxEntry(); + if (path_map.TryGetValue (path_hash_str, out string path_str)) + entry_info.Path = path_str; + var name_hash_str = BinaryToString (entry_hash); + if (name_map.TryGetValue (name_hash_str, out string name_str)) + entry_info.Name = name_str; + entry_info.Key = (long)entry_key; + var id = (uint)entry_id; + var uname = GetUnicodeName (id); + entry_info_map.Add (uname, entry_info); + } + } + return entry_info_map; + } + + internal virtual string GetUnicodeName(uint hash) + { + var buf = new char[4]; + var i = 0; + for (;;) + { + buf[i++] = (char)((hash & 0x3FFF) + 0x5000); + hash >>= 14; + if (hash == 0) + break; + } + var str = new string (buf, 0, i); + return str; + } + + internal virtual HxFilterKey CreateFilterKey(ulong entry_key, ulong header_key_seed) + { + var result = new HxFilterKey + { + Key = new ulong[2], + HeaderKey = new byte[16], + }; + + /* create file key */ + + uint key0 = (uint)(entry_key & 0xffffffff); + uint key1 = (uint)((entry_key >> 32) & 0xffffffff); + + var k0 = ExecuteXCode(key0); + result.Key[0] = (ulong)k0.Item1 | ((ulong)k0.Item2 << 32); + var k1 = ExecuteXCode(key1); + result.Key[1] = (ulong)k1.Item1 | ((ulong)k1.Item2 << 32); + + result.SplitPosition = (long)((this.m_offset + ((entry_key >> 16) & this.m_mask)) & 0xffffffff); + + /* create header key */ + + var k3 = ExecuteXCode((uint)header_key_seed); + var v5 = (ulong)k3.Item1 | ((ulong)k3.Item2 << 32); + v5 = ~v5; + + for (int i = 0, j = 56; i < 8; i += 1, j -= 8) + { + result.HeaderKey[i] = (byte)((v5 >> j) & 0xff); + } + + k3 = ExecuteXCode((uint)v5); + v5 = (ulong)k3.Item1 | ((ulong)k3.Item2 << 32); + v5 = ~v5; + + for (int i = 0, j = 56; i < 8; i += 1, j -= 8) + { + result.HeaderKey[i+8] = (byte)((v5 >> j) & 0xff); + } + + result.HasHeaderKey = true; + result.Flag = false; + + return result; + } + + internal virtual void CreateFilter(Xp3Entry entry) + { + var info = entry.Extra as HxEntry; + if (null == info) + return; + if (null != info.Filter) + return; + var entry_key = (ulong)info.Key ^ FilterKey; + var header_key = ~entry_key; + var key = CreateFilterKey (entry_key, header_key); + info.Filter = new HxFilter (key); + } + + public override void Init(ArcFile arc) + { + return; + } + + public override byte Decrypt(Xp3Entry entry, long offset, byte value) + { + if (entry.Extra == null) + return value; + var info = entry.Extra as HxEntry; + if (null == info) + return value; + if (null == info.Filter) + CreateFilter (entry); + if (null == info.Filter) + return value; + + var buf = new byte[1] { value }; + + info.Filter.Decrypt (offset, buf, 0, 1); + + return buf[0]; + } + + public override void Decrypt(Xp3Entry entry, long offset, byte[] buffer, int pos, int count) + { + if (entry.Extra == null) + return; + var info = entry.Extra as HxEntry; + if (null == info) + return; + if (null == info.Filter) + CreateFilter (entry); + if (null == info.Filter) + return; + + info.Filter.Decrypt (offset, buffer, pos, count); + + return; + } + + public override void Encrypt(Xp3Entry entry, long offset, byte[] values, int pos, int count) + { + throw new NotImplementedException(); + } + + internal override CxProgram NewProgram(uint seed) + { + return new HxProgram(seed, ControlBlock, RandomType); + } + } + + internal class HxEntry + { + public string Path; + public string Name; + public long Key; + public HxFilter Filter; + } + + internal class HxFilterKey + { + public ulong[] Key; + public long SplitPosition; + public byte[] HeaderKey; + public bool HasHeaderKey; + public bool Flag; + } + + internal class HxHeaderKey + { + public long Position; + public byte[] Key; + public int KeyPtr; + public int Length; + } + + internal class HxBufferSpan + { + public byte[] Buffer; + public int BufferPtr; + public int Length; + } + + internal class HxFilterSpan + { + public long Position; + public HxBufferSpan Data; + + bool AdjustHeaderKey(HxHeaderKey key, HxHeaderKey new_key) + { + if (this.Data.Buffer == null) + return false; + + if (this.Data.Length == 0) + return false; + + if (key.Key == null) + return false; + + if (key.Length == 0) + return false; + + long dataStart = this.Position; + long dataEnd = dataStart + this.Data.Length; + + if (dataStart <= key.Position) + dataStart = key.Position; + + if (dataEnd >= key.Position + key.Length) + dataEnd = key.Position + key.Length; + + if (dataStart >= dataEnd) + return false; + + new_key.Position = dataStart; + new_key.Key = key.Key; + new_key.KeyPtr = (int)(dataStart - key.Position); + new_key.Length = (int)(dataEnd - dataStart); + + return true; + } + + public void DecryptHeader(HxHeaderKey key) + { + var key2 = new HxHeaderKey(); + + if (AdjustHeaderKey(key, key2)) + { + for (uint i = 0; i < key2.Length; i++) + { + this.Data.Buffer[this.Data.BufferPtr + key2.Position - this.Position + i] + ^= key2.Key[key2.KeyPtr + i]; + } + } + } + + public int Split(long split_position, HxFilterSpan[] sub_span) + { + if (this.Data.Buffer == null) + return 0; + + if (this.Data.Length == 0) + return 0; + + if (split_position > Position) + { + if (split_position < Position + this.Data.Length) + { + sub_span[0] = new HxFilterSpan + { + Position = this.Position, + Data = new HxBufferSpan + { + Buffer = this.Data.Buffer, + BufferPtr = this.Data.BufferPtr, + Length = (int)(split_position - Position), + } + }; + sub_span[1] = new HxFilterSpan + { + Position = split_position, + Data = new HxBufferSpan + { + Buffer = this.Data.Buffer, + BufferPtr = this.Data.BufferPtr + sub_span[0].Data.Length, + Length = this.Data.Length - sub_span[0].Data.Length, + } + }; + return 3; + } + else + { + sub_span[0] = this; + sub_span[1] = new HxFilterSpan(); + return 1; + } + } + else + { + sub_span[0] = new HxFilterSpan(); + sub_span[1] = this; + return 2; + } + } + + public void FirstDecrypt(uint key) + { + if (this.Data.Buffer == null) + return; + + if (this.Data.Length == 0) + return; + + var buf = BitConverter.GetBytes(key); + + for (int i = 0; i < this.Data.Length; i++) + { + var j = Position + i; + this.Data.Buffer[this.Data.BufferPtr + i] ^= buf[j & 3]; + } + } + } + + internal class HxFilterSpanDecryptor + { + private long[] SpanPosition; + private uint FirstDecryptKey; + private uint DecryptKey; + + public HxFilterSpanDecryptor(ulong key, bool flag) + { + this.DecryptKey = (uint)((key >> 8) & 0xFF); + this.DecryptKey |= (uint)((key >> 8) & 0xFF00); + + this.SpanPosition = new long[2] + { + (long)((key >> 48) & 0xFFFF), + (long)((key >> 32) & 0xFFFF), + }; + + this.FirstDecryptKey = (uint)(key & 0xFF); + + if (this.SpanPosition[0] == this.SpanPosition[1]) + this.SpanPosition[1] += 1; + + if (flag) + this.DecryptKey = 0; + + if (!flag && this.FirstDecryptKey == 0) + this.FirstDecryptKey = 0xA5; + + this.FirstDecryptKey *= 0x1010101; + } + + public void Decrypt(HxFilterSpan span) + { + span.FirstDecrypt(FirstDecryptKey); + + byte key1 = (byte)(this.DecryptKey & 0xFF); + byte key2 = (byte)((this.DecryptKey >> 8) & 0xFF); + + if (key1 != 0) + { + if (this.SpanPosition[0] >= span.Position && + this.SpanPosition[0] < span.Position + span.Data.Length) + { + span.Data.Buffer[span.Data.BufferPtr + this.SpanPosition[0] - span.Position] ^= key1; + } + } + + if (key2 != 0) + { + if (this.SpanPosition[1] >= span.Position && + this.SpanPosition[1] < span.Position + span.Data.Length) + { + span.Data.Buffer[span.Data.BufferPtr + this.SpanPosition[1] - span.Position] ^= key2; + } + } + } + } + + internal class HxFilter + { + private HxFilterSpanDecryptor[] Span; + private long SplitPosition; + private HxHeaderKey HeaderKey; + + public HxFilter(HxFilterKey key) + { + this.Span = new HxFilterSpanDecryptor[2] + { + new HxFilterSpanDecryptor(key.Key[0], key.Flag), + new HxFilterSpanDecryptor(key.Key[1], key.Flag), + }; + + this.SplitPosition = key.SplitPosition; + + this.HeaderKey = new HxHeaderKey(); + + if (key.HasHeaderKey) + { + this.HeaderKey.Key = key.HeaderKey; + this.HeaderKey.Length = 16; + } + } + + public void Decrypt(long position, byte[] buffer, int buffer_ptr, int length) + { + var span = new HxFilterSpan + { + Position = position, + + Data = new HxBufferSpan + { + Buffer = buffer, + BufferPtr = buffer_ptr, + Length = length, + }, + }; + + if (span.Position < this.HeaderKey.Position + this.HeaderKey.Length) + { + span.DecryptHeader(this.HeaderKey); + } + + var sub_span = new HxFilterSpan[2]; + + var flags = span.Split(this.SplitPosition, sub_span); + + if ((flags & 1) != 0) + { + this.Span[0].Decrypt(sub_span[0]); + } + + if ((flags & 2) != 0) + { + this.Span[1].Decrypt(sub_span[1]); + } + } + } + + internal class HxSplittableRandom + { + private ulong m_seed; + + public HxSplittableRandom(ulong seed) + { + m_seed = seed; + } + + public ulong Next() + { + ulong z; + + m_seed += 0x9e3779b97f4a7c15; + z = m_seed; + + z ^= z >> 30; + z *= 0xbf58476d1ce4e5b9; + z ^= z >> 27; + z *= 0x94d049bb133111eb; + z ^= z >> 31; + + return z; + } + } + + internal class HxProgram : CxProgram + { + [StructLayout(LayoutKind.Explicit)] + struct M64 + { + [FieldOffset(0)] public ulong u64; + [FieldOffset(0)] public uint u32_lo; + [FieldOffset(4)] public uint u32_hi; + } + + readonly int m_random_method; + new readonly M64[] m_seed; + + public HxProgram(uint seed, uint[] control_block, int random_method) : base(seed, control_block) + { + m_random_method = random_method; + m_seed = new M64[2]; + + ulong s = seed; + s = (s & 0xffffffff) | (~s << 32); + + var r = new HxSplittableRandom(s); + + m_seed[0].u64 = r.Next(); + m_seed[1].u64 = r.Next(); + } + + ulong GetOldRandom() + { + /* These codes only work correctly in little endian mode! */ + + var a = new M64(); + var b = new M64(); + var c = new M64(); + var d = new M64(); + var e = new M64(); + + ulong t; + + a.u64 = m_seed[0].u64; + b.u64 = m_seed[1].u64; + + c.u32_lo = a.u32_hi ^ b.u32_hi; + c.u32_hi = a.u32_lo ^ b.u32_lo; + + e.u32_lo = c.u32_hi; + e.u32_hi = c.u32_lo; + + t = (ulong)(c.u32_hi) << 21; + t ^= a.u64 >> 15; + t ^= c.u32_hi; + m_seed[0].u32_lo = (uint)t; + + t = a.u32_hi >> 15; + t |= (ulong)(a.u32_lo) << 17; + t ^= e.u64 >> 11; + t ^= c.u32_lo; + m_seed[0].u32_hi = (uint)t; + + m_seed[1].u32_hi = (uint)(e.u64 >> 4); + m_seed[1].u32_lo = (uint)(c.u64 >> 4); + + d.u64 = a.u64 + b.u64; + + t = d.u64 << 17; + t |= d.u32_hi >> 15; + t += a.u64; + + return t; + } + + ulong GetNewRandom() + { + /* These codes only work correctly in little endian mode! */ + + var a = new M64(); + var b = new M64(); + var c = new M64(); + var d = new M64(); + + ulong t; + + a.u64 = m_seed[0].u64; + b.u64 = m_seed[1].u64; + + c.u32_lo = a.u32_lo ^ b.u32_lo; + c.u32_hi = a.u32_hi ^ b.u32_hi; + + t = (ulong)(a.u32_lo) << 24; + t |= a.u32_hi >> 8; + t ^= (ulong)(c.u32_lo) << 16; + t ^= c.u32_lo; + m_seed[0].u32_lo = (uint)t; + + t = c.u64 >> 16; + t ^= a.u64 >> 8; + t ^= c.u32_hi; + m_seed[0].u32_hi = (uint)t; + + t = c.u32_hi >> 27; + t |= (ulong)(c.u32_lo) << 5; + m_seed[1].u32_hi = (uint)t; + + m_seed[1].u32_lo = (uint)(c.u64 >> 27); + + d.u64 = 5 * a.u64; + + t = d.u32_hi >> 25; + t |= d.u64 << 7; + t *= 9; + + return t; + } + + public override uint GetRandom() + { + if (0 == m_random_method) + return (uint)GetOldRandom(); + else + return (uint)GetNewRandom(); + } + } + + internal class HxChachaDecryptor + { + internal class State + { + //public byte[] Constant; + //public byte[] Key0; + //public byte[] Key1; + //public byte[] Nonce; + public byte[] Data = new byte[64]; + } + + readonly State m_state; + + public HxChachaDecryptor(byte[] key, byte[] nonce, uint[] seed) + { + m_state = new State(); + + var constant = Encoding.ASCII.GetBytes ("expand 32-byte k"); + + Array.Copy (constant, 0, m_state.Data, 0, 16); + Array.Copy (key, 0, m_state.Data, 16, 16); + Array.Copy (key, 16, m_state.Data, 32, 16); + LittleEndian.Pack (seed[0], m_state.Data, 48); + LittleEndian.Pack (seed[1], m_state.Data, 52); + Array.Copy (nonce, 0, m_state.Data, 56, 8); + } + + void TransformState(State src, State dst) + { + uint z0, z1, z2, z3, z4, z5, z6, z7, + z8, z9, za, zb, zc, zd, ze, zf; + + z0 = LittleEndian.ToUInt32(src.Data, 0); + z1 = LittleEndian.ToUInt32(src.Data, 4); + z2 = LittleEndian.ToUInt32(src.Data, 8); + z3 = LittleEndian.ToUInt32(src.Data, 12); + z4 = LittleEndian.ToUInt32(src.Data, 16); + z5 = LittleEndian.ToUInt32(src.Data, 20); + z6 = LittleEndian.ToUInt32(src.Data, 24); + z7 = LittleEndian.ToUInt32(src.Data, 28); + z8 = LittleEndian.ToUInt32(src.Data, 32); + z9 = LittleEndian.ToUInt32(src.Data, 36); + za = LittleEndian.ToUInt32(src.Data, 40); + zb = LittleEndian.ToUInt32(src.Data, 44); + zc = LittleEndian.ToUInt32(src.Data, 48); + zd = LittleEndian.ToUInt32(src.Data, 52); + ze = LittleEndian.ToUInt32(src.Data, 56); + zf = LittleEndian.ToUInt32(src.Data, 60); + + for (int i = 0; i < 10; i++) + { + // QUARTER(z0, z4, z8, zc); + z0 += z4; zc = Binary.RotL(zc ^ z0, 16); + z8 += zc; z4 = Binary.RotL(z4 ^ z8, 12); + z0 += z4; zc = Binary.RotL(zc ^ z0, 8); + z8 += zc; z4 = Binary.RotL(z4 ^ z8, 7); + // QUARTER(z1, z5, z9, zd); + z1 += z5; zd = Binary.RotL(zd ^ z1, 16); + z9 += zd; z5 = Binary.RotL(z5 ^ z9, 12); + z1 += z5; zd = Binary.RotL(zd ^ z1, 8); + z9 += zd; z5 = Binary.RotL(z5 ^ z9, 7); + // QUARTER(z2, z6, za, ze); + z2 += z6; ze = Binary.RotL(ze ^ z2, 16); + za += ze; z6 = Binary.RotL(z6 ^ za, 12); + z2 += z6; ze = Binary.RotL(ze ^ z2, 8); + za += ze; z6 = Binary.RotL(z6 ^ za, 7); + // QUARTER(z3, z7, zb, zf); + z3 += z7; zf = Binary.RotL(zf ^ z3, 16); + zb += zf; z7 = Binary.RotL(z7 ^ zb, 12); + z3 += z7; zf = Binary.RotL(zf ^ z3, 8); + zb += zf; z7 = Binary.RotL(z7 ^ zb, 7); + // QUARTER(z0, z5, za, zf); + z0 += z5; zf = Binary.RotL(zf ^ z0, 16); + za += zf; z5 = Binary.RotL(z5 ^ za, 12); + z0 += z5; zf = Binary.RotL(zf ^ z0, 8); + za += zf; z5 = Binary.RotL(z5 ^ za, 7); + // QUARTER(z1, z6, zb, zc); + z1 += z6; zc = Binary.RotL(zc ^ z1, 16); + zb += zc; z6 = Binary.RotL(z6 ^ zb, 12); + z1 += z6; zc = Binary.RotL(zc ^ z1, 8); + zb += zc; z6 = Binary.RotL(z6 ^ zb, 7); + // QUARTER(z2, z7, z8, zd); + z2 += z7; zd = Binary.RotL(zd ^ z2, 16); + z8 += zd; z7 = Binary.RotL(z7 ^ z8, 12); + z2 += z7; zd = Binary.RotL(zd ^ z2, 8); + z8 += zd; z7 = Binary.RotL(z7 ^ z8, 7); + // QUARTER(z3, z4, z9, ze); + z3 += z4; ze = Binary.RotL(ze ^ z3, 16); + z9 += ze; z4 = Binary.RotL(z4 ^ z9, 12); + z3 += z4; ze = Binary.RotL(ze ^ z3, 8); + z9 += ze; z4 = Binary.RotL(z4 ^ z9, 7); + } + + LittleEndian.Pack(z0, dst.Data, 0); + LittleEndian.Pack(z1, dst.Data, 4); + LittleEndian.Pack(z2, dst.Data, 8); + LittleEndian.Pack(z3, dst.Data, 12); + LittleEndian.Pack(z4, dst.Data, 16); + LittleEndian.Pack(z5, dst.Data, 20); + LittleEndian.Pack(z6, dst.Data, 24); + LittleEndian.Pack(z7, dst.Data, 28); + LittleEndian.Pack(z8, dst.Data, 32); + LittleEndian.Pack(z9, dst.Data, 36); + LittleEndian.Pack(za, dst.Data, 40); + LittleEndian.Pack(zb, dst.Data, 44); + LittleEndian.Pack(zc, dst.Data, 48); + LittleEndian.Pack(zd, dst.Data, 52); + LittleEndian.Pack(ze, dst.Data, 56); + LittleEndian.Pack(zf, dst.Data, 60); + } + + public void Decrypt(byte[] input, int input_pos, byte[] output, int output_pos, int length) + { + var state = new State (); + var num_block = length / 64; + var input_ptr = input_pos; + var output_ptr = output_pos; + + for (int i = 0; i < num_block; i++) + { + TransformState (m_state, state); + + for (int j = 0; j < 64; j += 4) + { + var val = LittleEndian.ToUInt32 (input, input_ptr+j) ^ (LittleEndian.ToUInt32 (m_state.Data, j) + LittleEndian.ToUInt32 (state.Data, j)); + LittleEndian.Pack (val, output, output_ptr+j); + } + + input_ptr += 64; + output_ptr += 64; + + for (int k = 0; ;) + { + if (++m_state.Data[48+k] != 0) + break; + k++; + if (k == 8) + break; + } + } + + var num_bytes_remaining = length & 63; + + if (num_bytes_remaining > 0) + { + TransformState (m_state, state); + + var temp = new byte[64]; + + for (int i = 0; i < 64; i += 4) + { + var val = LittleEndian.ToUInt32 (m_state.Data, i) + LittleEndian.ToUInt32 (state.Data, i); + LittleEndian.Pack (val, temp, i); + } + + for (int i = 0; i < num_bytes_remaining; i++) + { + output[output_ptr+i] = (byte)(input[input_ptr+i] ^ temp[i]); + } + } + } + } + + internal class HxIndexDeserializer + { + public static object Deserialize(Stream stream) + { + using (var reader = new BinaryReader(stream, Encoding.ASCII, true)) + { + var obj = ReadObject(reader); + Debug.Assert(stream.Position == stream.Length); + return obj; + } + } + + static object ReadObject(BinaryReader reader) + { + var type = reader.ReadByte(); + + switch (type) + { + case 0x00: + { + return null; + } + case 0x01: + { + return null; + } + case 0x02: + { + return ReadString(reader); + } + case 0x03: + { + return ReadByteArray(reader); + } + case 0x04: + { + return ReadInt64(reader); + } + case 0x05: + { + return ReadInt64(reader); + } + case 0x81: + { + return ReadArray(reader); + } + case 0xC1: + { + return ReadDictionary(reader); + } + default: + { + throw new Exception("unknown object type"); + } + } + } + + static object ReadByteArray(BinaryReader reader) + { + var count = ReadInt32(reader); + var array = reader.ReadBytes(count); + return array; + } + + static object ReadArray(BinaryReader reader) + { + var count = ReadInt32(reader); + + var array = new List(count); + + for (int i = 0; i < count; i++) + { + var obj = ReadObject(reader); + array.Add(obj); + } + + return array.ToArray(); + } + + static object ReadDictionary(BinaryReader reader) + { + var count = ReadInt32(reader); + + var dictionary = new Dictionary(count); + + for (int i = 0; i < count; i++) + { + var name = ReadString(reader); + var obj = ReadObject(reader); + dictionary.Add(name, obj); + } + + return dictionary; + } + + static int ReadInt32(BinaryReader reader) + { + return Binary.BigEndian(reader.ReadInt32()); + } + + static long ReadInt64(BinaryReader reader) + { + return Binary.BigEndian(reader.ReadInt64()); + } + + static string ReadString(BinaryReader reader) + { + var length = ReadInt32(reader); + var buffer = reader.ReadBytes(sizeof(short) * length); + return Encoding.Unicode.GetString(buffer); + } + } +}