mirror of
https://github.com/crskycode/GARbro.git
synced 2026-07-08 01:30:18 +08:00
feat: support .epk and .fcd decryption
This commit is contained in:
@@ -63,7 +63,7 @@ namespace GameRes.Formats.Anchor {
|
|||||||
return null;
|
return null;
|
||||||
uint index_size = (uint)(data_start - offset);
|
uint index_size = (uint)(data_start - offset);
|
||||||
|
|
||||||
var key = QueryKey();
|
var key = DefaultScheme.ArchiveKey;
|
||||||
var dir = new List<Entry>(count);
|
var dir = new List<Entry>(count);
|
||||||
|
|
||||||
using (var input = file.CreateStream(offset, (uint)(data_start - offset)))
|
using (var input = file.CreateStream(offset, (uint)(data_start - offset)))
|
||||||
@@ -96,6 +96,8 @@ namespace GameRes.Formats.Anchor {
|
|||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
dir[i].Name = Binary.GetCString(names, (int)name_offsets[i], Encoding.UTF8);
|
dir[i].Name = Binary.GetCString(names, (int)name_offsets[i], Encoding.UTF8);
|
||||||
dir[i].Type = FormatCatalog.Instance.GetTypeFromName(dir[i].Name);
|
dir[i].Type = FormatCatalog.Instance.GetTypeFromName(dir[i].Name);
|
||||||
|
if (dir[i].Name.EndsWith(".epk"))
|
||||||
|
dir[i].Type = "script";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new FpdArchive(file, this, dir, key);
|
return new FpdArchive(file, this, dir, key);
|
||||||
@@ -105,17 +107,25 @@ namespace GameRes.Formats.Anchor {
|
|||||||
public override Stream OpenEntry(ArcFile arc, Entry entry) {
|
public override Stream OpenEntry(ArcFile arc, Entry entry) {
|
||||||
var farc = arc as FpdArchive;
|
var farc = arc as FpdArchive;
|
||||||
var pent = entry as PackedEntry;
|
var pent = entry as PackedEntry;
|
||||||
var input = farc.File.CreateStream(entry.Offset, entry.Size);
|
Stream input = farc.File.CreateStream(entry.Offset, entry.Size);
|
||||||
var decrypted = new ByteStringEncryptedStream(input, farc.Key);
|
input = new ByteStringEncryptedStream(input, farc.Key);
|
||||||
if (pent.IsPacked)
|
if (pent.IsPacked)
|
||||||
return new ZLibStream(decrypted, CompressionMode.Decompress);
|
input = new ZLibStream(input, CompressionMode.Decompress);
|
||||||
else
|
if (pent.Name.EndsWith(".epk")) {
|
||||||
return decrypted;
|
var mem = new MemoryStream();
|
||||||
// TODO: epk decryption
|
input.CopyTo(mem);
|
||||||
}
|
mem.Seek(-0x20, SeekOrigin.End);
|
||||||
|
var buf = new byte[4];
|
||||||
byte[] QueryKey() {
|
mem.Read(buf, 0, 4);
|
||||||
return DefaultScheme.ArchiveKey;
|
uint last = Binary.BigEndian(BitConverter.ToUInt32(buf, 0));
|
||||||
|
mem.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
var key = Encoding.UTF8.GetBytes(Path.GetFileNameWithoutExtension(pent.Name));
|
||||||
|
var encryption = new Mk2Blowfish(key, DefaultScheme.EpkContext);
|
||||||
|
input = new InputCryptoStream(mem, encryption.CreateDecryptor());
|
||||||
|
input = new LimitStream(input, last);
|
||||||
|
}
|
||||||
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
FpdScheme DefaultScheme = new FpdScheme();
|
FpdScheme DefaultScheme = new FpdScheme();
|
||||||
@@ -129,6 +139,6 @@ namespace GameRes.Formats.Anchor {
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class FpdScheme : ResourceScheme {
|
public class FpdScheme : ResourceScheme {
|
||||||
public byte[] ArchiveKey;
|
public byte[] ArchiveKey;
|
||||||
public byte[] EpkKey;
|
public byte[] EpkContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ using System;
|
|||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using GameRes.Cryptography;
|
||||||
|
using GameRes.Utility;
|
||||||
|
|
||||||
namespace GameRes.Formats.Anchor
|
namespace GameRes.Formats.Anchor
|
||||||
{
|
{
|
||||||
@@ -45,11 +47,57 @@ namespace GameRes.Formats.Anchor
|
|||||||
public override SoundInput TryOpen (IBinaryStream file)
|
public override SoundInput TryOpen (IBinaryStream file)
|
||||||
{
|
{
|
||||||
file.Position = 4;
|
file.Position = 4;
|
||||||
// guess: big endian, version=2, type=0 (ogg), offset=0xC
|
|
||||||
byte[] data = { 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x4F, 0x67, 0x67, 0x53 };
|
uint version = Binary.BigEndian (file.ReadUInt16());
|
||||||
if (!file.ReadBytes (0x0C).SequenceEqual (data))
|
if (version != 2)
|
||||||
throw new NotSupportedException();
|
return null;
|
||||||
return new OggInput (new StreamRegion (file.AsStream, 0x0C));
|
|
||||||
|
uint count = Binary.BigEndian (file.ReadUInt16());
|
||||||
|
uint offset = Binary.BigEndian (file.ReadUInt32());
|
||||||
|
Stream region = new StreamRegion (file.AsStream, offset);
|
||||||
|
|
||||||
|
if (count == 0 && offset == 0x0C)
|
||||||
|
return new OggInput (region);
|
||||||
|
|
||||||
|
if (count == 1 && offset == 0x1C)
|
||||||
|
{
|
||||||
|
file.Position = 0x0C;
|
||||||
|
uint type = Binary.BigEndian (file.ReadUInt32());
|
||||||
|
if (type != 2)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var md5 = new MD5();
|
||||||
|
md5.Initialize();
|
||||||
|
md5.Update (file.ReadBytes (0xC), 0, 0xC);
|
||||||
|
md5.Update (DefaultScheme.Md5Addition, 0, 0x4000);
|
||||||
|
md5.Final();
|
||||||
|
|
||||||
|
var key = new byte[16];
|
||||||
|
Buffer.BlockCopy (md5.State, 0, key, 0, 16);
|
||||||
|
var encryption = new Mk2Blowfish (key, DefaultScheme.Context);
|
||||||
|
var stream = new InputCryptoStream (region, encryption.CreateDecryptor());
|
||||||
|
var mem = new MemoryStream();
|
||||||
|
stream.CopyTo (mem);
|
||||||
|
mem.Position = 0;
|
||||||
|
return new OggInput (mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FcdScheme DefaultScheme = new FcdScheme();
|
||||||
|
|
||||||
|
public override ResourceScheme Scheme
|
||||||
|
{
|
||||||
|
get { return DefaultScheme; }
|
||||||
|
set { DefaultScheme = (FcdScheme)value; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class FcdScheme : ResourceScheme
|
||||||
|
{
|
||||||
|
public byte[] Context;
|
||||||
|
public byte[] Md5Addition;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
279
ArcFormats/aNCHOR/Blowfish.cs
Normal file
279
ArcFormats/aNCHOR/Blowfish.cs
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
// vi: shiftwidth=8 noexpandtab
|
||||||
|
/****************************************************************************
|
||||||
|
|
|
||||||
|
| Copyright (c) 2007 Novell, Inc.
|
||||||
|
| All Rights Reserved.
|
||||||
|
|
|
||||||
|
| This program is free software; you can redistribute it and/or
|
||||||
|
| modify it under the terms of version 2 of the GNU General Public License as
|
||||||
|
| published by the Free Software Foundation.
|
||||||
|
|
|
||||||
|
| This program is distributed in the hope that it will be useful,
|
||||||
|
| but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
| GNU General Public License for more details.
|
||||||
|
|
|
||||||
|
| You should have received a copy of the GNU General Public License
|
||||||
|
| along with this program; if not, contact Novell, Inc.
|
||||||
|
|
|
||||||
|
| To contact Novell about this file by physical or electronic mail,
|
||||||
|
| you may find current contact information at www.novell.com
|
||||||
|
|
|
||||||
|
| Author: Russ Young
|
||||||
|
| Thanks to: Bruce Schneier / Counterpane Labs
|
||||||
|
| for the Blowfish encryption algorithm and
|
||||||
|
| reference implementation. http://www.schneier.com/blowfish.html
|
||||||
|
|***************************************************************************/
|
||||||
|
|
||||||
|
//! \file Blowfish.cs
|
||||||
|
//! \date 2026-02-02
|
||||||
|
//! \brief Modified Blowfish encryption algorithm implementation.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using GameRes.Utility;
|
||||||
|
|
||||||
|
namespace GameRes.Formats.Anchor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that provides blowfish encryption.
|
||||||
|
/// </summary>
|
||||||
|
public class Mk2Blowfish
|
||||||
|
{
|
||||||
|
const int N = 16;
|
||||||
|
|
||||||
|
uint[] ctx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and initializes a blowfish instance with the supplied key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key to cipher with.</param>
|
||||||
|
public Mk2Blowfish(byte[] key, byte[] _ctx)
|
||||||
|
{
|
||||||
|
short i;
|
||||||
|
short j;
|
||||||
|
short k;
|
||||||
|
uint data;
|
||||||
|
uint datal;
|
||||||
|
uint datar;
|
||||||
|
|
||||||
|
ctx = new uint[N + 270];
|
||||||
|
|
||||||
|
for (i = 0; i < N + 2; ++i)
|
||||||
|
{
|
||||||
|
ctx[i] = BigEndian.ToUInt32 (_ctx, 4 * i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
for (j = 0; j < 256; ++j)
|
||||||
|
{
|
||||||
|
ctx[N + 2 + i * 4 + j] = BigEndian.ToUInt32 (_ctx, 4 * (N + 2 + i * 256 + j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
j = 0;
|
||||||
|
for (i = 0; i < N + 2; ++i)
|
||||||
|
{
|
||||||
|
data = 0x00000000;
|
||||||
|
for (k = 0; k < 4; ++k)
|
||||||
|
{
|
||||||
|
data = (data << 8) | key[j];
|
||||||
|
j++;
|
||||||
|
if (j >= key.Length)
|
||||||
|
{
|
||||||
|
j = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx[i] = ctx[i] ^ data;
|
||||||
|
}
|
||||||
|
|
||||||
|
datal = 0x00000000;
|
||||||
|
datar = 0x00000000;
|
||||||
|
|
||||||
|
for (i = 0; i < N + 2; i += 2)
|
||||||
|
{
|
||||||
|
Encipher(ref datal, ref datar);
|
||||||
|
ctx[i] = datal;
|
||||||
|
ctx[i + 1] = datar;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
for (j = 0; j < 256; j += 2)
|
||||||
|
{
|
||||||
|
Encipher(ref datal, ref datar);
|
||||||
|
|
||||||
|
ctx[N + 2 + i * 4 + j] = datal;
|
||||||
|
ctx[N + 3 + i * 4 + j] = datar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICryptoTransform CreateDecryptor ()
|
||||||
|
{
|
||||||
|
return new Mk2BlowfishDecryptor (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint F(uint x)
|
||||||
|
{
|
||||||
|
ushort a;
|
||||||
|
ushort b;
|
||||||
|
ushort c;
|
||||||
|
ushort d;
|
||||||
|
uint y;
|
||||||
|
|
||||||
|
d = (ushort)(x & 0x00FF);
|
||||||
|
x >>= 8;
|
||||||
|
c = (ushort)(x & 0x00FF);
|
||||||
|
x >>= 8;
|
||||||
|
b = (ushort)(x & 0x00FF);
|
||||||
|
x >>= 8;
|
||||||
|
a = (ushort)(x & 0x00FF);
|
||||||
|
|
||||||
|
y = ctx[18 + a] + ctx[22 + b];
|
||||||
|
y = y ^ ctx[26 + c];
|
||||||
|
y = y ^ ctx[30 + d];
|
||||||
|
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encrypts 8 bytes of data (1 block)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="xl">The left part of the 8 bytes.</param>
|
||||||
|
/// <param name="xr">The right part of the 8 bytes.</param>
|
||||||
|
private void Encipher(ref uint xl, ref uint xr)
|
||||||
|
{
|
||||||
|
uint Xl;
|
||||||
|
uint Xr;
|
||||||
|
uint temp;
|
||||||
|
short i;
|
||||||
|
|
||||||
|
Xl = xl;
|
||||||
|
Xr = xr;
|
||||||
|
|
||||||
|
for (i = 0; i < N; ++i)
|
||||||
|
{
|
||||||
|
Xl = Xl ^ ctx[i];
|
||||||
|
Xr = F(Xl) ^ Xr;
|
||||||
|
|
||||||
|
temp = Xl;
|
||||||
|
Xl = Xr;
|
||||||
|
Xr = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp = Xl;
|
||||||
|
Xl = Xr;
|
||||||
|
Xr = temp;
|
||||||
|
|
||||||
|
Xr = Xr ^ ctx[N];
|
||||||
|
Xl = Xl ^ ctx[N + 1];
|
||||||
|
|
||||||
|
xl = Xl;
|
||||||
|
xr = Xr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decrypts 8 bytes of data (1 block)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="xl">The left part of the 8 bytes.</param>
|
||||||
|
/// <param name="xr">The right part of the 8 bytes.</param>
|
||||||
|
public void Decipher(ref uint xl, ref uint xr)
|
||||||
|
{
|
||||||
|
uint Xl;
|
||||||
|
uint Xr;
|
||||||
|
uint temp;
|
||||||
|
short i;
|
||||||
|
|
||||||
|
Xl = xl;
|
||||||
|
Xr = xr;
|
||||||
|
|
||||||
|
for (i = N + 1; i > 1; --i)
|
||||||
|
{
|
||||||
|
Xl = Xl ^ ctx[i];
|
||||||
|
Xr = F(Xl) ^ Xr;
|
||||||
|
|
||||||
|
/* Exchange Xl and Xr */
|
||||||
|
temp = Xl;
|
||||||
|
Xl = Xr;
|
||||||
|
Xr = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exchange Xl and Xr */
|
||||||
|
temp = Xl;
|
||||||
|
Xl = Xr;
|
||||||
|
Xr = temp;
|
||||||
|
|
||||||
|
Xr = Xr ^ ctx[1];
|
||||||
|
Xl = Xl ^ ctx[0];
|
||||||
|
|
||||||
|
xl = Xl;
|
||||||
|
xr = Xr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ICryptoTransform implementation for use with CryptoStream.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Mk2BlowfishDecryptor : ICryptoTransform
|
||||||
|
{
|
||||||
|
Mk2Blowfish m_bf;
|
||||||
|
|
||||||
|
public const int BlockSize = 8;
|
||||||
|
|
||||||
|
public bool CanTransformMultipleBlocks { get { return true; } }
|
||||||
|
public bool CanReuseTransform { get { return true; } }
|
||||||
|
public int InputBlockSize { get { return BlockSize; } }
|
||||||
|
public int OutputBlockSize { get { return BlockSize; } }
|
||||||
|
|
||||||
|
public Mk2BlowfishDecryptor (Mk2Blowfish bf)
|
||||||
|
{
|
||||||
|
m_bf = bf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TransformBlock (byte[] inBuffer, int offset, int count, byte[] outBuffer, int outOffset)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < count; i += BlockSize)
|
||||||
|
{
|
||||||
|
uint xl = BigEndian.ToUInt32 (inBuffer, offset+i);
|
||||||
|
uint xr = BigEndian.ToUInt32 (inBuffer, offset+i+4);
|
||||||
|
m_bf.Decipher (ref xl, ref xr);
|
||||||
|
BigEndian.Pack (xl, outBuffer, outOffset+i);
|
||||||
|
BigEndian.Pack (xr, outBuffer, outOffset+i+4);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly byte[] EmptyArray = new byte[0];
|
||||||
|
|
||||||
|
public byte[] TransformFinalBlock (byte[] inBuffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (0 == count)
|
||||||
|
return EmptyArray;
|
||||||
|
|
||||||
|
var input = new byte[(count + BlockSize - 1) / BlockSize * BlockSize];
|
||||||
|
Buffer.BlockCopy (inBuffer, 0, input, 0, inBuffer.Length);
|
||||||
|
|
||||||
|
var output = new byte[input.Length];
|
||||||
|
TransformBlock (input, offset, count, output, 0);
|
||||||
|
|
||||||
|
Array.Resize (ref output, count);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable implementation
|
||||||
|
bool _disposed = false;
|
||||||
|
public void Dispose ()
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
GC.SuppressFinalize (this);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user