added some XP3 encryption schemes.

This commit is contained in:
morkt 2016-03-07 18:23:10 +04:00
parent f377b0088c
commit 9beff31fee
6 changed files with 263 additions and 3 deletions

View File

@ -66,12 +66,16 @@
<Compile Include="Abel\ImageGPS.cs" />
<Compile Include="Actgs\ArcDAT.cs" />
<Compile Include="ArcCG.cs" />
<Compile Include="ArcZIP.cs" />
<Compile Include="Cri\ArcCPK.cs" />
<Compile Include="Cri\AudioHCA.cs" />
<Compile Include="Cri\BigEndianReader.cs" />
<Compile Include="Cri\ImageXTX.cs" />
<Compile Include="Entis\ErisaMatrix.cs" />
<Compile Include="Hexenhaus\ArcARCC.cs" />
<Compile Include="ImageLZ.cs" />
<Compile Include="KiriKiri\ChainReactionCrypt.cs" />
<Compile Include="MnoViolet\ImageDIF.cs" />
<Compile Include="NitroPlus\ArcNPK.cs" />
<Compile Include="SimpleEncryption.cs" />
<Compile Include="Softpal\ArcPAC.cs" />

View File

@ -259,7 +259,10 @@ NextEntry:
header.BaseStream.Position = dir_offset;
}
}
return new ArcFile (file, this, dir);
var arc = new ArcFile (file, this, dir);
if (crypt_algorithm.IsValueCreated)
crypt_algorithm.Value.Init (arc);
return arc;
}
static readonly Regex ObfuscatedPathRe = new Regex (@"[^\\/]+[\\/]\.\.[\\/]");

View File

@ -0,0 +1,206 @@
//! \file ChainReactionCrypt.cs
//! \date Mon Mar 07 15:59:47 2016
//! \brief KiriKiri XP3 ecryption filter used in some games.
//
// 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 System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using GameRes.Compression;
using GameRes.Utility;
namespace GameRes.Formats.KiriKiri
{
// this encryption scheme encrypts first N bytes of file, where N varies depending on file's hash, and
// those variations are stored within "plugin/list.bin" file. by default N=512 (used when hash is not
// found within "list.bin").
//
// this implementation looks for "list.bin" upon archive open, parses it and remembers encryption
// threshold values in a dictionary.
//
// such implementation has some flaws, for one, it would fail if "list.bin" is stored within archive other
// than one being opened.
[Serializable]
public class ChainReactionCrypt : ICrypt
{
public override void Decrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count)
{
uint limit = GetEncryptionLimit (entry);
uint key = entry.Hash;
for (int i = 0; i < count && offset < limit; ++i, ++offset)
{
values[pos+i] ^= (byte)(offset ^ (key >> (((int)offset & 3) << 3)));
}
}
public override void Encrypt (Xp3Entry entry, long offset, byte[] values, int pos, int count)
{
throw new NotImplementedException (Strings.arcStrings.MsgEncNotImplemented);
// despite the fact that algorithm is symmetric, creating an archive without updating "list.bin"
// wouldn't make much sense
// Decrypt (entry, offset, values, pos, count);
}
uint GetEncryptionLimit (Xp3Entry entry)
{
uint limit;
if (EncryptionThresholdMap != null && EncryptionThresholdMap.TryGetValue (entry.Hash, out limit))
return limit;
else
return 0x200;
}
[NonSerialized]
Dictionary<uint, uint> EncryptionThresholdMap;
public override void Init (ArcFile arc)
{
var list_bin = arc.Dir.Where (e => e.Name == "plugin/list.bin").FirstOrDefault() as Xp3Entry;
if (null == list_bin || list_bin.UnpackedSize <= 0x30)
return;
var bin = new byte[list_bin.UnpackedSize];
using (var input = arc.OpenEntry (list_bin))
input.Read (bin, 0, bin.Length);
for (int i = 0; i < 3; ++i)
{
bin = DecodeListBin (bin);
if (null == bin)
return;
}
if (null == EncryptionThresholdMap)
EncryptionThresholdMap = new Dictionary<uint, uint>();
else
EncryptionThresholdMap.Clear();
ParseListBin (bin);
}
void ParseListBin (byte[] data)
{
using (var mem = new MemoryStream (data))
using (var input = new StreamReader (mem))
{
var converter = new UInt32Converter();
string line;
while ((line = input.ReadLine()) != null)
{
if (0 == line.Length || '0' != line[0])
continue;
var pair = line.Split (',');
if (pair.Length > 1)
{
uint hash = (uint)converter.ConvertFromString (pair[0]);
uint threshold = (uint)converter.ConvertFromString (pair[1]);
EncryptionThresholdMap[hash] = threshold;
}
}
}
}
static byte[] DecodeListBin (byte[] data)
{
var header = new byte[0x30];
DecodeDPD (data, 0, 0x30, header);
int packed_size = LittleEndian.ToInt32 (header, 0x0C);
int unpacked_size = LittleEndian.ToInt32 (header, 0x10);
if (packed_size <= 0 || packed_size > data.Length-0x30)
return null;
if (Binary.AsciiEqual (header, 0, "DPDC"))
{
var decrypted = new byte[packed_size];
DecodeDPD (data, 0x30, packed_size, decrypted);
return decrypted;
}
if (Binary.AsciiEqual (header, 0, "SZLC")) // LZSS
{
using (var input = new MemoryStream (data, 0x30, packed_size))
using (var lzss = new LzssReader (input, packed_size, unpacked_size))
{
lzss.Unpack();
return lzss.Data;
}
}
if (Binary.AsciiEqual (header, 0, "ELRC")) // RLE
{
var unpacked = new byte[unpacked_size];
int min_repeat = LittleEndian.ToInt32 (header, 0x1C);
DecodeRLE (data, 0x30, packed_size, unpacked, min_repeat);
return unpacked;
}
return null;
}
static void DecodeRLE (byte[] input, int offset, int length, byte[] output, int min_repeat)
{
int src = offset;
int src_end = offset+length;
int dst = 0;
while (src < src_end)
{
byte b = input[src++];
int repeat = 1;
while (repeat < min_repeat && src < src_end && input[src] == b)
{
++repeat;
++src;
}
if (repeat == min_repeat)
{
byte ctl = input[src++];
if (ctl > 0x7F)
repeat += input[src++] + ((ctl & 0x7F) << 8) + 0x80;
else
repeat += ctl;
}
for (int i = 0; i < repeat; ++i)
output[dst++] = b;
}
}
unsafe static void DecodeDPD (byte[] src, int offset, int length, byte[] dst)
{
if (offset > src.Length || length > dst.Length || length > src.Length - offset)
throw new IndexOutOfRangeException();
if (length < 8)
return;
int tail = length & 3;
if (tail != 0)
Buffer.BlockCopy (src, offset+length-tail, dst, length-tail, tail);
length /= 4;
fixed (byte* src8 = &src[offset], dst8 = dst)
{
uint* src32 = (uint*)src8;
uint* dst32 = (uint*)dst8;
for (int i = 0; i < length-1; ++i)
{
dst32[i] = src32[i] ^ src32[i+1];
}
dst32[length-1] = dst32[0] ^ src32[length-1];
}
}
}
}

