diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 6c65618f..ede066a5 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -154,6 +154,7 @@ + diff --git a/ArcFormats/KiriKiri/ArcXP3.cs b/ArcFormats/KiriKiri/ArcXP3.cs index c1ff2bfb..07f8987c 100644 --- a/ArcFormats/KiriKiri/ArcXP3.cs +++ b/ArcFormats/KiriKiri/ArcXP3.cs @@ -250,6 +250,21 @@ namespace GameRes.Formats.KiriKiri dir.Add (entry); } } + else if (0x3A7A7579 == entry_signature) // "yuz:" + { + if (entry_size >= 0x10 && crypt_algorithm.Value is RiddleCxCrypt) + { + long offset = header.ReadInt64(); + header.ReadUInt32(); // unpacked size + uint size = header.ReadUInt32(); + if (offset > 0 && offset + size <= file.MaxOffset) + { + var yuz = file.View.ReadBytes (offset, size); + var crypt = crypt_algorithm.Value as RiddleCxCrypt; + crypt.ReadYuzNames (yuz, filename_map); + } + } + } else if (entry_size > 7) { // 0x6E666E68 == entry_signature // "hnfn" @@ -838,6 +853,11 @@ NextEntry: m_md5_map[GetMd5Hash (filename)] = filename; } + public void AddShortcut (string shortcut, string filename) + { + m_md5_map[shortcut] = filename; + } + public string Get (uint hash, string md5) { string filename; diff --git a/ArcFormats/KiriKiri/YuzCrypt.cs b/ArcFormats/KiriKiri/YuzCrypt.cs new file mode 100644 index 00000000..d7d9321f --- /dev/null +++ b/ArcFormats/KiriKiri/YuzCrypt.cs @@ -0,0 +1,245 @@ +//! \file YuzCrypt.cs +//! \date 2018 Apr 01 +//! \brief YuzuSoft 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 System; +using System.IO; +using System.Text; +using GameRes.Compression; +using GameRes.Utility; + +namespace GameRes.Formats.KiriKiri +{ + [Serializable] + public class RiddleCxCrypt : CxEncryption + { + uint m_random_seed; + + public uint[] YuzKey; + + public RiddleCxCrypt (CxScheme scheme, uint seed) : base (scheme) + { + m_random_seed = seed; + } + + internal override CxProgram NewProgram (uint seed) + { + return new CxProgramNana (seed, m_random_seed, ControlBlock); + } + + public override void Decrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count) + { + ProcessFirstBytes (entry, offset, buffer, pos, count); + base.Decrypt (entry, offset, buffer, pos, count); + } + + public override void Encrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count) + { + base.Encrypt (entry, offset, buffer, pos, count); + ProcessFirstBytes (entry, offset, buffer, pos, count); + } + + internal void ProcessFirstBytes (Xp3Entry entry, long offset, byte[] buffer, int pos, int count) + { + if (offset < 8 && count > 0) + { + ulong key = GetKeyFromHash (entry.Hash); + key >>= (int)offset << 3; + int first_chunk = Math.Min (count, 8); + for (int i = 0; i < first_chunk; ++i) + { + buffer[pos+i] ^= (byte)key; + key >>= 8; + } + } + } + + internal ulong GetKeyFromHash (uint hash) + { + uint lo = hash ^ 0x55555555; + uint hi = (hash << 13) ^ hash; + hi = (hi >> 17) ^ hi; + hi = hi ^ (hi << 5) ^ 0xAAAAAAAA; + return (ulong)hi << 32 | lo; + } + + internal void ReadYuzNames (byte[] yuz, FilenameMap filename_map) + { + if (null == YuzKey) + throw new InvalidEncryptionScheme(); + var decryptor = new YuzDecryptor (ControlBlock, YuzKey, YuzKey[4], YuzKey[5]); + decryptor.Decrypt (yuz, Math.Min (yuz.Length, 0x100)); + using (var ystream = new MemoryStream (yuz)) + using (var zstream = ZLibCompressor.DeCompress (ystream)) + using (var input = new BinaryReader (zstream, Encoding.Unicode)) + { + long dir_offset = 0; + while (-1 != input.PeekChar()) + { + uint entry_signature = input.ReadUInt32(); + long entry_size = input.ReadInt64(); + if (entry_size < 0) + return; + dir_offset += 12 + entry_size; + uint hash = input.ReadUInt32(); + int name_size = input.ReadInt16(); + if (name_size > 0) + { + entry_size -= 6; + if (name_size * 2 <= entry_size) + { + var filename = new string (input.ReadChars (name_size)); + filename_map.Add (hash, filename); + } + } + input.BaseStream.Position = dir_offset; + } + filename_map.AddShortcut ("$", "startup.tjs"); + } + } + } + + internal class YuzDecryptor + { + byte[] m_state; + + public YuzDecryptor (uint[] key1, uint[] key2, uint seed1, uint seed2) + { + m_state = new byte[64]; + Buffer.BlockCopy (key2, 0, m_state, 0, 16); + Buffer.BlockCopy (key1, 0, m_state, 16, 32); + LittleEndian.Pack (~0, m_state, 48); + LittleEndian.Pack (~0, m_state, 52); + LittleEndian.Pack (~seed1, m_state, 56); + LittleEndian.Pack (~seed2, m_state, 60); + } + + public void Decrypt (byte[] data, int length) + { + var state1 = new byte[64]; + var state2 = new byte[64]; + int i = 0; + ulong offset = 0; + while (length > 0) + { + Buffer.BlockCopy (m_state, 0, state1, 0, 64); + LittleEndian.Pack (~offset++, state1, 48); + TransformState (state1, state2, 8); + int count = Math.Min (0x40, length); + for (int j = 0; j < count; ++j) + { + data[i++] ^= state2[j]; + } + length -= count; + } + } + + uint[] tmp = new uint[16]; + + void TransformState (byte[] state1, byte[] target, int length) + { + for (int i = 0; i < 16; ++i) + { + tmp[i] = ~LittleEndian.ToUInt32 (state1, i * 4); + } + if (length > 0) + { + for (int count = ((length - 1) >> 1) + 1; count > 0; --count) + { + uint t1 = tmp[4] + tmp[0]; + uint t2 = Binary.RotL (t1 ^ tmp[12], 16); + uint t3 = t2 + tmp[8]; + uint t4 = Binary.RotL (tmp[4] ^ t3, 12); + uint t5 = t4 + t1; + uint t6 = Binary.RotL (t5 ^ t2, 8); + tmp[12] = t6; + t6 += t3; + tmp[4] = Binary.RotL (t4 ^ t6, 7); + t4 = Binary.RotL ((tmp[5] + tmp[1]) ^ tmp[13], 16); + t3 = Binary.RotL (tmp[5] ^ (t4 + tmp[9]), 12); + t2 = t3 + tmp[5] + tmp[1]; + tmp[13] = Binary.RotL (t2 ^ t4, 8); + tmp[9] += tmp[13] + t4; + tmp[5] = Binary.RotL (t3 ^ tmp[9], 7); + t4 = Binary.RotL ((tmp[6] + tmp[2]) ^ tmp[14], 16); + tmp[10] += t4; + t1 = Binary.RotL (tmp[6] ^ tmp[10], 12); + t3 = t1 + tmp[6] + tmp[2]; + tmp[14] = Binary.RotL (t3 ^ t4, 8); + tmp[6] = Binary.RotL (t1 ^ (tmp[14] + tmp[10]), 7); + tmp[10] += tmp[14]; + t4 = (tmp[7] + tmp[3]) ^ tmp[15]; + tmp[3] += tmp[7]; + t4 = Binary.RotL (t4, 16); + tmp[11] += t4; + t1 = Binary.RotL (tmp[7] ^ tmp[11], 12); + t4 ^= t1 + tmp[3]; + tmp[3] += t1; + t4 = Binary.RotL (t4, 8); + tmp[11] += t4; + t1 = Binary.RotL (t1 ^ tmp[11], 7); + t5 += tmp[5]; + t2 += tmp[6]; + t4 = Binary.RotL (t5 ^ t4, 16); + tmp[10] += t4; + tmp[5] = Binary.RotL (tmp[5] ^ tmp[10], 12); + tmp[0] = tmp[5] + t5; + t4 = Binary.RotL (tmp[0] ^ t4, 8); + tmp[15] = t4; + tmp[10] += t4; + tmp[5] = Binary.RotL (tmp[5] ^ tmp[10], 7); + tmp[12] = Binary.RotL (tmp[12] ^ t2, 16); + tmp[11] += tmp[12]; + t4 = Binary.RotL (tmp[11] ^ tmp[6], 12); + tmp[1] = t4 + t2; + tmp[12] = Binary.RotL (tmp[12] ^ tmp[1], 8); + tmp[11] += tmp[12]; + tmp[6] = Binary.RotL (t4 ^ tmp[11], 7); + t3 += t1; + t4 = Binary.RotL (tmp[13] ^ t3, 16); + t2 = t4 + t6; + t1 = Binary.RotL (t2 ^ t1, 12); + tmp[2] = t1 + t3; + tmp[13] = Binary.RotL (t4 ^ tmp[2], 8); + tmp[8] = tmp[13] + t2; + tmp[7] = Binary.RotL (tmp[8] ^ t1, 7); + t6 = Binary.RotL (tmp[14] ^ (tmp[4] + tmp[3]), 16); + t1 = Binary.RotL (tmp[4] ^ (t6 + tmp[9]), 12); + tmp[3] += t1 + tmp[4]; + t3 = Binary.RotL (t6 ^ tmp[3], 8); + tmp[9] += t3 + t6; + tmp[4] = Binary.RotL (t1 ^ tmp[9], 7); + tmp[14] = t3; + } + } + int pos = 0; + for (int i = 0; i < 16; ++i) + { + uint x = tmp[i] + ~LittleEndian.ToUInt32 (state1, pos); + LittleEndian.Pack (x, target, pos); + pos += 4; + } + } + } +}