mirror of
https://github.com/crskycode/GARbro.git
synced 2025-01-11 20:39:29 +08:00
(EAGLS): auto-detect CG archive encryption scheme.
This commit is contained in:
parent
10e3846f51
commit
dbd95d62e3
@ -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;
|
||||||
return new ArcFile (file, this, dir);
|
if (dir[0].Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase)) // CG archive
|
||||||
|
return new CgArchive (file, this, dir);
|
||||||
|
else
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
const int A = 48271;
|
m_seed = (uint)seed;
|
||||||
const int Q = 44488;
|
}
|
||||||
const int R = 3399;
|
|
||||||
const int M = 2147483647;
|
public int Rand ()
|
||||||
seed = A * (seed % Q) - R * (seed / Q);
|
{
|
||||||
if (seed < 0)
|
m_seed = m_seed * 214013u + 2531011u;
|
||||||
seed += M;
|
return (int)(m_seed >> 16) & 0x7FFF;
|
||||||
return seed;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LehmerRandomGenerator : IRandomGenerator
|
||||||
|
{
|
||||||
|
int m_seed;
|
||||||
|
|
||||||
|
const int A = 48271;
|
||||||
|
const int Q = 44488;
|
||||||
|
const int R = 3399;
|
||||||
|
const int M = 2147483647;
|
||||||
|
|
||||||
|
public void SRand (int 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user