View File

@ -56,6 +56,13 @@ namespace GameRes.Formats.KiriKiri
{
throw new NotImplementedException (Strings.arcStrings.MsgEncNotImplemented);
}
/// <summary>
/// Perform necessary initialization specific to an archive being opened.
/// </summary>
public virtual void Init (ArcFile arc)
{
}
}
[Serializable]
@ -112,6 +119,44 @@ namespace GameRes.Formats.KiriKiri
}
}
[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
{
@ -504,7 +549,7 @@ namespace GameRes.Formats.KiriKiri
}
[Serializable]
public class IncubusCrypt : ICrypt
public class PoringSoftCrypt : ICrypt
{
public override byte Decrypt (Xp3Entry entry, long offset, byte value)
{
@ -578,7 +623,7 @@ namespace GameRes.Formats.KiriKiri
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.ks.tjs".Contains (ext))
if (".asd\0.ks\0.tjs\0".Contains (ext+'\0'))
return entry.Size;
}
else

Binary file not shown.

View File

@ -44,6 +44,7 @@ Salmon Pink<br/>
Shisho-san to Issho<br/>
Sensei 2<br/>
Shoujo Settai<br/>
Suzuri-sensei to 26-ko no Ecchi na Oppai<br/>
Tsukushite Agechau series<br/>
</td></tr>
<tr class="odd"><td>*.ggd</td><td><tt>\xB9\xAA\xB3\xB3</tt><br/><tt>\xAB\xAD\xAA\xBA</tt><br/><tt>\xB7\xB6\xB8\xB7</tt><br/><tt>\xCD\xCA\xC9\xB8</tt></td><td>Yes</td></tr>
@ -199,6 +200,7 @@ Imouto Style<br/>
Inaho no Mirai</br>
Mayoeru Futari to Sekai no Subete<br/>
Mahoutsukai no Yoru<br/>
Nakadashi Hara Maid series<br/>
Natsupochi<br/>
Nidaime wa ☆ Mahou Shoujo<br/>
Nuki Doki!<br/>