(EAGLS): auto-detect CG archive encryption scheme.

This commit is contained in:
morkt 2016-01-21 15:30:06 +04:00
parent 10e3846f51
commit dbd95d62e3
2 changed files with 109 additions and 40 deletions

View File

@ -2,7 +2,7 @@
//! \date Fri May 15 02:52:04 2015 //! \date Fri May 15 02:52:04 2015
//! \brief EAGLS system resource archives. //! \brief EAGLS system resource archives.
// //
// Copyright (C) 2015 by morkt // Copyright (C) 2015-2016 by morkt
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to // of this software and associated documentation files (the "Software"), to
@ -27,6 +27,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Linq;
using GameRes.Utility; using GameRes.Utility;
namespace GameRes.Formats.Eagls namespace GameRes.Formats.Eagls
@ -45,8 +46,11 @@ namespace GameRes.Formats.Eagls
Extensions = new string[] { "pak" }; Extensions = new string[] { "pak" };
} }
static readonly string IndexKey = "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-@:^[]"; internal static readonly string IndexKey = "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-@:^[]";
static readonly string Key = "EAGLS_SYSTEM"; internal static readonly string Key = "EAGLS_SYSTEM";
// FIXME not thread-safe
CRuntimeRandomGenerator m_rng = new CRuntimeRandomGenerator();
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
@ -94,45 +98,31 @@ namespace GameRes.Formats.Eagls
} }
if (0 == dir.Count) if (0 == dir.Count)
return null; return null;
if (dir[0].Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase)) // CG archive
return new CgArchive (file, this, dir);
else
return new ArcFile (file, this, dir); return new ArcFile (file, this, dir);
} }
public override Stream OpenEntry (ArcFile arc, Entry entry) public override Stream OpenEntry (ArcFile arc, Entry entry)
{ {
if (entry.Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase)) var cg_arc = arc as CgArchive;
return DecryptGr (arc, entry); if (null != cg_arc)
return cg_arc.DecryptEntry (entry);
if (entry.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) if (entry.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase))
return DecryptDat (arc, entry); return DecryptDat (arc, entry);
return arc.File.CreateStream (entry.Offset, entry.Size); return arc.File.CreateStream (entry.Offset, entry.Size);
} }
Stream DecryptGr (ArcFile arc, Entry entry)
{
var input = new byte[entry.Size];
arc.File.View.Read (entry.Offset, input, 0, entry.Size);
int seed = 0x75bd924 ^ input[input.Length-1];
int limit = Math.Min (input.Length-1, 0x174b);
for (int i = 0; i < limit; ++i)
{
seed = LRand (seed);
int index = (int)(seed * 4.656612875245797e-10 * 256);
input[i] ^= (byte)Key[index % Key.Length];
}
return new MemoryStream (input);
}
Stream DecryptDat (ArcFile arc, Entry entry) Stream DecryptDat (ArcFile arc, Entry entry)
{ {
byte[] input = new byte[entry.Size]; byte[] input = arc.File.View.ReadBytes (entry.Offset, entry.Size);
arc.File.View.Read (entry.Offset, input, 0, entry.Size);
int text_offset = 3600; int text_offset = 3600;
int text_length = (int)(entry.Size - text_offset - 2); int text_length = (int)(entry.Size - text_offset - 2);
int seed = (sbyte)input[input.Length-1]; m_rng.SRand ((sbyte)input[input.Length-1]);
for (int i = 0; i < text_length; i += 2) for (int i = 0; i < text_length; i += 2)
{ {
seed = seed * 0x343FD + 0x269EC3; input[text_offset + i] ^= (byte)Key[m_rng.Rand() % Key.Length];
int index = (int)(((uint)seed >> 16) & 0x7fff);
input[text_offset + i] ^= (byte)Key[index % Key.Length];
} }
return new MemoryStream (input); return new MemoryStream (input);
} }
@ -144,15 +134,13 @@ namespace GameRes.Formats.Eagls
using (var view = idx.CreateViewAccessor (0, (uint)idx.MaxOffset)) using (var view = idx.CreateViewAccessor (0, (uint)idx.MaxOffset))
unsafe unsafe
{ {
uint seed = view.ReadUInt32 (idx_size); m_rng.SRand (view.ReadInt32 (idx_size));
byte* ptr = view.GetPointer (0); byte* ptr = view.GetPointer (0);
try try
{ {
for (int i = 0; i < idx_size; ++i) for (int i = 0; i < idx_size; ++i)
{ {
seed = seed * 0x343FD + 0x269EC3; output[i] = (byte)(ptr[i] ^ IndexKey[m_rng.Rand() % IndexKey.Length]);
int index = (int)(seed >> 16) & 0x7FFF;
output[i] = (byte)(ptr[i] ^ IndexKey[index % IndexKey.Length]);
} }
return output; return output;
} }
@ -162,17 +150,97 @@ namespace GameRes.Formats.Eagls
} }
} }
} }
}
int LRand (int seed) internal interface IRandomGenerator
{ {
void SRand (int seed);
int Rand ();
}
internal class CRuntimeRandomGenerator : IRandomGenerator
{
uint m_seed;
public void SRand (int seed)
{
m_seed = (uint)seed;
}
public int Rand ()
{
m_seed = m_seed * 214013u + 2531011u;
return (int)(m_seed >> 16) & 0x7FFF;
}
}
internal class LehmerRandomGenerator : IRandomGenerator
{
int m_seed;
const int A = 48271; const int A = 48271;
const int Q = 44488; const int Q = 44488;
const int R = 3399; const int R = 3399;
const int M = 2147483647; const int M = 2147483647;
seed = A * (seed % Q) - R * (seed / Q);
if (seed < 0) public void SRand (int seed)
seed += M; {
return seed; m_seed = seed ^ 123459876;
}
public int Rand ()
{
m_seed = A * (m_seed % Q) - R * (m_seed / Q);
if (m_seed < 0)
m_seed += M;
return (int)(m_seed * 4.656612875245797e-10 * 256);
}
}
internal class CgArchive : ArcFile
{
IRandomGenerator m_rng;
public CgArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir)
: base (arc, impl, dir)
{
m_rng = DetectEncryptionScheme();
}
IRandomGenerator DetectEncryptionScheme ()
{
var first_entry = Dir.First();
int signature = (File.View.ReadInt32 (first_entry.Offset) >> 8) & 0xFFFF;
byte seed = File.View.ReadByte (first_entry.Offset+first_entry.Size-1);
IRandomGenerator[] rng_list = {
new LehmerRandomGenerator(),
new CRuntimeRandomGenerator(),
};
foreach (var rng in rng_list)
{
rng.SRand (seed);
rng.Rand(); // skip LZSS control byte
int test = signature;
test ^= PakOpener.Key[rng.Rand() % PakOpener.Key.Length];
test ^= PakOpener.Key[rng.Rand() % PakOpener.Key.Length] << 8;
// FIXME
// as key is rather short, this detection could produce false results sometimes
if (0x4D42 == test) // 'BM'
return rng;
}
throw new UnknownEncryptionScheme();
}
public Stream DecryptEntry (Entry entry)
{
var input = File.View.ReadBytes (entry.Offset, entry.Size);
m_rng.SRand (input[input.Length-1]);
int limit = Math.Min (input.Length-1, 0x174b);
for (int i = 0; i < limit; ++i)
{
input[i] ^= (byte)PakOpener.Key[m_rng.Rand() % PakOpener.Key.Length];
}
return new MemoryStream (input);
} }
} }
} }

