From 341800cda94bffeb55c85764dc883494d71b2e65 Mon Sep 17 00:00:00 2001 From: morkt Date: Tue, 8 Dec 2015 21:19:13 +0400 Subject: [PATCH] implemented G2 archives and PGX images. --- ArcFormats/ArcFormats.csproj | 2 + ArcFormats/Glib2/ArcG2.cs | 347 +++++++++++++++++++++++++++++++++++ ArcFormats/Glib2/ImagePGX.cs | 266 +++++++++++++++++++++++++++ supported.html | 6 + 4 files changed, 621 insertions(+) create mode 100644 ArcFormats/Glib2/ArcG2.cs create mode 100644 ArcFormats/Glib2/ImagePGX.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 2bcd592d..ff1c775a 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -82,6 +82,8 @@ WidgetMCG.xaml + + diff --git a/ArcFormats/Glib2/ArcG2.cs b/ArcFormats/Glib2/ArcG2.cs new file mode 100644 index 00000000..d9a29f32 --- /dev/null +++ b/ArcFormats/Glib2/ArcG2.cs @@ -0,0 +1,347 @@ +//! \file ArcG2.cs +//! \date Mon Dec 07 18:35:41 2015 +//! \brief Glib2 resource archive. +// +// Copyright (C) 2015 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; +using System.Linq; +using GameRes.Utility; + +namespace GameRes.Formats.Glib2 +{ + public delegate int PermutationDelegate (int offset, int value); + + public class G2Scheme + { + public byte[] SrcOrder; + public byte[] DstOrder; + + public PermutationDelegate FirstPermutation; + public PermutationDelegate SecondPermutation; + + public void Decrypt (byte[] input, int input_origin, byte[] output, int output_origin, int length) + { + int i; + for (i = 0; i < (length & ~3); ++i) + { + int src = input_origin + (i & ~3) + SrcOrder[i & 3]; + int dst = output_origin + (i & ~3) + DstOrder[i & 3]; + output[dst] = (byte)SecondPermutation (i, FirstPermutation (i, input[src])); + } + for (; i < length; ++i) + { + output[output_origin+i] = (byte)SecondPermutation (i, FirstPermutation (i, input[input_origin+i])); + } + } + + public void Decrypt (byte[] input, byte[] output) + { + Decrypt (input, 0, output, 0, input.Length); + } + + public byte[] Decrypt (byte[] encrypted) + { + var data = new byte[encrypted.Length]; + Decrypt (encrypted, data); + return data; + } + } + + internal class G2Entry : Entry + { + public readonly uint[] Keys = new uint[4]; + } + + [Export(typeof(ArchiveFormat))] + public class G2Opener : ArchiveFormat + { + public override string Tag { get { return "G2"; } } + public override string Description { get { return "Glib2 engine resource archive"; } } + public override uint Signature { get { return 0x47D33310; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return false; } } + + public G2Opener () + { + Extensions = new string[] { "g2", "stx" }; + } + + static readonly G2Scheme HeaderEncryption = G2MetaScheme.CreateInstance (0x8465B49B); + + public override ArcFile TryOpen (ArcView file) + { + var header = file.View.ReadBytes (0, 0x5C); + if (header.Length != 0x5C) + return null; + header = HeaderEncryption.Decrypt (header); + if (!Binary.AsciiEqual (header, "GLibArchiveData2.") || header[0x12] != 0) + return null; + int version = header[0x11] - '0'; + if (version != 0 && version != 1) + return null; + uint index_offset = LittleEndian.ToUInt32 (header, 0x54); + uint index_size = LittleEndian.ToUInt32 (header, 0x58); + byte[][] encrypted_index = new byte[2][]; + encrypted_index[0] = file.View.ReadBytes (index_offset, index_size); + if (encrypted_index[0].Length != index_size) + return null; + encrypted_index[1] = new byte[index_size]; + uint[] keys = { + LittleEndian.ToUInt32 (header, 0x44), + LittleEndian.ToUInt32 (header, 0x34), + LittleEndian.ToUInt32 (header, 0x24), + LittleEndian.ToUInt32 (header, 0x14), + }; + int i = 0; + foreach (var key in keys) + { + var decoder = G2MetaScheme.CreateInstance (key); + decoder.Decrypt (encrypted_index[i], encrypted_index[i^1]); + i ^= 1; + } + byte[] index = encrypted_index[i]; + if (!Binary.AsciiEqual (index, "CDBD")) + return null; + int count = LittleEndian.ToInt32 (index, 4); + int current_offset = 0x10; + int info_base = current_offset + LittleEndian.ToInt32 (index, 8); + int names_base = current_offset + count * 0x18; + var dir = new List (count); + for (i = 0; i < count; ++i) + { + int name_offset = names_base + LittleEndian.ToInt32 (index, current_offset); + int parent_dir = LittleEndian.ToInt32 (index, current_offset+8); + int attr = LittleEndian.ToInt32 (index, current_offset+0xC); + var name = Binary.GetCString (index, name_offset, info_base-name_offset); + if (parent_dir != -1) + name = Path.Combine (dir[parent_dir].Name, name); + var entry = new G2Entry { Name = name }; + if (0x100 == attr) + { + int info_offset = info_base + LittleEndian.ToInt32 (index, current_offset+0x10); + entry.Size = LittleEndian.ToUInt32 (index, info_offset+8); + entry.Offset = LittleEndian.ToUInt32 (index, info_offset+0xC); + entry.Type = FormatCatalog.Instance.GetTypeFromName (name); + for (int j = 0; j < 4; ++j) + { + info_offset += 0x10; + entry.Keys[j] = LittleEndian.ToUInt32 (index, info_offset); + } + } + dir.Add (entry); + current_offset += 0x18; + } + return new ArcFile (file, this, dir.Where (e => e.Offset != -1).ToList()); + } + + const int EntryChunkSize = 0x20000; + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var g2ent = entry as G2Entry; + if (null == g2ent) + return base.OpenEntry (arc, entry); + + int entry_size = (int)g2ent.Size; + int offset = 0; + var decoders = new G2Scheme[4]; + for (int i = 0; i < 4 && offset < entry_size; ++i) + { + decoders[i] = G2MetaScheme.CreateInstance (g2ent.Keys[i]); + if (null != decoders[i]) + offset += EntryChunkSize; + } + var output = new byte[entry_size]; + var input = new byte[EntryChunkSize]; + int current_decoder = 0; + offset = 0; + while (offset < entry_size) + { + int current_chunk_size = Math.Min (EntryChunkSize, entry_size - offset); + if (null != decoders[current_decoder]) + { + arc.File.View.Read (g2ent.Offset+offset, input, 0, (uint)current_chunk_size); + decoders[current_decoder].Decrypt (input, 0, output, offset, current_chunk_size); + } + else + { + arc.File.View.Read (g2ent.Offset+offset, output, offset, (uint)current_chunk_size); + } + current_decoder = (current_decoder + 1) & 3; + offset += current_chunk_size; + } + return new MemoryStream (output); + } + } + + public class G2MetaScheme + { + public static G2Scheme CreateInstance (uint key) + { + ushort hash = (ushort)((key * 0x5F) >> 13); + int i = Array.IndexOf (PermutationHashes, hash); + if (-1 == i) + return null; + int src_order = i / 150; + int dst_order = i / 30 - 5 * src_order; + if (dst_order >= src_order) + ++dst_order; + int second_action = (i / 5) % 6; + int first_action = i % 5; + if (first_action >= second_action) + ++first_action; + return new G2Scheme + { + SrcOrder = MutationOrder[src_order], + DstOrder = MutationOrder[dst_order], + FirstPermutation = Permutations[first_action], + SecondPermutation = Permutations[second_action], + }; + } + + static readonly byte[][] MutationOrder = new byte[][] + { + new byte[] { 3, 2, 1, 0 }, + new byte[] { 0, 2, 1, 3 }, + new byte[] { 1, 0, 3, 2 }, + new byte[] { 3, 0, 2, 1 }, + new byte[] { 2, 1, 3, 0 }, + new byte[] { 3, 2, 1, 0 }, + }; + + static readonly PermutationDelegate[] Permutations = { + (i, x) => Binary.RotByteR ((byte)x, i), + (i, x) => x ^ i, + (i, x) => ~x, + (i, x) => ~(x - 100), + (i, x) => x + i, + (i, x) => Binary.RotByteL ((byte)x, 4), + }; + + static readonly ushort[] PermutationHashes = { + 0x0DF0, 0xB0B7, 0x45B8, 0xC9B4, 0xCFB3, 0xF0B0, 0xA85F, 0x2C0B, 0x648D, + 0xD4E1, 0x11EA, 0xDAA7, 0xFB5F, 0xE83A, 0x82A4, 0x0F5D, 0xAE64, 0x6F6F, + 0xEC17, 0x040E, 0x25F2, 0xD2A1, 0x3F4E, 0x9EB3, 0xA3C3, 0x126C, 0xCBE3, + 0x8597, 0x6E19, 0xA4E5, 0x644E, 0xBD12, 0x43FF, 0x635D, 0xB5FB, 0x8B1E, + 0x7840, 0xFFB8, 0x6385, 0x8C22, 0x2D2F, 0x479B, 0x185C, 0x1970, 0x0C73, + 0xE029, 0xE04D, 0xF855, 0xA1F2, 0xDCE5, 0x464B, 0x997A, 0x50F3, 0xC59D, + 0x4FCF, 0x4434, 0x03E6, 0x4440, 0xB794, 0xAD98, 0xC4D3, 0x851C, 0x7643, + 0x0824, 0x624B, 0xF308, 0x8BC9, 0x8FCD, 0x0805, 0x28C6, 0x7FDA, 0x7171, + 0xC93C, 0x3F85, 0xE0B9, 0x64A8, 0xBF46, 0x5652, 0xEBAA, 0x9507, 0x4DF4, + 0x692C, 0x773D, 0x65CF, 0x2298, 0x7D43, 0xC1F2, 0x2DE8, 0x6758, 0xEA01, + 0x8B65, 0xE7FE, 0x5466, 0x8F59, 0x44B2, 0x1CCB, 0x92DA, 0xB384, 0xC5C4, + 0x5179, 0xDC2E, 0xBC2A, 0xA07D, 0x74D5, 0x3492, 0x0BF6, 0xC3F5, 0xB68C, + 0xAABB, 0x114E, 0xB7B8, 0x6636, 0xA419, 0xF92F, 0xE2B4, 0x273B, 0xCC0B, + 0x4F64, 0x2EFE, 0xBF77, 0xD00A, 0x4FA9, 0x1AFF, 0x5930, 0xC18B, 0x80BD, + 0x22D2, 0xD8D2, 0x8B8C, 0x1F8C, 0x6B41, 0x27A1, 0xE38A, 0xF328, 0x910B, + 0x6D91, 0x81A2, 0xC5AE, 0xE7B9, 0x98F7, 0x044D, 0x7851, 0xCF13, 0x5EE3, + 0x8C5A, 0x5D13, 0xE3A9, 0x5599, 0x60BE, 0x0775, 0x81AC, 0x63C8, 0x80A1, + 0x1EAB, 0xC3EB, 0x2847, 0x29C6, 0x5D02, 0xB5DB, 0xDA13, 0xC3DB, 0x8B4C, + 0x0347, 0x89B1, 0x8743, 0x3407, 0x9870, 0xA97D, 0xBC0F, 0x2452, 0xE7B5, + 0x1016, 0x1759, 0xE219, 0x41FE, 0x6BEE, 0x2EB6, 0xAE15, 0x9010, 0xD486, + 0x7C58, 0xBD7F, 0xBDC9, 0x5385, 0xC1DF, 0x55FD, 0x1543, 0x9DD2, 0xDF04, + 0x561C, 0xB184, 0x0E81, 0xB145, 0x23D7, 0xBBA8, 0x4C08, 0xC6DC, 0x68FE, + 0xB6FC, 0xA2E1, 0x49C2, 0x4C6E, 0x8474, 0x3FBC, 0x4059, 0xE75C, 0xF748, + 0x2542, 0x3E89, 0x1932, 0xAEC3, 0x1A71, 0x3229, 0x5F8F, 0xDF64, 0x34EA, + 0xB7AF, 0xBEA4, 0x5129, 0xBA1B, 0x00A0, 0x0731, 0x3F10, 0x1B52, 0xD0D6, + 0xCB01, 0x0CD2, 0xC522, 0xAAF5, 0x3B34, 0x7EEA, 0x1FE8, 0xAE3B, 0x144D, + 0xCA14, 0x3CD4, 0x9AFB, 0x673A, 0x3B41, 0x5EA0, 0x8A5F, 0x0289, 0x7DDC, + 0x9FBB, 0x75DC, 0x59D3, 0x4907, 0x83D5, 0x747C, 0xFC0D, 0x650B, 0xBA84, + 0xF6CF, 0x21AF, 0xFF7E, 0xACBA, 0xC8E1, 0x4DBB, 0xCBD9, 0x3415, 0x2BBB, + 0x6DA3, 0x349E, 0xD30B, 0xCEDB, 0x739E, 0xE0F3, 0xCB44, 0x7E1F, 0x274B, + 0x3FCE, 0xEEC8, 0x4306, 0x7EC3, 0x4CBF, 0x766B, 0x582C, 0x47D1, 0x688E, + 0x15BC, 0xEEEA, 0xD2B8, 0x8674, 0x088B, 0xE90E, 0x3A3F, 0xF70A, 0x2393, + 0x68B3, 0x871E, 0xF6FC, 0x4631, 0xFEAD, 0x89B5, 0x1242, 0xD68C, 0x1214, + 0x028A, 0xE3F3, 0xBC86, 0x679E, 0xB301, 0x2827, 0xC90F, 0xA4DD, 0x0E14, + 0x4225, 0x46EF, 0x0C69, 0x12BB, 0xA14B, 0x4319, 0x5054, 0x1BBE, 0x1144, + 0x29B8, 0x74E7, 0x94C8, 0xA217, 0x2001, 0xE86F, 0x01B3, 0x4713, 0x737A, + 0x45B2, 0x68BB, 0x86FD, 0x696D, 0xB604, 0x0D2E, 0xC643, 0x6F65, 0x6EC4, + 0xCBC0, 0xE505, 0x0677, 0x6427, 0x6481, 0xB195, 0xE877, 0x4216, 0xD938, + 0xD165, 0xBB70, 0x9980, 0x1F0A, 0xA96C, 0x1DB9, 0x7B0A, 0xB400, 0xC636, + 0x17EA, 0x129A, 0x46F8, 0x40DC, 0xEDC0, 0x7BC5, 0x4521, 0x15E5, 0xB0F5, + 0x4E1D, 0xD7AF, 0xF85C, 0xF100, 0x490E, 0x74E6, 0x9D7E, 0x112D, 0x5063, + 0x6704, 0xE9B9, 0xA519, 0xA626, 0x0CBC, 0x5880, 0x8224, 0xF706, 0x2A08, + 0x317B, 0x2E86, 0x46CF, 0xFE7E, 0x9D15, 0xBC5E, 0x35FC, 0x9BCD, 0xA1B0, + 0x9191, 0xFBFC, 0x54D9, 0xE1B3, 0xD381, 0xE3DA, 0x41BD, 0xD27D, 0x9B0C, + 0xBAB2, 0xC6FA, 0xB910, 0xD4C1, 0xAB62, 0x756D, 0xD4FF, 0xE1C4, 0xC052, + 0x32E2, 0x6063, 0x6CDF, 0x8BD6, 0xBCE0, 0x8026, 0x63E8, 0x2201, 0x9800, + 0x5572, 0x674C, 0xDD3B, 0x1E95, 0xE9EF, 0x698E, 0xF509, 0xA410, 0xCA5F, + 0xD175, 0xB525, 0xAD0F, 0x4E44, 0x3E58, 0x554E, 0x97B3, 0x50F5, 0x8BAC, + 0x5A32, 0x77AA, 0xD14F, 0x3ED8, 0x7892, 0x5A02, 0x0E66, 0x8269, 0x120A, + 0xADEA, 0x82A1, 0xE1F0, 0x8B35, 0xE7E1, 0xFD1F, 0xFD65, 0x7E3A, 0xB4B7, + 0x5FFC, 0xF75A, 0xB2DB, 0x5EEA, 0xE63E, 0x13C7, 0xB5F9, 0x02B9, 0x9F2E, + 0x620A, 0x2108, 0x10BB, 0xB124, 0x6617, 0x774D, 0x49A5, 0x93CD, 0xEDEC, + 0xA50B, 0x7996, 0x8E82, 0x1782, 0x87A5, 0x1D08, 0x8C23, 0x7208, 0x5FD5, + 0x3FFC, 0xE23C, 0x182C, 0x0338, 0xA32B, 0x9C08, 0x907C, 0xDF0E, 0xAF61, + 0x5613, 0xACB7, 0x8AA9, 0xF1BA, 0x6AC2, 0xCAD5, 0xED60, 0x2C74, 0xA172, + 0x4DC4, 0x30CE, 0x02AB, 0x3C3C, 0x260E, 0xAEDD, 0xCD27, 0x8D18, 0x8E4E, + 0x94CD, 0x8A82, 0x573A, 0x84A8, 0x5B10, 0x8E93, 0x1DB2, 0x8D7E, 0xB892, + 0xCD2E, 0x4D24, 0xC8FD, 0x571D, 0x978E, 0x94E2, 0x7471, 0x288C, 0x8722, + 0x7B92, 0x48ED, 0x2583, 0xBE7F, 0x0D86, 0x347B, 0x90EE, 0x955F, 0x6277, + 0x64E3, 0xDAFE, 0xBC9B, 0x96CE, 0xEBE4, 0xEFBA, 0x6C19, 0x42A8, 0xC2DE, + 0xA5FD, 0x68BD, 0xD469, 0x5511, 0x6C69, 0x671E, 0x2E77, 0xCF3B, 0x2052, + 0x88EC, 0x6577, 0xED28, 0xA811, 0xB7F5, 0x213F, 0x1F24, 0xEEB5, 0x9511, + 0xC44D, 0x6532, 0xD969, 0xAA6B, 0xF7E7, 0xC645, 0xC868, 0xB65B, 0x0029, + 0xEAD2, 0x58A4, 0x0153, 0x9E90, 0x6E05, 0xDC61, 0x2F9B, 0x8CD4, 0xCC13, + 0x5621, 0x056C, 0xC7A3, 0xD666, 0x40CC, 0x4ED9, 0x6071, 0x0E42, 0x23E5, + 0xE18F, 0x1126, 0x4889, 0x2AF2, 0xACBB, 0x2AD3, 0xDC2B, 0x54E8, 0x1478, + 0x9FF3, 0x9DD0, 0x63BB, 0x0430, 0xE5D4, 0x91C9, 0xB055, 0xE925, 0x6292, + 0x6228, 0x14DE, 0x4D80, 0x962A, 0x801E, 0x1576, 0x2D36, 0xE3C3, 0x6F7F, + 0x07EA, 0xAA9F, 0xCAE9, 0x8F76, 0xD1CB, 0xE490, 0x6956, 0x98CA, 0x4DB0, + 0x922E, 0x3E05, 0x1538, 0xFFA3, 0x4A24, 0x0467, 0x7940, 0x7B14, 0xD4E8, + 0xA9E8, 0xF9D2, 0xDDD3, 0xCEF2, 0x3A55, 0xF118, 0xD0F2, 0x5688, 0xE2BE, + 0xC0ED, 0xFED3, 0x33B1, 0xAC96, 0xA6F2, 0x0F0B, 0xE596, 0x0A88, 0xB346, + 0x2208, 0x18A1, 0xAE40, 0x7ECC, 0xA2E4, 0xB661, 0xBDEE, 0xC14C, 0xD218, + 0x8280, 0x7F61, 0x8FA3, 0x4AA3, 0xA61B, 0xDD56, 0xB4AD, 0xE436, 0x5AF9, + 0xE595, 0x8603, 0x6FF3, 0xBF3F, 0x2FCD, 0xD446, 0x475A, 0x508F, 0xC27E, + 0x6096, 0x9A42, 0x19C4, 0x6109, 0xEC7A, 0x5937, 0x218F, 0x6FFF, 0x2033, + 0x922C, 0x5C66, 0xC20D, 0xE424, 0xF193, 0x159D, 0xCF98, 0x7AE0, 0x79BA, + 0xF8AC, 0x0927, 0x0BAC, 0xE3F4, 0x26A7, 0x1C7C, 0x8D05, 0x5A6A, 0x6A85, + 0x6947, 0xEB07, 0x03F4, 0x166F, 0x8DE4, 0xFB48, 0xF828, 0x3977, 0x5A67, + 0x4D08, 0x838D, 0xCBB8, 0xD737, 0x0EFE, 0xED83, 0xF808, 0xB4AC, 0x721E, + 0x1F51, 0xD55C, 0xD2E0, 0xE243, 0x301F, 0x4630, 0x26D7, 0x3D02, 0xC71E, + 0x14F0, 0xE56A, 0xABC7, 0x26B7, 0x1609, 0x1A11, 0xADA1, 0xCB41, 0x6C4F, + 0x84F2, 0xB45D, 0x1CE3, 0x70A3, 0x9661, 0x4374, 0x6C5A, 0xE76C, 0x7333, + 0xE26D, 0xEFA8, 0xF66A, 0x6E77, 0xF1CC, 0xB7E8, 0x0433, 0x96F6, 0x8779, + 0x8181, 0xF982, 0xD352, 0x6338, 0x89E9, 0x9C75, 0x30F7, 0x4B4A, 0x2B4E, + 0xE942, 0x1A26, 0xB237, 0xD4F4, 0x5300, 0xC4D0, 0x58EF, 0xA81D, 0xD733, + 0x1A6C, 0x8096, 0xA37C, 0x9B5B, 0x390D, 0xE007, 0xED2F, 0x8287, 0xA549, + 0xB63B, 0xA3E4, 0xF115, 0xCD68, 0xF39A, 0xD125, 0x3F16, 0x47CD, 0x582E, + 0x508D, 0xED45, 0x9073, 0x60EF, 0x2591, 0xF962, 0x7C2E, 0xADB1, 0x2FB5, + 0x376F, 0x4F41, 0xA24A, 0x2AAA, 0xFA2B, 0x5FA6, 0xADCF, 0x1644, 0x575E, + 0x8583, 0x055D, 0x2AE7, 0xA515, 0x2700, 0x5FC3, 0x6001, 0xCB0E, 0x1351, + 0x7C19, 0xBFD3, 0xF360, 0x706C, 0x1227, 0x6411, 0x3649, 0xF843, 0x7CFE, + 0x5DC3, 0x640D, 0x7E05, 0x251C, 0x8EDF, 0x1F31, 0x39F8, 0x075B, 0x3BBA, + 0x75B8, 0x4CBC, 0xAA60, 0xE1D4, 0x35C9, 0x3516, 0xB0D0, 0x7B3B, 0xF861, + 0x6B87, 0x3362, 0x1704, 0x7F1C, 0x587B, 0x966B, 0xD7DF, 0xBE5D, 0x82EF, + 0xE193, 0xD841, 0xBC82, 0x9AF9, 0x8526, 0x7018, 0x8E13, 0x5E23, 0xF49D, + 0x4035, 0x9118, 0x2C41, 0x826C, 0x561A, 0x5E7B, 0x38D4, 0xC263, 0x5979, + 0xB15A, 0x4D89, 0xC11C, 0x8516, 0x0343, 0xF590, 0xFF38, 0x8385, 0x4B5B, + 0xEEF2, 0x99B4, 0xB11B, 0x94D6, 0x8961, 0xE9F9, 0x3138, 0xB3A0, 0x09CC, + 0x7A4F, 0x47DE, 0xE478, 0xD9FF, 0xE62C, 0x9453, 0x6D0C, 0x2FC2, 0x7444, + }; + } +} diff --git a/ArcFormats/Glib2/ImagePGX.cs b/ArcFormats/Glib2/ImagePGX.cs new file mode 100644 index 00000000..48cba5b2 --- /dev/null +++ b/ArcFormats/Glib2/ImagePGX.cs @@ -0,0 +1,266 @@ +//! \file ImagePGX.cs +//! \date Tue Dec 08 02:50:45 2015 +//! \brief Glib2 image format. +// +// Copyright (C) 2015 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 GameRes.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Windows.Media; + +namespace GameRes.Formats.Glib2 +{ + internal class PgxMetaData : ImageMetaData + { + public int PackedSize; + } + + internal class StxLayerInfo + { + public string Path; + public Rectangle? Rect; + public string Effect; + public int Blend; + } + + [Export(typeof(ImageFormat))] + public class PgxFormat : ImageFormat + { + public override string Tag { get { return "PGX"; } } + public override string Description { get { return "Glib2 engine image format"; } } + public override uint Signature { get { return 0x00584750; } } // 'PGX' + + static readonly InfoReader InfoCache = new InfoReader(); + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x18]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + return new PgxMetaData + { + Width = LittleEndian.ToUInt32 (header, 8), + Height = LittleEndian.ToUInt32 (header, 12), + BPP = LittleEndian.ToInt16 (header, 0x10) == 0 ? 24 : 32, + PackedSize = LittleEndian.ToInt32 (header, 0x14), + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = (PgxMetaData)info; + PixelFormat format = 32 == meta.BPP ? PixelFormats.Bgra32 : PixelFormats.Bgr32; + int stride = (int)meta.Width * 4; + var pixels = new byte[stride * (int)meta.Height]; + stream.Seek (-meta.PackedSize, SeekOrigin.End); + LzssUnpack (stream, pixels); + var layer = InfoCache.GetInfo (info.FileName); + if (null != layer && null != layer.Rect) + { + info.OffsetX = layer.Rect.Value.X; + info.OffsetY = layer.Rect.Value.Y; + } + return ImageData.Create (info, format, null, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("PgxFormat.Write not implemented"); + } + + static void LzssUnpack (Stream input, byte[] output) + { + var frame = new byte[0x1000]; + int frame_pos = 0xFEE; + using (var lz = new ArcView.Reader (input)) + { + int dst = 0; + int bits = 1; + while (dst < output.Length) + { + if (1 == bits) + bits = lz.ReadByte() | 0x100; + + if (0 != (bits & 1)) + { + byte b = lz.ReadByte(); + output[dst++] = b; + frame[frame_pos++] = b; + frame_pos &= 0xFFF; + } + else + { + byte lo = lz.ReadByte(); + byte hi = lz.ReadByte(); + int offset = (hi & 0xF0) << 4 | lo; + int count = Math.Min ((~hi & 0xF) + 3, output.Length-dst); + for (int i = 0; i < count; ++i) + { + byte b = frame[offset++ & 0xFFF]; + output[dst++] = b; + frame[frame_pos++] = b; + frame_pos &= 0xFFF; + } + } + bits >>= 1; + } + } + } + } + + internal class InfoReader + { + string m_last_info_dir; + Dictionary m_layer_map; + + internal class StxEntry + { + public string FullName; + public string Name; + public int Attr; + public uint InfoOffset; + public uint InfoSize; + } + + public StxLayerInfo GetInfo (string image_name) + { + try + { + var info_name = VFS.CombinePath (Path.GetDirectoryName (image_name), "info"); + if (!VFS.FileExists (info_name)) + return null; + if (string.IsNullOrEmpty (m_last_info_dir) + || string.Join (":", VFS.FullPath) != m_last_info_dir) + ParseInfo (info_name); + + var layer_name = Path.GetFileName (image_name); + return GetLayerInfo (layer_name); + } + catch (Exception X) + { + Trace.WriteLine (X.Message, "[Glib2] STX parse error"); + return null; + } + } + + StxLayerInfo GetLayerInfo (string layer_name) + { + if (null == m_layer_map) + return null; + StxLayerInfo info; + m_layer_map.TryGetValue (layer_name, out info); + return info; + } + + void ParseInfo (string info_name) + { + if (null == m_layer_map) + m_layer_map = new Dictionary(); + else + m_layer_map.Clear(); + + using (var info = VFS.OpenView (info_name)) + { + if (!info.View.AsciiEqual (0, "CDBD")) + return; + int count = info.View.ReadInt32 (4); + uint current_offset = 0x10; + uint info_base = current_offset + info.View.ReadUInt32 (8); + uint info_total_size = info.View.ReadUInt32 (12); + info.View.Reserve (0, info_base+info_total_size); + uint names_base = current_offset + (uint)count * 0x18; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + uint name_offset = names_base + info.View.ReadUInt32 (current_offset); + int parent_dir = info.View.ReadInt32 (current_offset+8); + int attr = info.View.ReadInt32 (current_offset+0xC); + uint info_offset = info.View.ReadUInt32 (current_offset+0x10); + uint info_size = info.View.ReadUInt32 (current_offset+0x14); + + var name = info.View.ReadString (name_offset, info_base-name_offset); + string path_name = name; + if (parent_dir != -1) + path_name = Path.Combine (dir[parent_dir].FullName, path_name); + + if (attr != -1 && info_size != 0) + info_offset += info_base; + var entry = new StxEntry { + FullName = path_name, + Name = name, + Attr = attr, + InfoOffset = info_offset, + InfoSize = info_size, + }; + if (name == "filename" && parent_dir != -1 && info_size != 0) + { + uint filename_length = info.View.ReadUInt32 (info_offset); + var filename = info.View.ReadString (info_offset+4, filename_length); + m_layer_map[filename] = new StxLayerInfo { + Path = dir[parent_dir].FullName + Path.DirectorySeparatorChar, + }; + } + dir.Add (entry); + current_offset += 0x18; + } + foreach (var layer in m_layer_map.Values) + { + foreach (var field in dir.Where (e => e.Attr != -1 && e.FullName.StartsWith (layer.Path))) + { + if ("rect" == field.Name && 0x14 == field.InfoSize) + { + int left = info.View.ReadInt32 (field.InfoOffset+4); + int top = info.View.ReadInt32 (field.InfoOffset+8); + int right = info.View.ReadInt32 (field.InfoOffset+12); + int bottom = info.View.ReadInt32 (field.InfoOffset+16); + layer.Rect = new Rectangle (left, top, right-left, bottom-top); + } + else if ("effect" == field.Name && field.InfoSize > 4) + { + // "norm" + uint effect_length = info.View.ReadUInt32 (field.InfoOffset); + layer.Effect = info.View.ReadString (field.InfoOffset+4, effect_length); + if (layer.Effect != "norm") + Trace.WriteLine (string.Format ("{0}: {1}effect = {2}", + info_name, layer.Path, layer.Effect), "[Glib2.STX]"); + } + else if ("blend" == field.Name && 4 == field.InfoSize) + { + // 0xFF -> opaque + layer.Blend = info.View.ReadInt32 (field.InfoOffset); + if (layer.Blend != 0xFF) + Trace.WriteLine (string.Format ("{0}: {1}blend = {2}", + info_name, layer.Path, layer.Blend), "[Glib2.STX]"); + } + } + } + } + m_last_info_dir = string.Join (":", VFS.FullPath); + } + } +} diff --git a/supported.html b/supported.html index ca0b162d..a48e10a0 100644 --- a/supported.html +++ b/supported.html @@ -66,6 +66,7 @@ Utatemeguri
Aoiro Rinne
Cartagra
PP -Pianissimo-
+Ryoujoku Guerilla Gari 3
Nagomibako
DATA.PackKCAPNoInterheart
Willow Soft @@ -82,6 +83,7 @@ Yakuchu!
*.tgf-No *.arcMajiroArcV1.000
MajiroArcV2.000
MajiroArcV3.000YesMajiro Amber Quartz
+Arpeggio ~Kimi Iro no Melody~
Chikan Kizoku
Narimono
Reconquista
@@ -546,6 +548,10 @@ Gakuen Saimin Reido
Hapymaher
*.pb3PB3BNo +*.g2
*.stx-NoGLib2 +Hitozuma Net Auction
+ +*.pgxPGXNo

1 Non-encrypted only