//! \file CryptAlgorithms.cs //! \date Thu Feb 04 12:08:40 2016 //! \brief KiriKiri engine encryption algorithms. // // Copyright (C) 2016 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 GameRes.Utility; namespace GameRes.Formats.KiriKiri { [Serializable] public abstract class ICrypt { /// /// whether Adler32 checksum should be calculated after contents have been encrypted. /// public virtual bool HashAfterCrypt { get { return false; } } /// /// sometimes startup.tjs file is not encrypted. /// public bool StratupTjsNotEncrypted { get; set; } /// /// whether XP3 index is obfuscated: /// - duplicate entries /// - entries have additional dummy segments /// public bool ObfuscatedIndex { get; set; } public virtual byte Decrypt (Xp3Entry entry, long offset, byte value) { byte[] buffer = new byte[1] { value }; Decrypt (entry, offset, buffer, 0, 1); return buffer[0]; } public abstract void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count); public virtual void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { throw new NotImplementedException (Strings.arcStrings.MsgEncNotImplemented); } /// /// Perform necessary initialization specific to an archive being opened. /// public virtual void Init (ArcFile arc) { } } [Serializable] public class NoCrypt : ICrypt { public override byte Decrypt (Xp3Entry entry, long offset, byte value) { return value; } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { return; } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { return; } } [Serializable] public class FateCrypt : ICrypt { public override bool HashAfterCrypt { get { return true; } } public override byte Decrypt (Xp3Entry entry, long offset, byte value) { byte result = (byte)(value ^ 0x36); if (0x13 == offset) result ^= 1; else if (0x2ea29 == offset) result ^= 3; return result; } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { for (int i = 0; i < count; ++i) { values[pos+i] ^= 0x36; } if (offset > 0x2ea29) return; if (offset + count > 0x2ea29) values[pos+0x2ea29-offset] ^= 3; if (offset > 0x13) return; if (offset + count > 0x13) values[pos+0x13-offset] ^= 1; } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } } [Serializable] public class MizukakeCrypt : ICrypt { public override bool HashAfterCrypt { get { return true; } } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { if (offset <= 0x103 && offset + count > 0x103) values[pos+0x103-offset]--; for (int i = 0; i < count; ++i) { values[pos+i] ^= 0xB6; } if (offset > 0x3F82) return; if (offset + count > 0x3F82) values[pos+0x3F82-offset] ^= 1; if (offset > 0x83) return; if (offset + count > 0x83) values[pos+0x83-offset] ^= 3; } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { for (int i = 0; i < count; ++i) { values[pos+i] ^= 0xB6; } if (offset <= 0x3F82 && offset + count > 0x3F82) values[pos+0x3F82-offset] ^= 1; if (offset <= 0x83 && offset + count > 0x83) values[pos+0x83-offset] ^= 3; if (offset <= 0x103 && offset + count > 0x103) values[pos+0x103-offset]++; } } [Serializable] public class HashCrypt : ICrypt { public override byte Decrypt (Xp3Entry entry, long offset, byte value) { return (byte)(value ^ entry.Hash); } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { byte key = (byte)entry.Hash; for (int i = 0; i < count; ++i) { values[pos+i] ^= key; } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } } [Serializable] public class XorCrypt : ICrypt { private byte m_key; public byte Key { get { return m_key; } set { m_key = value; } } public XorCrypt (uint key) { m_key = (byte)key; } public override byte Decrypt (Xp3Entry entry, long offset, byte value) { return (byte)(value ^ m_key); } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { for (int i = 0; i < count; ++i) { values[pos+i] ^= m_key; } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } } [Serializable] public class FlyingShineCrypt : ICrypt { static private byte Adjust (uint hash, out int shift) { shift = (int)(hash & 0xff); if (0 == shift) shift = 0x0f; byte key = (byte)(hash >> 8); if (0 == key) key = 0xf0; return key; } public override byte Decrypt (Xp3Entry entry, long offset, byte value) { int shift; byte xor = Adjust (entry.Hash, out shift); return Binary.RotByteR ((byte)(value ^ xor), shift); } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { int shift; byte xor = Adjust (entry.Hash, out shift); for (int i = 0; i < count; ++i) { byte data = (byte)(values[pos+i] ^ xor); values[pos+i] = Binary.RotByteR (data, shift); } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { int shift; byte xor = Adjust (entry.Hash, out shift); for (int i = 0; i < count; ++i) { byte data = Binary.RotByteL (values[pos+i], shift); values[pos+i] = (byte)(data ^ xor); } } } [Serializable] public class SeitenCrypt : ICrypt { public override byte Decrypt (Xp3Entry entry, long offset, byte value) { uint key = entry.Hash ^ (uint)offset; if (0 != (key & 2)) { int ecx = (int)key & 0x18; value ^= (byte)((key >> ecx) | (key >> (ecx & 8))); } if (0 != (key & 4)) { value += (byte)key; } if (0 != (key & 8)) { value -= (byte)(key >> (int)(key & 0x10)); } return value; } public override void Decrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count) { for (int i = 0; i < count; ++i) { int shift; uint key = entry.Hash ^ (uint)offset; byte v = buffer[pos+i]; if (0 != (key & 2)) { shift = (int)key & 0x18; uint ebx = key >> shift; shift &= 8; v ^= (byte)(ebx | (key >> shift)); } if (0 != (key & 4)) { v += (byte)key; } if (0 != (key & 8)) { shift = (int)key & 0x10; v -= (byte)(key >> shift); } buffer[pos+i] = v; ++offset; } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { for (int i = 0; i < count; ++i) { uint key = entry.Hash ^ (uint)offset; if (0 != (key & 8)) { values[pos+i] += (byte)(key >> (int)(key & 0x10)); } if (0 != (key & 4)) { values[pos+i] -= (byte)key; } if (0 != (key & 2)) { int ecx = (int)key & 0x18; values[pos+i] ^= (byte)((key >> ecx) | (key >> (ecx & 8))); } } } } [Serializable] public class OkibaCrypt : ICrypt { public override byte Decrypt (Xp3Entry entry, long offset, byte value) { if (offset < 0x65) return (byte)(value ^ (byte)(entry.Hash >> 4)); uint key = entry.Hash; // 0,1,2,3 -> 1,0,3,2 key = ((key & 0xff0000) << 8) | ((key & 0xff000000) >> 8) | ((key & 0xff00) >> 8) | ((key & 0xff) << 8); key >>= 8 * ((int)(offset - 0x65) & 3); return (byte)(value ^ (byte)key); } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { int i = 0; if (offset < 0x65) { uint key = entry.Hash >> 4; int limit = Math.Min (count, (int)(0x65 - offset)); for (; i < limit; ++i) { values[pos+i] ^= (byte)key; ++offset; } } if (i < count) { offset -= 0x65; uint key = entry.Hash; key = ((key & 0xff0000) << 8) | ((key & 0xff000000) >> 8) | ((key & 0xff00) >> 8) | ((key & 0xff) << 8); do { values[pos+i] ^= (byte)(key >> (8 * ((int)offset & 3))); ++offset; } while (++i < count); } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } } [Serializable] public class DieselmineCrypt : ICrypt { public override byte Decrypt (Xp3Entry entry, long offset, byte value) { byte key = (byte)entry.Hash; if (offset < 123) value ^= (byte)(21 * key); else if (offset < 246) value += (byte)(-32 * key); else if (offset < 369) value ^= (byte)(43 * key); else if (offset <= 0xffffffffL) value += (byte)(-54 * key); return value; } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { byte key = (byte)entry.Hash; for (int i = 0; i < count && offset <= 0xffffffffL; ++i, ++offset) { if (offset < 123) values[pos+i] ^= (byte)(21 * key); else if (offset < 246) values[pos+i] += (byte)(-32 * key); else if (offset < 369) values[pos+i] ^= (byte)(43 * key); else values[pos+i] += (byte)(-54 * key); } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { byte key = (byte)entry.Hash; for (int i = 0; i < count && offset <= 0xffffffffL; ++i, ++offset) { if (offset < 123) values[pos+i] ^= (byte)(21 * key); else if (offset < 246) values[pos+i] -= (byte)(-32 * key); else if (offset < 369) values[pos+i] ^= (byte)(43 * key); else values[pos+i] -= (byte)(-54 * key); } } } [Serializable] public class DameganeCrypt : ICrypt { public override byte Decrypt (Xp3Entry entry, long offset, byte value) { if (0 != (offset & 1)) return (byte)(value ^ entry.Hash); else return (byte)(value ^ offset); } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { for (int i = 0; i < count; ++i, ++offset) { if (0 != (offset & 1)) values[pos+i] ^= (byte)entry.Hash; else values[pos+i] ^= (byte)offset; } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } } [Serializable] public class GakuenButouCrypt : ICrypt { public override byte Decrypt (Xp3Entry entry, long offset, byte value) { if (0 != (offset & 1)) return (byte)(value ^ offset); else return (byte)(value ^ entry.Hash); } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { for (int i = 0; i < count; ++i, ++offset) { if (0 != (offset & 1)) values[pos+i] ^= (byte)offset; else values[pos+i] ^= (byte)entry.Hash; } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } } [Serializable] public class AlteredPinkCrypt : ICrypt { static readonly byte[] KeyTable = { 0x43, 0xF8, 0xAD, 0x08, 0xDF, 0xB7, 0x26, 0x44, 0xF0, 0xD9, 0xE9, 0x24, 0x1A, 0xC1, 0xEE, 0xB4, 0x11, 0x4B, 0xE4, 0xAF, 0x01, 0x5B, 0xF0, 0xAB, 0x6A, 0x70, 0x78, 0x84, 0xB0, 0x78, 0x4F, 0xED, 0x39, 0x52, 0x69, 0xAF, 0xC4, 0x92, 0x2A, 0x21, 0xDE, 0xDC, 0x6E, 0x63, 0x9D, 0x9B, 0x63, 0xE1, 0xB1, 0x94, 0x40, 0x6E, 0x3A, 0x52, 0x5A, 0x28, 0x08, 0x4D, 0xFB, 0x22, 0x18, 0xEB, 0xBA, 0x98, 0x49, 0x77, 0xBF, 0xAA, 0x43, 0x75, 0xF5, 0xD3, 0x83, 0x71, 0x58, 0xA4, 0xAF, 0x1B, 0x53, 0x99, 0x8A, 0x27, 0x5B, 0xC2, 0x7F, 0x7A, 0xCD, 0x8D, 0x33, 0x59, 0xEB, 0xA6, 0xFA, 0x7C, 0x00, 0x19, 0xC4, 0xAA, 0x24, 0xF8, 0x84, 0xCD, 0xF7, 0x20, 0x4B, 0xAB, 0xF1, 0xD5, 0x01, 0x6F, 0x7C, 0x91, 0x08, 0x7D, 0x8D, 0x89, 0x7C, 0x71, 0x65, 0x99, 0x9B, 0x6F, 0x3A, 0x1C, 0x49, 0xE3, 0xAF, 0x1F, 0xC6, 0xA5, 0x79, 0xFE, 0xAE, 0xA1, 0xCA, 0x59, 0x3C, 0xEE, 0xC1, 0x02, 0xBD, 0x2B, 0x8E, 0xC5, 0x7D, 0x38, 0x80, 0x8F, 0x72, 0xF3, 0x86, 0x5D, 0xF4, 0x20, 0x0A, 0x5B, 0xA0, 0xE3, 0x85, 0xB5, 0x67, 0x43, 0x96, 0xBB, 0x75, 0x86, 0x8D, 0x7E, 0x7E, 0xE6, 0xAA, 0x18, 0x57, 0xC4, 0xAA, 0x87, 0xDC, 0x74, 0x05, 0xAA, 0xBD, 0x5E, 0x4F, 0xA9, 0xB5, 0x5E, 0xC5, 0xE8, 0x11, 0x6D, 0x68, 0x89, 0x17, 0x7C, 0x10, 0x05, 0xA2, 0xBA, 0x43, 0x01, 0xD6, 0xFD, 0x26, 0x19, 0x57, 0xFA, 0x4D, 0x01, 0xB0, 0xED, 0x3A, 0x55, 0xEB, 0x65, 0x8E, 0xD1, 0x58, 0x27, 0xAD, 0xA1, 0x5E, 0x57, 0x3F, 0xA0, 0xEF, 0x59, 0x3E, 0xA4, 0xEB, 0x12, 0x15, 0x60, 0xBE, 0x95, 0x61, 0x0B, 0x98, 0xF5, 0xF4, 0x12, 0x1C, 0xD8, 0x62, 0x3F, 0xFD, 0xCF, 0x01, 0x3A, 0xE7, 0xC2, 0x19, 0x38, 0x6C, 0xC3, 0x90, 0x3E, }; public override byte Decrypt (Xp3Entry entry, long offset, byte value) { return (byte)(value ^ KeyTable[offset & 0xFF]); } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { for (int i = 0; i < count; ++i) { values[pos+i] ^= KeyTable[(offset+i) & 0xFF]; } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } } [Serializable] public class NatsupochiCrypt : ICrypt { public override byte Decrypt (Xp3Entry entry, long offset, byte value) { return (byte)(value ^ (entry.Hash >> 3)); } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { byte key = (byte)(entry.Hash >> 3); for (int i = 0; i < count; ++i) { values[pos+i] ^= key; } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } } [Serializable] public class PoringSoftCrypt : ICrypt { public override byte Decrypt (Xp3Entry entry, long offset, byte value) { return (byte)~(value ^ (entry.Hash + 1)); } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { byte key = (byte)~(entry.Hash + 1); for (int i = 0; i < count; ++i) { values[pos+i] ^= key; } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } } [Serializable] public class AppliqueCrypt : ICrypt { public override byte Decrypt (Xp3Entry entry, long offset, byte value) { return (byte)(value ^ (entry.Hash >> 12)); } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { byte key = (byte)(entry.Hash >> 12); for (int i = 0; i < count; ++i) { values[pos+i] ^= key; } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } } [Serializable] public class TokidokiCrypt : ICrypt { public override bool HashAfterCrypt { get { return true; } } public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { uint key; uint limit = GetParameters (entry, out key); for (int i = 0; i < count && offset < limit; ++i, ++offset) { values[pos+i] ^= (byte)(key >> (((int)offset & 3) << 3)); } } public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count) { Decrypt (entry, offset, values, pos, count); } uint GetParameters (Xp3Entry entry, out uint key) { var ext = System.IO.Path.GetExtension (entry.Name); if (!string.IsNullOrEmpty (ext)) { ext = ext.ToLowerInvariant(); var ext_bin = new byte[16]; Encodings.cp932.GetBytes (ext, 0, Math.Min (4, ext.Length), ext_bin, 0); key = ~LittleEndian.ToUInt32 (ext_bin, 0); if (".asd\0.ks\0.tjs\0".Contains (ext+'\0')) return entry.Size; } else key = uint.MaxValue; return Math.Min (entry.Size, 0x100u); } } }