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