mirror of
https://github.com/crskycode/GARbro.git
synced 2024-12-04 10:55:36 +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.Collections.Generic;
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using GameRes.Compression;
|
using GameRes.Compression;
|
||||||
using GameRes.Cryptography;
|
using GameRes.Cryptography;
|
||||||
@ -39,7 +40,7 @@ namespace GameRes.Formats.FC01
|
|||||||
internal class AgsiEntry : PackedEntry
|
internal class AgsiEntry : PackedEntry
|
||||||
{
|
{
|
||||||
public int Method;
|
public int Method;
|
||||||
public bool IsEncrypted;
|
public bool IsEncrypted { get { return Method >= 3 && (Method <= 5 || Method == 7); } }
|
||||||
public bool IsSpecial;
|
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))]
|
[Export(typeof(ArchiveFormat))]
|
||||||
public class PakOpener : ArchiveFormat
|
public class PakOpener : ArchiveFormat
|
||||||
{
|
{
|
||||||
@ -63,65 +70,62 @@ namespace GameRes.Formats.FC01
|
|||||||
public override bool IsHierarchic { get { return false; } }
|
public override bool IsHierarchic { get { return false; } }
|
||||||
public override bool CanWrite { get { return false; } }
|
public override bool CanWrite { get { return false; } }
|
||||||
|
|
||||||
static readonly Dictionary<string, byte[]> KnownKeys = new Dictionary<string,byte[]> {
|
public PakOpener ()
|
||||||
{ "01.pak", new byte[] { 0x4A, 0xC9, 0x75, 0x62, 0x39, 0xC1, 0xBE, 0x54 } },
|
{
|
||||||
{ "02.pak", new byte[] { 0xC9, 0x46, 0x32, 0x69, 0x7B, 0xD2, 0x58, 0x54 } },
|
Signatures = new uint[] { 0x4B434150, 0x24A02028, 0 }; // 'PACK'
|
||||||
{ "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 override ArcFile TryOpen (ArcView file)
|
public override ArcFile TryOpen (ArcView file)
|
||||||
{
|
{
|
||||||
var header = file.View.ReadBytes (0, 12);
|
var reader = IndexReader.Create (file);
|
||||||
byte k1 = file.View.ReadByte (file.MaxOffset-9);
|
if (null == reader)
|
||||||
byte k2 = file.View.ReadByte (file.MaxOffset-6);
|
|
||||||
DecryptHeader (header, k1, k2);
|
|
||||||
if (!header.AsciiEqual ("PACK"))
|
|
||||||
return null;
|
return null;
|
||||||
int count = header.ToInt32 (4);
|
var dir = reader.ReadIndex();
|
||||||
int entry_size = header.ToInt32 (8);
|
if (null == dir)
|
||||||
if (!IsSaneCount (count) || entry_size <= 0x10)
|
|
||||||
return null;
|
return null;
|
||||||
int index_size = count * entry_size;
|
if (dir.Cast<AgsiEntry>().Any (e => e.IsEncrypted))
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
var name = Binary.GetCString (index, index_offset+0x10, name_length);
|
var scheme = QueryScheme (file);
|
||||||
var entry = FormatCatalog.Instance.Create<AgsiEntry> (name);
|
if (null == scheme)
|
||||||
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))
|
|
||||||
return null;
|
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();
|
var arc_name = Path.GetFileName (file.Name).ToLowerInvariant();
|
||||||
byte[] key;
|
byte[] key;
|
||||||
if (!KnownKeys.TryGetValue (arc_name, out key) || key == null)
|
if (scheme.TryGetValue (arc_name, out key) && key != null)
|
||||||
return new ArcFile (file, this, dir);
|
|
||||||
return new AgsiArchive (file, this, dir, key);
|
return new AgsiArchive (file, this, dir, key);
|
||||||
}
|
}
|
||||||
|
return new ArcFile (file, this, dir);
|
||||||
|
}
|
||||||
|
|
||||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||||
{
|
{
|
||||||
var aent = entry as AgsiEntry;
|
var aent = (AgsiEntry)entry;
|
||||||
if (null == aent || 0 == aent.Method)
|
|
||||||
return base.OpenEntry (arc, entry);
|
|
||||||
var aarc = arc as AgsiArchive;
|
var aarc = arc as AgsiArchive;
|
||||||
Stream input;
|
Stream input;
|
||||||
if (aent.IsEncrypted && aarc != null && aarc.Key != null)
|
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;
|
uint enc_size = entry.Size;
|
||||||
if (enc_size > 1024)
|
if (enc_size > 1024)
|
||||||
@ -130,7 +134,7 @@ namespace GameRes.Formats.FC01
|
|||||||
}
|
}
|
||||||
using (var des = DES.Create())
|
using (var des = DES.Create())
|
||||||
{
|
{
|
||||||
des.Key = aarc.Key;
|
des.Key = arc.Key;
|
||||||
des.Mode = CipherMode.ECB;
|
des.Mode = CipherMode.ECB;
|
||||||
des.Padding = PaddingMode.Zeros;
|
des.Padding = PaddingMode.Zeros;
|
||||||
using (var enc = arc.File.CreateStream (entry.Offset, enc_size))
|
using (var enc = arc.File.CreateStream (entry.Offset, enc_size))
|
||||||
@ -139,35 +143,141 @@ namespace GameRes.Formats.FC01
|
|||||||
var output = new byte[enc_size];
|
var output = new byte[enc_size];
|
||||||
dec.Read (output, 0, output.Length);
|
dec.Read (output, 0, output.Length);
|
||||||
int header_size;
|
int header_size;
|
||||||
if (!aent.IsSpecial)
|
if (!entry.IsSpecial)
|
||||||
|
{
|
||||||
header_size = output.ToInt32 (output.Length-4);
|
header_size = output.ToInt32 (output.Length-4);
|
||||||
|
if (header_size > entry.UnpackedSize)
|
||||||
|
throw new InvalidEncryptionScheme();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
header_size = (int)aent.UnpackedSize;
|
header_size = (int)entry.UnpackedSize;
|
||||||
if (!aent.IsSpecial && entry.Size > enc_size)
|
if (!entry.IsSpecial && entry.Size > enc_size)
|
||||||
{
|
{
|
||||||
var header = new byte[header_size];
|
var header = new byte[header_size];
|
||||||
Buffer.BlockCopy (output, 0, header, 0, header_size);
|
Buffer.BlockCopy (output, 0, header, 0, header_size);
|
||||||
input = arc.File.CreateStream (entry.Offset + enc_size, entry.Size - enc_size);
|
var input = arc.File.CreateStream (entry.Offset + enc_size, entry.Size - enc_size);
|
||||||
input = new PrefixStream (header, input);
|
return new PrefixStream (header, input);
|
||||||
}
|
}
|
||||||
else
|
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;
|
int shift = k2 & 7;
|
||||||
if (0 == shift)
|
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);
|
var rnd = new MersenneTwister (seed);
|
||||||
for (int i = 0; i < length; ++i)
|
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