Formats update.

(HG3): attempt to recognize swapped Red/Blue channels.
Fairytale BDT archives.
(XP3, VF): add 'exe' to extensions list.
(DXR): consolidated Macromedia Director archives.
(SND): support mp3 streams.
(QPK): PSP resource archive.
(GRC): support encrypted images.
This commit is contained in:
morkt 2023-09-14 19:17:31 +04:00
parent b73bd0fb4d
commit bfd81f9ec4
23 changed files with 1081 additions and 426 deletions

View File

@ -103,14 +103,14 @@ namespace GameRes.Formats
{
bool ext_is_empty = string.IsNullOrEmpty (ext);
if (!ext_is_empty && '.' == ext[0])
return filename.EndsWith (ext, StringComparison.InvariantCultureIgnoreCase);
return filename.EndsWith (ext, StringComparison.OrdinalIgnoreCase);
int ext_start = GetExtensionIndex (filename);
// filename extension length
int l_ext_length = filename.Length - ext_start;
if (ext_is_empty)
return 0 == l_ext_length;
return (l_ext_length == ext.Length
&& filename.EndsWith (ext, StringComparison.InvariantCultureIgnoreCase));
&& filename.EndsWith (ext, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
@ -129,7 +129,7 @@ namespace GameRes.Formats
}
else if ('.' == ext[0] || l_ext_length == ext.Length)
{
if (filename.EndsWith (ext, StringComparison.InvariantCultureIgnoreCase))
if (filename.EndsWith (ext, StringComparison.OrdinalIgnoreCase))
return true;
}
}

View File

@ -106,6 +106,8 @@
<Compile Include="AdvSys\ImageGWD.cs" />
<Compile Include="Ail\ArcLNK2.cs" />
<Compile Include="AIRNovel\ArcAIR.cs" />
<Compile Include="FC01\ArcBDT.cs" />
<Compile Include="FC01\BdtTables.cs" />
<Compile Include="GScripter\ArcDATA.cs" />
<Compile Include="Ism\ImagePNG.cs" />
<Compile Include="Kogado\ArcARC.cs" />
@ -149,6 +151,7 @@
<Compile Include="Macromedia\Palettes.cs" />
<Compile Include="Mugi\ArcBIN.cs" />
<Compile Include="NScripter\Script.cs" />
<Compile Include="Psp\ArcQPK.cs" />
<Compile Include="ScrPlayer\ImageIMG.cs" />
<Compile Include="Software House Parsley\ArcScn.cs" />
<Compile Include="TechGian\ArcBIN.cs" />
@ -726,7 +729,7 @@
<Compile Include="AZSys\ArcAZSys.cs" />
<Compile Include="Ethornell\ArcBGI.cs" />
<Compile Include="Ffa\ArcBlackPackage.cs" />
<Compile Include="Macromedia\ArcCCT.cs" />
<None Include="Macromedia\ArcCCT.cs" />
<Compile Include="Cherry\ArcCherry.cs" />
<Compile Include="Circus\ArcCircus.cs" />
<Compile Include="ArcCommon.cs" />

View File

@ -30,6 +30,8 @@ using System.Windows.Media.Imaging;
using System.Windows.Media;
using GameRes.Compression;
using GameRes.Utility;
using System.Collections.Generic;
using System.Linq;
namespace GameRes.Formats.CatSystem
{
@ -285,9 +287,10 @@ namespace GameRes.Formats.CatSystem
byte[] UnpackJpeg ()
{
Flipped = false;
m_input.ReadInt32();
var toc = ReadSections();
m_input.Position = toc["img_jpg"] + 12;
var jpeg_size = m_input.ReadInt32();
long next_section = Source.Position + jpeg_size;
BitmapSource frame;
using (var jpeg = new StreamRegion (Source, Source.Position, jpeg_size, true))
{
@ -297,40 +300,69 @@ namespace GameRes.Formats.CatSystem
if (frame.Format.BitsPerPixel < 24)
throw new NotSupportedException ("Not supported HG-3 JPEG color depth");
int src_pixel_size = frame.Format.BitsPerPixel/8;
int stride = (int)m_info.Width * src_pixel_size;
var pixels = new byte[stride*(int)m_info.Height];
int stride = m_info.iWidth * src_pixel_size;
var pixels = new byte[stride * m_info.iHeight];
frame.CopyPixels (pixels, stride, 0);
var output = new byte[m_info.Width*m_info.Height*4];
uint total = m_info.Width * m_info.Height;
int total = m_info.iWidth * m_info.iHeight;
byte[] alpha = null;
if (toc.ContainsKey ("img_al"))
alpha = ReadAlpha (toc["img_al"]);
else
alpha = Enumerable.Repeat<byte> (0xFF, total).ToArray();
bool swap_rgb = toc.ContainsKey ("imgmode"); // XXX ???
var output = new byte[total * 4];
int src = 0;
int dst = 0;
int src_A = 0;
int src_R = 2, src_G = 1, src_B = 0;
if (swap_rgb)
{
src_R = 0;
src_B = 2;
}
for (uint i = 0; i < total; ++i)
{
output[dst++] = pixels[src+2];
output[dst++] = pixels[src+1];
output[dst++] = pixels[src];
output[dst++] = 0xFF;
output[dst++] = pixels[src+src_B];
output[dst++] = pixels[src+src_G];
output[dst++] = pixels[src+src_R];
output[dst++] = alpha[src_A++];
src += src_pixel_size;
}
m_input.Position = next_section;
var section_header = m_input.ReadBytes (8);
if (!Binary.AsciiEqual (section_header, "img_al\0\0"))
return output;
m_input.Seek (8, SeekOrigin.Current);
}
byte[] ReadAlpha (long start_pos)
{
m_input.Position = start_pos + 0x10;
int packed_size = m_input.ReadInt32();
int alpha_size = m_input.ReadInt32();
using (var alpha_in = new StreamRegion (Source, Source.Position+4, alpha_size, true))
using (var alpha_in = new StreamRegion (Source, Source.Position, packed_size, true))
using (var alpha = new ZLibStream (alpha_in, CompressionMode.Decompress))
{
for (int i = 3; i < output.Length; i += 4)
var alpha_data = new byte[alpha_size];
alpha.Read (alpha_data, 0, alpha_size);
return alpha_data;
}
}
Dictionary<string, long> ReadSections ()
{
int b = alpha.ReadByte();
if (-1 == b)
throw new EndOfStreamException();
output[i] = (byte)b;
}
return output;
long next_offset = m_info.HeaderSize;
var toc = new Dictionary<string, long>();
uint section_size;
do
{
m_input.Position = next_offset;
var section_name = m_input.ReadCString (8);
section_size = m_input.ReadUInt32();
toc[section_name] = next_offset;
next_offset += section_size;
}
while (section_size != 0);
return toc;
}
byte[] UnpackWebp ()

View File

@ -29,11 +29,11 @@ namespace GameRes.Formats.??????
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
{
public override string Tag { get => "ARC"; }
public override string Description { get => "?????? engine resource archive"; }
public override uint Signature { get => 0; }
public override bool IsHierarchic { get => false; }
public override bool CanWrite { get => false; }
public override string Tag => "ARC";
public override string Description => "?????? engine resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
@ -51,5 +51,10 @@ namespace GameRes.Formats.??????
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
return base.OpenEntry (arc, entry);
}
}
}

View File

