mirror of
https://github.com/crskycode/GARbro.git
synced 2024-11-27 07:34:00 +08:00
(Cocktail.PakOpener): merged with FC01.PakOpener.
This commit is contained in:
parent
1451963e32
commit
03eec71d9d
@ -27,6 +27,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using GameRes.Compression;
|
||||
using GameRes.Cryptography;
|
||||
@ -39,7 +40,7 @@ namespace GameRes.Formats.FC01
|
||||
internal class AgsiEntry : PackedEntry
|
||||
{
|
||||
public int Method;
|
||||
public bool IsEncrypted;
|
||||
public bool IsEncrypted { get { return Method >= 3 && (Method <= 5 || Method == 7); } }
|
||||
public bool IsSpecial;
|
||||
}
|
||||
|
||||
@ -54,6 +55,12 @@ namespace GameRes.Formats.FC01
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class AgsiScheme : ResourceScheme
|
||||
{
|
||||
public IDictionary<string, IDictionary<string, byte[]>> KnownSchemes;
|
||||
};
|
||||
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class PakOpener : ArchiveFormat
|
||||
{
|
||||
@ -63,65 +70,62 @@ namespace GameRes.Formats.FC01
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
static readonly Dictionary<string, byte[]> KnownKeys = new Dictionary<string,byte[]> {
|
||||
{ "01.pak", new byte[] { 0x4A, 0xC9, 0x75, 0x62, 0x39, 0xC1, 0xBE, 0x54 } },
|
||||
{ "02.pak", new byte[] { 0xC9, 0x46, 0x32, 0x69, 0x7B, 0xD2, 0x58, 0x54 } },
|
||||
{ "03.pak", new byte[] { 0x33, 0x34, 0x37, 0x38, 0x73, 0x68, 0x61, 0x6F } }, // "3478shao"
|
||||
{ "04.pak", new byte[] { 0x73, 0x69, 0x6F, 0x62, 0x6E, 0x72, 0x61, 0x68 } }, // "siobnrah"
|
||||
{ "05.pak", new byte[] { 0xB5, 0x37, 0x70, 0x38, 0x3D, 0x62, 0x48, 0xD1 } },
|
||||
{ "06.pak", new byte[] { 0x4F, 0x7D, 0x40, 0x24, 0x57, 0xCD, 0x68, 0x6E } },
|
||||
};
|
||||
public PakOpener ()
|
||||
{
|
||||
Signatures = new uint[] { 0x4B434150, 0x24A02028, 0 }; // 'PACK'
|
||||
}
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
var header = file.View.ReadBytes (0, 12);
|
||||
byte k1 = file.View.ReadByte (file.MaxOffset-9);
|
||||
byte k2 = file.View.ReadByte (file.MaxOffset-6);
|
||||
DecryptHeader (header, k1, k2);
|
||||
if (!header.AsciiEqual ("PACK"))
|
||||
var reader = IndexReader.Create (file);
|
||||
if (null == reader)
|
||||
return null;
|
||||
int count = header.ToInt32 (4);
|
||||
int entry_size = header.ToInt32 (8);
|
||||
if (!IsSaneCount (count) || entry_size <= 0x10)
|
||||
var dir = reader.ReadIndex();
|
||||
if (null == dir)
|
||||
return null;
|
||||
int index_size = count * entry_size;
|
||||
var index = file.View.ReadBytes (12, (uint)index_size);
|
||||
Decrypt (index, 0, index_size, 7524u);
|
||||
long data_offset = 12 + index_size;
|
||||
int index_offset = 0;
|
||||
int name_length = entry_size - 0x10;
|
||||
var dir = new List<Entry> (count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
if (dir.Cast<AgsiEntry>().Any (e => e.IsEncrypted))
|
||||
{
|
||||
var name = Binary.GetCString (index, index_offset+0x10, name_length);
|
||||
var entry = FormatCatalog.Instance.Create<AgsiEntry> (name);
|
||||
entry.UnpackedSize = index.ToUInt32 (index_offset);
|
||||
entry.Size = index.ToUInt32 (index_offset+4);
|
||||
entry.Method = index.ToInt32 (index_offset+8);
|
||||
entry.Offset = index.ToUInt32 (index_offset+0xC) + data_offset;
|
||||
if (!entry.CheckPlacement (file.MaxOffset))
|
||||
var scheme = QueryScheme (file);
|
||||
if (null == scheme)
|
||||
return null;
|
||||
entry.IsPacked = entry.Method != 0;
|
||||
entry.IsEncrypted = entry.Method >= 3 && (entry.Method <= 5 || entry.Method == 7);
|
||||
entry.IsSpecial = name.Equals ("Copyright.Dat", StringComparison.OrdinalIgnoreCase);
|
||||
dir.Add (entry);
|
||||
index_offset += entry_size;
|
||||
}
|
||||
var arc_name = Path.GetFileName (file.Name).ToLowerInvariant();
|
||||
byte[] key;
|
||||
if (!KnownKeys.TryGetValue (arc_name, out key) || key == null)
|
||||
return new ArcFile (file, this, dir);
|
||||
if (scheme.TryGetValue (arc_name, out key) && key != null)
|
||||
return new AgsiArchive (file, this, dir, key);
|
||||
}
|
||||
return new ArcFile (file, this, dir);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||
{
|
||||
var aent = entry as AgsiEntry;
|
||||
if (null == aent || 0 == aent.Method)
|
||||
return base.OpenEntry (arc, entry);
|
||||
var aent = (AgsiEntry)entry;
|
||||
var aarc = arc as AgsiArchive;
|
||||
Stream input;
|
||||
if (aent.IsEncrypted && aarc != null && aarc.Key != null)
|
||||
input = OpenEncryptedEntry (aarc, aent);
|
||||
else
|
||||
input = arc.File.CreateStream (entry.Offset, entry.Size);
|
||||
switch (aent.Method)
|
||||
{
|
||||
case 0: // no compression
|
||||
case 3:
|
||||
break;
|
||||
case 1: // RLE compression
|
||||
case 4:
|
||||
break;
|
||||
case 2: // LZSS bit stream
|
||||
case 5:
|
||||
input = new PackedStream<LzBitStream> (input, new LzBitStream ((int)aent.UnpackedSize));
|
||||
break;
|
||||
case 6: // LZSS compression
|
||||
case 7:
|
||||
input = new LzssStream (input);
|
||||
break;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
protected Stream OpenEncryptedEntry (AgsiArchive arc, AgsiEntry entry)
|
||||
{
|
||||
uint enc_size = entry.Size;
|
||||
if (enc_size > 1024)
|
||||
@ -130,7 +134,7 @@ namespace GameRes.Formats.FC01
|
||||
}
|
||||
using (var des = DES.Create())
|
||||
{
|
||||
des.Key = aarc.Key;
|
||||
des.Key = arc.Key;
|
||||
des.Mode = CipherMode.ECB;
|
||||
des.Padding = PaddingMode.Zeros;
|
||||
using (var enc = arc.File.CreateStream (entry.Offset, enc_size))
|
||||
@ -139,35 +143,141 @@ namespace GameRes.Formats.FC01
|
||||
var output = new byte[enc_size];
|
||||
dec.Read (output, 0, output.Length);
|
||||
int header_size;
|
||||
if (!aent.IsSpecial)
|
||||
if (!entry.IsSpecial)
|
||||
{
|
||||
header_size = output.ToInt32 (output.Length-4);
|
||||
if (header_size > entry.UnpackedSize)
|
||||
throw new InvalidEncryptionScheme();
|
||||
}
|
||||
else
|
||||
header_size = (int)aent.UnpackedSize;
|
||||
if (!aent.IsSpecial && entry.Size > enc_size)
|
||||
header_size = (int)entry.UnpackedSize;
|
||||
if (!entry.IsSpecial && entry.Size > enc_size)
|
||||
{
|
||||
var header = new byte[header_size];
|
||||
Buffer.BlockCopy (output, 0, header, 0, header_size);
|
||||
input = arc.File.CreateStream (entry.Offset + enc_size, entry.Size - enc_size);
|
||||
input = new PrefixStream (header, input);
|
||||
var input = arc.File.CreateStream (entry.Offset + enc_size, entry.Size - enc_size);
|
||||
return new PrefixStream (header, input);
|
||||
}
|
||||
else
|
||||
input = new BinMemoryStream (output, 0, header_size, entry.Name);
|
||||
return new BinMemoryStream (output, 0, header_size, entry.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
input = arc.File.CreateStream (entry.Offset, entry.Size);
|
||||
switch (aent.Method)
|
||||
{
|
||||
case 6:
|
||||
case 7:
|
||||
input = new LzssStream (input);
|
||||
break;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
void DecryptHeader (byte[] header, byte k1, byte k2)
|
||||
protected IDictionary<string, byte[]> QueryScheme (ArcView file)
|
||||
{
|
||||
var title = FormatCatalog.Instance.LookupGame (file.Name, @"..\*.sb");
|
||||
if (string.IsNullOrEmpty (title) || !KnownSchemes.ContainsKey (title))
|
||||
return null;
|
||||
return KnownSchemes[title];
|
||||
}
|
||||
|
||||
static AgsiScheme DefaultScheme = new AgsiScheme
|
||||
{
|
||||
KnownSchemes = new Dictionary<string, IDictionary<string, byte[]>>()
|
||||
};
|
||||
|
||||
public IDictionary<string, IDictionary<string, byte[]>> KnownSchemes
|
||||
{
|
||||
get { return DefaultScheme.KnownSchemes; }
|
||||
}
|
||||
|
||||
public override ResourceScheme Scheme
|
||||
{
|
||||
get { return DefaultScheme; }
|
||||
set { DefaultScheme = (AgsiScheme)value; }
|
||||
}
|
||||
}
|
||||
|
||||
internal class IndexReader
|
||||
{
|
||||
ArcView m_file;
|
||||
int m_count;
|
||||
int m_record_size;
|
||||
uint m_data_offset;
|
||||
|
||||
public bool IsEncrypted { get; set; }
|
||||
|
||||
public IndexReader (ArcView file, int count, int record_size, bool is_encrypted)
|
||||
{
|
||||
m_file = file;
|
||||
m_count = count;
|
||||
m_record_size = record_size;
|
||||
m_data_offset = (uint)(0xC + m_count * m_record_size);
|
||||
IsEncrypted = is_encrypted;
|
||||
}
|
||||
|
||||
public static IndexReader Create (ArcView file)
|
||||
{
|
||||
int count, record_size;
|
||||
bool is_encrypted = false;
|
||||
if (!file.View.AsciiEqual (0, "PACK"))
|
||||
{
|
||||
var header = file.View.ReadBytes (0, 12);
|
||||
byte k1 = file.View.ReadByte (file.MaxOffset-9);
|
||||
byte k2 = file.View.ReadByte (file.MaxOffset-6);
|
||||
DecryptHeader (header, k1, k2);
|
||||
if (!header.AsciiEqual ("PACK"))
|
||||
return null;
|
||||
count = header.ToInt32 (4);
|
||||
record_size = header.ToInt32 (8);
|
||||
is_encrypted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = file.View.ReadInt32 (4);
|
||||
record_size = file.View.ReadInt32 (8);
|
||||
}
|
||||
if (!ArchiveFormat.IsSaneCount (count) || record_size <= 0x10 || record_size > 0x100)
|
||||
return null;
|
||||
var reader = new IndexReader (file, count, record_size, is_encrypted);
|
||||
if (reader.m_data_offset >= file.MaxOffset)
|
||||
return null;
|
||||
return reader;
|
||||
}
|
||||
|
||||
public List<Entry> ReadIndex ()
|
||||
{
|
||||
using (var index = OpenIndex())
|
||||
{
|
||||
int name_size = m_record_size - 0x10;
|
||||
var dir = new List<Entry> (m_count);
|
||||
for (int i = 0; i < m_count; ++i)
|
||||
{
|
||||
var entry = new AgsiEntry();
|
||||
entry.UnpackedSize = index.ReadUInt32();
|
||||
entry.Size = index.ReadUInt32();
|
||||
entry.Method = index.ReadInt32();
|
||||
entry.Offset = index.ReadUInt32() + m_data_offset;
|
||||
if (!entry.CheckPlacement (m_file.MaxOffset))
|
||||
return null;
|
||||
var name = index.ReadCString (name_size);
|
||||
if (string.IsNullOrEmpty (name))
|
||||
return null;
|
||||
entry.Name = name;
|
||||
entry.Type = FormatCatalog.Instance.GetTypeFromName (name);
|
||||
entry.IsPacked = entry.Method != 0 && entry.Method != 3;
|
||||
entry.IsSpecial = name.Equals ("Copyright.Dat", StringComparison.OrdinalIgnoreCase);
|
||||
dir.Add (entry);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
IBinaryStream OpenIndex ()
|
||||
{
|
||||
int index_size = m_count * m_record_size;
|
||||
if (IsEncrypted)
|
||||
{
|
||||
var index = m_file.View.ReadBytes (12, (uint)index_size);
|
||||
DecryptIndex (index, 0, index_size, 7524u);
|
||||
return new BinMemoryStream (index);
|
||||
}
|
||||
else
|
||||
return m_file.CreateStream (12, (uint)index_size);
|
||||
}
|
||||
|
||||
static void DecryptHeader (byte[] header, byte k1, byte k2)
|
||||
{
|
||||
int shift = k2 & 7;
|
||||
if (0 == shift)
|
||||
@ -179,7 +289,7 @@ namespace GameRes.Formats.FC01
|
||||
}
|
||||
}
|
||||
|
||||
void Decrypt (byte[] data, int pos, int length, uint seed)
|
||||
static void DecryptIndex (byte[] data, int pos, int length, uint seed)
|
||||
{
|
||||
var rnd = new MersenneTwister (seed);
|
||||
for (int i = 0; i < length; ++i)
|
||||
@ -193,4 +303,80 @@ namespace GameRes.Formats.FC01
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class LzBitStream : Decompressor
|
||||
{
|
||||
MsbBitStream m_input;
|
||||
int m_unpacked_size;
|
||||
|
||||
public LzBitStream ()
|
||||
{
|
||||
}
|
||||
|
||||
public LzBitStream (int unpacked_size)
|
||||
{
|
||||
m_unpacked_size = unpacked_size;
|
||||
}
|
||||
|
||||
public override void Initialize (Stream input)
|
||||
{
|
||||
m_input = new MsbBitStream (input, true);
|
||||
}
|
||||
|
||||
protected override IEnumerator<int> Unpack ()
|
||||
{
|
||||
var frame = new byte[0x1000];
|
||||
int dst = 0;
|
||||
int frame_pos = 1;
|
||||
while (dst < m_unpacked_size)
|
||||
{
|
||||
int bit = m_input.GetNextBit();
|
||||
if (bit != 0)
|
||||
{
|
||||
if (-1 == bit)
|
||||
yield break;
|
||||
int v = m_input.GetBits (8);
|
||||
if (-1 == v)
|
||||
yield break;
|
||||
frame[frame_pos++ & 0xFFF] = m_buffer[m_pos++] = (byte)v;
|
||||
dst++;
|
||||
if (0 == --m_length)
|
||||
yield return m_pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
int offset = m_input.GetBits (12);
|
||||
if (-1 == offset)
|
||||
yield break;
|
||||
int count = m_input.GetBits (4);
|
||||
if (-1 == count)
|
||||
yield break;
|
||||
count += 2;
|
||||
dst += count;
|
||||
while (count --> 0)
|
||||
{
|
||||
byte v = frame[offset++ & 0xFFF];
|
||||
frame[frame_pos++ & 0xFFF] = v;
|
||||
m_buffer[m_pos++] = v;
|
||||
if (0 == --m_length)
|
||||
yield return m_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool m_disposed = false;
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (!m_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
m_input.Dispose();
|
||||
m_disposed = true;
|
||||
}
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,160 +0,0 @@
|
||||
//! \file ArcPAK.cs
|
||||
//! \date 2017 Dec 15
|
||||
//! \brief Cocktail Soft resource archive.
|
||||
//
|
||||
// Copyright (C) 2017 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;
|
||||
|
||||
namespace GameRes.Formats.Cocktail
|
||||
{
|
||||
internal class CompressedEntry : PackedEntry
|
||||
{
|
||||
public int Compression;
|
||||
}
|
||||
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class PakOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "PAK/COCKTAIL"; } }
|
||||
public override string Description { get { return "Cocktail Soft resource archive"; } }
|
||||
public override uint Signature { get { return 0x4B434150; } } // 'PACK'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
// static readonly uint[] KnownKeyCode = { 0x385AB4BA, 0x52CCCF4E };
|
||||
// static readonly uint[] KnownKeyCode = { 0x33C074B5, 0xB6744357 };
|
||||
static readonly uint[] KnownKeyCode = { 0xBBB64423, 0x4D765A33 };
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
int count = file.View.ReadInt32 (4);
|
||||
if (!IsSaneCount (count))
|
||||
return null;
|
||||
uint record_size = file.View.ReadUInt32 (8);
|
||||
if (record_size <= 0x10 || record_size > 0x100)
|
||||
return null;
|
||||
uint index_offset = 0xC;
|
||||
uint data_offset = index_offset + (uint)count * record_size;
|
||||
if (data_offset >= file.MaxOffset
|
||||
|| data_offset > file.View.Reserve (0, data_offset))
|
||||
return null;
|
||||
|
||||
uint name_size = record_size - 0x10;
|
||||
var dir = new List<Entry> (count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
var name = file.View.ReadString (index_offset+0x10, name_size);
|
||||
if (string.IsNullOrEmpty (name))
|
||||
return null;
|
||||
var entry = FormatCatalog.Instance.Create<CompressedEntry> (name);
|
||||
entry.UnpackedSize = file.View.ReadUInt32 (index_offset);
|
||||
entry.Size = file.View.ReadUInt32 (index_offset+4);
|
||||
entry.Compression = file.View.ReadInt32 (index_offset+8);
|
||||
entry.Offset = file.View.ReadUInt32 (index_offset+0xC) + data_offset;
|
||||
if (!entry.CheckPlacement (file.MaxOffset))
|
||||
return null;
|
||||
entry.IsPacked = entry.Compression != 0;
|
||||
index_offset += record_size;
|
||||
dir.Add (entry);
|
||||
}
|
||||
return new ArcFile (file, this, dir);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||
{
|
||||
var pent = entry as CompressedEntry;
|
||||
if (null == pent || pent.Compression != 2)
|
||||
return base.OpenEntry (arc, entry);
|
||||
|
||||
using (var input = arc.File.CreateStream (entry.Offset, entry.Size))
|
||||
using (var unpacker = new LzCompression (input, (int)pent.UnpackedSize))
|
||||
{
|
||||
var data = unpacker.Unpack();
|
||||
return new BinMemoryStream (data, entry.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class LzCompression : IDisposable
|
||||
{
|
||||
MsbBitStream m_input;
|
||||
byte[] m_output;
|
||||
byte[] m_frame;
|
||||
|
||||
public LzCompression (IBinaryStream input, int unpacked_size)
|
||||
{
|
||||
m_input = new MsbBitStream (input.AsStream);
|
||||
m_output = new byte[unpacked_size];
|
||||
m_frame = new byte[0x1000];
|
||||
}
|
||||
|
||||
public byte[] Unpack ()
|
||||
{
|
||||
int dst = 0;
|
||||
int frame_pos = 1;
|
||||
while (dst < m_output.Length)
|
||||
{
|
||||
int bit = m_input.GetNextBit();
|
||||
if (bit != 0)
|
||||
{
|
||||
if (-1 == bit)
|
||||
break;
|
||||
int v = m_input.GetBits (8);
|
||||
if (-1 == v)
|
||||
break;
|
||||
m_frame[frame_pos++ & 0xFFF] = m_output[dst++] = (byte)v;
|
||||
}
|
||||
else
|
||||
{
|
||||
int offset = m_input.GetBits (12);
|
||||
if (-1 == offset)
|
||||
break;
|
||||
int count = m_input.GetBits (4);
|
||||
if (-1 == count)
|
||||
break;
|
||||
count += 2;
|
||||
while (count --> 0)
|
||||
{
|
||||
byte v = m_frame[offset++ & 0xFFF];
|
||||
m_output[dst++] = v;
|
||||
m_frame[frame_pos++ & 0xFFF] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return m_output;
|
||||
}
|
||||
|
||||
bool m_disposed = false;
|
||||
public void Dispose ()
|
||||
{
|
||||
if (!m_disposed)
|
||||
{
|
||||
m_input.Dispose();
|
||||
m_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user