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

View File

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

View File

@ -30,6 +30,8 @@ using System.Windows.Media.Imaging;
using System.Windows.Media; using System.Windows.Media;
using GameRes.Compression; using GameRes.Compression;
using GameRes.Utility; using GameRes.Utility;
using System.Collections.Generic;
using System.Linq;
namespace GameRes.Formats.CatSystem namespace GameRes.Formats.CatSystem
{ {
@ -285,9 +287,10 @@ namespace GameRes.Formats.CatSystem
byte[] UnpackJpeg () byte[] UnpackJpeg ()
{ {
Flipped = false; Flipped = false;
m_input.ReadInt32(); var toc = ReadSections();
m_input.Position = toc["img_jpg"] + 12;
var jpeg_size = m_input.ReadInt32(); var jpeg_size = m_input.ReadInt32();
long next_section = Source.Position + jpeg_size;
BitmapSource frame; BitmapSource frame;
using (var jpeg = new StreamRegion (Source, Source.Position, jpeg_size, true)) using (var jpeg = new StreamRegion (Source, Source.Position, jpeg_size, true))
{ {
@ -297,42 +300,71 @@ namespace GameRes.Formats.CatSystem
if (frame.Format.BitsPerPixel < 24) if (frame.Format.BitsPerPixel < 24)
throw new NotSupportedException ("Not supported HG-3 JPEG color depth"); throw new NotSupportedException ("Not supported HG-3 JPEG color depth");
int src_pixel_size = frame.Format.BitsPerPixel/8; int src_pixel_size = frame.Format.BitsPerPixel/8;
int stride = (int)m_info.Width * src_pixel_size; int stride = m_info.iWidth * src_pixel_size;
var pixels = new byte[stride*(int)m_info.Height]; var pixels = new byte[stride * m_info.iHeight];
frame.CopyPixels (pixels, stride, 0); 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 src = 0;
int dst = 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) for (uint i = 0; i < total; ++i)
{ {
output[dst++] = pixels[src+2]; output[dst++] = pixels[src+src_B];
output[dst++] = pixels[src+1]; output[dst++] = pixels[src+src_G];
output[dst++] = pixels[src]; output[dst++] = pixels[src+src_R];
output[dst++] = 0xFF; output[dst++] = alpha[src_A++];
src += src_pixel_size; src += src_pixel_size;
} }
return output;
}
m_input.Position = next_section; byte[] ReadAlpha (long start_pos)
var section_header = m_input.ReadBytes (8); {
if (!Binary.AsciiEqual (section_header, "img_al\0\0")) m_input.Position = start_pos + 0x10;
return output; int packed_size = m_input.ReadInt32();
m_input.Seek (8, SeekOrigin.Current);
int alpha_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)) 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);
int b = alpha.ReadByte(); return alpha_data;
if (-1 == b)
throw new EndOfStreamException();
output[i] = (byte)b;
}
return output;
} }
} }
Dictionary<string, long> ReadSections ()
{
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 () byte[] UnpackWebp ()
{ {
throw new NotImplementedException ("HG-3 WebP decoder not implemented."); throw new NotImplementedException ("HG-3 WebP decoder not implemented.");

View File

@ -29,11 +29,11 @@ namespace GameRes.Formats.??????
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat public class PakOpener : ArchiveFormat
{ {
public override string Tag { get => "ARC"; } public override string Tag => "ARC";
public override string Description { get => "?????? engine resource archive"; } public override string Description => "?????? engine resource archive";
public override uint Signature { get => 0; } public override uint Signature => 0;
public override bool IsHierarchic { get => false; } public override bool IsHierarchic => false;
public override bool CanWrite { get => false; } public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
@ -51,5 +51,10 @@ namespace GameRes.Formats.??????
} }
return new ArcFile (file, this, dir); 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))] [Export(typeof(AudioFormat))]
public class xxxAudio : AudioFormat public class xxxAudio : AudioFormat
{ {
public override string Tag { get => "xxx"; } public override string Tag => "xxx";
public override string Description { get => "?????? audio resource"; } public override string Description => "?????? audio resource";
public override uint Signature { get => 0; } public override uint Signature => 0;
public override bool CanWrite { get => false; } public override bool CanWrite => false;
public override SoundInput TryOpen (IBinaryStream file) public override SoundInput TryOpen (IBinaryStream file)
{ {

View File

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

View File

@ -64,7 +64,7 @@ namespace GameRes.Formats.Emote
public PsbOpener () public PsbOpener ()
{ {
Extensions = new string[] { "psb", "pimg", "dpak", "psbz" }; Extensions = new string[] { "psb", "pimg", "dpak", "psbz", "psp" };
} }
public override ArcFile TryOpen (ArcView file) 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) public override Stream OpenEntry (ArcFile arc, Entry entry)
{ {
var aent = (AgsiEntry)entry; var aent = entry as AgsiEntry;
var aarc = arc as AgsiArchive; var aarc = arc as AgsiArchive;
if (null == aent)
return base.OpenEntry (arc, entry);
Stream input; Stream input;
if (!aent.IsEncrypted) if (!aent.IsEncrypted)
input = arc.File.CreateStream (entry.Offset, entry.Size); input = arc.File.CreateStream (entry.Offset, entry.Size);
@ -202,16 +204,16 @@ namespace GameRes.Formats.FC01
ArcView m_file; ArcView m_file;
int m_count; int m_count;
int m_record_size; int m_record_size;
uint m_data_offset;
public bool IsEncrypted { get; set; } 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_file = file;
m_count = count; m_count = count;
m_record_size = record_size; 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; IsEncrypted = is_encrypted;
} }
@ -239,7 +241,7 @@ namespace GameRes.Formats.FC01
if (!ArchiveFormat.IsSaneCount (count) || record_size <= 0x10 || record_size > 0x100) if (!ArchiveFormat.IsSaneCount (count) || record_size <= 0x10 || record_size > 0x100)
return null; return null;
var reader = new IndexReader (file, count, record_size, is_encrypted); 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 null;
return reader; return reader;
} }
@ -247,29 +249,32 @@ namespace GameRes.Formats.FC01
public List<Entry> ReadIndex () public List<Entry> ReadIndex ()
{ {
using (var index = OpenIndex()) 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);
for (int i = 0; i < m_count; ++i)
{ {
int name_size = m_record_size - 0x10; var entry = new AgsiEntry();
var dir = new List<Entry> (m_count); entry.UnpackedSize = index.ReadUInt32();
for (int i = 0; i < m_count; ++i) entry.Size = index.ReadUInt32();
{ entry.Method = index.ReadInt32();
var entry = new AgsiEntry(); entry.Offset = index.ReadUInt32() + DataOffset;
entry.UnpackedSize = index.ReadUInt32(); if (!entry.CheckPlacement (m_file.MaxOffset))
entry.Size = index.ReadUInt32(); return null;
entry.Method = index.ReadInt32(); var name = index.ReadCString (name_size);
entry.Offset = index.ReadUInt32() + m_data_offset; if (string.IsNullOrEmpty (name))
if (!entry.CheckPlacement (m_file.MaxOffset)) return null;
return null; entry.Name = name;
var name = index.ReadCString (name_size); entry.Type = FormatCatalog.Instance.GetTypeFromName (name);
if (string.IsNullOrEmpty (name)) entry.IsPacked = entry.Method != 0 && entry.Method != 3;
return null; entry.IsSpecial = name.Equals ("Copyright.Dat", StringComparison.OrdinalIgnoreCase);
entry.Name = name; dir.Add (entry);
entry.Type = FormatCatalog.Instance.GetTypeFromName (name);
entry.IsPacked = entry.Method != 0 && entry.Method != 3;
entry.IsSpecial = name.Equals ("Copyright.Dat", StringComparison.OrdinalIgnoreCase);
dir.Add (entry);
}
return dir;
} }
return dir;
} }
IBinaryStream OpenIndex () 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; var pent = (PdImageEntry)entry;
byte[] baseline = null; byte[] baseline = null;
var dir = arc.Dir as List<Entry>; 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) for (int i = pent.Number-1; i >= 0; --i)
{ {
var base_entry = dir[i]; var base_entry = dir[i];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,9 +23,11 @@
// IN THE SOFTWARE. // IN THE SOFTWARE.
// //
using GameRes.Compression;
using GameRes.Utility; using GameRes.Utility;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Text; using System.Text;
@ -66,24 +68,53 @@ namespace GameRes.Formats.Macromedia
internal class DirectorFile internal class DirectorFile
{ {
List<DirectorEntry> m_dir;
Dictionary<int, DirectorEntry> m_index = new Dictionary<int, DirectorEntry>();
MemoryMap m_mmap = new MemoryMap(); MemoryMap m_mmap = new MemoryMap();
KeyTable m_keyTable = new KeyTable(); KeyTable m_keyTable = new KeyTable();
DirectorConfig m_config = new DirectorConfig(); DirectorConfig m_config = new DirectorConfig();
List<Cast> m_casts = new List<Cast>(); List<Cast> m_casts = new List<Cast>();
Dictionary<int, byte[]> m_ilsMap = new Dictionary<int, byte[]>();
public MemoryMap MMap => m_mmap; public bool IsAfterBurned { get; private set; }
public MemoryMap MMap => m_mmap;
public KeyTable KeyTable => m_keyTable; public KeyTable KeyTable => m_keyTable;
public DirectorConfig Config => m_config; public DirectorConfig Config => m_config;
public List<Cast> Casts => m_casts; 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) public bool Deserialize (SerializationContext context, Reader reader)
{ {
reader.Position = 8; reader.Position = 8;
string codec = reader.ReadFourCC(); string codec = reader.ReadFourCC();
if (codec != "MV93" && codec != "MC95") if (codec == "MV93" || codec == "MC95")
{
if (!ReadMMap (context, reader))
return false;
}
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 false;
return ReadMMap (context, reader) }
&& ReadKeyTable (context, reader) return ReadKeyTable (context, reader)
&& ReadConfig (context, reader) && ReadConfig (context, reader)
&& ReadCasts (context, reader); && ReadCasts (context, reader);
} }
@ -99,25 +130,129 @@ namespace GameRes.Formats.Macromedia
return false; return false;
reader.Position = mmap_pos + 8; reader.Position = mmap_pos + 8;
MMap.Deserialize (context, reader); 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; 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) bool ReadKeyTable (SerializationContext context, Reader reader)
{ {
var key_chunk = MMap.Find ("KEY*"); var key_chunk = Find ("KEY*");
if (null == key_chunk) if (null == key_chunk)
return false; return false;
reader.Position = key_chunk.Offset; reader = GetChunkReader (key_chunk, reader);
KeyTable.Deserialize (context, reader); KeyTable.Deserialize (context, reader);
return true; return true;
} }
bool ReadConfig (SerializationContext context, Reader reader) 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) if (null == config_chunk)
return false; return false;
reader.Position = config_chunk.Offset; reader = GetChunkReader (config_chunk, reader);
Config.Deserialize (context, reader); Config.Deserialize (context, reader);
context.Version = Config.Version; context.Version = Config.Version;
return true; return true;
@ -125,21 +260,23 @@ namespace GameRes.Formats.Macromedia
bool ReadCasts (SerializationContext context, Reader reader) bool ReadCasts (SerializationContext context, Reader reader)
{ {
Reader cas_reader;
if (context.Version > 1200) if (context.Version > 1200)
{ {
var mcsl = MMap.Find ("MCsL"); var mcsl = Find ("MCsL");
if (mcsl != null) if (mcsl != null)
{ {
reader.Position = mcsl.Offset; var mcsl_reader = GetChunkReader (mcsl, reader);
var cast_list = new CastList(); var cast_list = new CastList();
cast_list.Deserialize (context, reader); cast_list.Deserialize (context, mcsl_reader);
foreach (var entry in cast_list.Entries) foreach (var entry in cast_list.Entries)
{ {
var key_entry = KeyTable.FindByCast (entry.Id, "CAS*"); var key_entry = KeyTable.FindByCast (entry.Id, "CAS*");
if (key_entry != null) if (key_entry != null)
{ {
var mmap_entry = MMap[key_entry.Id]; var cas_entry = Index[key_entry.Id];
var cast = new Cast (context, reader, mmap_entry); cas_reader = GetChunkReader (cas_entry, reader);
var cast = new Cast (context, cas_reader, cas_entry);
if (!PopulateCast (cast, context, reader, entry)) if (!PopulateCast (cast, context, reader, entry))
return false; return false;
Casts.Add (cast); Casts.Add (cast);
@ -148,11 +285,12 @@ namespace GameRes.Formats.Macromedia
return true; return true;
} }
} }
var cas_chunk = MMap.Find ("CAS*"); var cas_chunk = Find ("CAS*");
if (null == cas_chunk) if (null == cas_chunk)
return false; return false;
var new_entry = new CastListEntry { Name = "internal", Id = 0x400, MinMember = Config.MinMember }; 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)) if (!PopulateCast (new_cast, context, reader, new_entry))
return false; return false;
Casts.Add (new_cast); Casts.Add (new_cast);
@ -162,30 +300,16 @@ namespace GameRes.Formats.Macromedia
public bool PopulateCast (Cast cast, SerializationContext context, Reader reader, CastListEntry entry) public bool PopulateCast (Cast cast, SerializationContext context, Reader reader, CastListEntry entry)
{ {
cast.Name = entry.Name; 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) for (int i = 0; i < cast.Index.Length; ++i)
{ {
int chunk_id = cast.Index[i]; int chunk_id = cast.Index[i];
if (chunk_id > 0) if (chunk_id > 0)
{ {
var chunk = MMap[chunk_id]; var chunk = this.Index[chunk_id];
var member = new CastMember(); var member = new CastMember();
member.Id = chunk_id; member.Id = chunk_id;
reader.Position = chunk.Offset; var cast_reader = GetChunkReader (chunk, reader);
member.Deserialize (context, reader); member.Deserialize (context, cast_reader);
cast.Members[member.Id] = member; cast.Members[member.Id] = member;
} }
} }
@ -293,11 +417,10 @@ namespace GameRes.Formats.Macromedia
public string Name; public string Name;
public Dictionary<int, CastMember> Members = new Dictionary<int, CastMember>(); 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); int count = (int)(entry.Size / 4);
Index = new int[count]; Index = new int[count];
reader.Position = entry.Offset;
Deserialize (context, reader); Deserialize (context, reader);
} }
@ -391,54 +514,6 @@ namespace GameRes.Formats.Macromedia
public int Id; 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 internal class DirectorConfig
{ {
public short Length; public short Length;
@ -548,11 +623,9 @@ namespace GameRes.Formats.Macromedia
public int ChunkCountMax; public int ChunkCountMax;
public int ChunkCountUsed; public int ChunkCountUsed;
public int FreeHead; 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 DirectorEntry this[int index] => Dir[index];
public MemoryMapEntry Find (string four_cc) => Dir.Find (e => e.FourCC == four_cc);
public void Deserialize (SerializationContext context, Reader reader) 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 int Id;
public string FourCC; public string FourCC;
}
internal class MemoryMapEntry : DirectorEntry
{
public ushort Flags; public ushort Flags;
public MemoryMapEntry (int id = 0) public MemoryMapEntry (int id = 0)
@ -596,10 +673,28 @@ namespace GameRes.Formats.Macromedia
public void Deserialize (SerializationContext context, Reader reader) public void Deserialize (SerializationContext context, Reader reader)
{ {
FourCC = reader.ReadFourCC(); FourCC = reader.ReadFourCC();
Size = reader.ReadU32(); Size = reader.ReadU32();
Offset = reader.ReadU32() + 8; Offset = reader.ReadU32() + 8;
Flags = reader.ReadU16(); 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); SetByteOrder (e);
} }
public Stream Source { get => m_input; } public Stream Source => m_input;
public ByteOrder ByteOrder { get; private set; } public ByteOrder ByteOrder { get; private set; }
@ -701,6 +796,32 @@ namespace GameRes.Formats.Macromedia
return buffer; 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) public Reader CloneUnless (ByteOrder order)
{ {
if (this.ByteOrder != order) if (this.ByteOrder != order)

View File

@ -1,8 +1,8 @@
//! \file ImageBITD.cs //! \file ImageBITD.cs
//! \date Fri Jun 26 07:45:01 2015 //! \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 // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to // of this software and associated documentation files (the "Software"), to
@ -34,174 +34,9 @@ using GameRes.Utility;
namespace GameRes.Formats.Macromedia namespace GameRes.Formats.Macromedia
{ {
[Export(typeof(ImageFormat))] internal class BitdMetaData : ImageMetaData
public class BitdFormat : ImageFormat
{ {
public override string Tag { get { return "BITD"; } } public byte DepthType;
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];
}
}
}
} }
internal class BitdDecoder : IImageDecoder internal class BitdDecoder : IImageDecoder
@ -216,13 +51,13 @@ namespace GameRes.Formats.Macromedia
BitmapPalette m_palette; BitmapPalette m_palette;
public Stream Source => m_input; public Stream Source => m_input;
public ImageFormat SourceFormat => null; public ImageFormat SourceFormat { get; private set; }
public ImageMetaData Info => m_info; public ImageMetaData Info => m_info;
public ImageData Image => m_image ?? (m_image = GetImageData()); public ImageData Image => m_image ?? (m_image = GetImageData());
public PixelFormat Format { get; private set; } public PixelFormat Format { get; private set; }
public byte[] AlphaChannel { get; 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_input = input;
m_info = info; m_info = info;
@ -235,23 +70,55 @@ namespace GameRes.Formats.Macromedia
: info.BPP == 4 ? PixelFormats.Indexed4 : info.BPP == 4 ? PixelFormats.Indexed4
: info.BPP == 8 ? PixelFormats.Indexed8 : info.BPP == 8 ? PixelFormats.Indexed8
: info.BPP == 16 ? PixelFormats.Bgr555 : 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; 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 () protected ImageData GetImageData ()
{ {
BitmapSource bitmap = null;
m_input.Position = 0; 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(); Unpack8bpp();
else else
UnpackChannels (Info.BPP / 8); m_input.Read (m_output, 0, m_output.Length);
if (AlphaChannel != null) 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 = new FormatConvertedBitmap (bitmap, PixelFormats.Bgr32, null, 0); 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_stride = bitmap.PixelWidth * 4;
m_output = new byte[bitmap.PixelHeight * m_stride]; m_output = new byte[bitmap.PixelHeight * m_stride];
bitmap.CopyPixels (m_output, m_stride, 0); bitmap.CopyPixels (m_output, m_stride, 0);
@ -324,7 +191,8 @@ namespace GameRes.Formats.Macromedia
{ {
int b = m_input.ReadByte(); int b = m_input.ReadByte();
if (-1 == b) 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; int count = b;
if (b > 0x7f) if (b > 0x7f)
count = (byte)-(sbyte)b; count = (byte)-(sbyte)b;

View File

@ -32,7 +32,7 @@ using GameRes.Utility;
namespace GameRes.Formats.Mebius namespace GameRes.Formats.Mebius
{ {
[Export(typeof(AudioFormat))] [Export(typeof(AudioFormat))]
[ExportMetadata("Priority", -1)] [ExportMetadata("Priority", 1)]
public class KoeAudio : AudioFormat public class KoeAudio : AudioFormat
{ {
public override string Tag { get { return "KOE/MEBIUS"; } } public override string Tag { get { return "KOE/MEBIUS"; } }
@ -65,8 +65,68 @@ namespace GameRes.Formats.Mebius
return new WaveInput (data); return new WaveInput (data);
} }
static readonly Dictionary<string, byte[]> DefaultKeys = new Dictionary<string, byte[]> { Dictionary<string, byte[]> DefaultKeys => SchemeMap["Mebinya!"];
{ "koe", new byte[] {
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, 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, 0x79, 0x32, 0x36, 0x7A, 0x76, 0xC5, 0xF4, 0x06, 0xC5, 0x08, 0xF5, 0x1E, 0xE4, 0xD5, 0xED, 0x72,
0x0B, 0xEC, 0x2A, 0x52, 0x6D, 0x87, 0xC3, 0x55, 0xD9, 0xC0, 0x07, 0x7A, 0x5E, 0x84, 0x35, 0x38, 0x0B, 0xEC, 0x2A, 0x52, 0x6D, 0x87, 0xC3, 0x55, 0xD9, 0xC0, 0x07, 0x7A, 0x5E, 0x84, 0x35, 0x38,
@ -83,8 +143,8 @@ namespace GameRes.Formats.Mebius
0x5A, 0xAD, 0xE8, 0xFB, 0x78, 0x8B, 0x76, 0xD2, 0x86, 0x7B, 0x79, 0x0B, 0x96, 0xC4, 0x51, 0x04, 0x5A, 0xAD, 0xE8, 0xFB, 0x78, 0x8B, 0x76, 0xD2, 0x86, 0x7B, 0x79, 0x0B, 0x96, 0xC4, 0x51, 0x04,
0x43, 0x30, 0x20, 0x3F, 0x19, 0x19, 0x88, 0xE3, 0x27, 0x10, 0x65, 0xFE, 0xC8, 0x4A, 0x11, 0x67, 0x43, 0x30, 0x20, 0x3F, 0x19, 0x19, 0x88, 0xE3, 0x27, 0x10, 0x65, 0xFE, 0xC8, 0x4A, 0x11, 0x67,
0x01, 0x55, 0x46, 0xEE, 0x80, 0x68, 0xC9, 0xC1, 0x1B, 0x4C, 0x49, 0x14, 0xC9, 0x95, 0xA9, 0x7F, 0x01, 0x55, 0x46, 0xEE, 0x80, 0x68, 0xC9, 0xC1, 0x1B, 0x4C, 0x49, 0x14, 0xC9, 0x95, 0xA9, 0x7F,
} }, } },
{ "mse", new byte[] { { "mse", new byte[] {
0x06, 0xDE, 0xEF, 0x76, 0xD2, 0xDA, 0xE7, 0x95, 0x7A, 0x87, 0x6D, 0x7C, 0xF6, 0x17, 0x44, 0x9F, 0x06, 0xDE, 0xEF, 0x76, 0xD2, 0xDA, 0xE7, 0x95, 0x7A, 0x87, 0x6D, 0x7C, 0xF6, 0x17, 0x44, 0x9F,
0x08, 0xD2, 0xC5, 0x89, 0xDC, 0xDE, 0xA1, 0x0F, 0x2D, 0xCB, 0xCA, 0xB8, 0x6E, 0xBB, 0x7F, 0x8A, 0x08, 0xD2, 0xC5, 0x89, 0xDC, 0xDE, 0xA1, 0x0F, 0x2D, 0xCB, 0xCA, 0xB8, 0x6E, 0xBB, 0x7F, 0x8A,
0x9E, 0x63, 0x70, 0x58, 0xCC, 0xA8, 0x61, 0x34, 0x68, 0x98, 0xD8, 0xB3, 0x74, 0x18, 0x2C, 0x9B, 0x9E, 0x63, 0x70, 0x58, 0xCC, 0xA8, 0x61, 0x34, 0x68, 0x98, 0xD8, 0xB3, 0x74, 0x18, 0x2C, 0x9B,
@ -101,8 +161,8 @@ namespace GameRes.Formats.Mebius
0x7B, 0x65, 0x21, 0x24, 0x42, 0x5C, 0x37, 0x4F, 0x64, 0x45, 0x58, 0x0C, 0xBC, 0xC1, 0xB7, 0xAD, 0x7B, 0x65, 0x21, 0x24, 0x42, 0x5C, 0x37, 0x4F, 0x64, 0x45, 0x58, 0x0C, 0xBC, 0xC1, 0xB7, 0xAD,
0xC7, 0xB6, 0xE3, 0x21, 0xBB, 0xC8, 0xD2, 0x15, 0x1F, 0xF1, 0x39, 0x3F, 0x87, 0x86, 0x88, 0xBE, 0xC7, 0xB6, 0xE3, 0x21, 0xBB, 0xC8, 0xD2, 0x15, 0x1F, 0xF1, 0x39, 0x3F, 0x87, 0x86, 0x88, 0xBE,
0x84, 0xD7, 0x1A, 0x63, 0xD5, 0x51, 0x63, 0xDB, 0x74, 0x39, 0x4C, 0x12, 0x12, 0xF1, 0x6E, 0x2C, 0x84, 0xD7, 0x1A, 0x63, 0xD5, 0x51, 0x63, 0xDB, 0x74, 0x39, 0x4C, 0x12, 0x12, 0xF1, 0x6E, 0x2C,
} }, } },
{ "bgm", new byte[] { { "bgm", new byte[] {
0xB0, 0x6F, 0xA4, 0xD7, 0x8B, 0x81, 0xBD, 0xF3, 0x82, 0xAF, 0x95, 0x6B, 0x9D, 0x3E, 0x88, 0x73, 0xB0, 0x6F, 0xA4, 0xD7, 0x8B, 0x81, 0xBD, 0xF3, 0x82, 0xAF, 0x95, 0x6B, 0x9D, 0x3E, 0x88, 0x73,
0xB8, 0xF9, 0xD8, 0x09, 0x31, 0xF3, 0x84, 0xDA, 0xCC, 0xAF, 0x54, 0x60, 0xFD, 0x97, 0x04, 0xA6, 0xB8, 0xF9, 0xD8, 0x09, 0x31, 0xF3, 0x84, 0xDA, 0xCC, 0xAF, 0x54, 0x60, 0xFD, 0x97, 0x04, 0xA6,
0x05, 0x65, 0x20, 0x9A, 0xA7, 0x62, 0xD9, 0xD7, 0x5C, 0x98, 0x6F, 0x2D, 0x3A, 0x6E, 0x07, 0xF8, 0x05, 0x65, 0x20, 0x9A, 0xA7, 0x62, 0xD9, 0xD7, 0x5C, 0x98, 0x6F, 0x2D, 0x3A, 0x6E, 0x07, 0xF8,
@ -119,6 +179,7 @@ namespace GameRes.Formats.Mebius
0x03, 0x7A, 0x14, 0x10, 0x32, 0x7B, 0xF1, 0x33, 0xDE, 0xBA, 0x52, 0x74, 0xC7, 0x6E, 0xF8, 0x7E, 0x03, 0x7A, 0x14, 0x10, 0x32, 0x7B, 0xF1, 0x33, 0xDE, 0xBA, 0x52, 0x74, 0xC7, 0x6E, 0xF8, 0x7E,
0x4C, 0x2C, 0x58, 0x3B, 0xA9, 0x7A, 0x51, 0x5C, 0xFD, 0xA5, 0xCF, 0x67, 0xB8, 0x34, 0x85, 0x3D, 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, 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 = { static string[] s_known_dir_names = {
"image/actor", "image/back", "image/mask", "image/visual", "image/actor/big", "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", "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); 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); return new GUI.WidgetSJDAT (DefaultScheme.KnownSchemes.Keys);
} }
byte[] QueryKey (string filename) internal byte[] QueryKey (string filename)
{ {
var options = Query<SjOptions> (arcStrings.ArcImageEncrypted); var options = Query<SjOptions> (arcStrings.ArcImageEncrypted);
return options.Key; return options.Key;

View File

@ -28,6 +28,8 @@ using System.IO;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
// [020412][Ciel] Maid Hunter Zero One ~Nora Maid~
namespace GameRes.Formats.Jikkenshitsu namespace GameRes.Formats.Jikkenshitsu
{ {
internal class GrcMetaData : ImageMetaData internal class GrcMetaData : ImageMetaData
@ -38,6 +40,8 @@ namespace GameRes.Formats.Jikkenshitsu
public int DataLength; public int DataLength;
public int AlphaOffset; public int AlphaOffset;
public int AlphaLength; public int AlphaLength;
public bool IsEncrypted;
public byte[] Key;
} }
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
@ -47,15 +51,24 @@ namespace GameRes.Formats.Jikkenshitsu
public override string Description { get { return "Studio Jikkenshitsu image format"; } } public override string Description { get { return "Studio Jikkenshitsu image format"; } }
public override uint Signature { get { return 0x08; } } 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) public override ImageMetaData ReadMetaData (IBinaryStream file)
{ {
if (!file.Name.HasExtension (".grc")) if (!file.Name.HasExtension (".grc"))
return null; return null;
var header = file.ReadHeader (0x20); var header = file.ReadHeader (0x20);
int bpp = header.ToInt32 (0); int bpp = header[0];
if (bpp != 8) if (bpp != 8)
return null; return null;
return new GrcMetaData { var info = new GrcMetaData {
Width = header.ToUInt16 (4), Width = header.ToUInt16 (4),
Height = header.ToUInt16 (6), Height = header.ToUInt16 (6),
BPP = bpp, BPP = bpp,
@ -65,7 +78,16 @@ namespace GameRes.Formats.Jikkenshitsu
DataLength = header.ToInt32 (20), DataLength = header.ToInt32 (20),
AlphaOffset = header.ToInt32 (24), AlphaOffset = header.ToInt32 (24),
AlphaLength = header.ToInt32 (28), 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) public override ImageData Read (IBinaryStream file, ImageMetaData info)
@ -96,12 +118,25 @@ namespace GameRes.Formats.Jikkenshitsu
m_info = info; m_info = info;
m_stride = m_info.iWidth * m_info.BPP / 8; m_stride = m_info.iWidth * m_info.BPP / 8;
m_output = new byte[m_stride * m_info.iHeight]; m_output = new byte[m_stride * m_info.iHeight];
Format = PixelFormats.Indexed8;
} }
public ImageData Unpack () 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; m_input.Position = 0x20;
Format = PixelFormats.Indexed8;
if (8 == m_info.BPP) if (8 == m_info.BPP)
Palette = ImageFormat.ReadPalette (m_input.AsStream); Palette = ImageFormat.ReadPalette (m_input.AsStream);