@ -26,10 +26,10 @@ namespace GameRes.Formats.??????
[Export(typeof(AudioFormat))]
public class xxxAudio : AudioFormat
{
public override string Tag { get => "xxx"; }
public override string Description { get => "?????? audio resource"; }
public override uint Signature { get => 0; }
public override bool CanWrite { get => false; }
public override string Tag => "xxx";
public override string Description => "?????? audio resource";
public override uint Signature => 0;
public override bool CanWrite => false;
public override SoundInput TryOpen (IBinaryStream file)
{

View File

@ -28,9 +28,9 @@ namespace GameRes.Formats.??????
[Export(typeof(ImageFormat))]
public class xxxFormat : ImageFormat
{
public override string Tag { get => "xxx"; }
public override string Description { get => "?????? image format"; }
public override uint Signature { get => 0; }
public override string Tag => "xxx";
public override string Description => "?????? image format";
public override uint Signature => 0;
public override ImageMetaData ReadMetaData (IBinaryStream file)
{

View File

@ -64,7 +64,7 @@ namespace GameRes.Formats.Emote
public PsbOpener ()
{
Extensions = new string[] { "psb", "pimg", "dpak", "psbz" };
Extensions = new string[] { "psb", "pimg", "dpak", "psbz", "psp" };
}
public override ArcFile TryOpen (ArcView file)

151
ArcFormats/FC01/ArcBDT.cs Normal file
View File

@ -0,0 +1,151 @@
//! \file ArcBDT.cs
//! \date 2023 Sep 10
//! \brief RPA resource archive.
//
// Copyright (C) 2023 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.Globalization;
using System.IO;
using System.Linq;
// [021228][Fairytale Kagetsu Gumi] Tsuki Jong
namespace GameRes.Formats.FC01
{
[Export(typeof(ArchiveFormat))]
public partial class BdtOpener : PakOpener
{
public override string Tag => "BDT";
public override string Description => "Fairytale resource archive";
public override uint Signature => 0x4B434150; // 'PACK'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public BdtOpener ()
{
Signatures = new[] { 0x4B434150u, 0u };
}
public override ArcFile TryOpen (ArcView file)
{
uint signature = file.View.ReadUInt32 (0);
if (signature != this.Signature)
return BogusMediaArchive (file, signature);
var arc_name = Path.GetFileNameWithoutExtension (file.Name).ToLowerInvariant();
if (!arc_name.StartsWith ("dt0"))
return null;
uint index_pos = 12;
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count) || file.MaxOffset <= index_pos)
return null;
int entry_size = file.View.ReadInt32 (8);
string arc_num_str = arc_name.Substring (3);
int arc_num = int.Parse (arc_num_str, NumberStyles.HexNumber);
var dir = ReadVomIndex (file, arc_num, count, entry_size);
if (null == dir)
return null;
var arc_key = GetKey (arc_num);
return new AgsiArchive (file, this, dir, arc_key);
}
List<Entry> ReadVomIndex (ArcView file, int arc_num, int count, int record_size)
{
if (4 == arc_num) // dt004.bdt is an archive of indexes
{
var reader = new IndexReader (file, count, record_size);
return reader.ReadIndex();
}
var dt4name = VFS.ChangeFileName (file.Name, "dt004.bdt");
using (var dt4file = VFS.OpenView (dt4name))
{
var dt4arc = TryOpen (dt4file);
if (null == dt4arc)
return null;
using (dt4arc)
{
int vom_idx = arc_num;
if (arc_num > 4)
--vom_idx;
var voms = string.Format ("vom{0:D3}.dat", vom_idx);
var vom_entry = dt4arc.Dir.First (e => e.Name == voms);
using (var input = dt4arc.OpenEntry (vom_entry))
using (var index = BinaryStream.FromStream (input, vom_entry.Name))
{
var reader = new IndexReader (file, count, record_size);
return reader.ReadIndex (index);
}
}
}
}
ArcFile BogusMediaArchive (ArcView file, uint signature)
{
if (!file.Name.HasExtension (".bdt"))
return null;
string ext = null;
string type = "";
if (OggAudio.Instance.Signature == signature)
{
ext = ".ogg";
type = "audio";
}
else if (AudioFormat.Wav.Signature == signature && file.View.AsciiEqual (8, "AVI "))
{
ext = ".avi";
}
if (null == ext)
return null;
var name = Path.GetFileNameWithoutExtension (file.Name);
var dir = new List<Entry> {
new Entry {
Name = name + ext,
Type = type,
Offset = 0,
Size = (uint)file.MaxOffset
}
};
return new ArcFile (file, this, dir);
}
byte[] GetKey (int index)
{
int src = index * 8;
if (src + 8 > KeyOffsetTable.Length)
throw new ArgumentException ("Invalid AGSI key index.");
var key = new byte[8];
key[0] = KeySource[KeyOffsetTable[src++]];
key[1] = KeySource[KeyOffsetTable[src++]];
key[2] = KeySource[KeyOffsetTable[src++]];
key[3] = KeySource[KeyOffsetTable[src++] + 480];
key[4] = KeySource[KeyOffsetTable[src++] + 480];
key[5] = KeySource[KeyOffsetTable[src++] + 480];
key[6] = KeySource[KeyOffsetTable[src++] + 480];
key[7] = KeySource[KeyOffsetTable[src++] + 480];
return key;
}
}
}

View File

@ -102,8 +102,10 @@ namespace GameRes.Formats.FC01
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var aent = (AgsiEntry)entry;
var aent = entry as AgsiEntry;
var aarc = arc as AgsiArchive;
if (null == aent)
return base.OpenEntry (arc, entry);
Stream input;
if (!aent.IsEncrypted)
input = arc.File.CreateStream (entry.Offset, entry.Size);
@ -202,16 +204,16 @@ namespace GameRes.Formats.FC01
ArcView m_file;
int m_count;
int m_record_size;
uint m_data_offset;
public bool IsEncrypted { get; set; }
public uint DataOffset { get; set; }
public IndexReader (ArcView file, int count, int record_size, bool is_encrypted)
public IndexReader (ArcView file, int count, int record_size, bool is_encrypted = false)
{
m_file = file;
m_count = count;
m_record_size = record_size;
m_data_offset = (uint)(0xC + m_count * m_record_size);
DataOffset = (uint)(0xC + m_count * m_record_size);
IsEncrypted = is_encrypted;
}
@ -239,7 +241,7 @@ namespace GameRes.Formats.FC01
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)
if (reader.DataOffset >= file.MaxOffset)
return null;
return reader;
}
@ -247,6 +249,10 @@ namespace GameRes.Formats.FC01
public List<Entry> ReadIndex ()
{
using (var index = OpenIndex())
return ReadIndex (index);
}
public List<Entry> ReadIndex (IBinaryStream index)
{
int name_size = m_record_size - 0x10;
var dir = new List<Entry> (m_count);
@ -256,7 +262,7 @@ namespace GameRes.Formats.FC01
entry.UnpackedSize = index.ReadUInt32();
entry.Size = index.ReadUInt32();
entry.Method = index.ReadInt32();
entry.Offset = index.ReadUInt32() + m_data_offset;
entry.Offset = index.ReadUInt32() + DataOffset;
if (!entry.CheckPlacement (m_file.MaxOffset))
return null;
var name = index.ReadCString (name_size);
@ -270,7 +276,6 @@ namespace GameRes.Formats.FC01
}
return dir;
}
}
IBinaryStream OpenIndex ()
{

View File

@ -0,0 +1,159 @@
//! \file BdtTables.cs
//! \date 2023 Sep 10
//! \brief BDT archives key tables.
//
namespace GameRes.Formats.FC01
{
public partial class BdtOpener
{
internal static readonly ushort[] KeyOffsetTable = new ushort[] { // dword_4C7D80
0x029, 0x0E3, 0x05E, 0x104, 0x261, 0x16C, 0x256, 0x22E, 0x08A, 0x023, 0x1B4, 0x01D, 0x115, 0x0D5,
0x178, 0x1EB, 0x07F, 0x1D6, 0x02F, 0x14C, 0x238, 0x04A, 0x099, 0x124, 0x1C9, 0x0F9, 0x071, 0x260,
0x094, 0x15F, 0x044, 0x078, 0x072, 0x1B9, 0x18C, 0x0E3, 0x0DF, 0x1F3, 0x162, 0x053, 0x03D, 0x1BF,
0x198, 0x210, 0x0DB, 0x218, 0x05B, 0x139, 0x0E8, 0x1A9, 0x098, 0x148, 0x1A7, 0x17F, 0x0B3, 0x1E1,
0x119, 0x05F, 0x131, 0x121, 0x1F7, 0x023, 0x240, 0x120, 0x172, 0x048, 0x1BE, 0x130, 0x10E, 0x1A5,
0x05A, 0x199, 0x15D, 0x116, 0x195, 0x0B6, 0x19F, 0x189, 0x003, 0x06A, 0x0D6, 0x1AA, 0x05A, 0x22C,
0x1BE, 0x047, 0x1DE, 0x234, 0x04E, 0x0EE, 0x043, 0x1F9, 0x098, 0x0C3, 0x11D, 0x0BB, 0x099, 0x1AB,
0x0AE, 0x060, 0x1FE, 0x0A1, 0x1A6, 0x111, 0x1B5, 0x15F, 0x102, 0x24E, 0x106, 0x000, 0x0BA, 0x1BB,
0x0C3, 0x0CA, 0x0DC, 0x090, 0x18D, 0x1E2, 0x1BD, 0x161, 0x0E5, 0x137, 0x17C, 0x20D, 0x042, 0x1DA,
0x0D8, 0x131, 0x013, 0x01F, 0x177, 0x069, 0x1AF, 0x129, 0x18F, 0x251, 0x070, 0x106, 0x091, 0x209,
0x0C0, 0x01A, 0x05E, 0x23A, 0x0F3, 0x03E, 0x1B6, 0x0F7, 0x066, 0x19B, 0x268, 0x08B, 0x0FD, 0x156,
0x01D, 0x10F, 0x13B, 0x1F1, 0x083, 0x163, 0x010, 0x1B8, 0x1C8, 0x1CD, 0x103, 0x1C0, 0x0DD, 0x18A,
0x035, 0x0A3, 0x0FF, 0x0B0, 0x1D3, 0x00E, 0x025, 0x1FB, 0x193, 0x083, 0x0CD, 0x235, 0x0F4, 0x05F,
0x0C8, 0x18B, 0x055, 0x0DE, 0x017, 0x24A, 0x046, 0x036, 0x207, 0x123, 0x0E9, 0x18D, 0x146, 0x073,
0x09D, 0x00D, 0x0AF, 0x153, 0x0C2, 0x0A8, 0x0AF, 0x21F, 0x1D4, 0x074, 0x026, 0x20C, 0x170, 0x13B,
0x173, 0x0D2, 0x122, 0x076, 0x00C, 0x200, 0x0AA, 0x046, 0x0D4, 0x25A, 0x03D, 0x0C7, 0x0CF, 0x1DD,
0x17E, 0x176, 0x13C, 0x1D7, 0x084, 0x002, 0x02E, 0x203, 0x014, 0x067, 0x12A, 0x00F, 0x0C2, 0x1F0,
0x0E2, 0x222, 0x0C9, 0x079, 0x12B, 0x1A0, 0x1BA, 0x257, 0x156, 0x0A0, 0x109, 0x17A, 0x15E, 0x048,
0x02D, 0x01E, 0x24D, 0x082, 0x1AD, 0x0D0, 0x042, 0x029, 0x068, 0x214, 0x14A, 0x0B9, 0x0CB, 0x134,
0x027, 0x223, 0x0A3, 0x0B4, 0x0EC, 0x197, 0x107, 0x136, 0x0B9, 0x12F, 0x20B, 0x220, 0x145, 0x07F,
0x052, 0x11E, 0x02C, 0x0C1, 0x0CD, 0x110, 0x1C3, 0x1C8, 0x044, 0x162, 0x09D, 0x087, 0x15E, 0x0B7,
0x055, 0x17D, 0x076, 0x01A, 0x0B0, 0x017, 0x05C, 0x012, 0x247, 0x081, 0x088, 0x0A6, 0x14C, 0x075,
0x248, 0x151, 0x0E8, 0x004, 0x032, 0x111, 0x1A4, 0x1A9, 0x0BF, 0x137, 0x21A, 0x0D7, 0x189, 0x04A,
0x12E, 0x170, 0x0DA, 0x019, 0x04D, 0x0D6, 0x16A, 0x168, 0x19C, 0x19C, 0x188, 0x1C4, 0x204, 0x12B,
0x125, 0x128, 0x00B, 0x114, 0x0E7, 0x070, 0x06C, 0x1FD, 0x0C6, 0x1B0, 0x003, 0x15D, 0x085, 0x07A,
0x1C2, 0x237, 0x041, 0x07D, 0x183, 0x08E, 0x216, 0x126, 0x224, 0x010, 0x0E4, 0x059, 0x019, 0x0EB,
0x1E8, 0x04C, 0x1F6, 0x091, 0x18E, 0x08C, 0x0F0, 0x13F, 0x0F0, 0x065, 0x157, 0x1B8, 0x08E, 0x142,
0x033, 0x20F, 0x03B, 0x242, 0x1AC, 0x0E0, 0x005, 0x1A3, 0x17B, 0x03C, 0x175, 0x22D, 0x239, 0x10A,
0x022, 0x006, 0x07B, 0x16D, 0x014, 0x052, 0x241, 0x1B1, 0x12C, 0x074, 0x0F1, 0x1FF, 0x0B1, 0x118,
0x0AE, 0x062, 0x139, 0x0DD, 0x103, 0x193, 0x11A, 0x02B, 0x06E, 0x077, 0x0A7, 0x158, 0x15C, 0x21D,
0x230, 0x009, 0x194, 0x215, 0x171, 0x09F, 0x0DB, 0x174, 0x04F, 0x17E, 0x022, 0x1FC, 0x196, 0x06C,
0x04F, 0x16A, 0x1C6, 0x142, 0x1D1, 0x1A1, 0x045, 0x024, 0x092, 0x16F, 0x0A7, 0x1B0, 0x231, 0x1D6,
0x129, 0x181, 0x145, 0x0AC, 0x050, 0x0E6, 0x024, 0x154, 0x002, 0x152, 0x016, 0x23B, 0x1B2, 0x14F,
0x132, 0x17C, 0x11C, 0x00D, 0x085, 0x208, 0x229, 0x1D9, 0x158, 0x13E, 0x135, 0x018, 0x0E2, 0x12C,
0x1D5, 0x1C5, 0x168, 0x054, 0x108, 0x17F, 0x09C, 0x1ED, 0x021, 0x127, 0x160, 0x03A, 0x0BF, 0x0B3,
0x10B, 0x140, 0x008, 0x13A, 0x232, 0x186, 0x051, 0x061, 0x056, 0x09B, 0x059, 0x19A, 0x0B5, 0x169,
0x066, 0x16E, 0x07C, 0x072, 0x0A4, 0x144, 0x196, 0x133, 0x0F7, 0x16B, 0x118, 0x1E0, 0x16E, 0x17B,
0x034, 0x11B, 0x06F, 0x08F, 0x0C0, 0x0EE, 0x213, 0x03F, 0x181, 0x141, 0x0A5, 0x09E, 0x120, 0x0F9,
0x0F8, 0x030, 0x187, 0x155, 0x050, 0x069, 0x151, 0x03E, 0x164, 0x032, 0x0E9, 0x0D4, 0x000, 0x0AD,
0x178, 0x19E, 0x1C1, 0x21C, 0x10C, 0x21E, 0x0AB, 0x0FC, 0x026, 0x1AA, 0x01C, 0x177, 0x058, 0x04B,
0x10D, 0x0DF, 0x081, 0x079, 0x08C, 0x1D2, 0x21B, 0x0B2, 0x049, 0x025, 0x14D, 0x18C, 0x018, 0x19D,
0x202, 0x125, 0x073, 0x0D9, 0x191, 0x198, 0x1AB, 0x07B, 0x005, 0x1C9, 0x114, 0x065, 0x0F6, 0x02A,
0x0C4, 0x0DC, 0x056, 0x108, 0x009, 0x07E, 0x180, 0x07E, 0x0A5, 0x1A8, 0x09F, 0x1C7, 0x094, 0x0BD,
0x028, 0x11E, 0x107, 0x0AB, 0x0EF, 0x05D, 0x0C8, 0x096, 0x037, 0x1BF, 0x1EE, 0x06F, 0x201, 0x219,
0x147, 0x0FA, 0x060, 0x0D9, 0x185, 0x09C, 0x1CB, 0x1DF, 0x087, 0x021, 0x0B1, 0x15A, 0x0E5, 0x040,
0x063, 0x167, 0x04B, 0x10C, 0x0EA, 0x04E, 0x028, 0x1F4, 0x016, 0x0CA, 0x10F, 0x127, 0x04C, 0x166,
0x0CE, 0x190, 0x1E9, 0x15B, 0x14B, 0x159, 0x11B, 0x1B9, 0x013, 0x0DE, 0x0F1, 0x0F6, 0x00A, 0x06D,
0x140, 0x0BD, 0x226, 0x1EA, 0x088, 0x0ED, 0x16F, 0x153, 0x0A2, 0x1EF, 0x0CC, 0x020, 0x00B, 0x1DB,
0x06E, 0x0CF, 0x090, 0x031, 0x1B5, 0x0FC, 0x080, 0x001, 0x14F, 0x01E, 0x113, 0x0A2, 0x221, 0x22A,
0x1CA, 0x0AA, 0x110, 0x0B6, 0x0D5, 0x1D0, 0x228, 0x07C, 0x18E, 0x1B6, 0x064, 0x063, 0x05C, 0x011,
0x1E7, 0x105, 0x1E4, 0x146, 0x12F, 0x0BC, 0x011, 0x195, 0x183, 0x1CC, 0x0FF, 0x17A, 0x114, 0x065,
0x0F6, 0x02A, 0x0C4, 0x0DC, 0x056, 0x108, 0x009, 0x07E, 0x180, 0x07E, 0x0A5, 0x1A8, 0x09F, 0x1C7,
0x094, 0x0BD, 0x028, 0x11E, 0x107, 0x0AB, 0x0EF, 0x05D, 0x0C8, 0x096, 0x037, 0x1BF, 0x1EE, 0x06F,
0x201, 0x219, 0x147, 0x0FA, 0x060, 0x0D9, 0x185, 0x09C, 0x1CB, 0x1DF, 0x087, 0x021, 0x0B1, 0x15A,
0x0E5, 0x040, 0x063, 0x167, 0x04B, 0x10C, 0x0EA, 0x04E, 0x028, 0x1F4, 0x016, 0x0CA, 0x114, 0x065,
0x0F6, 0x02A, 0x0C4, 0x0DC, 0x056, 0x108, 0x009, 0x07E, 0x180, 0x07E, 0x0A5, 0x1A8, 0x09F, 0x1C7,
0x094, 0x0BD, 0x028, 0x11E, 0x107, 0x0AB, 0x0EF, 0x05D, 0x0C8, 0x096, 0x037, 0x1BF, 0x1EE, 0x06F,
0x201, 0x219, 0x147, 0x0FA, 0x060, 0x0D9, 0x185, 0x09C, 0x1CB, 0x1DF, 0x087, 0x021, 0x0B1, 0x15A,
0x0E5, 0x040, 0x063, 0x167, 0x04B, 0x10C, 0x0EA, 0x04E, 0x028, 0x1F4, 0x016, 0x0CA, 0x002, 0x152,
0x016, 0x23B, 0x1B2, 0x14F, 0x132, 0x17C, 0x11C, 0x00D, 0x085, 0x208, 0x229, 0x1D9, 0x158, 0x13E,
0x135, 0x018, 0x0E2, 0x12C, 0x1D5, 0x1C5, 0x168, 0x054, 0x108, 0x17F, 0x09C, 0x1ED, 0x021, 0x127,
0x160, 0x03A, 0x0BF, 0x0B3, 0x10B, 0x140, 0x008, 0x13A, 0x232, 0x186, 0x051, 0x061, 0x056, 0x09B,
0x059, 0x19A, 0x0B5, 0x169, 0x066, 0x16E, 0x07C, 0x072, 0x0A4, 0x144, 0x196, 0x133, 0x0F7, 0x16B,
0x118, 0x1E0, 0x16E, 0x17B, 0x034, 0x11B, 0x06F, 0x08F, 0x0C0, 0x0EE, 0x213, 0x03F, 0x181, 0x141,
0x0A5, 0x09E, 0x120, 0x0F9, 0x0F8, 0x030, 0x187, 0x155, 0x050, 0x069, 0x151, 0x03E, 0x164, 0x032,
0x0E9, 0x0D4, 0x000, 0x0AD, 0x178, 0x19E, 0x1C1, 0x21C, 0x10C, 0x21E, 0x0AB, 0x0FC, 0x026, 0x1AA,
0x01C, 0x177, 0x058, 0x04B, 0x10D, 0x0DF, 0x081, 0x079, 0x08C, 0x1D2, 0x21B, 0x0B2, 0x049, 0x025,
0x14D, 0x18C, 0x018, 0x19D, 0x202, 0x125, 0x073, 0x0D9, 0x191, 0x198, 0x1AB, 0x07B, 0x005, 0x1C9,
0x114, 0x065, 0x0F6, 0x02A, 0x0C4, 0x0DC, 0x056, 0x108, 0x009, 0x07E, 0x180, 0x07E, 0x0A5, 0x1A8,
0x09F, 0x1C7, 0x094, 0x0BD, 0x028, 0x11E, 0x107, 0x0AB, 0x0EF, 0x05D, 0x0C8, 0x096, 0x037, 0x1BF,
0x1EE, 0x06F, 0x201, 0x219, 0x147, 0x0FA, 0x060, 0x0D9, 0x185, 0x09C, 0x1CB, 0x1DF, 0x087, 0x021,
0x0B1, 0x15A, 0x0E5, 0x040, 0x063, 0x167, 0x04B, 0x10C, 0x0EA, 0x04E, 0x028, 0x1F4, 0x016, 0x0CA,
0x10F, 0x127, 0x04C, 0x166, 0x0CE, 0x190, 0x1E9, 0x15B, 0x14B, 0x159, 0x11B, 0x1B9, 0x013, 0x0DE,
0x0F1, 0x0F6,
};
internal static readonly byte[] KeySource = new byte[] { // byte_4C7920
0x5A, 0x4F, 0xC6, 0xCD, 0x58, 0xD3, 0xA1, 0x3F, 0x74, 0xC5, 0xA2, 0xD8, 0x47, 0x6A, 0xEF, 0x42,
0x4A, 0x65, 0x54, 0x75, 0xD9, 0x2E, 0xB1, 0x76, 0x48, 0x41, 0x30, 0xDF, 0xBE, 0xD9, 0x5F, 0xD9,
0x39, 0xA8, 0xAD, 0xB3, 0xBD, 0xAC, 0xB0, 0xCB, 0xC6, 0x29, 0xB6, 0xE6, 0xD0, 0x0C, 0x67, 0xDE,
0x1C, 0x1B, 0x72, 0xAF, 0x74, 0x24, 0x5C, 0xA9, 0xFE, 0x77, 0x9D, 0x6B, 0x6E, 0x45, 0x64, 0x93,
0x48, 0xB0, 0x39, 0xDC, 0x4D, 0x79, 0xBF, 0xA7, 0x3B, 0xD2, 0x53, 0x2B, 0xB0, 0x44, 0x7E, 0x6D,
0x76, 0x4E, 0xBE, 0xB3, 0x3E, 0x27, 0x3C, 0x02, 0xB8, 0xAD, 0x53, 0x04, 0x42, 0x87, 0xBE, 0x40,
0x5D, 0x60, 0xB6, 0x6A, 0x59, 0x5D, 0xBB, 0x71, 0x30, 0x4B, 0x22, 0xDD, 0x6C, 0x37, 0x57, 0x2C,
0x50, 0x7D, 0xBE, 0x33, 0x32, 0xE4, 0x2B, 0x56, 0xDD, 0xA9, 0x61, 0xBE, 0x2F, 0x5A, 0x3A, 0xB7,
0x7B, 0x5C, 0x6E, 0xD8, 0x3D, 0x50, 0x8C, 0xD7, 0x68, 0x5C, 0xBB, 0xE7, 0x4C, 0xC4, 0x40, 0x2E,
0x6C, 0x61, 0xB8, 0x67, 0xD5, 0xBC, 0x21, 0x2F, 0x32, 0x61, 0xF2, 0xFF, 0xCF, 0xBE, 0xAF, 0x74,
0xC9, 0xCC, 0x26, 0x79, 0x0F, 0x74, 0xCA, 0x54, 0x3B, 0x13, 0x54, 0xAF, 0x90, 0x72, 0xD1, 0x6A,
0x70, 0x59, 0xA9, 0x27, 0x24, 0x11, 0xC8, 0x1E, 0xAD, 0xC5, 0x4B, 0x41, 0xB2, 0xA4, 0xE5, 0xBD,
0xDC, 0x92, 0xB1, 0xD5, 0x28, 0x3F, 0x7C, 0x62, 0x2A, 0x46, 0xD4, 0x28, 0xF5, 0x28, 0xB8, 0xB6,
0xBF, 0xAE, 0xFD, 0xA9, 0xD9, 0xBC, 0xC9, 0x49, 0x2B, 0xB9, 0x31, 0x4F, 0xD4, 0xA6, 0x59, 0x5B,
0x51, 0xCA, 0x31, 0x23, 0xB1, 0x32, 0x80, 0xEC, 0x36, 0x68, 0xB8, 0x2E, 0x45, 0x1A, 0x2D, 0x39,
0xD1, 0x34, 0x35, 0xDA, 0xAA, 0xBE, 0x5A, 0xD3, 0x1C, 0x5A, 0xCF, 0x65, 0xBF, 0x6A, 0xCE, 0x52,
0x77, 0xBD, 0x2C, 0xD8, 0x57, 0xB3, 0xD4, 0x61, 0x43, 0xB1, 0xD3, 0x28, 0x37, 0xDA, 0xC9, 0x3C,
0x61, 0x7B, 0x17, 0x40, 0x32, 0x0A, 0x72, 0x4E, 0x50, 0x44, 0x16, 0x32, 0x2F, 0x32, 0x71, 0xF6,
0xB5, 0x0F, 0x86, 0x10, 0x13, 0xCA, 0xD2, 0x36, 0x27, 0x62, 0xA5, 0x79, 0x69, 0x2D, 0x33, 0x30,
0x84, 0x66, 0xB7, 0x26, 0xA1, 0xA1, 0xC9, 0x22, 0xB6, 0xC8, 0xA2, 0xC1, 0x6C, 0x78, 0xD8, 0x02,
0x53, 0xD1, 0x43, 0xEE, 0xAF, 0x5C, 0x49, 0xCA, 0x13, 0x21, 0xBA, 0xA3, 0xBE, 0xA9, 0x59, 0x3A,
0x29, 0x6B, 0xC3, 0xCC, 0xDE, 0xC6, 0x73, 0xA6, 0xD0, 0xB9, 0x2A, 0x65, 0x71, 0xB9, 0xDF, 0x39,
0xCF, 0xF4, 0xD0, 0x47, 0x36, 0x21, 0xEB, 0xDD, 0xB4, 0x4B, 0x73, 0xBE, 0xA2, 0x6A, 0xB8, 0x46,
0x2F, 0xDB, 0x32, 0xC0, 0x99, 0xA8, 0xD9, 0x2A, 0x71, 0x12, 0x26, 0xCE, 0xCC, 0xC0, 0xD1, 0xCD,
0x6C, 0x27, 0xE0, 0x4E, 0x34, 0x91, 0x20, 0x6E, 0xC7, 0x66, 0x62, 0x4F, 0x2F, 0xD4, 0x26, 0x3D,
0x84, 0x35, 0xEE, 0xD3, 0xA0, 0x49, 0xDF, 0xA8, 0xDB, 0x69, 0x0C, 0x22, 0x31, 0xD0, 0x24, 0x7F,
0x18, 0x3F, 0x28, 0xA1, 0xCD, 0xC0, 0xA9, 0xCB, 0x01, 0x49, 0xCC, 0xCD, 0xFF, 0x34, 0xD9, 0xBD,
0xC1, 0xE4, 0x64, 0xB8, 0xA6, 0xD7, 0x54, 0x33, 0xD0, 0xCE, 0x8B, 0x9A, 0xD9, 0xC3, 0xA1, 0x3B,
0x40, 0x8E, 0x06, 0x61, 0x37, 0x74, 0x32, 0x83, 0x57, 0xA6, 0x20, 0x0C, 0xAF, 0x83, 0xFD, 0x23,
0xA2, 0x44, 0x31, 0x5C, 0xB3, 0x18, 0x47, 0xDA, 0x30, 0x09, 0xE1, 0x61, 0x34, 0x75, 0x77, 0x50,
0xC9, 0xDF, 0x5E, 0xD5, 0x68, 0xD5, 0x9A, 0xED, 0x5F, 0x29, 0x4A, 0x55, 0xC2, 0x25, 0x43, 0xB4,
0xDE, 0xC4, 0x32, 0x2D, 0xA6, 0x63, 0x4E, 0xA7, 0x23, 0x5E, 0x3B, 0xFB, 0x75, 0xDB, 0x6D, 0x3D,
0xD8, 0xC7, 0xB0, 0xC4, 0x61, 0x3C, 0xD6, 0x49, 0x7A, 0xA6, 0xDC, 0x6B, 0xE1, 0x29, 0x4E, 0x54,
0x5E, 0x6E, 0x21, 0xF9, 0x21, 0x02, 0xC2, 0x2C, 0x4E, 0xB6, 0xBF, 0xCA, 0x5A, 0xD1, 0x36, 0x3D,
0x55, 0x0A, 0xD3, 0x97, 0xD4, 0xC2, 0xD0, 0xD6, 0xDA, 0x79, 0xC8, 0x46, 0xA5, 0x4C, 0xD5, 0xB8,
0x3B, 0x9F, 0xB4, 0x61, 0xA3, 0x46, 0xB5, 0x79, 0x58, 0x42, 0xB0, 0x40, 0x54, 0xD6, 0x33, 0x60,
0x72, 0x7C, 0xDB, 0x4E, 0x70, 0xB3, 0xB1, 0x8C, 0x3F, 0xC0, 0xB2, 0x80, 0x59, 0x3F, 0x68, 0xBB,
0x27, 0xE0, 0x73, 0x6A, 0xC1, 0x7D, 0xA7, 0x62, 0xCB, 0xCB, 0xB8, 0x2A, 0x4A, 0x4D, 0x57, 0xA8,
0x3A, 0x5B, 0x62, 0xD4, 0x66, 0x64, 0x47, 0xBE, 0x37, 0x0A, 0x5F, 0x46, 0x36, 0x74, 0x2D, 0x2A,
0xA4, 0x42, 0x9A, 0x43, 0x25, 0x4B, 0x59, 0x30, 0x57, 0x43, 0x5A, 0xDE, 0x45, 0x79, 0xBB, 0xB2,
0x63, 0x4F, 0xDD, 0x3F, 0x51, 0xDF, 0x2B, 0x6E, 0x94, 0xDC, 0xA6, 0x5F, 0x4A, 0x6C, 0xC6, 0x61,
0xDB, 0x4D, 0x55, 0xAD, 0x41, 0x2B, 0x2C, 0xC2, 0xB5, 0x6C, 0x33, 0x69, 0x5A, 0xB5, 0x9D, 0x44,
0x7B, 0x3B, 0x47, 0x49, 0x77, 0x2E, 0x9C, 0x55, 0x59, 0x64, 0x21, 0x87, 0x7A, 0x75, 0xBF, 0x38,
0x98, 0x27, 0x7E, 0xA8, 0x7D, 0x3E, 0x4C, 0xA6, 0x48, 0xD6, 0xA1, 0x32, 0x5D, 0xAD, 0x2C, 0xDB,
0xCF, 0x48, 0x38, 0x68, 0xEE, 0x26, 0x32, 0x79, 0x4F, 0xDB, 0x98, 0x67, 0x65, 0x72, 0xBC, 0xC3,
0x2F, 0x74, 0x33, 0x48, 0xC5, 0x48, 0x43, 0xCE, 0xD5, 0xA4, 0xBD, 0x6E, 0xB0, 0xDE, 0xF1, 0x3D,
0xC2, 0xA1, 0xC3, 0x76, 0x6C, 0xBE, 0xCB, 0x77, 0x69, 0x0E, 0x2A, 0x49, 0xD9, 0xB5, 0x22, 0x2F,
0x7E, 0x77, 0xC7, 0x80, 0x4D, 0x3C, 0xBC, 0x3E, 0x56, 0xC2, 0x61, 0x2B, 0x0B, 0x61, 0xBC, 0xE2,
0xB7, 0xD0, 0xA8, 0xCD, 0xBB, 0xA3, 0xB1, 0x50, 0xB9, 0x78, 0xCE, 0xB0, 0x6C, 0xB7, 0xB1, 0x2C,
0x22, 0xD3, 0x75, 0xD8, 0x11, 0xB3, 0x76, 0x3A, 0x64, 0x78, 0xB9, 0x67, 0x69, 0xA3, 0x47, 0x21,
0x5D, 0x31, 0x43, 0x08, 0x7E, 0xDC, 0xDB, 0xCD, 0x7D, 0x15, 0x6D, 0x28, 0xB3, 0x1C, 0xEB, 0x4A,
0xE9, 0xB8, 0x5A, 0x40, 0x40, 0x40, 0xC2, 0x73, 0x28, 0xB8, 0xCC, 0x66, 0xBF, 0x23, 0xDB, 0x5D,
0x59, 0xCF, 0x2D, 0x4A, 0xB1, 0xB7, 0x7D, 0xD9, 0xDE, 0xB6, 0x74, 0x55, 0xAE, 0x65, 0xD6, 0x60,
0xCB, 0xDA, 0x5C, 0x13, 0x37, 0xBE, 0xEA, 0xDC, 0x24, 0x60, 0x4A, 0x43, 0x6F, 0x41, 0xB5, 0xDC,
0x3E, 0x6D, 0x59, 0x51, 0x95, 0x2F, 0x34, 0x6A, 0xAD, 0xC5, 0xAE, 0x3D, 0x26, 0x59, 0x77, 0x44,
0xB1, 0x25, 0xDC, 0x38, 0x29, 0x22, 0x7D, 0x28, 0x68, 0xCA, 0xB2, 0x3E, 0xAA, 0x40, 0xA8, 0x7E,
0x7A, 0xA5, 0xF2, 0xAF, 0x6D, 0xDA, 0x4A, 0x49, 0x68, 0xD3, 0x74, 0xAD, 0xD5, 0x80, 0xD2, 0x2B,
0x3C, 0xC0, 0x30, 0xE5, 0x83, 0x3F, 0xC3, 0x57, 0x53, 0xB9, 0x76, 0x74, 0x45, 0x35, 0x67, 0xD4,
0xBB, 0xB8, 0xA6, 0x78, 0xCF, 0xA4, 0xD9, 0xA2, 0x73, 0xB2, 0xC0, 0x53, 0x3C, 0x68, 0xE9, 0xA0,
0x78, 0x65, 0x49, 0x25, 0x28, 0xCD, 0x5B, 0xA1, 0xC4, 0x29, 0x2D, 0xCA, 0xEC, 0x28, 0xBF, 0x29,
0x6B, 0x4F, 0x7E, 0x67, 0xC3, 0x0F, 0xFA, 0x71, 0x41, 0xC6, 0x3E, 0x5E, 0x02, 0x57, 0xAE, 0x7E,
0x65, 0x68, 0x3B, 0xCE, 0x5C, 0x8F, 0xD5, 0x6B, 0xCA, 0x66, 0xE4, 0x45, 0x57, 0x42, 0x72, 0x2C,
0xB5, 0x73, 0x5B, 0x30, 0xD5, 0xE1, 0xF9, 0xC9, 0xC8, 0xBE, 0xB9, 0x46, 0xA9, 0xCF, 0xD1, 0x7E,
0xAE, 0x2D, 0x22, 0xCA, 0x5E, 0x72, 0x3D, 0x56, 0x50, 0x60, 0xB4, 0x74, 0x5E, 0x48, 0x4E, 0xA5,
0x36, 0xBA, 0x2A, 0xC2, 0x60, 0xEE, 0x37, 0x3C, 0x2B, 0x43, 0xB9, 0x03, 0xBF, 0x65, 0x49, 0xCB,
0x78, 0x7B, 0x49, 0x8D, 0xD6, 0xCE, 0xAA, 0x4C, 0x4D, 0x27, 0x70, 0xA7, 0x17, 0xB1, 0xAE, 0x05,
0x30, 0xC9, 0x6F, 0x05, 0x29, 0xC6, 0x82, 0xAA, 0x41, 0x7F, 0x2D, 0x28, 0xC0, 0x3E, 0x53, 0xEF,
0x6A, 0x5F, 0x12, 0x42, 0xE9, 0x3F, 0x52, 0x78, 0x8B, 0x31, 0x23, 0x4F, 0xB1, 0x8A, 0x77, 0xF7,
0x38, 0xD6, 0x90, 0xAE, 0x04, 0x9F, 0xED, 0xD6, 0x69, 0x12, 0x26, 0x7F, 0xEC, 0xAE, 0xFC, 0x45,
0x01, 0x74, 0xD7, 0x6D, 0x9F, 0x9A, 0xA7, 0x75, 0x5A, 0x30, 0xCD, 0x90, 0xA9, 0xA5, 0x87, 0x4B,
};
}
}

View File

@ -129,6 +129,8 @@ namespace GameRes.Formats.KAAS
var pent = (PdImageEntry)entry;
byte[] baseline = null;
var dir = arc.Dir as List<Entry>;
// actual baseline image index is hard-coded in game script
// we just look back at the first non-incremental image
for (int i = pent.Number-1; i >= 0; --i)
{
var base_entry = dir[i];

View File

@ -89,6 +89,7 @@ namespace GameRes.Formats.KiriKiri
public Xp3Opener ()
{
Signatures = new uint[] { 0x0d335058, 0 };
Extensions = new[] { "XP3", "EXE" };
ContainedFormats = new[] { "TLG", "BMP", "PNG", "JPEG", "OGG", "WAV", "TXT" };
}

View File

@ -44,7 +44,7 @@ namespace GameRes.Formats.LiveMaker
public VffOpener ()
{
Extensions = new string[] { "dat" };
Extensions = new string[] { "dat", "exe" };
Signatures = new uint[] { 0x666676, 0 };
}

View File

@ -198,10 +198,10 @@ namespace GameRes.Formats.Macromedia
if (t >= 0)
{
string ext = new string (type_buf, 0, t+1);
return string.Format ("{0:X8}.{1}", id, ext.ToLowerInvariant());
return string.Format ("{0:D8}.{1}", id, ext.ToLowerInvariant());
}
else
return id.ToString ("X8");
return id.ToString ("D8");
}
byte[] ZlibUnpack (long offset, uint size, out int actual_size, int unpacked_size_hint = 0)

View File

@ -23,13 +23,13 @@
// IN THE SOFTWARE.
//
using GameRes.Compression;
using GameRes.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@ -46,12 +46,12 @@ namespace GameRes.Formats.Macromedia
public DxrOpener ()
{
Extensions = new[] { "dxr", "cxt" };
Extensions = new[] { "dxr", "cxt", "cct", "dcr" };
Signatures = new[] { 0x52494658u, 0x58464952u };
}
internal static readonly HashSet<string> RawChunks = new HashSet<string> {
"RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", "snd "
"RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", //"snd "
};
internal bool ConvertText = true;
@ -71,9 +71,9 @@ namespace GameRes.Formats.Macromedia
var dir = new List<Entry> ();
ImportMedia (dir_file, dir);
foreach (MemoryMapEntry entry in dir_file.MMap.Dir)
foreach (DirectorEntry entry in dir_file.Directory)
{
if (entry.Size != 0 && RawChunks.Contains (entry.FourCC))
if (entry.Size != 0 && entry.Offset != -1 && RawChunks.Contains (entry.FourCC))
{
entry.Name = string.Format ("{0:D6}.{1}", entry.Id, entry.FourCC.Trim());
if ("snd " == entry.FourCC)
@ -90,19 +90,30 @@ namespace GameRes.Formats.Macromedia
var snd = entry as SoundEntry;
if (snd != null)
return OpenSound (arc, snd);
var ment = entry as MemoryMapEntry;
if (!ConvertText || null == ment || ment.FourCC != "STXT")
var pent = entry as PackedEntry;
if (null == pent)
return base.OpenEntry (arc, entry);
uint offset = Binary.BigEndian (arc.File.View.ReadUInt32 (entry.Offset));
uint length = Binary.BigEndian (arc.File.View.ReadUInt32 (entry.Offset + 4));
return arc.File.CreateStream (entry.Offset + offset, length);
var input = OpenChunkStream (arc.File, pent);
var ment = entry as DirectorEntry;
if (null == ment || !ConvertText || ment.FourCC != "STXT")
return input.AsStream;
using (input)
{
uint offset = Binary.BigEndian (input.ReadUInt32());
uint length = Binary.BigEndian (input.ReadUInt32());
input.Position = offset;
var text = input.ReadBytes ((int)length);
return new BinMemoryStream (text, entry.Name);
}
}
internal Stream OpenSound (ArcFile arc, SoundEntry entry)
{
if (null == entry.Header)
return base.OpenEntry (arc, entry);
var header = arc.File.View.ReadBytes (entry.Header.Offset, entry.Header.Size);
var header = new byte[entry.Header.UnpackedSize];
using (var input = OpenChunkStream (arc.File, entry.Header))
input.Read (header, 0, header.Length);
var format = entry.DeserializeHeader (header);
var riff = new MemoryStream (0x2C);
WaveAudio.WriteRiffHeader (riff, format, entry.Size);
@ -110,12 +121,14 @@ namespace GameRes.Formats.Macromedia
{
using (riff)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
var input = OpenChunkStream (arc.File, entry).AsStream;
return new PrefixStream (riff.ToArray(), input);
}
}
// samples are stored in big-endian format
var samples = arc.File.View.ReadBytes (entry.Offset, entry.Size);
var samples = new byte[entry.UnpackedSize];
using (var input = OpenChunkStream (arc.File, entry))
input.Read (samples, 0, samples.Length);
for (int i = 1; i < samples.Length; i += 2)
{
byte s = samples[i-1];
@ -130,7 +143,6 @@ namespace GameRes.Formats.Macromedia
void ImportMedia (DirectorFile dir_file, List<Entry> dir)
{
var seen_ids = new HashSet<int>();
var mmap = dir_file.MMap;
foreach (var cast in dir_file.Casts)
{
foreach (var piece in cast.Members.Values)
@ -157,17 +169,35 @@ namespace GameRes.Formats.Macromedia
{
if ("ediM" == elem.FourCC)
{
var ediM = dir_file.MMap[elem.Id];
if (string.IsNullOrEmpty (name))
name = ediM.Id.ToString ("D6");
return new Entry
var ediM = dir_file.Index[elem.Id];
name = SanitizeName(name, ediM.Id);
return new PackedEntry
{
Name = name + ".mp3",
Name = name + ".ediM",
Type = "audio",
Offset = ediM.Offset,
Size = ediM.Size,
UnpackedSize = ediM.UnpackedSize,
IsPacked = ediM.IsPacked
};
}
else if ("snd " == elem.FourCC)
{
var snd = dir_file.Index[elem.Id];
if (snd.Size != 0)
{
name = SanitizeName (name, snd.Id);
return new PackedEntry
{
Name = name + ".snd",
Type = "audio",
Offset = snd.Offset,
Size = snd.Size,
UnpackedSize = snd.Size,
IsPacked = false,
};
}
}
if (null == sndHrec && "sndH" == elem.FourCC)
sndHrec = elem;
else if (null == sndSrec && "sndS" == elem.FourCC)
@ -175,66 +205,100 @@ namespace GameRes.Formats.Macromedia
}
if (sndHrec == null || sndSrec == null)
return null;
var sndH = dir_file.MMap[sndHrec.Id];
var sndS = dir_file.MMap[sndSrec.Id];
if (string.IsNullOrEmpty (name))
name = sndSrec.Id.ToString ("D6");
var sndH = dir_file.Index[sndHrec.Id];
var sndS = dir_file.Index[sndSrec.Id];
name = SanitizeName (name, sndSrec.Id);
return new SoundEntry
{
Name = name + ".snd",
Type = "audio",
Offset = sndS.Offset,
Size = sndS.Size,
UnpackedSize = sndS.UnpackedSize,
IsPacked = sndS.IsPacked,
Header = sndH,
};
}
Entry ImportBitmap (CastMember bitmap, DirectorFile dir_file, Cast cast)
{
// var bitd = dir_file.KeyTable.FindByCast (bitmap.Id, "BITD");
KeyTableEntry bitd = null, alfa = null;
KeyTableEntry bitd = null, edim = null, alfa = null;
foreach (var elem in dir_file.KeyTable.Table.Where (e => e.CastId == bitmap.Id))
{
if (null == bitd && "BITD" == elem.FourCC)
bitd = elem;
else if (null == edim && "ediM" == elem.FourCC)
edim = elem;
else if (null == alfa && "ALFA" == elem.FourCC)
alfa = elem;
}
if (bitd == null)
if (bitd == null && edim == null)
return null;
var entry = new BitmapEntry();
if (bitd != null)
{
entry.DeserializeHeader (bitmap.SpecificData);
var name = bitmap.Info.Name;
if (string.IsNullOrEmpty (name))
name = bitd.Id.ToString ("D6");
var chunk = dir_file.MMap[bitd.Id];
var name = SanitizeName (bitmap.Info.Name, bitd.Id);
var chunk = dir_file.Index[bitd.Id];
entry.Name = name + ".BITD";
entry.Type = "image";
entry.Offset = chunk.Offset;
entry.Size = chunk.Size;
entry.IsPacked = chunk.IsPacked;
entry.UnpackedSize = chunk.UnpackedSize;
if (entry.Palette > 0)
{
var cast_id = cast.Index[entry.Palette-1];
var clut = dir_file.KeyTable.FindByCast (cast_id, "CLUT");
if (clut != null)
entry.PaletteRef = dir_file.MMap[clut.Id];
entry.PaletteRef = dir_file.Index[clut.Id];
}
}
else // if (edim != null)
{
var name = SanitizeName (bitmap.Info.Name, edim.Id);
var chunk = dir_file.Index[edim.Id];
entry.Name = name + ".jpg";
entry.Type = "image";
entry.Offset = chunk.Offset;
entry.Size = chunk.Size;
entry.IsPacked = false;
entry.UnpackedSize = entry.Size;
}
if (alfa != null)
entry.AlphaRef = dir_file.MMap[alfa.Id];
entry.AlphaRef = dir_file.Index[alfa.Id];
return entry;
}
static readonly Regex ForbiddenCharsRe = new Regex (@"[:?*<>/\\]");
string SanitizeName (string name, int id)
{
name = name?.Trim();
if (string.IsNullOrEmpty (name))
name = id.ToString ("D6");
else
name = ForbiddenCharsRe.Replace (name, "_");
return name;
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var bent = entry as BitmapEntry;
if (null == bent)
return base.OpenImage (arc, entry);
return base.OpenImage(arc, entry);
if (entry.Name.HasExtension (".jpg"))
return OpenJpeg (arc, bent);
BitmapPalette palette = null;
if (bent.PaletteRef != null)
{
var pal_bytes = arc.File.View.ReadBytes (bent.PaletteRef.Offset, bent.PaletteRef.Size);
using (var pal = OpenChunkStream (arc.File, bent.PaletteRef))
{
var pal_bytes = pal.ReadBytes ((int)bent.PaletteRef.UnpackedSize);
palette = ReadPalette (pal_bytes);
}
}
else if (bent.BitDepth <= 8)
{
switch (bent.Palette)
@ -247,28 +311,54 @@ namespace GameRes.Formats.Macromedia
case -101: palette = Palettes.SystemWindows; break;
}
}
var info = new ImageMetaData {
var info = new BitdMetaData {
Width = (uint)(bent.Right - bent.Left),
Height = (uint)(bent.Bottom - bent.Top),
BPP = bent.BitDepth
BPP = bent.BitDepth,
DepthType = bent.DepthType,
};
byte[] alpha_channel = null;
if (bent.AlphaRef != null)
alpha_channel = ReadAlphaChannel (arc.File, bent.AlphaRef, info);
var input = OpenChunkStream (arc.File, bent).AsStream;
return new BitdDecoder (input, info, palette) { AlphaChannel = alpha_channel };
}
IImageDecoder OpenJpeg (ArcFile arc, BitmapEntry entry)
{
using (var alpha = arc.File.CreateStream (bent.AlphaRef.Offset, bent.AlphaRef.Size))
if (null == entry.AlphaRef)
return base.OpenImage (arc, entry);
// jpeg with alpha-channel
var input = arc.File.CreateStream (entry.Offset, entry.Size);
try
{
var alpha_info = new ImageMetaData {
var info = ImageFormat.Jpeg.ReadMetaData (input);
if (null == info)
throw new InvalidFormatException ("Invalid 'ediM' chunk.");
var alpha_channel = ReadAlphaChannel (arc.File, entry.AlphaRef, info);
return BitdDecoder.FromJpeg (input, info, alpha_channel);
}
catch
{
input.Dispose();
throw;
}
}
byte[] ReadAlphaChannel (ArcView file, DirectorEntry entry, ImageMetaData info)
{
using (var alpha = OpenChunkStream (file, entry))
{
var alpha_info = new BitdMetaData {
Width = info.Width,
Height = info.Height,
BPP = 8,
DepthType = 0x80,
};
var decoder = new BitdDecoder (alpha, alpha_info, null);
alpha_channel = decoder.Unpack8bpp();
var decoder = new BitdDecoder (alpha.AsStream, alpha_info, null);
return decoder.Unpack8bpp();
}
}
var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new BitdDecoder (input.AsStream, info, palette) { AlphaChannel = alpha_channel };
}
BitmapPalette ReadPalette (byte[] data)
{
@ -280,9 +370,20 @@ namespace GameRes.Formats.Macromedia
}
return new BitmapPalette (colors);
}
IBinaryStream OpenChunkStream (ArcView file, PackedEntry entry)
{
var input = file.CreateStream (entry.Offset, entry.Size);
if (!entry.IsPacked)
return input;
var data = new byte[entry.UnpackedSize];
using (var zstream = new ZLibStream (input, CompressionMode.Decompress))
zstream.Read (data, 0, data.Length);
return new BinMemoryStream (data, entry.Name);
}
}
internal class BitmapEntry : Entry
internal class BitmapEntry : PackedEntry
{
public byte Flags;
public byte DepthType;
@ -292,8 +393,8 @@ namespace GameRes.Formats.Macromedia
public int Right;
public int BitDepth;
public int Palette;
public Entry PaletteRef;
public Entry AlphaRef;
public DirectorEntry PaletteRef;
public DirectorEntry AlphaRef;
public void DeserializeHeader (byte[] data)
{
@ -314,9 +415,9 @@ namespace GameRes.Formats.Macromedia
}
}
internal class SoundEntry : Entry
internal class SoundEntry : PackedEntry
{
public Entry Header;
public DirectorEntry Header;
public WaveFormat DeserializeHeader (byte[] header)
{

View File

@ -31,10 +31,12 @@ namespace GameRes.Formats.Macromedia
[Export(typeof(AudioFormat))]
public class SndAudio : AudioFormat
{
public override string Tag { get => "SND"; }
public override string Description { get => "Macromedia Director audio resource"; }
public override uint Signature { get => 0; }
public override bool CanWrite { get => false; }
public override string Tag => "SND";
public override string Description => "Macromedia Director audio resource";
public override uint Signature => 0;
public override bool CanWrite => false;
static readonly ResourceInstance<AudioFormat> Mp3 = new ResourceInstance<AudioFormat> ("MP3");
public override SoundInput TryOpen (IBinaryStream file)
{
@ -83,6 +85,13 @@ namespace GameRes.Formats.Macromedia
if (bps != 16 && bps != 8)
return null;
// try mp3
var samples_stream = new StreamRegion (reader.Source, reader.Position);
var mp3_input = new BinaryStream (samples_stream, file.Name);
var mp3 = Mp3.Value.TryOpen (mp3_input);
if (mp3 != null)
return mp3;
var format = new WaveFormat {
FormatTag = 1,
Channels = channels,
@ -93,8 +102,7 @@ namespace GameRes.Formats.Macromedia
format.SetBPS();
if (8 == bps)
{
var data = new StreamRegion (file.AsStream, file.Position);
return new RawPcmInput (data, format);
return new RawPcmInput (samples_stream, format);
}
int sample_count = frames_count * channels;
var samples = file.ReadBytes (sample_count);

View File

@ -23,9 +23,11 @@
// IN THE SOFTWARE.
//
using GameRes.Compression;
using GameRes.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
@ -66,24 +68,53 @@ namespace GameRes.Formats.Macromedia
internal class DirectorFile
{
List<DirectorEntry> m_dir;
Dictionary<int, DirectorEntry> m_index = new Dictionary<int, DirectorEntry>();
MemoryMap m_mmap = new MemoryMap();
KeyTable m_keyTable = new KeyTable();
DirectorConfig m_config = new DirectorConfig();
List<Cast> m_casts = new List<Cast>();
Dictionary<int, byte[]> m_ilsMap = new Dictionary<int, byte[]>();
public bool IsAfterBurned { get; private set; }
public MemoryMap MMap => m_mmap;
public KeyTable KeyTable => m_keyTable;
public DirectorConfig Config => m_config;
public List<Cast> Casts => m_casts;
public List<DirectorEntry> Directory => m_dir;
public Dictionary<int, DirectorEntry> Index => m_index;
public DirectorEntry Find (string four_cc) => Directory.Find (e => e.FourCC == four_cc);
public DirectorEntry FindById (int id)
{
DirectorEntry entry;
m_index.TryGetValue (id, out entry);
return entry;
}
public bool Deserialize (SerializationContext context, Reader reader)
{
reader.Position = 8;
string codec = reader.ReadFourCC();
if (codec != "MV93" && codec != "MC95")
if (codec == "MV93" || codec == "MC95")
{
if (!ReadMMap (context, reader))
return false;
return ReadMMap (context, reader)
&& ReadKeyTable (context, reader)
}
else if (codec == "FGDC" || codec == "FGDM")
{
IsAfterBurned = true;
if (!ReadAfterBurner (context, reader))
return false;
}
else
{
Trace.WriteLine (string.Format ("Unknown codec '{0}'", codec), "DXR");
return false;
}
return ReadKeyTable (context, reader)
&& ReadConfig (context, reader)
&& ReadCasts (context, reader);
}
@ -99,25 +130,129 @@ namespace GameRes.Formats.Macromedia
return false;
reader.Position = mmap_pos + 8;
MMap.Deserialize (context, reader);
m_dir = MMap.Dir;
for (int i = 0; i < m_dir.Count; ++i)
{
m_index[i] = m_dir[i];
}
return true;
}
bool ReadAfterBurner (SerializationContext context, Reader reader)
{
if (reader.ReadFourCC() != "Fver")
return false;
int length = reader.ReadVarInt();
long next_pos = reader.Position + length;
int version = reader.ReadVarInt();
if (version > 0x400)
{
reader.ReadVarInt(); // imap version
reader.ReadVarInt(); // director version
}
if (version > 0x500)
{
int str_len = reader.ReadU8();
reader.Skip (str_len); // version string
}
reader.Position = next_pos;
if (reader.ReadFourCC() != "Fcdr")
return false;
// skip compression table, assume everything is zlib-compressed
length = reader.ReadVarInt();
reader.Position += length;
if (reader.ReadFourCC() != "ABMP")
return false;
length = reader.ReadVarInt();
next_pos = reader.Position + length;
reader.ReadVarInt(); // compression type, index within 'Fcdr' compression table
int unpacked_size = reader.ReadVarInt();
using (var abmp = new ZLibStream (reader.Source, CompressionMode.Decompress, true))
{
var abmp_reader = new Reader (abmp, reader.ByteOrder);
if (!ReadABMap (context, abmp_reader))
return false;
}
reader.Position = next_pos;
if (reader.ReadFourCC() != "FGEI")
return false;
reader.ReadVarInt();
long base_offset = reader.Position;
foreach (var entry in m_dir)
{
m_index[entry.Id] = entry;
if (entry.Offset >= 0)
entry.Offset += base_offset;
}
var ils_chunk = FindById (2);
if (null == ils_chunk)
return false;
using (var ils = new ZLibStream (reader.Source, CompressionMode.Decompress, true))
{
uint pos = 0;
var ils_reader = new Reader (ils, reader.ByteOrder);
while (pos < ils_chunk.UnpackedSize)
{
int id = ils_reader.ReadVarInt();
var chunk = m_index[id];
m_ilsMap[id] = ils_reader.ReadBytes ((int)chunk.Size);
pos += ils_reader.GetVarIntLength ((uint)id) + chunk.Size;
}
}
return true;
}
bool ReadABMap (SerializationContext context, Reader reader)
{
reader.ReadVarInt();
reader.ReadVarInt();
int count = reader.ReadVarInt();
m_dir = new List<DirectorEntry> (count);
for (int i = 0; i < count; ++ i)
{
var entry = new AfterBurnerEntry();
entry.Deserialize (context, reader);
m_dir.Add (entry);
}
return true;
}
Reader GetChunkReader (DirectorEntry chunk, Reader reader)
{
if (-1 == chunk.Offset)
{
byte[] chunk_data;
if (!m_ilsMap.TryGetValue (chunk.Id, out chunk_data))
throw new InvalidFormatException (string.Format ("Can't find chunk {0} in ILS", chunk.FourCC));
var input = new BinMemoryStream (chunk_data, null);
reader = new Reader (input, reader.ByteOrder);
}
else
{
reader.Position = chunk.Offset;
}
return reader;
}
bool ReadKeyTable (SerializationContext context, Reader reader)
{
var key_chunk = MMap.Find ("KEY*");
var key_chunk = Find ("KEY*");
if (null == key_chunk)
return false;
reader.Position = key_chunk.Offset;
reader = GetChunkReader (key_chunk, reader);
KeyTable.Deserialize (context, reader);
return true;
}
bool ReadConfig (SerializationContext context, Reader reader)
{
var config_chunk = MMap.Find ("VWCF") ?? MMap.Find ("DRCF");
var config_chunk = Find ("VWCF") ?? Find ("DRCF");
if (null == config_chunk)
return false;
reader.Position = config_chunk.Offset;
reader = GetChunkReader (config_chunk, reader);
Config.Deserialize (context, reader);
context.Version = Config.Version;
return true;
@ -125,21 +260,23 @@ namespace GameRes.Formats.Macromedia
bool ReadCasts (SerializationContext context, Reader reader)
{
Reader cas_reader;
if (context.Version > 1200)
{
var mcsl = MMap.Find ("MCsL");
var mcsl = Find ("MCsL");
if (mcsl != null)
{
reader.Position = mcsl.Offset;
var mcsl_reader = GetChunkReader (mcsl, reader);
var cast_list = new CastList();
cast_list.Deserialize (context, reader);
cast_list.Deserialize (context, mcsl_reader);
foreach (var entry in cast_list.Entries)
{
var key_entry = KeyTable.FindByCast (entry.Id, "CAS*");
if (key_entry != null)
{
var mmap_entry = MMap[key_entry.Id];
var cast = new Cast (context, reader, mmap_entry);
var cas_entry = Index[key_entry.Id];
cas_reader = GetChunkReader (cas_entry, reader);
var cast = new Cast (context, cas_reader, cas_entry);
if (!PopulateCast (cast, context, reader, entry))
return false;
Casts.Add (cast);
@ -148,11 +285,12 @@ namespace GameRes.Formats.Macromedia
return true;
}
}
var cas_chunk = MMap.Find ("CAS*");
var cas_chunk = Find ("CAS*");
if (null == cas_chunk)
return false;
var new_entry = new CastListEntry { Name = "internal", Id = 0x400, MinMember = Config.MinMember };
var new_cast = new Cast (context, reader, cas_chunk);
cas_reader = GetChunkReader (cas_chunk, reader);
var new_cast = new Cast (context, cas_reader, cas_chunk);
if (!PopulateCast (new_cast, context, reader, new_entry))
return false;
Casts.Add (new_cast);
@ -162,30 +300,16 @@ namespace GameRes.Formats.Macromedia
public bool PopulateCast (Cast cast, SerializationContext context, Reader reader, CastListEntry entry)
{
cast.Name = entry.Name;
/*
var lctx_ref = KeyTable.Table.Find (e => e.CastId == entry.Id && (e.FourCC == "Lctx" || e.FourCC == "LctX"));
MemoryMapEntry lctx_chunk = null;
if (lctx_ref != null)
lctx_chunk = MMap[lctx_ref.Id];
else
lctx_chunk = MMap.Dir.Find (e => e.FourCC == "Lctx" || e.FourCC == "LctX");
if (null == lctx_chunk)
return false;
reader.Position = lctx_chunk.Offset;
var lctx = new ScriptContext();
lctx.Deserialize (context, reader);
cast.Context = lctx;
*/
for (int i = 0; i < cast.Index.Length; ++i)
{
int chunk_id = cast.Index[i];
if (chunk_id > 0)
{
var chunk = MMap[chunk_id];
var chunk = this.Index[chunk_id];
var member = new CastMember();
member.Id = chunk_id;
reader.Position = chunk.Offset;
member.Deserialize (context, reader);
var cast_reader = GetChunkReader (chunk, reader);
member.Deserialize (context, cast_reader);
cast.Members[member.Id] = member;
}
}
@ -293,11 +417,10 @@ namespace GameRes.Formats.Macromedia
public string Name;
public Dictionary<int, CastMember> Members = new Dictionary<int, CastMember>();
public Cast (SerializationContext context, Reader reader, MemoryMapEntry entry)
public Cast (SerializationContext context, Reader reader, DirectorEntry entry)
{
int count = (int)(entry.Size / 4);
Index = new int[count];
reader.Position = entry.Offset;
Deserialize (context, reader);
}
@ -391,54 +514,6 @@ namespace GameRes.Formats.Macromedia
public int Id;
}
internal class ScriptContext
{
public int EntriesOffset;
public int LnamChunkId;
public int ValidCount;
public ushort Flags;
public short FreePtr;
public List<ScriptContextMap> ChunkMap = new List<ScriptContextMap>();
public void Deserialize (SerializationContext context, Reader reader)
{
long base_offset = reader.Position;
reader = reader.CloneUnless (ByteOrder.BigEndian);
reader.Skip (8);
int count = reader.ReadI32();
reader.Skip (4);
EntriesOffset = reader.ReadU16();
reader.Skip (14);
LnamChunkId = reader.ReadI32();
ValidCount = reader.ReadU16();
Flags = reader.ReadU16();
FreePtr = reader.ReadI16();
reader.Position = base_offset + EntriesOffset;
ChunkMap.Clear();
ChunkMap.Capacity = count;
for (int i = 0; i < count; ++i)
{
var entry = new ScriptContextMap();
entry.Deserialize (context, reader);
ChunkMap.Add (entry);
}
}
}
internal class ScriptContextMap
{
public int Key;
public int ChunkId;
public void Deserialize (SerializationContext context, Reader reader)
{
Key = reader.ReadI32();
ChunkId = reader.ReadI32();
reader.Skip (4);
}
}
internal class DirectorConfig
{
public short Length;
@ -548,11 +623,9 @@ namespace GameRes.Formats.Macromedia
public int ChunkCountMax;
public int ChunkCountUsed;
public int FreeHead;
public readonly List<MemoryMapEntry> Dir = new List<MemoryMapEntry>();
public readonly List<DirectorEntry> Dir = new List<DirectorEntry>();
public MemoryMapEntry this[int index] => Dir[index];
public MemoryMapEntry Find (string four_cc) => Dir.Find (e => e.FourCC == four_cc);
public DirectorEntry this[int index] => Dir[index];
public void Deserialize (SerializationContext context, Reader reader)
{
@ -582,10 +655,14 @@ namespace GameRes.Formats.Macromedia
}
}
internal class MemoryMapEntry : Entry
internal class DirectorEntry : PackedEntry
{
public int Id;
public string FourCC;
}
internal class MemoryMapEntry : DirectorEntry
{
public ushort Flags;
public MemoryMapEntry (int id = 0)
@ -599,7 +676,25 @@ namespace GameRes.Formats.Macromedia
Size = reader.ReadU32();
Offset = reader.ReadU32() + 8;
Flags = reader.ReadU16();
int Next = reader.ReadI32();
reader.ReadI32(); // next
UnpackedSize = Size;
IsPacked = false;
}
}
internal class AfterBurnerEntry : DirectorEntry
{
public int CompMethod;
public void Deserialize (SerializationContext context, Reader reader)
{
Id = reader.ReadVarInt();
Offset = reader.ReadVarInt();
Size = (uint)reader.ReadVarInt();
UnpackedSize = (uint)reader.ReadVarInt();
CompMethod = reader.ReadVarInt(); // assume zlib
FourCC = reader.ReadFourCC();
IsPacked = Size != UnpackedSize;
}
}
@ -619,7 +714,7 @@ namespace GameRes.Formats.Macromedia
SetByteOrder (e);
}
public Stream Source { get => m_input; }
public Stream Source => m_input;
public ByteOrder ByteOrder { get; private set; }
@ -701,6 +796,32 @@ namespace GameRes.Formats.Macromedia
return buffer;
}
public int ReadVarInt ()
{
int n = 0;
for (int i = 0; i < 5; ++i)
{
int bits = m_input.ReadByte();
if (-1 == bits)
throw new EndOfStreamException();
n = n << 7 | bits & 0x7F;
if (0 == (bits & 0x80))
return n;
}
throw new InvalidFormatException();
}
public uint GetVarIntLength (uint i)
{
uint n = 1;
while (i > 0x7F)
{
i >>= 7;
++n;
}
return n;
}
public Reader CloneUnless (ByteOrder order)
{
if (this.ByteOrder != order)

View File

@ -1,8 +1,8 @@
//! \file ImageBITD.cs
//! \date Fri Jun 26 07:45:01 2015
//! \brief Selen image format.
//! \brief Macromedia Director image format.
//
// Copyright (C) 2015 by morkt
// Copyright (C) 2015-2023 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
@ -34,174 +34,9 @@ using GameRes.Utility;
namespace GameRes.Formats.Macromedia
{
[Export(typeof(ImageFormat))]
public class BitdFormat : ImageFormat
internal class BitdMetaData : ImageMetaData
{
public override string Tag { get { return "BITD"; } }
public override string Description { get { return "Selen RLE-compressed bitmap"; } }
public override uint Signature { get { return 0; } }
public override ImageMetaData ReadMetaData (IBinaryStream stream)
{
if (stream.Length > 0xffffff)
return null;
var scanner = new BitdScanner (stream.AsStream);
return scanner.GetInfo();
}
public override ImageData Read (IBinaryStream stream, ImageMetaData info)
{
var reader = new BitdReader (stream.AsStream, info);
reader.Unpack();
return ImageData.Create (info, reader.Format, null, reader.Data);
}
public override void Write (Stream file, ImageData image)
{
throw new NotImplementedException ("BitdFormat.Write not implemented");
}
}
internal class BitdScanner
{
Stream m_input;
protected Stream Input { get { return m_input; } }
public BitdScanner (Stream input)
{
m_input = input;
}
const int MaxScanLine = 2048;
public ImageMetaData GetInfo ()
{
int total = 0;
var scan_lines = new Dictionary<int, int>();
var key_lines = new List<int>();
for (;;)
{
int b = m_input.ReadByte();
if (-1 == b)
break;
int count = b;
if (b > 0x7f)
count = (byte)-(sbyte)b;
++count;
if (count > 0x7f)
return null;
if (b > 0x7f)
{
if (-1 == m_input.ReadByte())
return null;
}
else
m_input.Seek (count, SeekOrigin.Current);
key_lines.Clear();
key_lines.AddRange (scan_lines.Keys);
foreach (var line in key_lines)
{
int width = scan_lines[line];
if (width < count)
scan_lines.Remove (line);
else if (width == count)
scan_lines[line] = line;
else
scan_lines[line] = width - count;
}
total += count;
if (total <= MaxScanLine && total >= 8)
scan_lines[total] = total;
if (total > MaxScanLine && !scan_lines.Any())
return null;
}
int rem;
total = Math.DivRem (total, 4, out rem);
if (rem != 0)
return null;
var valid_lines = from line in scan_lines where line.Key == line.Value
orderby line.Key
select line.Key;
bool is_eof = -1 == m_input.ReadByte();
foreach (var width in valid_lines)
{
int height = Math.DivRem (total, width, out rem);
if (0 == rem)
{
return new ImageMetaData
{
Width = (uint)width,
Height = (uint)height,
BPP = 32,
};
}
}
return null;
}
}
internal class BitdReader : BitdScanner
{
byte[] m_output;
int m_width;
int m_height;
public byte[] Data { get { return m_output; } }
public PixelFormat Format { get; private set; }
public BitdReader (Stream input, ImageMetaData info) : base (input)
{
m_width = (int)info.Width;
m_height = (int)info.Height;
m_output = new byte[m_width * m_height * 4];
Format = PixelFormats.Bgra32;
}
public void Unpack ()
{
int stride = m_width * 4;
var scan_line = new byte[stride];
for (int line = 0; line < m_output.Length; line += stride)
{
int dst = 0;
while (dst < stride)
{
int b = Input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
int count = b;
if (b > 0x7f)
count = (byte)-(sbyte)b;
++count;
if (dst + count > stride)
throw new InvalidFormatException();
if (b > 0x7f)
{
b = Input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
for (int i = 0; i < count; ++i)
scan_line[dst++] = (byte)b;
}
else
{
Input.Read (scan_line, dst, count);
dst += count;
}
}
dst = line;
for (int x = 0; x < m_width; ++x)
{
m_output[dst++] = scan_line[x+m_width*3];
m_output[dst++] = scan_line[x+m_width*2];
m_output[dst++] = scan_line[x+m_width];
m_output[dst++] = scan_line[x];
}
}
}
public byte DepthType;
}
internal class BitdDecoder : IImageDecoder
@ -216,13 +51,13 @@ namespace GameRes.Formats.Macromedia
BitmapPalette m_palette;
public Stream Source => m_input;
public ImageFormat SourceFormat => null;
public ImageFormat SourceFormat { get; private set; }
public ImageMetaData Info => m_info;
public ImageData Image => m_image ?? (m_image = GetImageData());
public PixelFormat Format { get; private set; }
public byte[] AlphaChannel { get; set; }
public BitdDecoder (Stream input, ImageMetaData info, BitmapPalette palette)
public BitdDecoder (Stream input, BitdMetaData info, BitmapPalette palette)
{
m_input = input;
m_info = info;
@ -235,22 +70,54 @@ namespace GameRes.Formats.Macromedia
: info.BPP == 4 ? PixelFormats.Indexed4
: info.BPP == 8 ? PixelFormats.Indexed8
: info.BPP == 16 ? PixelFormats.Bgr555
: PixelFormats.Bgr32;
: info.DepthType == 0x87 // i have no clue what this is
|| info.DepthType == 0x8A ? PixelFormats.Bgra32 // depth type 0x87/0x8A
: PixelFormats.Bgra32; // depth type 0x82/84/85/86/8C
m_palette = palette;
}
private BitdDecoder (Stream input, ImageMetaData info, byte[] alpha_channel)
{
m_input = input;
m_info = info;
m_width = info.iWidth;
m_height = info.iHeight;
m_stride = (m_width * m_info.BPP + 7) / 8;
Format = PixelFormats.Bgra32;
AlphaChannel = alpha_channel;
SourceFormat = ImageFormat.Jpeg;
}
public static IImageDecoder FromJpeg (Stream input, ImageMetaData info, byte[] alpha_channel)
{
return new BitdDecoder (input, info, alpha_channel);
}
protected ImageData GetImageData ()
{
BitmapSource bitmap = null;
m_input.Position = 0;
if (Info.BPP <= 8)
if (SourceFormat == ImageFormat.Jpeg)
{
var decoder = new JpegBitmapDecoder (m_input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
bitmap = decoder.Frames[0];
if (null == AlphaChannel)
return new ImageData (bitmap, m_info);
}
else if (Info.BPP > 8)
UnpackChannels (Info.BPP / 8);
else if (m_output.Length != m_input.Length)
Unpack8bpp();
else
UnpackChannels (Info.BPP / 8);
m_input.Read (m_output, 0, m_output.Length);
if (AlphaChannel != null)
{
if (Info.BPP != 32)
if (Info.BPP != 32 || bitmap != null)
{
BitmapSource bitmap = BitmapSource.Create (Info.iWidth, Info.iHeight, ImageData.DefaultDpiX, ImageData.DefaultDpiY, Format, m_palette, m_output, m_stride);
if (bitmap == null)
bitmap = BitmapSource.Create (m_width, m_height, ImageData.DefaultDpiX, ImageData.DefaultDpiY, Format, m_palette, m_output, m_stride);
if (Info.BPP != 32)
bitmap = new FormatConvertedBitmap (bitmap, PixelFormats.Bgr32, null, 0);
m_stride = bitmap.PixelWidth * 4;
m_output = new byte[bitmap.PixelHeight * m_stride];
@ -324,7 +191,8 @@ namespace GameRes.Formats.Macromedia
{
int b = m_input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
break; // one in 5000 images somehow stumbles here
// throw new InvalidFormatException ("Unexpected end of file");
int count = b;
if (b > 0x7f)
count = (byte)-(sbyte)b;

View File

@ -32,7 +32,7 @@ using GameRes.Utility;
namespace GameRes.Formats.Mebius
{
[Export(typeof(AudioFormat))]
[ExportMetadata("Priority", -1)]
[ExportMetadata("Priority", 1)]
public class KoeAudio : AudioFormat
{
public override string Tag { get { return "KOE/MEBIUS"; } }
@ -65,7 +65,67 @@ namespace GameRes.Formats.Mebius
return new WaveInput (data);
}
static readonly Dictionary<string, byte[]> DefaultKeys = new Dictionary<string, byte[]> {
Dictionary<string, byte[]> DefaultKeys => SchemeMap["Mebinya!"];
static readonly Dictionary<string, Dictionary<string, byte[]>> SchemeMap =
new Dictionary<string, Dictionary<string, byte[]>> {
{ "Mebinya!", new Dictionary<string, byte[]> {
{ "koe", new byte[] {
0xCE, 0xC5, 0x94, 0xE8, 0xD5, 0x7F, 0xEB, 0xF4, 0x96, 0xCA, 0xAA, 0x80, 0xAC, 0x45, 0x60, 0x58,
0x71, 0x50, 0xDD, 0x72, 0x20, 0x39, 0x08, 0x73, 0xFE, 0x46, 0x07, 0xC5, 0x78, 0x77, 0xC0, 0x23,
0x49, 0x9F, 0xFC, 0xD1, 0x9A, 0x0F, 0x99, 0x7F, 0x3E, 0x7B, 0xAE, 0xF4, 0x66, 0xEE, 0x14, 0x94,
0x75, 0xD0, 0x0E, 0xD8, 0x64, 0x60, 0xB4, 0x3B, 0x40, 0x33, 0xC3, 0x4E, 0x40, 0x0E, 0xE4, 0x6C,
0x8D, 0x26, 0xBA, 0xB0, 0x17, 0xA5, 0x40, 0xB7, 0x27, 0x80, 0x79, 0x58, 0x92, 0xF8, 0x79, 0x3E,
0x2A, 0xDA, 0xC8, 0x29, 0xD3, 0x43, 0x66, 0xC0, 0xE5, 0x16, 0xAB, 0x25, 0x35, 0x68, 0x60, 0xC1,
0x77, 0x6E, 0x2B, 0x0E, 0x50, 0x58, 0xDC, 0xAE, 0xC5, 0x97, 0xE9, 0x27, 0xE1, 0xF3, 0x03, 0xA2,
0x43, 0x77, 0x13, 0xF0, 0xEC, 0x8C, 0x40, 0xB4, 0x7F, 0x62, 0x8B, 0x84, 0x40, 0x68, 0xAF, 0xD2,
0x10, 0xF2, 0xFE, 0x79, 0x3D, 0x63, 0x3D, 0xB4, 0x43, 0x65, 0xB8, 0x5F, 0x77, 0x13, 0x32, 0x56,
0xA4, 0x93, 0xC9, 0x3D, 0x9F, 0x89, 0xFE, 0x0B, 0xD0, 0x6C, 0x81, 0x2D, 0x3F, 0x94, 0xDD, 0x16,
0x1A, 0x12, 0x3A, 0x83, 0xC7, 0x26, 0xC3, 0xE0, 0xFE, 0xF1, 0xEC, 0x82, 0x6C, 0xAF, 0xA0, 0x30,
0xEB, 0xFD, 0x1A, 0xA1, 0xD0, 0xA9, 0xEC, 0x7A, 0x52, 0x6D, 0x83, 0xE4, 0x84, 0x97, 0x8F, 0x44,
0x89, 0x0E, 0xB7, 0xC1, 0x4F, 0xA1, 0x89, 0x8C, 0x09, 0xA6, 0xE5, 0x98, 0x4C, 0xC3, 0x7A, 0xCA,
0xE6, 0x6D, 0x06, 0xB7, 0x5B, 0x82, 0x6C, 0x02, 0x2E, 0x03, 0x57, 0xF3, 0xD6, 0x3D, 0x79, 0x5B,
0x87, 0x0E, 0xA2, 0x4E, 0xA6, 0xFE, 0xB8, 0x56, 0xA6, 0x55, 0xD3, 0x2B, 0x17, 0x6F, 0x7F, 0x84,
0x16, 0xF7, 0xE6, 0x99, 0x8A, 0x4E, 0x73, 0xDE, 0x45, 0x2E, 0x1A, 0xA6, 0xEF, 0x78, 0x67, 0x1A,
} },
{ "mse", new byte[] {
0x40, 0xBA, 0x96, 0x7E, 0x07, 0xE1, 0x92, 0x95, 0x7E, 0x95, 0x17, 0x47, 0x3D, 0x1C, 0x08, 0x94,
0x02, 0xA5, 0x39, 0x7D, 0x95, 0xCB, 0xD8, 0x57, 0x09, 0x52, 0x67, 0xFD, 0x86, 0x57, 0xFD, 0x81,
0x04, 0xB9, 0x70, 0x54, 0x14, 0xC7, 0x8E, 0xA5, 0xA0, 0x11, 0xF5, 0xE2, 0xC5, 0x6E, 0xDB, 0x01,
0xA8, 0x8C, 0xA9, 0x25, 0xEB, 0x98, 0xD6, 0xBA, 0xAD, 0xD9, 0x62, 0x00, 0xAE, 0x50, 0xCA, 0x3E,
0x04, 0xAA, 0xF7, 0x98, 0xF9, 0x2C, 0xAE, 0xA4, 0x11, 0xCE, 0xF8, 0xCC, 0xAD, 0xB8, 0x07, 0xA5,
0xE8, 0xDF, 0x28, 0x2A, 0xA1, 0xE4, 0x81, 0x1F, 0x35, 0x7B, 0x4C, 0x7F, 0xFA, 0x04, 0x75, 0x31,
0x77, 0x0D, 0xD1, 0x79, 0xD3, 0x68, 0x2C, 0xDB, 0x16, 0x27, 0xBB, 0xD5, 0x2A, 0xFB, 0x2C, 0xBC,
0xB1, 0x70, 0xE2, 0x1C, 0xA8, 0xF6, 0x1E, 0x53, 0xDA, 0xA0, 0x89, 0xED, 0xB9, 0x25, 0x0A, 0x55,
0x08, 0x01, 0x37, 0xE7, 0x6B, 0xB4, 0xDB, 0x18, 0xE2, 0x13, 0x6B, 0x8E, 0x25, 0x98, 0x40, 0x05,
0xE7, 0x32, 0x1F, 0x4B, 0xA9, 0x7C, 0xC8, 0x24, 0x51, 0x54, 0x16, 0xFD, 0x6F, 0xC8, 0x67, 0x2B,
0xD2, 0xCD, 0x78, 0x18, 0xC2, 0xB0, 0xB6, 0xAA, 0x25, 0xB2, 0x4E, 0xCD, 0x3A, 0xD7, 0x0D, 0x43,
0x64, 0xBD, 0x35, 0x52, 0xFC, 0x07, 0x70, 0x67, 0xBE, 0x48, 0xFB, 0xA9, 0xD2, 0x67, 0xC3, 0xB8,
0x6A, 0xDC, 0x76, 0x04, 0x0E, 0xDD, 0xD3, 0xEB, 0x7A, 0x49, 0x39, 0xAC, 0xBD, 0xE5, 0x31, 0xBB,
0x71, 0xCC, 0x91, 0x8A, 0xB1, 0x09, 0x57, 0xF3, 0x39, 0xD2, 0x5E, 0xAB, 0x4F, 0x5F, 0x24, 0x86,
0xD5, 0x3D, 0xA8, 0xE7, 0x36, 0x23, 0x21, 0x32, 0x76, 0x3C, 0x98, 0x0A, 0x34, 0x51, 0x1E, 0xB8,
0x51, 0x40, 0x34, 0x93, 0x0B, 0x5C, 0x94, 0x24, 0x50, 0x6A, 0x72, 0x85, 0x04, 0xF1, 0xE5, 0x20,
} },
{ "bgm", new byte[] {
0x16, 0x83, 0x0A, 0x4D, 0x6E, 0x39, 0xBF, 0xD8, 0x9C, 0x2B, 0x9E, 0x9F, 0xAE, 0x13, 0x8C, 0x63,
0xBE, 0x53, 0x95, 0x2E, 0x61, 0xB3, 0xFC, 0x26, 0x1C, 0xA5, 0xBF, 0x99, 0x69, 0x29, 0x3C, 0x99,
0xD7, 0x1E, 0x8B, 0xFD, 0xBD, 0x98, 0xC9, 0x12, 0x0E, 0x93, 0x5F, 0x59, 0x4E, 0x89, 0x7B, 0x26,
0xA7, 0x53, 0x50, 0xF1, 0xB6, 0x52, 0x5A, 0xA6, 0x6D, 0xCD, 0x20, 0xD9, 0xC3, 0x82, 0xCB, 0x21,
0xFD, 0x4D, 0x8B, 0xFA, 0x49, 0xEA, 0xC3, 0x7C, 0x81, 0x42, 0xEE, 0x38, 0xC3, 0xAB, 0xE0, 0x1A,
0xBD, 0x9F, 0xB4, 0x98, 0x4F, 0x59, 0x60, 0x8D, 0xEE, 0x41, 0x92, 0x87, 0xEB, 0x30, 0x2A, 0x66,
0xF4, 0x69, 0xA2, 0xA4, 0x0F, 0x53, 0xB6, 0x04, 0x4E, 0x4A, 0xB8, 0x9E, 0x8B, 0x23, 0xE0, 0xF8,
0xE6, 0xA2, 0x1F, 0xA4, 0x46, 0x9B, 0x34, 0x09, 0x33, 0xE3, 0x0B, 0x66, 0xB7, 0xCC, 0x1F, 0xA9,
0x1F, 0xEE, 0xF6, 0x1D, 0x42, 0x55, 0xE6, 0x19, 0x44, 0x61, 0xBA, 0xAE, 0x57, 0xFC, 0x6D, 0x08,
0xFE, 0x6B, 0x84, 0x5C, 0x69, 0x50, 0xD0, 0xCC, 0xC3, 0xBC, 0x92, 0x7C, 0x33, 0x59, 0x4D, 0x2D,
0x50, 0x00, 0x47, 0xCE, 0x4C, 0xDB, 0x7A, 0xB0, 0x25, 0x61, 0x07, 0x55, 0x8A, 0xAD, 0x50, 0x0B,
0xD3, 0x2D, 0x6C, 0xC9, 0x39, 0x94, 0x82, 0x0F, 0x9B, 0xF9, 0x45, 0x95, 0x1C, 0xBA, 0xA5, 0xB9,
0xD2, 0x60, 0xE3, 0xE3, 0xC7, 0x34, 0xAA, 0x43, 0x27, 0xC7, 0xC2, 0x3D, 0xBD, 0x8A, 0xA6, 0x4B,
0xA9, 0x3F, 0xEF, 0xBB, 0x6B, 0xE4, 0x6B, 0x89, 0x2A, 0xE9, 0xD1, 0xC0, 0xE5, 0x3A, 0xED, 0x1A,
0x61, 0xF9, 0xB3, 0xCC, 0x03, 0x0F, 0x82, 0xCD, 0x74, 0x36, 0x2A, 0xD8, 0x3E, 0x4E, 0xE0, 0x17,
0x37, 0x1B, 0x41, 0xC2, 0xE8, 0xA7, 0x81, 0x7C, 0xD3, 0x02, 0xFD, 0x51, 0xB4, 0x02, 0x43, 0x9E,
} },
} },
{ "Tomodachi Ijou Koibito Miman", new Dictionary<string, byte[]> {
{ "koe", new byte[] {
0x15, 0xEE, 0x1F, 0x83, 0x32, 0x20, 0xF8, 0x17, 0x53, 0xE3, 0x7B, 0xC0, 0x6A, 0x75, 0x93, 0xA5,
0x79, 0x32, 0x36, 0x7A, 0x76, 0xC5, 0xF4, 0x06, 0xC5, 0x08, 0xF5, 0x1E, 0xE4, 0xD5, 0xED, 0x72,
@ -120,6 +180,7 @@ namespace GameRes.Formats.Mebius
0x4C, 0x2C, 0x58, 0x3B, 0xA9, 0x7A, 0x51, 0x5C, 0xFD, 0xA5, 0xCF, 0x67, 0xB8, 0x34, 0x85, 0x3D,
0x7D, 0x93, 0xE9, 0x7E, 0x9E, 0x6E, 0xC3, 0xB2, 0xB1, 0xD0, 0x5C, 0x83, 0x61, 0x6F, 0x27, 0x18,
} },
} },
};
}
}

View File

@ -123,7 +123,7 @@ namespace GameRes.Formats.Neko
static string[] s_known_dir_names = {
"image/actor", "image/back", "image/mask", "image/visual", "image/actor/big",
"image/face", "image/actor/b", "image/actor/bb", "image/actor/s", "image/actor/ss",
"sound/bgm", "sound/env", "sound/se", "voice", "script", "system", "count",
"sound/bgm", "sound/env", "sound/se", "sound/bgv", "voice", "script", "system", "count",
};
static Lazy<string[]> s_known_file_names = new Lazy<string[]> (ReadNekoPackLst);

103
ArcFormats/Psp/ArcQPK.cs Normal file
View File

@ -0,0 +1,103 @@
//! \file ArcQPK.cs
//! \date 2023 Sep 13
//! \brief PSP resource archive.
//
// Copyright (C) 2023 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.Compression;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Psp
{
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
{
public override string Tag => "QPK";
public override string Description => "PSP resource archive";
public override uint Signature => 0x4B5051; // 'QPK'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
var index_name = Path.ChangeExtension (file.Name, "QPI");
List<Entry> dir;
using (var index = VFS.OpenView (index_name))
{
if (!index.View.AsciiEqual (0, "QPI\0"))
return null;
int count = index.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
var base_name = Path.GetFileNameWithoutExtension (file.Name);
string ext = "";
string type = "";
if ("TGA" == base_name)
{
ext = ".tga";
type = "image";
}
dir = new List<Entry> (count);
uint index_pos = 0x1C;
for (int i = 0; i < count; ++i)
{
uint offset = index.View.ReadUInt32 (index_pos);
uint size = index.View.ReadUInt32 (index_pos+4);
if (offset > file.MaxOffset)
return null;
index_pos += 8;
if ((size & 0x80000000) != 0 || size == 0)
continue;
var entry = new PackedEntry {
Name = string.Format ("{0}#{1:D5}{2}", base_name, i, ext),
Type = type,
Offset = offset,
UnpackedSize = size & 0x3FFFFFFF,
IsPacked = (size & 0x40000000) != 0,
};
dir.Add (entry);
}
long last_offset = file.MaxOffset;
for (int i = dir.Count - 1; i >= 0; --i)
{
dir[i].Size = (uint)(last_offset - dir[i].Offset);
last_offset = dir[i].Offset;
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
if (!pent.IsPacked || !arc.File.View.AsciiEqual (pent.Offset, "CZL\0"))
return base.OpenEntry (arc, pent);
uint size = arc.File.View.ReadUInt32 (pent.Offset+4);
var input = arc.File.CreateStream (pent.Offset+12, size);
return new ZLibStream (input, CompressionMode.Decompress);
}
}
}

View File

@ -133,7 +133,7 @@ namespace GameRes.Formats.Jikkenshitsu
return new GUI.WidgetSJDAT (DefaultScheme.KnownSchemes.Keys);
}
byte[] QueryKey (string filename)
internal byte[] QueryKey (string filename)
{
var options = Query<SjOptions> (arcStrings.ArcImageEncrypted);
return options.Key;

View File

@ -28,6 +28,8 @@ using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [020412][Ciel] Maid Hunter Zero One ~Nora Maid~
namespace GameRes.Formats.Jikkenshitsu
{
internal class GrcMetaData : ImageMetaData
@ -38,6 +40,8 @@ namespace GameRes.Formats.Jikkenshitsu
public int DataLength;
public int AlphaOffset;
public int AlphaLength;
public bool IsEncrypted;
public byte[] Key;
}
[Export(typeof(ImageFormat))]
@ -47,15 +51,24 @@ namespace GameRes.Formats.Jikkenshitsu
public override string Description { get { return "Studio Jikkenshitsu image format"; } }
public override uint Signature { get { return 0x08; } }
public GrcFormat ()
{
Signatures = new[] { 0x08u, 0x8008u };
}
static readonly ResourceInstance<SpDatFormat> SpeedFormat = new ResourceInstance<SpDatFormat> ("DAT/SPEED");
byte[] DefaultKey = null;
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".grc"))
return null;
var header = file.ReadHeader (0x20);
int bpp = header.ToInt32 (0);
int bpp = header[0];
if (bpp != 8)
return null;
return new GrcMetaData {
var info = new GrcMetaData {
Width = header.ToUInt16 (4),
Height = header.ToUInt16 (6),
BPP = bpp,
@ -65,7 +78,16 @@ namespace GameRes.Formats.Jikkenshitsu
DataLength = header.ToInt32 (20),
AlphaOffset = header.ToInt32 (24),
AlphaLength = header.ToInt32 (28),
IsEncrypted = (header[1] & 0x80) != 0,
};
if (info.IsEncrypted)
{
DefaultKey = DefaultKey ?? SpeedFormat.Value.QueryKey (file.Name);
if (null == DefaultKey)
return null;
info.Key = DefaultKey;
}
return info;
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
@ -96,12 +118,25 @@ namespace GameRes.Formats.Jikkenshitsu
m_info = info;
m_stride = m_info.iWidth * m_info.BPP / 8;
m_output = new byte[m_stride * m_info.iHeight];
Format = PixelFormats.Indexed8;
}
public ImageData Unpack ()
{
if (m_info.IsEncrypted)
{
int packed_size = (int)(m_input.Length - 0x20);
m_input.Position = 0x20;
using (var enc = new InputProxyStream (m_input.AsStream, true))
using (var dec = new InputCryptoStream (enc, new SjTransform (m_info.Key)))
{
var data = new byte[m_input.Length];
dec.Read (data, 0x20, packed_size);
// memory stream is not disposed, not a big deal
m_input = new BinMemoryStream (data, m_input.Name);
}
}
m_input.Position = 0x20;
Format = PixelFormats.Indexed8;
if (8 == m_info.BPP)
Palette = ImageFormat.ReadPalette (m_input.AsStream);