View File

@ -328,6 +328,7 @@ Warusa<br/>
<tr class="odd"><td>*.mgd<br/>*.mgs</td><td><tt>MGD</tt><br/><tt>MGS</tt></td><td>No</td><td rowspan="2">MEGU</td><td rowspan="2">Seduce</tr> <tr class="odd"><td>*.mgd<br/>*.mgs</td><td><tt>MGD</tt><br/><tt>MGS</tt></td><td>No</td><td rowspan="2">MEGU</td><td rowspan="2">Seduce</tr>
<tr class="odd"><td>*.agc</td><td><tt>AGd</tt></td><td>No</td></tr> <tr class="odd"><td>*.agc</td><td><tt>AGd</tt></td><td>No</td></tr>
<tr><td>*.pak+*.idx</td><td>-</td><td>No</td><td rowspan="2">EAGLS</td><td rowspan="2"> <tr><td>*.pak+*.idx</td><td>-</td><td>No</td><td rowspan="2">EAGLS</td><td rowspan="2">
Futago Hinyuu x 3<br/>
Oppai Baka<br/> Oppai Baka<br/>
Mainichi Shabutte Ii Desu ka?<br/> Mainichi Shabutte Ii Desu ka?<br/>
Senpai - Oppai - Kako ni Modori Pai<br/> Senpai - Oppai - Kako ni Modori Pai<br/>