mirror of
https://github.com/crskycode/GARbro.git
synced 2024-12-29 22:34:13 +08:00
1287 lines
46 KiB
C#
1287 lines
46 KiB
C#
//! \file ArcWARC.cs
|
||
//! \date Fri Apr 10 03:10:42 2015
|
||
//! \brief ShiinaRio engine archive format.
|
||
//
|
||
// Copyright (C) 2015 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.Composition;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Runtime.InteropServices;
|
||
using System.Windows;
|
||
using System.Windows.Media.Imaging;
|
||
using GameRes.Compression;
|
||
using GameRes.Formats.Properties;
|
||
using GameRes.Formats.Strings;
|
||
using GameRes.Utility;
|
||
|
||
namespace GameRes.Formats.ShiinaRio // 椎名里緒
|
||
{
|
||
internal class WarOptions : ResourceOptions
|
||
{
|
||
public EncryptionScheme Scheme { get; set; }
|
||
}
|
||
|
||
internal class WarcEntry : PackedEntry
|
||
{
|
||
public long FileTime;
|
||
public uint Flags;
|
||
}
|
||
|
||
internal class WarcFile : ArcFile
|
||
{
|
||
public readonly Decoder Decoder;
|
||
|
||
public WarcFile (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, Decoder decoder)
|
||
: base (arc, impl, dir)
|
||
{
|
||
this.Decoder = decoder;
|
||
}
|
||
}
|
||
|
||
[Export(typeof(ArchiveFormat))]
|
||
public class WarOpener : ArchiveFormat
|
||
{
|
||
public override string Tag { get { return "WAR"; } }
|
||
public override string Description { get { return "ShiinaRio engine resource archive"; } }
|
||
public override uint Signature { get { return 0x43524157; } } // 'WARC'
|
||
public override bool IsHierarchic { get { return false; } }
|
||
public override bool CanCreate { get { return false; } }
|
||
|
||
public override ArcFile TryOpen (ArcView file)
|
||
{
|
||
if (!file.View.AsciiEqual (4, " 1."))
|
||
return null;
|
||
int version = file.View.ReadByte (7) - 0x30;
|
||
version = 100 + version * 10;
|
||
if (170 != version && 130 != version && 150 != version)
|
||
throw new NotSupportedException ("Not supported WARC version");
|
||
uint index_offset = 0xf182ad82u ^ file.View.ReadUInt32 (8);
|
||
if (index_offset >= file.MaxOffset)
|
||
return null;
|
||
|
||
var scheme = QueryEncryption();
|
||
if (null == scheme)
|
||
return null;
|
||
var decoder = new Decoder (version, scheme);
|
||
|
||
uint max_index_len = decoder.MaxIndexLength;
|
||
uint index_length = (uint)Math.Min (max_index_len, file.MaxOffset - index_offset);
|
||
if (index_length < 8)
|
||
return null;
|
||
var enc_index = new byte[max_index_len];
|
||
if (index_length != file.View.Read (index_offset, enc_index, 0, index_length))
|
||
return null;
|
||
decoder.DecryptIndex (index_offset, enc_index);
|
||
Stream index;
|
||
if (version >= 170)
|
||
{
|
||
if (0x78 != enc_index[8]) // check if it looks like ZLib stream
|
||
return null;
|
||
var zindex = new MemoryStream (enc_index, 8, (int)index_length-8);
|
||
index = new ZLibStream (zindex, CompressionMode.Decompress);
|
||
}
|
||
else
|
||
{
|
||
var unpacked = new byte[max_index_len];
|
||
index_length = UnpackRNG (enc_index, 0, index_length, unpacked);
|
||
if (0 == index_length)
|
||
return null;
|
||
index = new MemoryStream (unpacked, 0, (int)index_length);
|
||
}
|
||
using (var header = new BinaryReader (index))
|
||
{
|
||
byte[] name_buf = new byte[decoder.EntryNameSize];
|
||
var dir = new List<Entry> ();
|
||
while (name_buf.Length == header.Read (name_buf, 0, name_buf.Length))
|
||
{
|
||
var name = Binary.GetCString (name_buf, 0, name_buf.Length);
|
||
var entry = new WarcEntry {
|
||
Name = name,
|
||
Type = FormatCatalog.Instance.GetTypeFromName (name)
|
||
};
|
||
entry.Offset = header.ReadUInt32();
|
||
entry.Size = header.ReadUInt32();
|
||
if (!entry.CheckPlacement (file.MaxOffset))
|
||
return null;
|
||
entry.UnpackedSize = header.ReadUInt32();
|
||
entry.IsPacked = entry.Size != entry.UnpackedSize;
|
||
entry.FileTime = header.ReadInt64();
|
||
entry.Flags = header.ReadUInt32();
|
||
if (0 != name.Length)
|
||
dir.Add (entry);
|
||
}
|
||
if (0 == dir.Count)
|
||
return null;
|
||
return new WarcFile (file, this, dir, decoder);
|
||
}
|
||
}
|
||
|
||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||
{
|
||
var warc = arc as WarcFile;
|
||
var wentry = entry as WarcEntry;
|
||
if (null == warc || null == wentry || entry.Size < 8)
|
||
return arc.File.CreateStream (entry.Offset, entry.Size);
|
||
var enc_data = new byte[entry.Size];
|
||
if (entry.Size != arc.File.View.Read (entry.Offset, enc_data, 0, entry.Size))
|
||
return Stream.Null;
|
||
uint sig = LittleEndian.ToUInt32 (enc_data, 0);
|
||
uint unpacked_size = LittleEndian.ToUInt32 (enc_data, 4);
|
||
sig ^= (unpacked_size ^ 0x82AD82) & 0xffffff;
|
||
|
||
if (0 != (wentry.Flags & 0x80000000u) && entry.Size > 8) // encrypted entry
|
||
warc.Decoder.Decrypt (enc_data, 8, entry.Size-8);
|
||
if (0 != (wentry.Flags & 0x20000000u) && entry.Size > 8)
|
||
warc.Decoder.Decrypt2 (enc_data, 8, entry.Size-8);
|
||
|
||
byte[] unpacked = enc_data;
|
||
UnpackMethod unpack = null;
|
||
switch (sig & 0xffffff)
|
||
{
|
||
case 0x314859: // 'YH1'
|
||
unpack = UnpackYH1;
|
||
break;
|
||
case 0x4b5059: // 'YPK'
|
||
unpack = UnpackYPK;
|
||
break;
|
||
case 0x5a4c59: // 'YLZ'
|
||
unpack = UnpackYLZ;
|
||
break;
|
||
}
|
||
if (null != unpack)
|
||
{
|
||
unpacked = new byte[unpacked_size];
|
||
unpack (enc_data, unpacked);
|
||
if (0 != (wentry.Flags & 0x40000000))
|
||
{
|
||
warc.Decoder.Decrypt2 (unpacked, 0, (uint)unpacked.Length);
|
||
if (warc.Decoder.SchemeVersion >= 2490)
|
||
warc.Decoder.Decrypt3 (unpacked, 0, (uint)unpacked.Length);
|
||
}
|
||
}
|
||
return new MemoryStream (unpacked);
|
||
}
|
||
|
||
delegate void UnpackMethod (byte[] input, byte[] output);
|
||
|
||
void UnpackYH1 (byte[] input, byte[] output)
|
||
{
|
||
if (0 != input[3])
|
||
{
|
||
uint key = 0x6393528e^0x4b4du; // 'KM'
|
||
unsafe
|
||
{
|
||
fixed (byte* buf_raw = input)
|
||
{
|
||
uint* encoded = (uint*)buf_raw;
|
||
int i;
|
||
for (i = 2; i < input.Length/4; ++i)
|
||
encoded[i] ^= key;
|
||
}
|
||
}
|
||
var decoder = new HuffmanReader (input, 8, input.Length-8, output);
|
||
decoder.Unpack();
|
||
}
|
||
}
|
||
|
||
void UnpackYPK (byte[] input, byte[] output)
|
||
{
|
||
if (0 != input[3])
|
||
{
|
||
uint key = ~0x4b4d4b4du; // 'KMKM'
|
||
unsafe
|
||
{
|
||
fixed (byte* buf_raw = input)
|
||
{
|
||
uint* encoded = (uint*)buf_raw;
|
||
int i;
|
||
for (i = 2; i < input.Length/4; ++i)
|
||
encoded[i] ^= key;
|
||
for (i *= 4; i < input.Length; ++i)
|
||
buf_raw[i] ^= (byte)key;
|
||
}
|
||
}
|
||
}
|
||
if (0x78 != input[8])
|
||
throw new ApplicationException ("Invalid decryption scheme");
|
||
var src = new MemoryStream (input, 8, input.Length-8);
|
||
using (var zlib = new ZLibStream (src, CompressionMode.Decompress))
|
||
zlib.Read (output, 0, output.Length);
|
||
}
|
||
|
||
void UnpackYLZ (byte[] input, byte[] output)
|
||
{
|
||
if (0 != input[3])
|
||
{
|
||
uint key = 0x4b4d4b4du; // 'KMKM'
|
||
unsafe
|
||
{
|
||
fixed (byte* buf_raw = input)
|
||
{
|
||
uint* encoded = (uint*)buf_raw;
|
||
int i;
|
||
for (i = 2; i < input.Length/4; ++i)
|
||
encoded[i] ^= key;
|
||
for (i *= 4; i < input.Length; ++i)
|
||
{
|
||
buf_raw[i] ^= (byte)key;
|
||
key >>= 8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
var decoder = new YlzReader (input, 8, output);
|
||
decoder.Unpack();
|
||
}
|
||
|
||
uint UnpackRNG (byte[] input, int in_start, uint input_size, byte[] output)
|
||
{
|
||
var coder = new Kogado.CRangeCoder();
|
||
coder.InitQSModel (257, 12, 2000, null, false);
|
||
return coder.Decode (output, 0, (uint)output.Length, input, (uint)in_start, input_size);
|
||
}
|
||
|
||
public override ResourceOptions GetDefaultOptions ()
|
||
{
|
||
return new WarOptions {
|
||
Scheme = GetScheme (Settings.Default.WARCScheme),
|
||
};
|
||
}
|
||
|
||
public override object GetAccessWidget ()
|
||
{
|
||
return new GUI.WidgetWARC();
|
||
}
|
||
|
||
EncryptionScheme QueryEncryption ()
|
||
{
|
||
var options = Query<WarOptions> (arcStrings.ArcEncryptedNotice);
|
||
return options.Scheme;
|
||
}
|
||
|
||
static EncryptionScheme GetScheme (string scheme)
|
||
{
|
||
return Decoder.KnownSchemes.Where (s => s.Name == scheme).FirstOrDefault();
|
||
}
|
||
}
|
||
|
||
internal class YlzReader
|
||
{
|
||
byte[] m_input;
|
||
byte[] m_output;
|
||
int m_src;
|
||
uint m_ctl = 0;
|
||
uint m_mask = 0;
|
||
|
||
public YlzReader (byte[] input, int src_offset, byte[] output)
|
||
{
|
||
m_input = input;
|
||
m_src = src_offset;
|
||
m_output = output;
|
||
}
|
||
|
||
bool GetCtlBit ()
|
||
{
|
||
bool bit = 0 != (m_ctl & m_mask);
|
||
m_mask >>= 1;
|
||
if (0 == m_mask)
|
||
{
|
||
m_ctl = LittleEndian.ToUInt32 (m_input, m_src);
|
||
m_src += 4;
|
||
m_mask = 0x80000000;
|
||
}
|
||
return bit;
|
||
}
|
||
|
||
int GetBits (int n)
|
||
{
|
||
int v = 0;
|
||
for (int i = 0; i < n; ++i)
|
||
{
|
||
v <<= 1;
|
||
if (GetCtlBit())
|
||
v |= 1;
|
||
}
|
||
return v;
|
||
}
|
||
|
||
public void Unpack ()
|
||
{
|
||
GetCtlBit();
|
||
int dst = 0;
|
||
while (dst < m_output.Length)
|
||
{
|
||
if (GetCtlBit())
|
||
{
|
||
m_output[dst++] = m_input[m_src++];
|
||
continue;
|
||
}
|
||
bool next_bit = GetCtlBit();
|
||
int offset = m_input[m_src++] | ~0xffff;
|
||
int ah = 0xff;
|
||
int count = 0;
|
||
if (next_bit) // 5e
|
||
{
|
||
if (GetCtlBit()) // 10d
|
||
{
|
||
ah = (ah << 1) | GetBits (1);
|
||
}
|
||
else if (GetCtlBit()) // 13e
|
||
{
|
||
ah = (ah << 1) | GetBits (1);
|
||
offset -= 0x200;
|
||
}
|
||
else if (GetCtlBit()) // 174
|
||
{
|
||
ah = (ah << 2) | GetBits (2);
|
||
offset -= 0x400;
|
||
}
|
||
else if (GetCtlBit()) // 1c0
|
||
{
|
||
ah = (ah << 3) | GetBits (3);
|
||
offset -= 0x800;
|
||
}
|
||
else
|
||
{
|
||
ah = (ah << 4) | GetBits (4);
|
||
offset -= 0x1000;
|
||
}
|
||
|
||
if (GetCtlBit()) // 296
|
||
{
|
||
count = 3;
|
||
}
|
||
else if (GetCtlBit()) // 2a2
|
||
{
|
||
count = 4;
|
||
}
|
||
else if (GetCtlBit()) // 2c2
|
||
{
|
||
count = 5 + GetBits (1);
|
||
}
|
||
else if (GetCtlBit()) // 2f8
|
||
{
|
||
count = 7 + GetBits (2);
|
||
}
|
||
else if (GetCtlBit()) // 33f
|
||
{
|
||
count = 0x0b + GetBits (3);
|
||
}
|
||
else
|
||
{
|
||
count = 0x13 + m_input[m_src++];
|
||
}
|
||
}
|
||
else if (GetCtlBit()) // 94
|
||
{
|
||
ah <<= 3; // b2
|
||
ah |= GetBits (3);
|
||
ah = (ah - 1) & 0xff;
|
||
count = 2;
|
||
}
|
||
else if (0xff == (offset & 0xff))
|
||
{
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
count = 2;
|
||
}
|
||
offset += (ah & 0xff) << 8; // 280
|
||
Binary.CopyOverlapped (m_output, dst + offset, dst, count);
|
||
dst += count;
|
||
}
|
||
}
|
||
}
|
||
|
||
internal class HuffmanReader
|
||
{
|
||
byte[] m_src;
|
||
byte[] m_dst;
|
||
|
||
uint[] lhs = new uint[511];
|
||
uint[] rhs = new uint[511];
|
||
|
||
int m_origin;
|
||
int m_total;
|
||
int m_input_pos;
|
||
int m_remaining;
|
||
int m_curbits;
|
||
uint m_cache;
|
||
uint m_curindex;
|
||
|
||
public HuffmanReader (byte[] src, int index, int length, byte[] dst)
|
||
{
|
||
m_src = src;
|
||
m_dst = dst;
|
||
m_origin = index;
|
||
m_total = length;
|
||
}
|
||
|
||
public HuffmanReader (byte[] src, byte[] dst) : this (src, 0, src.Length, dst)
|
||
{
|
||
}
|
||
|
||
public byte[] Unpack ()
|
||
{
|
||
m_input_pos = m_origin;
|
||
m_remaining = m_total;
|
||
m_curbits = 0;
|
||
m_curindex = 256;
|
||
uint index = CreateTree();
|
||
for (int i = 0; i < m_dst.Length; ++i)
|
||
{
|
||
uint idx = index;
|
||
while (idx >= 256)
|
||
{
|
||
uint is_right;
|
||
|
||
if (--m_curbits < 0)
|
||
{
|
||
m_curbits = 31;
|
||
m_cache = ReadUInt32();
|
||
is_right = m_cache >> 31;
|
||
}
|
||
else
|
||
is_right = (m_cache >> m_curbits) & 1;
|
||
|
||
if (0 != is_right)
|
||
idx = rhs[idx];
|
||
else
|
||
idx = lhs[idx];
|
||
}
|
||
m_dst[i] = (byte)idx;
|
||
}
|
||
return m_dst;
|
||
}
|
||
|
||
uint ReadUInt32 ()
|
||
{
|
||
if (0 == m_remaining)
|
||
throw new InvalidFormatException ("Unexpected end of file");
|
||
uint v;
|
||
if (m_remaining >= 4)
|
||
{
|
||
v = LittleEndian.ToUInt32 (m_src, m_input_pos);
|
||
m_input_pos += 4;
|
||
m_remaining -= 4;
|
||
}
|
||
else
|
||
{
|
||
v = m_src[m_input_pos++];
|
||
int shift = 8;
|
||
while (--m_remaining != 0)
|
||
{
|
||
v |= (uint)(m_src[m_input_pos++] << shift);
|
||
shift += 8;
|
||
}
|
||
}
|
||
return v;
|
||
}
|
||
|
||
uint GetBits (int req_bits)
|
||
{
|
||
uint ret_val = 0;
|
||
if (req_bits > m_curbits)
|
||
{
|
||
do
|
||
{
|
||
req_bits -= m_curbits;
|
||
ret_val |= (m_cache & ((1u << m_curbits) - 1u)) << req_bits;
|
||
m_cache = ReadUInt32();
|
||
m_curbits = 32;
|
||
}
|
||
while (req_bits > 32);
|
||
}
|
||
m_curbits -= req_bits;
|
||
return ret_val | ((1u << req_bits) - 1u) & (m_cache >> m_curbits);
|
||
}
|
||
|
||
uint CreateTree ()
|
||
{
|
||
uint not_leaf;
|
||
|
||
if (m_curbits-- < 1)
|
||
{
|
||
m_curbits = 31;
|
||
m_cache = ReadUInt32();
|
||
not_leaf = m_cache >> 31;
|
||
}
|
||
else
|
||
not_leaf = (m_cache >> m_curbits) & 1;
|
||
|
||
uint i;
|
||
if (0 != not_leaf)
|
||
{
|
||
i = m_curindex++;
|
||
lhs[i] = CreateTree();
|
||
rhs[i] = CreateTree();
|
||
}
|
||
else
|
||
i = GetBits (8);
|
||
return i;
|
||
}
|
||
}
|
||
|
||
internal class CachedResource
|
||
{
|
||
Dictionary<string, byte[]> ResourceCache = new Dictionary<string, byte[]>();
|
||
Dictionary<string, byte[]> RegionCache = new Dictionary<string, byte[]>();
|
||
|
||
public static Stream Open (string name)
|
||
{
|
||
var assembly = typeof(CachedResource).Assembly;
|
||
string qualified_name = "GameRes.Formats.Resources." + name;
|
||
Stream stream = assembly.GetManifestResourceStream (qualified_name);
|
||
if (null != stream)
|
||
return stream;
|
||
stream = assembly.GetManifestResourceStream (qualified_name + ".z");
|
||
if (null != stream)
|
||
using (stream)
|
||
return ZLibCompressor.DeCompress (stream);
|
||
throw new FileNotFoundException ("Resource not found", name);
|
||
}
|
||
|
||
public byte[] Load (string name)
|
||
{
|
||
byte[] res;
|
||
if (!ResourceCache.TryGetValue (name, out res))
|
||
{
|
||
using (var stream = Open (name))
|
||
{
|
||
res = new byte[stream.Length];
|
||
stream.Read (res, 0, res.Length);
|
||
ResourceCache[name] = res;
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
|
||
// FIXME: this approach disregards possible differences in regions width or height
|
||
public byte[] LoadRegion (string name, int width, int height)
|
||
{
|
||
byte[] region;
|
||
if (!RegionCache.TryGetValue (name, out region))
|
||
{
|
||
using (var png = Open (name))
|
||
{
|
||
region = new byte[width*height*4];
|
||
var decoder = new PngBitmapDecoder (png, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||
var bitmap = decoder.Frames[0];
|
||
width = Math.Min (width, bitmap.PixelWidth);
|
||
height = Math.Min (height, bitmap.PixelHeight);
|
||
int stride = bitmap.PixelWidth * bitmap.Format.BitsPerPixel / 8;
|
||
Int32Rect rect = new Int32Rect (0, 0, width, height);
|
||
bitmap.CopyPixels (rect, region, stride, 0);
|
||
RegionCache[name] = region;
|
||
}
|
||
}
|
||
return region;
|
||
}
|
||
}
|
||
|
||
internal class EncryptionScheme
|
||
{
|
||
public string Name { get; set; }
|
||
public string OriginalTitle { get; set; }
|
||
public int Version { get; set; }
|
||
public int EntryNameSize;
|
||
public byte[] CryptKey;
|
||
public uint[] HelperKey { get; set; }
|
||
public byte[] ShiinaImage;
|
||
public byte[] Region;
|
||
public byte[] DecodeBin;
|
||
|
||
private static CachedResource Resource = new CachedResource();
|
||
|
||
public static readonly string DefaultCrypt = "Crypt Type 20011002 - Copyright(C) 2000 Y.Yamada/STUDIO よしくん";
|
||
public static readonly uint[] ZeroKey = new uint[] { 0, 0, 0, 0, 0 };
|
||
|
||
public static EncryptionScheme Create (string name, int version, int entry_name_size,
|
||
string key1, uint[] key2,
|
||
string image, string region_src, string decode_bin = null,
|
||
string original_title = "", uint? decode_patch = null)
|
||
{
|
||
var scheme = new EncryptionScheme
|
||
{
|
||
Name = name,
|
||
Version = version,
|
||
OriginalTitle = original_title,
|
||
EntryNameSize = entry_name_size,
|
||
CryptKey = Encodings.cp932.GetBytes (key1),
|
||
HelperKey = key2,
|
||
ShiinaImage = Resource.Load (image),
|
||
Region = Resource.LoadRegion (region_src, 48, 48),
|
||
};
|
||
if (null != decode_bin)
|
||
{
|
||
scheme.DecodeBin = Resource.Load (decode_bin);
|
||
if (null != decode_patch)
|
||
{
|
||
scheme.DecodeBin = scheme.DecodeBin.Clone() as byte[];
|
||
LittleEndian.Pack (decode_patch.Value, scheme.DecodeBin, 0x1020);
|
||
}
|
||
}
|
||
return scheme;
|
||
}
|
||
|
||
public static EncryptionScheme Create (int version, string name, string original_title, uint[] key2, uint? decode_patch = null)
|
||
{
|
||
string decode = version < 2410 ? "DecodeV1.bin"
|
||
: version < 2490 ? "DecodeV241.bin"
|
||
: "DecodeV249.bin";
|
||
string image = version < 2390 ? "ShiinaRio1.png"
|
||
: version < 2490 ? "ShiinaRio3.jpg"
|
||
: "ShiinaRio4.jpg";
|
||
int entry_name_size = version <= 2390 ? 0x10 : 0x20;
|
||
return Create (name, version, entry_name_size, DefaultCrypt, key2,
|
||
image, "ShiinaRio2.png", decode, original_title, decode_patch);
|
||
}
|
||
|
||
public static EncryptionScheme Create (int version, string name)
|
||
{
|
||
return Create (version, name, "", ZeroKey);
|
||
}
|
||
}
|
||
|
||
internal class Decoder
|
||
{
|
||
EncryptionScheme m_scheme;
|
||
|
||
public int SchemeVersion { get { return m_scheme.Version; } }
|
||
public int WarcVersion { get; private set; }
|
||
public uint MaxIndexLength { get; private set; }
|
||
public int EntryNameSize { get { return m_scheme.EntryNameSize; } }
|
||
|
||
private uint Rand { get; set; }
|
||
|
||
public Decoder (int version, EncryptionScheme scheme)
|
||
{
|
||
m_scheme = scheme;
|
||
WarcVersion = version;
|
||
MaxIndexLength = GetMaxIndexLength (version);
|
||
}
|
||
|
||
public void Decrypt (byte[] data, int index, uint data_length)
|
||
{
|
||
if (data_length < 3)
|
||
return;
|
||
uint effective_length = Math.Min (data_length, 1024u);
|
||
int a, b;
|
||
uint fac = 0;
|
||
if (WarcVersion > 120)
|
||
{
|
||
Rand = data_length;
|
||
a = (sbyte)data[index] ^ (sbyte)data_length;
|
||
b = (sbyte)data[index+1] ^ (sbyte)(data_length / 2);
|
||
if (data_length != MaxIndexLength)
|
||
{
|
||
// ... regular entry decryption
|
||
int idx = (int)((double)NextRand() * (m_scheme.ShiinaImage.Length / 4294967296.0));
|
||
if (WarcVersion >= 160)
|
||
{
|
||
fac = Rand + m_scheme.ShiinaImage[idx];
|
||
fac = DecryptHelper3 (fac) & 0xfffffff;
|
||
if (effective_length > 0x80)
|
||
{
|
||
DecryptHelper4 (data, index+4, m_scheme.HelperKey);
|
||
index += 0x80;
|
||
effective_length -= 0x80;
|
||
}
|
||
}
|
||
else if (150 == WarcVersion)
|
||
{
|
||
fac = Rand + m_scheme.ShiinaImage[idx];
|
||
fac ^= (fac & 0xfff) * (fac & 0xfff);
|
||
uint v = 0;
|
||
for (int i = 0; i < 32; ++i)
|
||
{
|
||
uint bit = fac & 1;
|
||
fac >>= 1;
|
||
if (0 != bit)
|
||
v += fac;
|
||
}
|
||
fac = v;
|
||
}
|
||
else if (140 == WarcVersion)
|
||
{
|
||
fac = m_scheme.ShiinaImage[idx];
|
||
}
|
||
else if (130 == WarcVersion)
|
||
{
|
||
fac = m_scheme.ShiinaImage[idx & 0xff];
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
a = data[index];
|
||
b = data[index+1];
|
||
}
|
||
Rand ^= (uint)(DecryptHelper1 (a) * 100000000.0);
|
||
|
||
double token = 0.0;
|
||
if (0 != (a|b))
|
||
{
|
||
token = Math.Acos ((double)a / Math.Sqrt ((double)(a*a + b*b)));
|
||
token = token / Math.PI * 180.0;
|
||
}
|
||
if (b < 0)
|
||
token = 360.0 - token;
|
||
|
||
uint x = (fac + (byte)DecryptHelper2 (token)) % (uint)m_scheme.CryptKey.Length;
|
||
int n = 0;
|
||
for (int i = 2; i < effective_length; ++i)
|
||
{
|
||
byte d = data[index+i];
|
||
if (WarcVersion > 120)
|
||
d ^= (byte)((double)NextRand() / 16777216.0);
|
||
else
|
||
d ^= (byte)((double)NextRand() / 4294967296.0); // ? effectively a no-op
|
||
d = (byte)(((d & 1) << 7) | (d >> 1));
|
||
d ^= (byte)(m_scheme.CryptKey[n++] ^ m_scheme.CryptKey[x]);
|
||
data[index+i] = d;
|
||
x = d % (uint)m_scheme.CryptKey.Length;
|
||
if (n >= m_scheme.CryptKey.Length)
|
||
n = 0;
|
||
}
|
||
}
|
||
|
||
public void DecryptIndex (uint index_offset, byte[] enc_index)
|
||
{
|
||
Decrypt (enc_index, 0, (uint)enc_index.Length);
|
||
unsafe
|
||
{
|
||
fixed (byte* buf_raw = enc_index)
|
||
{
|
||
uint* encoded = (uint*)buf_raw;
|
||
for (int i = 0; i < enc_index.Length/4; ++i)
|
||
encoded[i] ^= index_offset;
|
||
if (WarcVersion >= 170)
|
||
{
|
||
byte key = (byte)~WarcVersion;
|
||
for (int i = 0; i < enc_index.Length; ++i)
|
||
buf_raw[i] ^= key;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public void Decrypt2 (byte[] data, int index, uint length)
|
||
{
|
||
if (length < 0x400 || null == m_scheme.DecodeBin)
|
||
return;
|
||
uint crc = 0xffffffff;
|
||
for (int i = 0; i < 0x100; ++i)
|
||
{
|
||
crc ^= (uint)data[index++] << 24;
|
||
for (int j = 0; j < 8; ++j)
|
||
{
|
||
uint bit = crc & 0x80000000u;
|
||
crc <<= 1;
|
||
if (0 != bit)
|
||
crc ^= 0x04c11db7;
|
||
}
|
||
}
|
||
for (int i = 0; i < 0x40; ++i)
|
||
{
|
||
uint src = LittleEndian.ToUInt32 (data, index) & 0x1ffcu;
|
||
src = LittleEndian.ToUInt32 (m_scheme.DecodeBin, (int)src);
|
||
uint key = src ^ crc;
|
||
data[index++ + 0x100] ^= (byte)key;
|
||
data[index++ + 0x100] ^= (byte)(key >> 8);
|
||
data[index++ + 0x100] ^= (byte)(key >> 16);
|
||
data[index++ + 0x100] ^= (byte)(key >> 24);
|
||
}
|
||
}
|
||
|
||
public void Decrypt3 (byte[] data, int index, uint length)
|
||
{
|
||
if (length < 0x400)
|
||
return;
|
||
int src = index;
|
||
uint key = 0;
|
||
for (uint i = (length & 0x7eu) + 1; i != 0; --i)
|
||
{
|
||
key ^= data[src++];
|
||
for (int j = 0; j < 8; ++j)
|
||
{
|
||
uint bit = key & 1;
|
||
key = bit << 15 | key >> 1;
|
||
if (0 == bit)
|
||
key ^= 0x408;
|
||
}
|
||
}
|
||
data[index+0x104] ^= (byte)key;
|
||
data[index+0x105] ^= (byte)(key >> 8);
|
||
}
|
||
|
||
double DecryptHelper1 (double a)
|
||
{
|
||
if (a < 0)
|
||
return -DecryptHelper1 (-a);
|
||
|
||
double v0;
|
||
double v1;
|
||
if (a < 18.0)
|
||
{
|
||
v0 = a;
|
||
v1 = a;
|
||
double v2 = -(a * a);
|
||
|
||
for (int j = 3; j < 1000; j += 2)
|
||
{
|
||
v1 *= v2 / (j * (j - 1));
|
||
v0 += v1 / j;
|
||
if (v0 == v2)
|
||
break;
|
||
}
|
||
return v0;
|
||
}
|
||
|
||
int flags = 0;
|
||
double v0_l = 0;
|
||
v1 = 0;
|
||
double div = 1 / a;
|
||
double v1_h = 2.0;
|
||
double v0_h = 2.0;
|
||
double v1_l = 0;
|
||
v0 = 0;
|
||
int i = 0;
|
||
|
||
do
|
||
{
|
||
v0 += div;
|
||
div *= ++i / a;
|
||
if (v0 < v0_h)
|
||
v0_h = v0;
|
||
else
|
||
flags |= 1;
|
||
|
||
v1 += div;
|
||
div *= ++i / a;
|
||
if (v1 < v1_h)
|
||
v1_h = v1;
|
||
else
|
||
flags |= 2;
|
||
|
||
v0 -= div;
|
||
div *= ++i / a;
|
||
if (v0 > v0_l)
|
||
v0_l = v0;
|
||
else
|
||
flags |= 4;
|
||
|
||
v1 -= div;
|
||
div *= ++i / a;
|
||
if (v1 > v1_l)
|
||
v1_l = v1;
|
||
else
|
||
flags |= 8;
|
||
}
|
||
while (flags != 0xf);
|
||
|
||
return ((Math.PI - Math.Cos(a) * (v0_l + v0_h)) - (Math.Sin(a) * (v1_l + v1_h))) / 2.0;
|
||
}
|
||
|
||
uint DecryptHelper2 (double a)
|
||
{
|
||
double v0, v1, v2, v3;
|
||
|
||
if (a > 1.0)
|
||
{
|
||
v0 = Math.Sqrt (a * 2 - 1);
|
||
for (;;)
|
||
{
|
||
v1 = 1 - (double)NextRand() / 4294967296.0;
|
||
v2 = 2.0 * (double)NextRand() / 4294967296.0 - 1.0;
|
||
if (v1 * v1 + v2 * v2 > 1.0)
|
||
continue;
|
||
|
||
v2 /= v1;
|
||
v3 = v2 * v0 + a - 1.0;
|
||
if (v3 <= 0)
|
||
continue;
|
||
|
||
v1 = (a - 1.0) * Math.Log (v3 / (a - 1.0)) - v2 * v0;
|
||
if (v1 < -50.0)
|
||
continue;
|
||
|
||
if (((double)NextRand() / 4294967296.0) <= (Math.Exp(v1) * (v2 * v2 + 1.0)))
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
v0 = Math.Exp(1.0) / (a + Math.Exp(1.0));
|
||
do
|
||
{
|
||
v1 = (double)NextRand() / 4294967296.0;
|
||
v2 = (double)NextRand() / 4294967296.0;
|
||
if (v1 < v0)
|
||
{
|
||
v3 = Math.Pow(v2, 1.0 / a);
|
||
v1 = Math.Exp(-v3);
|
||
} else
|
||
{
|
||
v3 = 1.0 - Math.Log(v2);
|
||
v1 = Math.Pow(v3, a - 1.0);
|
||
}
|
||
}
|
||
while ((double)NextRand() / 4294967296.0 >= v1);
|
||
}
|
||
|
||
if (WarcVersion > 120)
|
||
return (uint)(v3 * 256.0);
|
||
else
|
||
return (byte)((double)NextRand() / 4294967296.0);
|
||
}
|
||
|
||
[StructLayout(LayoutKind.Explicit)]
|
||
struct Union
|
||
{
|
||
[FieldOffset(0)]
|
||
public int i;
|
||
[FieldOffset (0)]
|
||
public uint u;
|
||
[FieldOffset(0)]
|
||
public float f;
|
||
[FieldOffset(0)]
|
||
public byte b0;
|
||
[FieldOffset(1)]
|
||
public byte b1;
|
||
[FieldOffset(2)]
|
||
public byte b2;
|
||
[FieldOffset(3)]
|
||
public byte b3;
|
||
}
|
||
|
||
uint DecryptHelper3 (uint key)
|
||
{
|
||
var p = new Union();
|
||
p.u = key;
|
||
var fv = new Union();
|
||
fv.f = (float)(1.5 * (double)p.b0 + 0.1);
|
||
uint v0 = Binary.BigEndian (fv.u);
|
||
fv.f = (float)(1.5 * (double)p.b1 + 0.1);
|
||
uint v1 = (uint)fv.f;
|
||
fv.f = (float)(1.5 * (double)p.b2 + 0.1);
|
||
uint v2 = (uint)-fv.i;
|
||
fv.f = (float)(1.5 * (double)p.b3 + 0.1);
|
||
uint v3 = ~fv.u;
|
||
|
||
return ((v0 + v1) | (v2 - v3));
|
||
}
|
||
|
||
void DecryptHelper4 (byte[] data, int index, uint[] key_src)
|
||
{
|
||
uint[] buf = new uint[0x50];
|
||
int i;
|
||
for (i = 0; i < 0x10; ++i)
|
||
{
|
||
buf[i] = BigEndian.ToUInt32 (data, index+40+4*i);
|
||
}
|
||
for (; i < 0x50; ++i)
|
||
{
|
||
uint v = buf[i-16];
|
||
v ^= buf[i-14];
|
||
v ^= buf[i-8];
|
||
v ^= buf[i-3];
|
||
v = v << 1 | v >> 31;
|
||
buf[i] = v;
|
||
}
|
||
uint[] key = new uint[10];
|
||
Array.Copy (key_src, key, 5);
|
||
uint k0 = key[0];
|
||
uint k1 = key[1];
|
||
uint k2 = key[2];
|
||
uint k3 = key[3];
|
||
uint k4 = key[4];
|
||
|
||
uint pc = 0;
|
||
uint v26 = 0;
|
||
int buf_idx = 0;
|
||
for (int ebp = 0; ebp < 0x50; ++ebp)
|
||
{
|
||
if (ebp >= 0x10)
|
||
{
|
||
if (ebp >= 0x20)
|
||
{
|
||
if (ebp >= 0x30)
|
||
{
|
||
uint v27 = ~k3;
|
||
if (ebp >= 0x40)
|
||
{
|
||
v26 = k1 ^ (k2 | v27);
|
||
pc = 0xA953FD4E;
|
||
}
|
||
else
|
||
{
|
||
v26 = k1 & k3 | k2 & v27;
|
||
pc = 0x8F1BBCDC;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
v26 = k3 ^ (k1 | ~k2);
|
||
pc = 0x6ED9EBA1;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
v26 = k1 & k2 | k3 & ~k1;
|
||
pc = 0x5A827999;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
v26 = k1 ^ k2 ^ k3;
|
||
pc = 0;
|
||
}
|
||
uint v28 = buf[buf_idx] + k4 + v26 + pc + (k0 << 5 | k0 >> 27);
|
||
uint v29 = (k1 >> 2) | (k1 << 30);
|
||
k1 = k0;
|
||
k4 = k3;
|
||
k3 = k2;
|
||
k2 = v29;
|
||
k0 = v28;
|
||
++buf_idx;
|
||
}
|
||
key[0] += k0;
|
||
key[1] += k1;
|
||
key[2] += k2;
|
||
key[3] += k3;
|
||
key[4] += k4;
|
||
var ft = new FILETIME {
|
||
DateTimeLow = key[1],
|
||
DateTimeHigh = key[0] & 0x7FFFFFFF
|
||
};
|
||
var sys_time = new SYSTEMTIME (ft);
|
||
key[5] = (uint)(sys_time.Year | sys_time.Month << 16);
|
||
key[7] = (uint)(sys_time.Hour | sys_time.Minute << 16);
|
||
key[8] = (uint)(sys_time.Second | sys_time.Milliseconds << 16);
|
||
|
||
uint flags = LittleEndian.ToUInt32 (data, index+40) | 0x80000000;
|
||
// uint rgb = BigEndian.ToUInt32 (data, index+44) >> 8;
|
||
uint rgb = buf[1] >> 8;
|
||
if (0 == (flags & 0x78000000))
|
||
flags |= 0x98000000;
|
||
key[6] = RegionCrc32 (m_scheme.Region, flags, rgb);
|
||
key[9] = (uint)(((int)key[2] * (long)(int)key[3]) >> 8);
|
||
if (m_scheme.Version >= 2390)
|
||
key[6] += key[9];
|
||
unsafe
|
||
{
|
||
fixed (byte* data_fixed = data)
|
||
{
|
||
uint* encoded = (uint*)(data_fixed+index);
|
||
for (i = 0; i < 10; ++i)
|
||
{
|
||
encoded[i] ^= key[i];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static readonly uint[] CustomCrcTable = InitCrcTable();
|
||
|
||
static uint[] InitCrcTable ()
|
||
{
|
||
var table = new uint[0x100];
|
||
for (uint i = 0; i != 256; ++i)
|
||
{
|
||
uint poly = i;
|
||
for (int j = 0; j < 8; ++j)
|
||
{
|
||
uint bit = poly & 1;
|
||
poly = poly >> 1 | poly << 31; // ror 1
|
||
if (0 == bit)
|
||
poly ^= 0x6DB88320;
|
||
}
|
||
table[i] = poly;
|
||
}
|
||
return table;
|
||
}
|
||
|
||
uint RegionCrc32 (byte[] src, uint flags, uint rgb)
|
||
{
|
||
int src_alpha = (int)flags & 0x1ff;
|
||
int dst_alpha = (int)(flags >> 12) & 0x1ff;
|
||
flags >>= 24;
|
||
if (0 == (flags & 0x10))
|
||
dst_alpha = 0;
|
||
if (0 == (flags & 8))
|
||
src_alpha = 0x100;
|
||
int y_step = 0;
|
||
int x_step = 4;
|
||
int width = 48;
|
||
int pos = 0;
|
||
if (0 != (flags & 0x40)) // horizontal flip
|
||
{
|
||
y_step += width;
|
||
pos += (width-1)*4;
|
||
x_step = -x_step;
|
||
}
|
||
if (0 != (flags & 0x20)) // vertical flip
|
||
{
|
||
y_step -= width;
|
||
pos += width*0x2f*4; // width*(height-1)*4;
|
||
}
|
||
y_step <<= 3;
|
||
uint checksum = 0;
|
||
for (int y = 0; y < 48; ++y)
|
||
{
|
||
for (int x = 0; x < 48; ++x)
|
||
{
|
||
int alpha = src[pos+3] * src_alpha;
|
||
alpha >>= 8;
|
||
uint color = rgb;
|
||
for (int i = 0; i < 3; ++i)
|
||
{
|
||
int v = src[pos+i];
|
||
int c = (int)(color & 0xff); // rgb[i];
|
||
c -= v;
|
||
c = (c * dst_alpha) >> 8;
|
||
c = (c + v) & 0xff;
|
||
c = (c * alpha) >> 8;
|
||
checksum = (checksum >> 8) ^ CustomCrcTable[(c ^ checksum) & 0xff];
|
||
color >>= 8;
|
||
}
|
||
pos += x_step;
|
||
}
|
||
pos += y_step;
|
||
}
|
||
return checksum;
|
||
}
|
||
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
private struct FILETIME
|
||
{
|
||
public uint DateTimeLow;
|
||
public uint DateTimeHigh;
|
||
}
|
||
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
private struct SYSTEMTIME
|
||
{
|
||
[MarshalAs(UnmanagedType.U2)] public ushort Year;
|
||
[MarshalAs(UnmanagedType.U2)] public ushort Month;
|
||
[MarshalAs(UnmanagedType.U2)] public ushort DayOfWeek;
|
||
[MarshalAs(UnmanagedType.U2)] public ushort Day;
|
||
[MarshalAs(UnmanagedType.U2)] public ushort Hour;
|
||
[MarshalAs(UnmanagedType.U2)] public ushort Minute;
|
||
[MarshalAs(UnmanagedType.U2)] public ushort Second;
|
||
[MarshalAs(UnmanagedType.U2)] public ushort Milliseconds;
|
||
|
||
public SYSTEMTIME (FILETIME ft)
|
||
{
|
||
FileTimeToSystemTime (ref ft, out this);
|
||
}
|
||
|
||
[DllImport ("kernel32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
|
||
static extern bool FileTimeToSystemTime (ref FILETIME lpFileTime, out SYSTEMTIME lpSystemTime);
|
||
}
|
||
|
||
uint NextRand ()
|
||
{
|
||
Rand = 1566083941u * Rand + 1u;
|
||
return Rand;
|
||
}
|
||
|
||
uint GetMaxIndexLength (int version)
|
||
{
|
||
int max_index_entries = version < 150 ? 8192 : 16384;
|
||
return (uint)((m_scheme.EntryNameSize + 0x18) * max_index_entries);
|
||
}
|
||
|
||
public static EncryptionScheme[] KnownSchemes = new EncryptionScheme[]
|
||
{
|
||
EncryptionScheme.Create (2360, "ShiinaRio v2.36 and older"),
|
||
EncryptionScheme.Create (2370, "ShiinaRio v2.37", "椎名里緒 v2.37",
|
||
new uint[] { 0xF182C682, 0xE882AA82, 0x718E5896, 0x8183CC82, 0xDAC98283 }),
|
||
|
||
EncryptionScheme.Create (2480, "Bloody Rondo", "BLOODY†RONDO",
|
||
new uint[] { 0xFBFBF8F6, 0xFBE6EDF0, 0xFBF0FA, 0, 0 }),
|
||
|
||
EncryptionScheme.Create (2460, "Chikan Circle", "痴漢サークル",
|
||
new uint[] { 0x6B6B6E70, 0x6E724058, 0x587E736B, 0x00003653, 0 }),
|
||
EncryptionScheme.Create (2470, "Chikan Circle 2", "痴漢サークル2",
|
||
new uint[] { 0x6464617F, 0x617D4F57, 0x57717C64, 0x00003A5C, 0 }, 0x20080108),
|
||
EncryptionScheme.Create (2470, "Chikan Circle 3", "痴漢サークル3",
|
||
new uint[] { 0x6565607E, 0x607C4E56, 0x56707D65, 0x00003A4A, 0 }, 0x20090706),
|
||
|
||
EncryptionScheme.Create (2450, "Chuuchuu Nurse", "ちゅうちゅうナース",
|
||
new uint[] { 0xB0D1ECD1, 0xECD1F7D1, 0xF7D1B0D1, 0x08D23AD0, 0x13D20BD0 }),
|
||
EncryptionScheme.Create (2470, "Damatte Watashi no Muko ni Nare!", "黙って私のムコになれ!",
|
||
new uint[] { 0x44075C13, 0x010B4107, 0x05064907, 0x4C07D706, 0x6F074D07 }),
|
||
EncryptionScheme.Create (2470, "Hana to Otome ni Shukufuku wo Royal Bouquet", "花と乙女に祝福を ロイヤルブーケ",
|
||
new uint[] { 0xE3A7F1AC, 0xB2AA96AC, 0x4FAAECA7, 0xD5A7BAB0, 0x44A754A7 }),
|
||
EncryptionScheme.Create (2400, "Helter Skelter", "ヘルタースケルター",
|
||
new uint[] { 0x747C887C, 0xA47EA17C, 0xAF7CA77C, 0xA17C747C, 0x0000A47E }),
|
||
EncryptionScheme.Create (2390, "Hitozuma Onna Kyoushi Reika", "人妻女教師・麗香",
|
||
new uint[] { 0x3772936F, 0x4C746870, 0x12688b71, 0x0A687E72, 0x3A6B4076 }),
|
||
EncryptionScheme.Create (2470, "Mahou Shoujo no Taisetsu na Koto", "魔法少女の大切なこと。",
|
||
new uint[] { 0x51879387, 0x869EBC9E, 0xF480DD93, 0xD993C981, 0xD793A093 }),
|
||
EncryptionScheme.Create (2460, "Mikoko", "みここ",
|
||
new uint[] { 0x7DAC51AC, 0x51AC7DAC, 0x7DAC7DAC, 0x7DAC51AC, 0x51AC7DAC }),
|
||
EncryptionScheme.Create (2470, "Najimi no Oba-chan", "馴染みのオバちゃん",
|
||
new uint[] { 0x6161647A, 0x64784A52, 0x52747961, 0x00004C43, 0 }),
|
||
EncryptionScheme.Create (2390, "Nagagutsu wo Haita Deco", "長靴をはいたデコ",
|
||
new uint[] { 0x486D887E, 0x0F7DBC73, 0x5D7D327D, 0x997C427D, 0x877EAD7C }),
|
||
EncryptionScheme.Create (2470, "Nakadashi Trilogy", "なかだしトリロジー",
|
||
new uint[] { 0x928ADB9E, 0xB087BB87, 0x928ADB9E, 0xB087BB87, 0xB087BB87 }),
|
||
EncryptionScheme.Create (2470, "Onedari Onapet", "おねだりオナペット",
|
||
new uint[] { 0xD59CB69C, 0xF69CA09C, 0x779D579D, 0x7C9D679D, 0x0000799D }),
|
||
EncryptionScheme.Create (2480, "Onegan!", "おねガン!",
|
||
new uint[] { 0xC881AB81, 0x90804880, 0xAB814A82, 0x4880C881, 0x4A829080 }),
|
||
EncryptionScheme.Create (2470, "Oreimo Plus", "俺妹プラス",
|
||
new uint[] { 0x24371528, 0x2822D722, 0x1528F922, 0xD7222437, 0xF9222822 }),
|
||
EncryptionScheme.Create (2470, "Pure Love!", "ぴゅあらっ!",
|
||
new uint[] { 0x31500050, 0x35507250, 0x87821350, 0x9D9E9780, 0x00009784 }),
|
||
EncryptionScheme.Create (2470, "Ren'ai Saimin", "恋愛催眠",
|
||
new uint[] { 0x6E423C5D, 0x7A5C0947, 0x6E423C5D, 0x7A5C0947, 0x6E423C5D }),
|
||
|
||
EncryptionScheme.Create (2470, "Ran→Sem", "RAN→SEM~白濁デルモ妻のミイラ捕り~",
|
||
new uint[] { 0x63636678, 0x667A4850, 0x50767B63, 0x00004E5D, 0 }, 0x20100427),
|
||
EncryptionScheme.Create (2470, "Rin x Sen", "RIN×SEN~白濁女教師と野郎ども~",
|
||
new uint[] { 0x6666637D, 0x637F4D55, 0x55737E66, 0x00004F58, 0 }),
|
||
|
||
EncryptionScheme.Create (2480, "Sensei! Shite Ageru", "先生っ! シてあげる",
|
||
new uint[] { 0x70562056, 0x87470744, 0x02449045, 0x76446644, 0x8F472F44 }),
|
||
EncryptionScheme.Create (2480, "Tanetsuke Mura", "種憑け村",
|
||
new uint[] { 0x7C7C7967, 0x7965574F, 0x4F69647C, 0x00005144, 0 }),
|
||
EncryptionScheme.Create (2470, "You~Gaku", "よう∽ガク",
|
||
new uint[] { 0x4DE2AB4D, 0x98AB5D46, 0x66496349, 0x485D685D, 0x5F4D4C5D }),
|
||
EncryptionScheme.Create (2410, "Zansho Omimai Moushiagemasu", "残暑お見舞い申し上げます。",
|
||
new uint[] { 0x3A123012, 0x6C3A6C36, 0x3C36323C, 0x6C360F16, 0x369DD012 }),
|
||
EncryptionScheme.Create (2490, "Shojo Mama", "処女ママ",
|
||
new uint[] { 0x4B535453, 0xA15FA15F, 0, 0, 0 }),
|
||
EncryptionScheme.Create ("ShiinaRio v2.49", 2490, 0x20, EncryptionScheme.DefaultCrypt,
|
||
new uint[] { 0x4B535453, 0xA15FA15F, 0, 0, 0 },
|
||
"ShiinaRio4.jpg", "ShiinaRio2.png", "DecodeV249.bin"),
|
||
};
|
||
}
|
||
}
|