diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index b9fafad6..da36c74d 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -239,6 +239,7 @@ + diff --git a/ArcFormats/KiriKiri/HxCryptLite.cs b/ArcFormats/KiriKiri/HxCryptLite.cs new file mode 100644 index 00000000..4d49a2b2 --- /dev/null +++ b/ArcFormats/KiriKiri/HxCryptLite.cs @@ -0,0 +1,349 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace GameRes.Formats.KiriKiri +{ + /// + /// Wamsoft HxCrypt无Hash青春版 (CxdecV1.9) + /// + [Serializable] + public class HxCryptLite : CxEncryption + { + private byte[] mHeaderKey; + private long mHeaderSplitPosition; + + private bool mFileCryptFlag; + private int mRandomType; + + public HxCryptLite(CxScheme scheme) : base(scheme) { } + + /// + /// 设置首次解密参数 + /// + /// 长度大于等于8时启用, 否则不启用 + /// + public void SetHeaderDecryptParam(byte[] key, long splitPos) + { + this.mHeaderKey = key; + this.mHeaderSplitPosition = splitPos; + } + + /// + /// 设置文件解密中的单字节解密模式 + /// + public void SetSingleByteCryptFlag(bool flag) + { + this.mFileCryptFlag = flag; + } + + /// + /// 设置随机数算法模式 + /// + public void SetRandomType(int type) + { + this.mRandomType = type; + } + + public override void Init(ArcFile arc) + { + return; + } + + public override byte Decrypt(Xp3Entry entry, long offset, byte value) + { + var buf = new byte[1] { value }; + this.Decrypt(entry, offset, buf, 0, 1); + return buf[0]; + } + + + public override void Decrypt(Xp3Entry entry, long offset, byte[] buffer, int pos, int count) + { + if (this.mHeaderKey != null) + { + HxHeaderDecryptor headerDecryptor = new HxHeaderDecryptor(entry.Hash, this.mHeaderKey, this.mHeaderSplitPosition); + headerDecryptor.Decrypt(buffer, offset, pos, count); + } + + ulong key1, key2; + { + Tuple ret1 = ExecuteXCode(entry.Hash); + Tuple ret2 = ExecuteXCode(entry.Hash ^ (entry.Hash >> 16)); + + key1 = ((ulong)ret1.Item2 << 32) | ret1.Item1; + key2 = ((ulong)ret2.Item2 << 32) | ret2.Item1; + } + + long splitPosition = m_offset + (entry.Hash & m_mask); + + HxFileDecryptor fileDecryptor1 = new HxFileDecryptor(key1, this.mFileCryptFlag); + HxFileDecryptor fileDecryptor2 = new HxFileDecryptor(key2, this.mFileCryptFlag); + + if (splitPosition > offset) + { + if (splitPosition < offset + count) + { + long blockLen1 = splitPosition - offset; + long blockLen2 = offset + count - splitPosition; + + fileDecryptor1.Decrypt(buffer, offset, pos, (int)blockLen1); + fileDecryptor2.Decrypt(buffer, offset + blockLen1, (int)(pos + blockLen1), (int)blockLen2); + } + else + { + fileDecryptor1.Decrypt(buffer, offset, pos, count); + } + } + else + { + fileDecryptor2.Decrypt(buffer, offset, pos, count); + } + } + + 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 HxProgramLite(seed, this.ControlBlock, this.mRandomType); + } + } + + internal class HxFileDecryptor + { + private long mSplitPosition1; + private long mSplitPosition2; + private uint mGlobalKey; + private byte mKey1; + private byte mKey2; + + public HxFileDecryptor(ulong key, bool fileKeyFlag) + { + byte[] keyPtr = BitConverter.GetBytes(key); + + this.mGlobalKey = keyPtr[0]; + this.mKey1 = keyPtr[1]; + this.mKey2 = keyPtr[2]; + + this.mSplitPosition1 = BitConverter.ToUInt16(keyPtr, 6); + this.mSplitPosition2 = BitConverter.ToUInt16(keyPtr, 4); + + if (this.mSplitPosition1 == this.mSplitPosition2) + { + this.mSplitPosition2 += 1; + } + + if (this.mGlobalKey == 0) + { + this.mGlobalKey = 1; + } + + this.mGlobalKey *= 0x01010101; + + if (fileKeyFlag) + { + this.mKey1 = 0; + this.mKey2 = 0; + } + } + + + public unsafe void Decrypt(byte[] data, long offset, int position, int count) + { + int dataLen = count; + + if (dataLen == 0) + { + return; + } + + //全局解密 + { + byte[] key = BitConverter.GetBytes(this.mGlobalKey); + + int keyIndex = (int)(offset & 3); + + for (int i = 0; i < dataLen; ++i) + { + data[i + position] ^= key[keyIndex]; + + ++keyIndex; + keyIndex &= 3; + } + } + + //第一个解密点 + if (this.mSplitPosition1 >= offset && this.mSplitPosition1 < offset + dataLen) + { + data[(int)(this.mSplitPosition1 - offset) + position] ^= this.mKey1; + } + //第二个解密点 + if (this.mSplitPosition2 >= offset && this.mSplitPosition2 < offset + dataLen) + { + data[(int)(this.mSplitPosition2 - offset) + position] ^= this.mKey2; + } + } + } + + + internal class HxHeaderDecryptor + { + private byte[] mKey = null; // 解密Key + private long mSplitPosition = 0; // 分界点 + + public HxHeaderDecryptor(uint hash, byte[] key, long splitPos) + { + if (key != null && key.Length >= 8) + { + this.mSplitPosition = splitPos; + + uint[] keyPtr = new uint[2] { BitConverter.ToUInt32(key, 0), BitConverter.ToUInt32(key, 4) }; + + uint s0 = hash ^ keyPtr[1]; + uint s1 = hash ^ (hash << 13); + uint s2 = s1 ^ (s1 >> 17); + uint s3 = s2 ^ (s2 << 5) ^ keyPtr[0]; + + this.mKey = BitConverter.GetBytes(((ulong)s3 << 32) | s0); + } + } + + public void Decrypt(byte[] data, long offset, int position, int count) + { + if (this.mKey is null) + { + return; + } + + int keyLen = this.mKey.Length; + int dataLen = count; + + //起始解密位置 + long startPos = offset; + if (startPos <= this.mSplitPosition) + { + startPos = this.mSplitPosition; + } + + //结束解密位置 + long endPos = offset + dataLen; + if (endPos >= this.mSplitPosition + keyLen) + { + endPos = this.mSplitPosition + keyLen; + } + + if (startPos >= endPos) + { + return; + } + + //解密长度与解密起始索引 + long decryptLen = endPos - startPos; + long keyStartIndex = startPos - this.mSplitPosition; + long dataStartIndex = startPos - offset + position; + + //解密 + for (int i = 0; i < decryptLen; ++i) + { + data[(int)(dataStartIndex + i)] ^= this.mKey[keyStartIndex + i]; + } + } + } + + internal class HxProgramLite : CxProgram + { + private readonly int mRandomType; //随机数方法类型 + private readonly uint[] mRandomBlock; //随机子表 + private int mBlockPosition; //表当前位置 + + public HxProgramLite(uint seed, uint[] control_block, int random_method) : base(seed, control_block) + { + this.mRandomType = random_method; + + //生成子表 + { + this.mBlockPosition = 0x270; + + uint[] block = new uint[0x270]; + + block[0] = seed; + + for (int i = 1; i < block.Length; ++i) + { + block[i] = (block[i - 1] ^ (block[i - 1] >> 0x1E)) * 0x6C078965 + (uint)i; + } + + this.mRandomBlock = block; + } + } + + public override uint GetRandom() + { + if (this.mRandomType == 0) + { + return base.GetRandom(); + } + else + { + return this.GetRandomNew(); + } + } + + private uint GetRandomNew() + { + if (this.mBlockPosition == this.mRandomBlock.Length) + { + this.TransformBlock(); + } + + uint s0 = this.mRandomBlock[this.mBlockPosition]; + uint s1 = (s0 >> 11) ^ s0; + uint s2 = ((s1 & 0xFF3A58AD) << 7) ^ s1; + uint s3 = ((s2 & 0xFFFFDF8C) << 15) ^ s2; + uint s4 = (s3 >> 18) ^ s3; + + ++this.mBlockPosition; + + return s4; + } + + private void TransformBlock() + { + this.mBlockPosition = 0; + + uint[] block = this.mRandomBlock; + + //0-0xE2 + for (int i = 0; i < 0xE3; ++i) + { + uint s0 = (block[i + 1] & 1) != 0 ? 0x9908B0DF : 0; + uint s1 = (((block[i] ^ block[i + 1]) & 0x7FFFFFFE) ^ block[i]) >> 1; + uint s2 = s0 ^ s1 ^ block[i + 0x18D]; + + block[i] = s2; + } + + //0xE3-0x26E + for (int i = 0; i < 0x18C; ++i) + { + uint s0 = (block[i + 1 + 0xE3] & 1) != 0 ? 0x9908B0DF : 0; + uint s1 = (((block[i + 0xE3] ^ block[i + 1 + 0xE3]) & 0x7FFFFFFE) ^ block[i + 0xE3]) >> 1; + uint s2 = s0 ^ s1 ^ block[i]; + + block[i + 0xE3] = s2; + } + + //0x26F + { + uint s0 = (block[0] & 1) != 0 ? 0x9908B0DF : 0; + uint s1 = (((block[0x26F] ^ block[0]) & 0x7FFFFFFE) ^ block[0x26F]) >> 1; + uint s2 = s0 ^ s1 ^ block[0x18C]; + + block[0x26F] = s2; + } + } + } +} diff --git a/ArcFormats/Resources/Formats.dat b/ArcFormats/Resources/Formats.dat index ccf6cb4d..2f6b7c66 100644 Binary files a/ArcFormats/Resources/Formats.dat and b/ArcFormats/Resources/Formats.dat differ