diff --git a/ArcFormats/FC01/ArcPAK.cs b/ArcFormats/FC01/ArcPAK.cs index fda8051f..0de3c25c 100644 --- a/ArcFormats/FC01/ArcPAK.cs +++ b/ArcFormats/FC01/ArcPAK.cs @@ -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> KnownSchemes; + }; + [Export(typeof(ArchiveFormat))] public class PakOpener : ArchiveFormat { @@ -63,103 +70,54 @@ namespace GameRes.Formats.FC01 public override bool IsHierarchic { get { return false; } } public override bool CanWrite { get { return false; } } - static readonly Dictionary KnownKeys = new Dictionary { - { "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 (count); - for (int i = 0; i < count; ++i) + if (dir.Cast().Any (e => e.IsEncrypted)) { - var name = Binary.GetCString (index, index_offset+0x10, name_length); - var entry = FormatCatalog.Instance.Create (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 (scheme.TryGetValue (arc_name, out key) && key != null) + return new AgsiArchive (file, this, dir, key); } - 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); - 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) - { - uint enc_size = entry.Size; - if (enc_size > 1024) - { - enc_size = 1032; - } - using (var des = DES.Create()) - { - des.Key = aarc.Key; - des.Mode = CipherMode.ECB; - des.Padding = PaddingMode.Zeros; - using (var enc = arc.File.CreateStream (entry.Offset, enc_size)) - using (var dec = new InputCryptoStream (enc, des.CreateDecryptor())) - { - var output = new byte[enc_size]; - dec.Read (output, 0, output.Length); - int header_size; - if (!aent.IsSpecial) - header_size = output.ToInt32 (output.Length-4); - else - header_size = (int)aent.UnpackedSize; - if (!aent.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); - } - else - input = new BinMemoryStream (output, 0, header_size, entry.Name); - } - } - } + input = OpenEncryptedEntry (aarc, aent); else input = arc.File.CreateStream (entry.Offset, entry.Size); switch (aent.Method) { - case 6: + case 0: // no compression + case 3: + break; + case 1: // RLE compression + case 4: + break; + case 2: // LZSS bit stream + case 5: + input = new PackedStream (input, new LzBitStream ((int)aent.UnpackedSize)); + break; + case 6: // LZSS compression case 7: input = new LzssStream (input); break; @@ -167,7 +125,159 @@ namespace GameRes.Formats.FC01 return input; } - void DecryptHeader (byte[] header, byte k1, byte k2) + protected Stream OpenEncryptedEntry (AgsiArchive arc, AgsiEntry entry) + { + uint enc_size = entry.Size; + if (enc_size > 1024) + { + enc_size = 1032; + } + using (var des = DES.Create()) + { + des.Key = arc.Key; + des.Mode = CipherMode.ECB; + des.Padding = PaddingMode.Zeros; + using (var enc = arc.File.CreateStream (entry.Offset, enc_size)) + using (var dec = new InputCryptoStream (enc, des.CreateDecryptor())) + { + var output = new byte[enc_size]; + dec.Read (output, 0, output.Length); + int header_size; + if (!entry.IsSpecial) + { + header_size = output.ToInt32 (output.Length-4); + if (header_size > entry.UnpackedSize) + throw new InvalidEncryptionScheme(); + } + else + 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); + var input = arc.File.CreateStream (entry.Offset + enc_size, entry.Size - enc_size); + return new PrefixStream (header, input); + } + else + return new BinMemoryStream (output, 0, header_size, entry.Name); + } + } + } + + protected IDictionary 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>() + }; + + public IDictionary> 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 ReadIndex () + { + using (var index = OpenIndex()) + { + int name_size = m_record_size - 0x10; + var dir = new List (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 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); + } + } + } } diff --git a/Legacy/CocktailSoft/ArcPAK.cs b/Legacy/CocktailSoft/ArcPAK.cs deleted file mode 100644 index fdf67123..00000000 --- a/Legacy/CocktailSoft/ArcPAK.cs +++ /dev/null @@ -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 (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 (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; - } - } - } -}