bunch of stuff.

This commit is contained in:
morkt 2023-08-24 01:33:50 +04:00
parent ea096c52ef
commit 77fde27d26
119 changed files with 11078 additions and 619 deletions

View File

@ -0,0 +1,255 @@
//! \file ArcAIR.cs
//! \date 2022 Jun 10
//! \brief AIRNovel engine encrypted resources.
//
// Copyright (C) 2022 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;
using System.Text;
using System.Xml;
using GameRes.Cryptography;
using GameRes.Formats.PkWare;
using SharpZip = ICSharpCode.SharpZipLib.Zip;
namespace GameRes.Formats.AirNovel
{
internal class AirEntry : ZipEntry
{
public bool IsEncrypted { get; set; }
public AirEntry (SharpZip.ZipEntry zip_entry) : base (zip_entry)
{
IsEncrypted = Name.EndsWith ("_");
if (IsEncrypted)
{
Name = Name.Substring (0, Name.Length-1);
Type = FormatCatalog.Instance.GetTypeFromName (Name);
}
}
}
internal class AirArchive : PkZipArchive
{
public byte[] Key;
public uint CoderLength;
public AirArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, SharpZip.ZipFile native, byte[] key) : base (arc, impl, dir, native)
{
Key = key;
}
}
[Serializable]
public class AirNovelScheme : ResourceScheme
{
public IDictionary<string, string> KnownKeys;
}
[Export(typeof(ArchiveFormat))]
public class AirOpener : ArchiveFormat
{
public override string Tag { get { return "AIR"; } }
public override string Description { get { return "Adobe AIR resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
static readonly ResourceInstance<ArchiveFormat> Zip = new ResourceInstance<ArchiveFormat> ("ZIP");
public override ArcFile TryOpen (ArcView file)
{
if (!file.Name.HasExtension (".air"))
return null;
var input = file.CreateStream();
SharpZip.ZipFile zip = null;
try
{
SharpZip.ZipStrings.CodePage = Encoding.UTF8.CodePage;
zip = new SharpZip.ZipFile (input);
var files = zip.Cast<SharpZip.ZipEntry>().Where (z => !z.IsDirectory);
bool has_encrypted = false;
var dir = new List<Entry>();
foreach (var f in files)
{
var entry = new AirEntry (f);
has_encrypted |= entry.IsEncrypted;
dir.Add (entry);
}
if (has_encrypted)
{
uint coder_length;
if (FindAirNovelCoderLength (zip, out coder_length))
{
var key = QueryEncryptionKey (file);
if (!string.IsNullOrEmpty (key))
{
var rc4_key = AirRc4Crypt.GenerateKey (key);
return new AirArchive (file, this, dir, zip, rc4_key) { CoderLength = coder_length };
}
}
}
return new PkZipArchive (file, Zip.Value, dir, zip);
}
catch
{
if (zip != null)
zip.Close();
input.Dispose();
throw;
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var zarc = (AirArchive)arc;
var zent = (AirEntry)entry;
var input = zarc.Native.GetInputStream (zent.NativeEntry);
if (!zent.IsEncrypted)
return input;
var data = new byte[zent.UnpackedSize];
using (input)
input.Read (data, 0, data.Length);
int enc_len;
if (zent.Name.HasExtension (".an"))
enc_len = data.Length;
else
enc_len = Math.Min (data.Length, (int)zarc.CoderLength);
var rc4 = new AirRc4Crypt (zarc.Key);
rc4.Decrypt (data, 0, enc_len);
return new BinMemoryStream (data, entry.Name);
}
bool FindAirNovelCoderLength (SharpZip.ZipFile zip, out uint coder_length)
{
coder_length = 0;
var config = zip.GetEntry ("config.anprj");
if (null == config)
return false;
using (var input = zip.GetInputStream (config))
{
var coder = FindConfigNode (input, "/config/coder[@len]");
if (null == coder)
return false;
var lenAttr = coder.Attributes["len"].Value;
var styles = NumberStyles.Integer;
if (lenAttr.StartsWith ("0x"))
{
lenAttr = lenAttr.Substring (2, lenAttr.Length-2);
styles = NumberStyles.HexNumber;
}
return UInt32.TryParse (lenAttr, styles, CultureInfo.InvariantCulture, out coder_length);
}
}
XmlNode FindConfigNode (Stream input, string xpath)
{
using (var reader = new StreamReader (input))
{
var xml = new XmlDocument();
xml.Load (reader);
return xml.DocumentElement.SelectSingleNode (xpath);
}
}
AirNovelScheme DefaultScheme = new AirNovelScheme { KnownKeys = new Dictionary<string, string>() };
internal IDictionary<string, string> KnownKeys { get { return DefaultScheme.KnownKeys; } }
public override ResourceScheme Scheme
{
get { return DefaultScheme; }
set { DefaultScheme = (AirNovelScheme)value; }
}
string QueryEncryptionKey (ArcView file)
{
var title = FormatCatalog.Instance.LookupGame (file.Name);
if (string.IsNullOrEmpty (title))
return null;
string key;
if (!KnownKeys.TryGetValue (title, out key))
return null;
return key;
}
}
internal class AirRc4Crypt
{
const int KeyLength = 0xFF;
byte[] KeyState = new byte[KeyLength+1];
public AirRc4Crypt (byte[] key)
{
for (int i = 0; i <= KeyLength; ++i)
{
KeyState[i] = (byte)i;
}
int j = 0;
for (int i = 0; i <= KeyLength; ++i)
{
j = (j + KeyState[i] + key[i]) & KeyLength;
KeyState[i] ^= KeyState[j];
KeyState[j] ^= KeyState[i];
KeyState[i] ^= KeyState[j];
}
}
public static byte[] GenerateKey (string passPhrase)
{
if (string.IsNullOrEmpty (passPhrase))
throw new ArgumentException ("passPhrase");
var key = new byte[KeyLength+1];
for (int i = 0; i < key.Length; ++i)
{
key[i] = (byte)passPhrase[i % passPhrase.Length];
}
return key;
}
public void Decrypt (byte[] data, int pos, int length)
{
int i = 0;
int j = 0;
var keyCopy = KeyState.Clone() as byte[];
int last = Math.Min (pos + length, data.Length);
while (pos < last)
{
i = (i + 1) & KeyLength;
j = (j + keyCopy[i]) & KeyLength;
// i was wondering why standard RC4 encryption class doesn't work here
// well, this swap fails when i == j lol
keyCopy[i] ^= keyCopy[j];
keyCopy[j] ^= keyCopy[i];
keyCopy[i] ^= keyCopy[j];
int k = (keyCopy[i] + keyCopy[j]) & KeyLength;
data[pos++] ^= keyCopy[k];
}
}
}
}

View File

@ -78,4 +78,9 @@ namespace GameRes.Formats.Abel
[ExportMetadata("Extension", "ALP")] [ExportMetadata("Extension", "ALP")]
[ExportMetadata("Target", "DAT/GENERIC")] [ExportMetadata("Target", "DAT/GENERIC")]
public class AlpFormat : ResourceAlias { } public class AlpFormat : ResourceAlias { }
[Export(typeof(ResourceAlias))]
[ExportMetadata("Extension", "JCP")]
[ExportMetadata("Target", "WAV")]
public class JcpFormat : ResourceAlias { }
} }

View File

@ -24,7 +24,6 @@
// //
using System; using System;
using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using GameRes.Compression; using GameRes.Compression;
@ -50,14 +49,16 @@ namespace GameRes.Formats.Abel
public GpsFormat () public GpsFormat ()
{ {
Extensions = new string[] { "gps", "cmp" }; Extensions = new[] { "gps", "gp2", "cmp" };
Signatures = new[] { 0x535047u, 0x325047u }; // 'GPS', 'GP2'
} }
public override ImageMetaData ReadMetaData (IBinaryStream file) public override ImageMetaData ReadMetaData (IBinaryStream file)
{ {
var header = file.ReadHeader (0x29); var header = file.ReadHeader (0x29);
bool is_gp2 = header.AsciiEqual ("GP2");
var gps = new GpsMetaData(); var gps = new GpsMetaData();
if (header.ToUInt32 (4) == 0xCCCCCCCC) if (!is_gp2 && header.ToUInt32 (4) == 0xCCCCCCCC)
{ {
gps.HeaderSize = 0x19; gps.HeaderSize = 0x19;
gps.Compression = 2; gps.Compression = 2;
@ -69,6 +70,8 @@ namespace GameRes.Formats.Abel
gps.HeaderSize = 0x29; gps.HeaderSize = 0x29;
gps.Compression = header[0x10]; gps.Compression = header[0x10];
gps.UnpackedSize = header.ToInt32 (0x11); gps.UnpackedSize = header.ToInt32 (0x11);
if (is_gp2)
gps.UnpackedSize = - 1 - gps.UnpackedSize;
gps.PackedSize = header.ToInt32 (0x15); gps.PackedSize = header.ToInt32 (0x15);
gps.Width = header.ToUInt32 (0x19); gps.Width = header.ToUInt32 (0x19);
gps.Height = header.ToUInt32 (0x1D); gps.Height = header.ToUInt32 (0x1D);

View File

@ -53,6 +53,7 @@ namespace GameRes.Formats.Abogado
{ "PCM1", "ADP" }, { "PCM1", "ADP" },
{ "PCM2", "ADP" }, { "PCM2", "ADP" },
{ "PCM", "ADP" }, { "PCM", "ADP" },
{ "ADPCM", "ADP" },
{ "GRAPHIC", "KG" }, { "GRAPHIC", "KG" },
{ "GRPFILE", "KG" }, { "GRPFILE", "KG" },
{ "EFCFILE", "ADP" }, { "EFCFILE", "ADP" },

118
ArcFormats/Adobe/ArcAIR.cs Normal file
View File

@ -0,0 +1,118 @@
//! \file ArcAIR.cs
//! \date 2023 Aug 22
//! \brief Adobe AIR 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.Utility;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.IO.Compression;
using System.Text;
namespace GameRes.Formats.Adobe
{
[Export(typeof(ArchiveFormat))]
public class DatOpener : ArchiveFormat
{
public override string Tag { get => "DAT/AIR"; }
public override string Description { get => "Adobe AIR resource archive"; }
public override uint Signature { get => 0; }
public override bool IsHierarchic { get => false; }
public override bool CanWrite { get => false; }
public override ArcFile TryOpen (ArcView file)
{
uint index_pos = Binary.BigEndian (file.View.ReadUInt32 (0));
if (index_pos >= file.MaxOffset || 0 == index_pos || file.MaxOffset > 0x40000000)
return null;
uint index_size = (uint)(file.MaxOffset - index_pos);
if (index_size > 0x100000) // arbitrary max size for compressed index
return null;
using (var input = file.CreateStream (index_pos, index_size))
using (var unpacked = new DeflateStream (input, CompressionMode.Decompress))
using (var index = new BinaryStream (unpacked, file.Name))
{
if (0x0A != index.ReadUInt8() ||
0x0B != index.ReadUInt8() ||
0x01 != index.ReadUInt8())
return null;
var name_buffer = new byte[0x80];
var dir = new List<Entry>();
while (index.PeekByte() != -1)
{
int length = index.ReadUInt8();
if (0 == (length & 1))
return null;
length >>= 1;
if (0 == length)
break;
index.Read (name_buffer, 0, length);
var name = Encoding.UTF8.GetString (name_buffer, 0, length);
if (0x09 != index.ReadUInt8() ||
0x05 != index.ReadUInt8() ||
0x01 != index.ReadUInt8())
return null;
if (0x04 != index.ReadUInt8()) // invalid number signature
return null;
uint offset = ReadInteger (index);
if (0x04 != index.ReadUInt8())
return null;
uint size = ReadInteger (index);
var entry = Create<PackedEntry> (name);
entry.Offset = offset;
entry.Size = size;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
if (0 == entry.Size)
return Stream.Null;
var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new DeflateStream (input, CompressionMode.Decompress);
}
internal static uint ReadInteger (IBinaryStream input)
{
uint u = input.ReadUInt8();
if (u < 0x80)
return u;
u = (u & 0x7F) << 7;
uint b = input.ReadUInt8();
if (b < 0x80)
return u | b;
u = (u | b & 0x7F) << 7;
b = input.ReadUInt8();
if (b < 0x80)
return u | b;
u = (u | b & 0x7F) << 8;
return u | input.ReadUInt8();
}
}
}

View File

@ -27,6 +27,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Text;
using GameRes.Compression; using GameRes.Compression;
namespace GameRes.Formats.AliceSoft namespace GameRes.Formats.AliceSoft
@ -40,6 +41,16 @@ namespace GameRes.Formats.AliceSoft
public override bool IsHierarchic { get { return true; } } public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
public AfaOpener ()
{
ContainedFormats = new[] { "QNT", "AJP", "DCF", "OGG" };
Settings = new[] { AfaEncoding };
}
internal readonly EncodingSetting AfaEncoding = new EncodingSetting ("AFAEncodingCP", "DefaultEncoding");
internal Encoding NameEncoding { get { return AfaEncoding.Get<Encoding>(); } }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
if (!file.View.AsciiEqual (8, "AlicArch")) if (!file.View.AsciiEqual (8, "AlicArch"))
@ -54,6 +65,7 @@ namespace GameRes.Formats.AliceSoft
if (!IsSaneCount (count)) if (!IsSaneCount (count))
return null; return null;
var default_enc = NameEncoding;
var dir = new List<Entry> (count); var dir = new List<Entry> (count);
var name_buf = new byte[0x40]; var name_buf = new byte[0x40];
using (var input = file.CreateStream (0x2C, packed_size)) using (var input = file.CreateStream (0x2C, packed_size))
@ -70,7 +82,7 @@ namespace GameRes.Formats.AliceSoft
name_buf = new byte[index_step]; name_buf = new byte[index_step];
if (index_step != index.Read (name_buf, 0, index_step)) if (index_step != index.Read (name_buf, 0, index_step))
return null; return null;
var name = Encodings.cp932.GetString (name_buf, 0, name_length); var name = default_enc.GetString (name_buf, 0, name_length);
var entry = FormatCatalog.Instance.Create<Entry> (name); var entry = FormatCatalog.Instance.Create<Entry> (name);
index.ReadInt32(); index.ReadInt32();
index.ReadInt32(); index.ReadInt32();

View File

@ -2,7 +2,7 @@
//! \date Fri Jul 29 14:07:15 2016 //! \date Fri Jul 29 14:07:15 2016
//! \brief AliceSoft incremental image format. //! \brief AliceSoft incremental image format.
// //
// Copyright (C) 2016-2018 by morkt // Copyright (C) 2016-2022 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
@ -27,6 +27,7 @@ using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text;
using System.Windows.Media; using System.Windows.Media;
using GameRes.Compression; using GameRes.Compression;
using GameRes.Utility; using GameRes.Utility;
@ -37,6 +38,15 @@ namespace GameRes.Formats.AliceSoft
{ {
public string BaseName; public string BaseName;
public long DataOffset; public long DataOffset;
public bool IsPcf;
}
internal interface IBaseImageReader
{
int BPP { get; }
byte[] Data { get; }
void Unpack ();
} }
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
@ -46,17 +56,25 @@ namespace GameRes.Formats.AliceSoft
public override string Description { get { return "AliceSoft System incremental image"; } } public override string Description { get { return "AliceSoft System incremental image"; } }
public override uint Signature { get { return 0x20666364; } } // 'dcf ' public override uint Signature { get { return 0x20666364; } } // 'dcf '
public DcfFormat ()
{
Extensions = new[] { "dcf", "pcf" };
Signatures = new[] { 0x20666364u, 0x20666370u };
}
static readonly ResourceInstance<AfaOpener> Afa = new ResourceInstance<AfaOpener> ("AFA");
public override ImageMetaData ReadMetaData (IBinaryStream stream) public override ImageMetaData ReadMetaData (IBinaryStream stream)
{ {
stream.Seek (4, SeekOrigin.Current); var header = stream.ReadHeader (0x1C);
uint header_size = stream.ReadUInt32(); uint header_size = header.ToUInt32 (4);
long data_pos = stream.Position + header_size; long data_pos = 8 + header_size;
if (stream.ReadInt32() != 1) if (header.ToInt32 (8) != 1)
return null; return null;
uint width = stream.ReadUInt32(); uint width = header.ToUInt32 (0x0C);
uint height = stream.ReadUInt32(); uint height = header.ToUInt32 (0x10);
int bpp = stream.ReadInt32(); int bpp = header.ToInt32 (0x14);
int name_length = stream.ReadInt32(); int name_length = header.ToInt32 (0x18);
if (name_length <= 0) if (name_length <= 0)
return null; return null;
int shift = (name_length % 7) + 1; int shift = (name_length % 7) + 1;
@ -70,18 +88,17 @@ namespace GameRes.Formats.AliceSoft
Width = width, Width = width,
Height = height, Height = height,
BPP = bpp, BPP = bpp,
BaseName = Encodings.cp932.GetString (name_bits), BaseName = Afa.Value.NameEncoding.GetString (name_bits),
DataOffset = data_pos, DataOffset = data_pos,
IsPcf = stream.Signature == 0x20666370u,
}; };
} }
public override ImageData Read (IBinaryStream stream, ImageMetaData info) public override ImageData Read (IBinaryStream stream, ImageMetaData info)
{ {
using (var reader = new DcfReader (stream, (DcfMetaData)info)) var reader = new DcfReader (stream, (DcfMetaData)info);
{ reader.Unpack();
reader.Unpack(); return ImageData.Create (reader.Info, reader.Format, null, reader.Data, reader.Stride);
return ImageData.Create (info, reader.Format, null, reader.Data, reader.Stride);
}
} }
public override void Write (Stream file, ImageData image) public override void Write (Stream file, ImageData image)
@ -90,7 +107,7 @@ namespace GameRes.Formats.AliceSoft
} }
} }
internal sealed class DcfReader : IDisposable internal sealed class DcfReader : IBaseImageReader
{ {
IBinaryStream m_input; IBinaryStream m_input;
DcfMetaData m_info; DcfMetaData m_info;
@ -100,10 +117,14 @@ namespace GameRes.Formats.AliceSoft
int m_overlay_bpp; int m_overlay_bpp;
int m_base_bpp; int m_base_bpp;
static readonly Lazy<ImageFormat> s_QntFormat = new Lazy<ImageFormat> (() => ImageFormat.FindByTag ("QNT")); static readonly ResourceInstance<ImageFormat> s_QntFormat = new ResourceInstance<ImageFormat> ("QNT");
static readonly ResourceInstance<ImageFormat> s_DcfFormat = new ResourceInstance<ImageFormat> ("DCF");
internal ImageFormat Qnt { get { return s_QntFormat.Value; } } internal ImageFormat Qnt { get { return s_QntFormat.Value; } }
internal ImageFormat Dcf { get { return s_DcfFormat.Value; } }
public int BPP { get { return m_base_bpp; } }
public ImageMetaData Info { get; private set; }
public byte[] Data { get { return m_output; } } public byte[] Data { get { return m_output; } }
public PixelFormat Format { get; private set; } public PixelFormat Format { get; private set; }
public int Stride { get; private set; } public int Stride { get; private set; }
@ -112,10 +133,13 @@ namespace GameRes.Formats.AliceSoft
{ {
m_input = input; m_input = input;
m_info = info; m_info = info;
Info = info;
} }
public void Unpack () public void Unpack ()
{ {
int pt_x = 0;
int pt_y = 0;
long next_pos = m_info.DataOffset; long next_pos = m_info.DataOffset;
for (;;) for (;;)
{ {
@ -131,7 +155,12 @@ namespace GameRes.Formats.AliceSoft
using (var input = new ZLibStream (m_input.AsStream, CompressionMode.Decompress, true)) using (var input = new ZLibStream (m_input.AsStream, CompressionMode.Decompress, true))
input.Read (m_mask, 0, unpacked_size); input.Read (m_mask, 0, unpacked_size);
} }
else if (0x64676364 == id) // 'dcgd' else if (0x6C647470 == id) // 'ptdl'
{
pt_x = m_input.ReadInt32();
pt_y = m_input.ReadInt32();
}
else if (0x64676364 == id || 0x64676370 == id) // 'dcgd' || 'pcgd'
break; break;
} }
long qnt_pos = m_input.Position; long qnt_pos = m_input.Position;
@ -147,18 +176,28 @@ namespace GameRes.Formats.AliceSoft
var overlay = new QntFormat.Reader (reg, qnt_info); var overlay = new QntFormat.Reader (reg, qnt_info);
overlay.Unpack(); overlay.Unpack();
m_overlay_bpp = overlay.BPP; m_overlay_bpp = overlay.BPP;
if (m_mask != null) if (m_mask != null || m_info.IsPcf)
ReadBaseImage(); ReadBaseImage();
if (m_base != null) if (m_info.IsPcf)
{ {
m_output = ApplyOverlay (overlay.Data); if (null == m_base)
SetFormat ((int)m_info.Width, m_overlay_bpp); SetEmptyBase();
qnt_info.OffsetX = pt_x;
qnt_info.OffsetY = pt_y;
BlendOverlay (qnt_info, overlay.Data);
m_output = m_base;
SetFormat (m_info.iWidth, m_base_bpp);
}
else if (m_base != null)
{
m_output = MaskOverlay (overlay.Data);
SetFormat (m_info.iWidth, m_overlay_bpp);
} }
else else
{ {
m_output = overlay.Data; m_output = overlay.Data;
SetFormat ((int)qnt_info.Width, m_overlay_bpp); SetFormat (qnt_info.iWidth, m_overlay_bpp);
} }
} }
} }
@ -169,14 +208,20 @@ namespace GameRes.Formats.AliceSoft
Stride = width * (bpp / 8); Stride = width * (bpp / 8);
} }
byte[] ApplyOverlay (byte[] overlay) void SetEmptyBase ()
{ {
int blocks_x = (int)m_info.Width / 0x10; m_base_bpp = 32;
int blocks_y = (int)m_info.Height / 0x10; m_base = new byte[m_info.Width * m_info.Height * 4];
}
byte[] MaskOverlay (byte[] overlay)
{
int blocks_x = m_info.iWidth / 0x10;
int blocks_y = m_info.iHeight / 0x10;
int base_step = m_base_bpp / 8; int base_step = m_base_bpp / 8;
int overlay_step = m_overlay_bpp / 8; int overlay_step = m_overlay_bpp / 8;
int base_stride = (int)m_info.Width * base_step; int base_stride = m_info.iWidth * base_step;
int overlay_stride = (int)m_info.Width * overlay_step; int overlay_stride = m_info.iWidth * overlay_step;
int mask_pos = 4; int mask_pos = 4;
for (int y = 0; y < blocks_y; ++y) for (int y = 0; y < blocks_y; ++y)
{ {
@ -208,6 +253,58 @@ namespace GameRes.Formats.AliceSoft
return overlay; return overlay;
} }
void BlendOverlay (ImageMetaData overlay_info, byte[] overlay)
{
int ovl_x = overlay_info.OffsetX;
int ovl_y = overlay_info.OffsetY;
int ovl_width = overlay_info.iWidth;
int ovl_height = overlay_info.iHeight;
int base_width = m_info.iWidth;
int base_height = m_info.iHeight;
if (checked(ovl_x + ovl_width) > base_width)
ovl_width = base_width - ovl_x;
if (checked(ovl_y + ovl_height) > base_height)
ovl_height = base_height - ovl_y;
if (ovl_height <= 0 || ovl_width <= 0)
return;
int dst_stride = m_info.iWidth * 4;
int src_stride = overlay_info.iWidth * 4;
int dst = ovl_y * dst_stride + ovl_x * 4;
int src = 0;
int gap = dst_stride - src_stride;
for (int y = 0; y < overlay_info.iHeight; ++y)
{
for (int x = 0; x < overlay_info.iWidth; ++x)
{
byte src_alpha = overlay[src+3];
if (src_alpha != 0)
{
if (0xFF == src_alpha || 0 == m_base[dst+3])
{
m_base[dst] = overlay[src];
m_base[dst+1] = overlay[src+1];
m_base[dst+2] = overlay[src+2];
m_base[dst+3] = src_alpha;
}
else
{
m_base[dst+0] = (byte)((overlay[src+0] * src_alpha
+ m_base[dst+0] * (0xFF - src_alpha)) / 0xFF);
m_base[dst+1] = (byte)((overlay[src+1] * src_alpha
+ m_base[dst+1] * (0xFF - src_alpha)) / 0xFF);
m_base[dst+2] = (byte)((overlay[src+2] * src_alpha
+ m_base[dst+2] * (0xFF - src_alpha)) / 0xFF);
m_base[dst+3] = (byte)Math.Max (src_alpha, m_base[dst+3]);
}
}
dst += 4;
src += 4;
}
dst += gap;
}
}
void ReadBaseImage () void ReadBaseImage ()
{ {
try try
@ -215,13 +312,29 @@ namespace GameRes.Formats.AliceSoft
string dir_name = VFS.GetDirectoryName (m_info.FileName); string dir_name = VFS.GetDirectoryName (m_info.FileName);
string base_name = Path.ChangeExtension (m_info.BaseName, "qnt"); string base_name = Path.ChangeExtension (m_info.BaseName, "qnt");
base_name = VFS.CombinePath (dir_name, base_name); base_name = VFS.CombinePath (dir_name, base_name);
ImageFormat base_format = null;
Func<IBinaryStream, ImageMetaData, IBaseImageReader> create_reader;
if (VFS.FileExists (base_name))
{
base_format = Qnt;
create_reader = (s, m) => new QntFormat.Reader (s.AsStream, (QntMetaData)m);
}
else
{
base_name = Path.ChangeExtension (m_info.BaseName, "pcf");
if (VFS.IsPathEqualsToFileName (m_info.FileName, base_name))
return;
base_name = VFS.CombinePath (dir_name, base_name);
base_format = Dcf;
create_reader = (s, m) => new DcfReader (s, (DcfMetaData)m);
}
using (var base_file = VFS.OpenBinaryStream (base_name)) using (var base_file = VFS.OpenBinaryStream (base_name))
{ {
var base_info = Qnt.ReadMetaData (base_file) as QntMetaData; var base_info = base_format.ReadMetaData (base_file);
if (null != base_info && m_info.Width == base_info.Width && m_info.Height == base_info.Height) if (null != base_info && m_info.Width == base_info.Width && m_info.Height == base_info.Height)
{ {
base_info.FileName = base_name; base_info.FileName = base_name;
var reader = new QntFormat.Reader (base_file.AsStream, base_info); var reader = create_reader (base_file, base_info);
reader.Unpack(); reader.Unpack();
m_base_bpp = reader.BPP; m_base_bpp = reader.BPP;
m_base = reader.Data; m_base = reader.Data;
@ -233,11 +346,5 @@ namespace GameRes.Formats.AliceSoft
Trace.WriteLine (X.Message, "[DCF]"); Trace.WriteLine (X.Message, "[DCF]");
} }
} }
#region IDisposable Members
public void Dispose ()
{
}
#endregion
} }
} }

View File

@ -97,7 +97,7 @@ namespace GameRes.Formats.AliceSoft
return ImageData.Create (info, format, null, reader.Data, stride); return ImageData.Create (info, format, null, reader.Data, stride);
} }
internal class Reader internal class Reader : IBaseImageReader
{ {
byte[] m_input; byte[] m_input;
byte[] m_alpha; byte[] m_alpha;

View File

@ -339,7 +339,7 @@ namespace GameRes.Formats.Amaterasu
} }
[Export(typeof(ScriptFormat))] [Export(typeof(ScriptFormat))]
public class ScrFormat : ScriptFormat public class ScrFormat : GenericScriptFormat
{ {
public override string Tag { get { return "SCR/AMI"; } } public override string Tag { get { return "SCR/AMI"; } }
public override string Description { get { return Strings.arcStrings.SCRDescription; } } public override string Description { get { return Strings.arcStrings.SCRDescription; } }

View File

@ -83,126 +83,33 @@ namespace GameRes.Formats.Apricot
dir.Add (entry); dir.Add (entry);
} }
} }
return new Mpf2Archive (file, this, dir, arc_list); var parts = new List<ArcView> (arc_list.Count);
try
{
foreach (var arc_entry in arc_list)
{
var arc_file = VFS.OpenView (arc_entry);
parts.Add (arc_file);
}
}
catch
{
foreach (var part in parts)
part.Dispose();
throw;
}
return new MultiFileArchive (file, this, dir, parts);
} }
} }
public override Stream OpenEntry (ArcFile arc, Entry entry) public override Stream OpenEntry (ArcFile arc, Entry entry)
{ {
var mpf = (Mpf2Archive)arc; var mpf = (MultiFileArchive)arc;
var input = mpf.ViewChain.CreateStream (entry.Offset, entry.Size); var input = mpf.OpenStream (entry);
var pent = entry as PackedEntry; var pent = entry as PackedEntry;
if (pent != null && pent.IsPacked) if (pent != null && pent.IsPacked)
input = new ZLibStream (input, CompressionMode.Decompress); input = new ZLibStream (input, CompressionMode.Decompress);
return input; return input;
} }
} }
internal class Mpf2Archive : ArcFile
{
ArcViewChain m_parts;
public ArcViewChain ViewChain { get { return m_parts; } }
public Mpf2Archive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, IReadOnlyList<Entry> arc_list)
: base (arc, impl, dir)
{
m_parts = new ArcViewChain (arc, arc_list);
}
bool m_disposed = false;
protected override void Dispose (bool disposing)
{
if (disposing && !m_disposed)
{
if (m_parts != null)
m_parts.Dispose();
m_disposed = true;
}
}
}
internal sealed class ArcViewChain : IDisposable
{
ArcView m_file;
IReadOnlyList<ArcView> m_parts;
public IEnumerable<ArcView> Parts {
get {
yield return m_file;
if (m_parts != null)
foreach (var part in m_parts)
yield return part;
}
}
public ArcViewChain (ArcView file, IReadOnlyList<Entry> arc_list)
{
var parts = new List<ArcView> (arc_list.Count);
try
{
foreach (var arc_entry in arc_list)
{
var arc_file = VFS.OpenView (arc_entry);
parts.Add (arc_file);
}
}
catch
{
foreach (var part in parts)
part.Dispose();
throw;
}
m_file = file;
m_parts = parts;
}
public Stream CreateStream (long offset, uint size)
{
Stream input = null;
try
{
long part_offset = 0;
long entry_start = offset;
long entry_end = offset + size;
foreach (var part in Parts)
{
long part_end_offset = part_offset + part.MaxOffset;
if (entry_start < part_end_offset)
{
uint part_size = (uint)Math.Min (entry_end - entry_start, part_end_offset - entry_start);
var entry_part = part.CreateStream (entry_start - part_offset, part_size);
if (input != null)
input = new ConcatStream (input, entry_part);
else
input = entry_part;
entry_start += part_size;
if (entry_start >= entry_end)
break;
}
part_offset = part_end_offset;
}
return input ?? Stream.Null;
}
catch
{
if (input != null)
input.Dispose();
throw;
}
}
bool m_disposed = false;
public void Dispose ()
{
if (m_disposed)
return;
if (m_parts != null)
{
foreach (var arc in m_parts)
arc.Dispose();
}
m_disposed = true;
}
}
} }

View File

@ -350,4 +350,10 @@ namespace GameRes.Formats
public override string Description { get { return "Unidentified data file"; } } public override string Description { get { return "Unidentified data file"; } }
public override uint Signature { get { return 0; } } public override uint Signature { get { return 0; } }
} }
// [970725][Guilty] Onii-chan e
[Export(typeof(ResourceAlias))]
[ExportMetadata("Extension", "GLT")]
[ExportMetadata("Target", "BMP")]
public class GltFormat : ResourceAlias { }
} }

View File

@ -98,15 +98,22 @@
<Compile Include="Abogado\ImageKG.cs" /> <Compile Include="Abogado\ImageKG.cs" />
<Compile Include="Actgs\ArcCG.cs" /> <Compile Include="Actgs\ArcCG.cs" />
<Compile Include="Actgs\ArcDAT.cs" /> <Compile Include="Actgs\ArcDAT.cs" />
<Compile Include="Adobe\ArcAIR.cs" />
<Compile Include="Ads\ArcPAC.cs" /> <Compile Include="Ads\ArcPAC.cs" />
<Compile Include="AdvDx\ArcPKD.cs" /> <Compile Include="AdvDx\ArcPKD.cs" />
<Compile Include="AdvScripter\ArcPAK.cs" /> <Compile Include="AdvScripter\ArcPAK.cs" />
<Compile Include="AdvSys\ArcAdvSys3.cs" /> <Compile Include="AdvSys\ArcAdvSys3.cs" />
<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="BlueGale\ImageBBM.cs" /> <Compile Include="BlueGale\ImageBBM.cs" />
<Compile Include="ClickTeam\ArcMF.cs" />
<Compile Include="DxLib\ArcDX8.cs" />
<Compile Include="Hypatia\ArcLPK.cs" /> <Compile Include="Hypatia\ArcLPK.cs" />
<Compile Include="Hypatia\ImageLSG.cs" /> <Compile Include="Hypatia\ImageLSG.cs" />
<Compile Include="Illusion\ArcPP.cs" />
<Compile Include="Interheart\ImageBMP.cs" />
<Compile Include="Interheart\ImageHMP.cs" />
<Compile Include="JamCreation\ArcDAT.cs" /> <Compile Include="JamCreation\ArcDAT.cs" />
<Compile Include="JamCreation\ImageDPO.cs" /> <Compile Include="JamCreation\ImageDPO.cs" />
<Compile Include="AliceSoft\ArcAAR.cs" /> <Compile Include="AliceSoft\ArcAAR.cs" />
@ -130,6 +137,12 @@
<Compile Include="Apricot\ArcDAT.cs" /> <Compile Include="Apricot\ArcDAT.cs" />
<Compile Include="ArcARCX.cs" /> <Compile Include="ArcARCX.cs" />
<Compile Include="Artemis\ArcMJA.cs" /> <Compile Include="Artemis\ArcMJA.cs" />
<Compile Include="Macromedia\ArcDXR.cs" />
<Compile Include="Macromedia\DirectorFile.cs" />
<Compile Include="Macromedia\Palettes.cs" />
<Compile Include="NScripter\Script.cs" />
<Compile Include="TechGian\ArcBIN.cs" />
<Compile Include="Zyx\ImageXMG.cs" />
<None Include="AudioAIFF.cs" /> <None Include="AudioAIFF.cs" />
<Compile Include="Basil\ArcMIF.cs" /> <Compile Include="Basil\ArcMIF.cs" />
<Compile Include="Basil\AudioWHC.cs" /> <Compile Include="Basil\AudioWHC.cs" />
@ -251,6 +264,9 @@
<Compile Include="Mnp\ArcMMA.cs" /> <Compile Include="Mnp\ArcMMA.cs" />
<Compile Include="MultiFileArchive.cs" /> <Compile Include="MultiFileArchive.cs" />
<Compile Include="Musica\ArcANI.cs" /> <Compile Include="Musica\ArcANI.cs" />
<Compile Include="MyAdv\ArcPAC.cs" />
<Compile Include="Nekopack\ArcNEKO2.cs" />
<Compile Include="Nekopack\ArcNEKO3.cs" />
<Compile Include="NitroPlus\ArcLAY.cs" /> <Compile Include="NitroPlus\ArcLAY.cs" />
<Compile Include="NitroPlus\ArcMPK.cs" /> <Compile Include="NitroPlus\ArcMPK.cs" />
<Compile Include="NitroPlus\ArcNPP.cs" /> <Compile Include="NitroPlus\ArcNPP.cs" />
@ -335,6 +351,8 @@
<Compile Include="Software House Parsley\ArcPAC.cs" /> <Compile Include="Software House Parsley\ArcPAC.cs" />
<Compile Include="Software House Parsley\ArcUCG.cs" /> <Compile Include="Software House Parsley\ArcUCG.cs" />
<Compile Include="Software House Parsley\ImagePCG.cs" /> <Compile Include="Software House Parsley\ImagePCG.cs" />
<Compile Include="Sohfu\ArcSKA.cs" />
<Compile Include="Sohfu\ImageDTL.cs" />
<Compile Include="Speed\ArcARC.cs" /> <Compile Include="Speed\ArcARC.cs" />
<Compile Include="Stack\ArcGPK.cs" /> <Compile Include="Stack\ArcGPK.cs" />
<Compile Include="Strikes\ArcPCK.cs" /> <Compile Include="Strikes\ArcPCK.cs" />
@ -744,7 +762,7 @@
<Compile Include="MangaGamer\ArcMGPK.cs" /> <Compile Include="MangaGamer\ArcMGPK.cs" />
<Compile Include="MnoViolet\ArcMnoViolet.cs" /> <Compile Include="MnoViolet\ArcMnoViolet.cs" />
<Compile Include="FC01\ArcMRG.cs" /> <Compile Include="FC01\ArcMRG.cs" />
<Compile Include="ArcNEKO.cs" /> <Compile Include="Nekopack\ArcNEKO.cs" />
<Compile Include="Nexas\ArcPAC.cs" /> <Compile Include="Nexas\ArcPAC.cs" />
<Compile Include="NitroPlus\ArcNitro.cs" /> <Compile Include="NitroPlus\ArcNitro.cs" />
<Compile Include="Entis\ArcNOA.cs" /> <Compile Include="Entis\ArcNOA.cs" />
@ -824,6 +842,7 @@
<Compile Include="Will\ArcPNA.cs" /> <Compile Include="Will\ArcPNA.cs" />
<Compile Include="Will\ArcPulltop.cs" /> <Compile Include="Will\ArcPulltop.cs" />
<Compile Include="Will\ArcWIP.cs" /> <Compile Include="Will\ArcWIP.cs" />
<Compile Include="Winters\ArcCFP.cs" />
<Compile Include="Winters\ArcDAT.cs" /> <Compile Include="Winters\ArcDAT.cs" />
<Compile Include="Winters\ArcIFP.cs" /> <Compile Include="Winters\ArcIFP.cs" />
<Compile Include="Winters\ArcIFX.cs" /> <Compile Include="Winters\ArcIFX.cs" />
@ -832,6 +851,7 @@
<Compile Include="Xuse\ArcWAG.cs" /> <Compile Include="Xuse\ArcWAG.cs" />
<Compile Include="Xuse\ArcXARC.cs" /> <Compile Include="Xuse\ArcXARC.cs" />
<Compile Include="Xuse\ArcXuse.cs" /> <Compile Include="Xuse\ArcXuse.cs" />
<Compile Include="Xuse\ImageP.cs" />
<Compile Include="YaneSDK\ArcDAT.cs" /> <Compile Include="YaneSDK\ArcDAT.cs" />
<Compile Include="YaneSDK\ArcHibiki.cs" /> <Compile Include="YaneSDK\ArcHibiki.cs" />
<Compile Include="Yatagarasu\ArcPKG.cs" /> <Compile Include="Yatagarasu\ArcPKG.cs" />

View File

@ -27,11 +27,13 @@ using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using GameRes.Compression; using GameRes.Compression;
using GameRes.Formats.Maika;
using GameRes.Utility;
namespace GameRes.Formats.BellDa namespace GameRes.Formats.BellDa
{ {
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
public sealed class BldOpener : ArchiveFormat public sealed class BldOpener : Mk2Opener
{ {
public override string Tag { get { return "DAT/BLD"; } } public override string Tag { get { return "DAT/BLD"; } }
public override string Description { get { return "BELL-DA resource archive"; } } public override string Description { get { return "BELL-DA resource archive"; } }
@ -39,6 +41,13 @@ namespace GameRes.Formats.BellDa
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
public BldOpener ()
{
Signatures = new[] { this.Signature };
Settings = null;
Scheme = null;
}
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
var version_str = file.View.ReadString (4, 4).TrimEnd ('\x1A'); var version_str = file.View.ReadString (4, 4).TrimEnd ('\x1A');
@ -66,26 +75,5 @@ namespace GameRes.Formats.BellDa
} }
return new ArcFile (file, this, dir); return new ArcFile (file, this, dir);
} }
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
// XXX compression method identical to Maika.Mk2Opener
var pent = (PackedEntry)entry;
if (null == pent || !pent.IsPacked)
{
var id_str = arc.File.View.ReadString (entry.Offset, 2);
if (id_str != "B1" && id_str != "C1" && id_str != "D1" && id_str != "E1")
return base.OpenEntry (arc, entry);
uint packed_size = arc.File.View.ReadUInt32 (entry.Offset+2);
if (packed_size != entry.Size - 10)
return base.OpenEntry (arc, entry);
pent.Size = packed_size;
pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset+6);
pent.Offset += 10;
pent.IsPacked = true;
}
var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new LzssStream (input);
}
} }
} }

View File

@ -136,14 +136,18 @@ namespace GameRes.Formats.Cadath
if (0 != (bits[i >> 3] & 1)) if (0 != (bits[i >> 3] & 1))
{ {
byte v = m_input.ReadUInt8(); byte v = m_input.ReadUInt8();
int count = m_input.ReadUInt8() + 3; int count = Math.Min (m_input.ReadUInt8() + 3, data.Length - dst);
for (int j = 0; j < count; ++j) for (int j = 0; j < count; ++j)
{
data[dst++] = v; data[dst++] = v;
}
} }
else else
{ {
data[dst++] = m_input.ReadUInt8(); data[dst++] = m_input.ReadUInt8();
} }
if (dst >= data.Length)
break;
bits[i >> 3] >>= 1; bits[i >> 3] >>= 1;
} }
CopyChannels (data); CopyChannels (data);

View File

@ -0,0 +1,100 @@
//! \file ArcMF.cs
//! \date 2023 Aug 09
//! \brief Multimedia Fusion 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.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Text;
namespace GameRes.Formats.ClickTeam
{
[Export(typeof(ArchiveFormat))]
public class MfOpener : ArchiveFormat
{
public override string Tag { get { return "MFS"; } }
public override string Description { get { return "Multimedia Fusion resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
public MfOpener ()
{
Signatures = new uint[] { 0x77777777, 0 }; // 'wwww'
Extensions = new string[] { "" };
}
static readonly byte[] s_mfs_header = {
(byte)'w', (byte)'w', (byte)'w', (byte)'w', 0x49, 0x87, 0x47, 0x12,
};
public override ArcFile TryOpen (ArcView file)
{
long base_offset = 0;
if (0x5A4D == file.View.ReadUInt16 (0)) // 'MZ'
{
var exe = new ExeFile (file);
base_offset = exe.Overlay.Offset;
}
if (base_offset + 0x20 > file.MaxOffset || !file.View.BytesEqual (base_offset, s_mfs_header))
return null;
int count = file.View.ReadInt32 (base_offset + 0x1C);
if (!IsSaneCount (count))
return null;
long index_pos = base_offset + 0x20;
var dir = new List<Entry> (count);
var name_buffer = new byte[520];
for (int i = 0; i < count; ++i)
{
int name_length = file.View.ReadUInt16 (index_pos) * 2;
if (name_length > name_buffer.Length)
return null;
file.View.Read (index_pos + 2, name_buffer, 0, (uint)name_length);
var name = Encoding.Unicode.GetString (name_buffer, 0, name_length);
index_pos += 2 + name_length;
var entry = Create<PackedEntry> (name);
entry.IsPacked = name != "mmfs2.dll";
entry.Size = file.View.ReadUInt32 (index_pos + 4);
entry.Offset = index_pos + 8;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_pos = entry.Offset + entry.Size;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
Stream input = arc.File.CreateStream (entry.Offset, entry.Size);
if (pent.IsPacked)
input = new ZLibStream (input, CompressionMode.Decompress);
return input;
}
}
}

View File

@ -28,6 +28,7 @@ using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using GameRes.Formats.Cmvs;
using GameRes.Utility; using GameRes.Utility;
namespace GameRes.Formats.Purple namespace GameRes.Formats.Purple
@ -133,18 +134,38 @@ namespace GameRes.Formats.Purple
var index_copy = new CowArray<byte> (index, 0, file_table_size).ToArray(); var index_copy = new CowArray<byte> (index, 0, file_table_size).ToArray();
var cmvs_md5 = cpz.CmvsMd5.Clone() as uint[]; var cmvs_md5 = cpz.CmvsMd5.Clone() as uint[];
foreach (var scheme in KnownSchemes.Values.Where (s => s.Version == cpz.Version)) var scheme = CreateCpz5Scheme(Md5Variant.Mirai);
// foreach (var scheme in KnownSchemes.Values.Where (s => s.Version == cpz.Version))
{ {
var arc = ReadIndex (file, scheme, cpz, index, arc_key); var arc = ReadIndex (file, scheme, cpz, index, arc_key);
if (null != arc) if (null != arc)
return arc; return arc;
// both CmvsMd5 and index was altered by ReadIndex in decryption attempt // both CmvsMd5 and index was altered by ReadIndex in decryption attempt
Array.Copy (cmvs_md5, cpz.CmvsMd5, 4); Array.Copy (cmvs_md5, cpz.CmvsMd5, 4);
Array.Copy (index, index_copy, file_table_size); Array.Copy (index_copy, index, file_table_size);
} }
throw new UnknownEncryptionScheme(); throw new UnknownEncryptionScheme();
} }
static uint[] DefaultCpz5Secret = {
0xCD90F089, 0xE982B782, 0xA282AB88, 0xCD82718E, 0x52838A83, 0xA882AA82, 0x7592648E, 0xB582AB82,
0xE182BF82, 0xDC82A282, 0x4281B782, 0xED82F48E, 0xBF82EA82, 0xA282E182, 0xB782DC82, 0x6081E682,
0xC6824181, 0xA482A282, 0xE082A982, 0xF48EA482, 0xBF82C182, 0xA282E182, 0xB582DC82, 0xF481BD82,
};
static CmvsScheme CreateCpz5Scheme(Md5Variant md5)
{
return new CmvsScheme
{
Version = 5,
Cpz5Secret = DefaultCpz5Secret,
Md5Variant = md5,
DecoderFactor = 0x1A743125,
EntryInitKey = 0x2547A39E,
EntryTailKey = 0xBC,
};
}
internal ArcFile ReadIndex (ArcView file, CmvsScheme scheme, CpzHeader cpz, byte[] index, ArchiveKey arc_key) internal ArcFile ReadIndex (ArcView file, CmvsScheme scheme, CpzHeader cpz, byte[] index, ArchiveKey arc_key)
{ {
var cmvs_md5 = Cmvs.MD5.Create (scheme.Md5Variant); var cmvs_md5 = Cmvs.MD5.Create (scheme.Md5Variant);

View File

@ -27,7 +27,7 @@ using GameRes.Utility;
namespace GameRes.Formats.Cmvs namespace GameRes.Formats.Cmvs
{ {
public enum Md5Variant { A, B, Chrono, Memoria, Natsu, Aoi } public enum Md5Variant { A, B, Chrono, Memoria, Natsu, Aoi, Mirai }
public abstract class MD5 : Cryptography.MD5Base public abstract class MD5 : Cryptography.MD5Base
{ {
@ -46,6 +46,7 @@ namespace GameRes.Formats.Cmvs
case Md5Variant.Memoria: return new Md5Memoria(); case Md5Variant.Memoria: return new Md5Memoria();
case Md5Variant.Natsu: return new Md5Natsu(); case Md5Variant.Natsu: return new Md5Natsu();
case Md5Variant.Aoi: return new Md5Aoi(); case Md5Variant.Aoi: return new Md5Aoi();
case Md5Variant.Mirai: return new Md5Mirai();
default: throw new System.ArgumentException ("Unknown MD5 variant", "variant"); default: throw new System.ArgumentException ("Unknown MD5 variant", "variant");
} }
} }
@ -153,6 +154,25 @@ namespace GameRes.Formats.Cmvs
} }
} }
public class Md5Mirai : MD5
{
protected override void InitState ()
{
m_state[0] = 0x67452301;
m_state[1] = 0xEFCDAB89;
m_state[2] = 0x98BADCFE;
m_state[3] = 0x10325476;
}
protected override void SetResult (uint[] data)
{
data[0] = m_state[0];
data[1] = m_state[1];
data[2] = m_state[2];
data[3] = m_state[3];
}
}
public class Md5Aoi : MD5 public class Md5Aoi : MD5
{ {
protected override void InitState () protected override void InitState ()

View File

@ -1,4 +1,4 @@
// Copyright (C) 2022 by morkt // Copyright (C) 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
@ -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 { return "ARC"; } } public override string Tag { get => "ARC"; }
public override string Description { get { return "?????? engine resource archive"; } } public override string Description { get => "?????? engine resource archive"; }
public override uint Signature { get { return 0; } } public override uint Signature { get => 0; }
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get => false; }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get => false; }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {

View File

@ -1,4 +1,4 @@
// Copyright (C) 2022 by morkt // Copyright (C) 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
@ -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 { return "xxx"; } } public override string Tag { get => "xxx"; }
public override string Description { get { return "?????? audio resource"; } } public override string Description { get => "?????? audio resource"; }
public override uint Signature { get { return 0; } } public override uint Signature { get => 0; }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get => false; }
public override SoundInput TryOpen (IBinaryStream file) public override SoundInput TryOpen (IBinaryStream file)
{ {

View File

@ -1,4 +1,4 @@
// Copyright (C) 2022 by morkt // Copyright (C) 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
@ -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 { return "xxx"; } } public override string Tag { get => "xxx"; }
public override string Description { get { return "?????? image format"; } } public override string Description { get => "?????? image format"; }
public override uint Signature { get { return 0; } } public override uint Signature { get => 0; }
public override ImageMetaData ReadMetaData (IBinaryStream file) public override ImageMetaData ReadMetaData (IBinaryStream file)
{ {

View File

@ -0,0 +1,70 @@
//! \file ArcDX8.cs
//! \date 2022 Jun 05
//! \brief DxLib archive version 8.
//
// Copyright (C) 2022 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.DxLib
{
[Export(typeof(ArchiveFormat))]
public class Dx8Opener : DxOpener
{
public override string Tag { get { return "BIN/DXLIB"; } }
public override string Description { get { return "DxLib archive version 8"; } }
public override uint Signature { get { return 0x00085844; } }
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
public Dx8Opener ()
{
Extensions = new[] { "bin" };
Signatures = new[] { 0x00085844u };
}
static readonly byte[] DefaultKey = new byte[] { 0xBE, 0xC8, 0x8A, 0xF5, 0x28, 0x50, 0xC9 };
public override ArcFile TryOpen (ArcView file)
{
var dx = new DxHeader {
IndexSize = file.View.ReadUInt32 (4),
BaseOffset = file.View.ReadInt64 (8),
IndexOffset = file.View.ReadInt64 (0x10),
FileTable = (uint)file.View.ReadInt64 (0x18),
DirTable = (uint)file.View.ReadInt64 (0x20),
CodePage = file.View.ReadInt32 (0x28),
};
if (dx.DirTable >= dx.IndexSize || dx.FileTable >= dx.IndexSize)
return null;
var key = DefaultKey;
var index = file.View.ReadBytes (dx.IndexOffset, dx.IndexSize);
Decrypt (index, 0, index.Length, 0, key);
// decrypt-2
// decompress
return null;
}
}
}

View File

@ -148,7 +148,7 @@ namespace GameRes.Formats.Emote
ImageFormat TlgFormat { get { return s_TlgFormat.Value; } } ImageFormat TlgFormat { get { return s_TlgFormat.Value; } }
static Lazy<ImageFormat> s_TlgFormat = new Lazy<ImageFormat> (() => ImageFormat.FindByTag ("TLG")); static ResourceInstance<ImageFormat> s_TlgFormat = new ResourceInstance<ImageFormat> ("TLG");
} }
/// <summary> /// <summary>

View File

@ -56,6 +56,7 @@ namespace GameRes.Formats.Entis
float[] m_ptrMatrixBuf; float[] m_ptrMatrixBuf;
float[] m_ptrIQParamBuf; float[] m_ptrIQParamBuf;
byte[] m_ptrIQParamTable; byte[] m_ptrIQParamTable;
// float[] m_ptrBuffer2;
sbyte[] m_ptrBlockLineBuf; sbyte[] m_ptrBlockLineBuf;
sbyte[] m_ptrNextBlockBuf; sbyte[] m_ptrNextBlockBuf;
@ -769,6 +770,7 @@ namespace GameRes.Formats.Entis
{ {
throw new NotImplementedException(); throw new NotImplementedException();
/* /*
if (m_nChannelCount < 3) if (m_nChannelCount < 3)
throw new InvalidFormatException(); throw new InvalidFormatException();
m_nDstPixelBytes = m_info.BPP >> 3; m_nDstPixelBytes = m_info.BPP >> 3;
@ -1137,6 +1139,42 @@ namespace GameRes.Formats.Entis
} }
} }
void LossyRestoreRGB24_V9 ()
{
/*
int nLineOffset = 0;
int nBytesPerPixel = m_nDstPixelBytes;
int ptrDstLine = m_ptrDstBlock;
for (uint y = 0; y < m_nDstHeight; ++y)
{
int nOffsetPos = nLineOffset;
int ptrNextDst = ptrDstLine;
for (uint x = 0; x < m_nDstWidth; ++x)
{
m_output[ptrNextDst ] = RoundR32ToByte (m_ptrBuffer2[0][nOffsetPos]);
m_output[ptrNextDst+1] = RoundR32ToByte (m_ptrBuffer2[1][nOffsetPos]);
m_output[ptrNextDst+2] = RoundR32ToByte (m_ptrBuffer2[2][nOffsetPos]);
++nOffsetPos;
ptrNextDst += nBytesPerPixel;
}
nLineOffset += m_nBlockSize;
ptrDstLine += m_nDstLineBytes;
}
*/
}
static byte RoundR32ToByte (float r)
{
int n = Erisa.RoundR32ToInt (r);
if (n < 0)
return 0;
else if (n >= 0x100)
return 0xFF;
return (byte)n;
}
void RestoreDeltaRGBA32 () void RestoreDeltaRGBA32 ()
{ {
int ptrDstLine = m_ptrDstBlock; int ptrDstLine = m_ptrDstBlock;

View File

@ -35,6 +35,7 @@ namespace GameRes.Formats.Eternity
public bool HasAlpha; public bool HasAlpha;
public int BlockSize; public int BlockSize;
public uint DataOffset; public uint DataOffset;
public uint AlphaOffset;
} }
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
@ -64,6 +65,7 @@ namespace GameRes.Formats.Eternity
HasAlpha = header.ToInt32 (8) != 0, HasAlpha = header.ToInt32 (8) != 0,
BlockSize = header.ToUInt16 (0xC), BlockSize = header.ToUInt16 (0xC),
DataOffset = header.ToUInt32 (0x14), DataOffset = header.ToUInt32 (0x14),
AlphaOffset = header.ToUInt32 (0x1C),
}; };
} }
@ -87,13 +89,14 @@ namespace GameRes.Formats.Eternity
byte[] m_output; byte[] m_output;
public byte[] Data { get { return m_output; } } public byte[] Data { get { return m_output; } }
public PixelFormat Format { get { return PixelFormats.Bgr24; } } public PixelFormat Format { get; private set; }
public SgfReader (IBinaryStream input, SgfMetaData info) public SgfReader (IBinaryStream input, SgfMetaData info)
{ {
m_input = input; m_input = input;
m_info = info; m_info = info;
m_output = new byte[3 * info.Width * info.Height]; m_output = new byte[3 * info.Width * info.Height];
Format = PixelFormats.Bgr24;
} }
public byte[] Unpack () public byte[] Unpack ()
@ -128,9 +131,79 @@ namespace GameRes.Formats.Eternity
g = m_output[start_pos+1]; g = m_output[start_pos+1];
r = m_output[start_pos+2]; r = m_output[start_pos+2];
} }
if (m_info.HasAlpha)
{
var alpha = ReadAlpha();
if (alpha != null)
{
var pixels = new byte[m_info.iWidth * m_info.iHeight * 4];
dst = 0;
int p = 0;
int a = 0;
while (dst < pixels.Length)
{
pixels[dst++] = m_output[p++];
pixels[dst++] = m_output[p++];
pixels[dst++] = m_output[p++];
pixels[dst++] = alpha[a++];
}
m_output = pixels;
Format = PixelFormats.Bgra32;
}
}
return m_output; return m_output;
} }
byte[] ReadAlpha ()
{
m_input.Position = m_info.AlphaOffset;
var signature = m_input.ReadUInt16();
if (0x2041 == signature) // 'A '
return ReadASection();
else if (0x4D42 == signature) // 'BM'
return ReadBmpSection();
else
return null;
}
byte[] ReadASection ()
{
var alpha = new byte[m_info.iWidth * m_info.iHeight];
m_input.Position = m_info.AlphaOffset + 8;
int block_size = m_input.ReadUInt16();
m_input.Position = m_info.AlphaOffset + 0x10;
uint offset = m_input.ReadUInt32();
uint next_pos = m_info.AlphaOffset + offset;
int height = (int)m_info.Height;
int dst = alpha.Length - m_info.iWidth;
byte a = 0;
for (int y = 0; y < height; ++y)
{
int start_pos = dst;
if (0 == (y % block_size))
{
m_input.Position = next_pos;
next_pos += m_input.ReadUInt32();
a = (byte)m_input.ReadUInt32();
mask1 = mask2 = mask3 = mask4 = mask5 = 1;
}
for (uint x = 0; x < m_info.Width; ++x)
{
a = GetNextByte (a);
alpha[dst++] = a;
}
a = alpha[start_pos];
dst = start_pos - m_info.iWidth;
}
return alpha;
}
byte[] ReadBmpSection ()
{
// throw new NotImplementedException ("ReadBmpSection not implemented.");
return null;
}
uint mask1; uint mask1;
uint mask2; uint mask2;
uint mask3; uint mask3;

View File

@ -62,6 +62,11 @@ namespace GameRes.Formats.ExHibit
public override string Description { get { return "ExHIBIT engine image format"; } } public override string Description { get { return "ExHIBIT engine image format"; } }
public override uint Signature { get { return 0x1A555947; } } // 'GYU' public override uint Signature { get { return 0x1A555947; } } // 'GYU'
public GyuFormat ()
{
Extensions = new[] { "gyu", "lvg" };
}
GyuMap DefaultScheme = new GyuMap { GyuMap DefaultScheme = new GyuMap {
NumericKeys = new Dictionary<string, Dictionary<int, uint>>(), NumericKeys = new Dictionary<string, Dictionary<int, uint>>(),
StringKeys = new Dictionary<string, Dictionary<string, uint>>(), StringKeys = new Dictionary<string, Dictionary<string, uint>>(),

View File

@ -37,6 +37,8 @@ namespace GameRes.Formats
ArcView m_file; ArcView m_file;
Dictionary<string, Section> m_section_table; Dictionary<string, Section> m_section_table;
Section m_overlay; Section m_overlay;
uint m_image_base = 0;
List<ImageSection> m_section_list;
public ExeFile (ArcView file) public ExeFile (ArcView file)
{ {
@ -79,6 +81,16 @@ namespace GameRes.Formats
} }
} }
public uint ImageBase
{
get
{
if (0 == m_image_base)
InitImageBase();
return m_image_base;
}
}
/// <summary> /// <summary>
/// Structure representing section of executable file in the form of its offset and size. /// Structure representing section of executable file in the form of its offset and size.
/// </summary> /// </summary>
@ -88,6 +100,16 @@ namespace GameRes.Formats
public uint Size; public uint Size;
} }
public class ImageSection // IMAGE_SECTION_HEADER
{
public string Name;
public uint VirtualSize;
public uint VirtualAddress;
public uint SizeOfRawData;
public uint PointerToRawData;
public uint Characteristics;
}
/// <summary> /// <summary>
/// Returns true if executable file contains section <paramref name="name"/>. /// Returns true if executable file contains section <paramref name="name"/>.
/// </summary> /// </summary>
@ -140,6 +162,16 @@ namespace GameRes.Formats
return -1; return -1;
} }
public Section SectionByOffset (long offset)
{
foreach (var section in Sections.Values)
{
if (offset >= section.Offset && offset < section.Offset + section.Size)
return section;
}
return new Section { Offset = Whole.Size, Size = 0 };
}
public long FindAsciiString (Section section, string seq, int step = 1) public long FindAsciiString (Section section, string seq, int step = 1)
{ {
return FindString (section, Encoding.ASCII.GetBytes (seq), step); return FindString (section, Encoding.ASCII.GetBytes (seq), step);
@ -152,26 +184,101 @@ namespace GameRes.Formats
return FindString (section, bytes, step); return FindString (section, bytes, step);
} }
/// <summary>
/// Convert virtual address into raw file offset.
/// </summary>
public long GetAddressOffset (uint address)
{
var section = GetAddressSection (address);
if (null == section)
return m_file.MaxOffset;
uint rva = address - ImageBase;
return section.PointerToRawData + (rva - section.VirtualAddress);
}
public string GetCString (uint address)
{
return GetCString (address, Encodings.cp932);
}
static readonly byte[] ZeroByte = new byte[1] { 0 };
/// <summary>
/// Returns null-terminated string from specified virtual address.
/// </summary>
public string GetCString (uint address, Encoding enc)
{
var section = GetAddressSection (address);
if (null == section)
return null;
uint rva = address - ImageBase;
uint offset = section.PointerToRawData + (rva - section.VirtualAddress);
uint size = section.PointerToRawData + section.SizeOfRawData - offset;
long eos = FindString (new Section { Offset = offset, Size = size }, ZeroByte);
if (eos < 0)
return null;
return View.ReadString (offset, (uint)(eos - offset), enc);
}
private ImageSection GetAddressSection (uint address)
{
var img_base = ImageBase;
if (address < img_base)
throw new ArgumentException ("Invalid virtual address.");
if (null == m_section_list)
InitSectionTable();
uint rva = address - img_base;
foreach (var section in m_section_list)
{
if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.SizeOfRawData)
return section;
}
return null;
}
private void InitImageBase ()
{
long hdr_offset = GetHeaderOffset() + 0x18;
if (View.ReadUInt16 (hdr_offset) != 0x010B)
throw new InvalidFormatException ("File is not a valid Windows 32-bit executable.");
m_image_base = View.ReadUInt32 (hdr_offset+0x1C); // ImageBase
}
private long GetHeaderOffset ()
{
long pe_offset = View.ReadUInt32 (0x3C);
if (pe_offset >= m_file.MaxOffset-0x58 || !View.AsciiEqual (pe_offset, "PE\0\0"))
throw new InvalidFormatException ("File is not a valid Windows 32-bit executable.");
return pe_offset;
}
private void InitSectionTable () private void InitSectionTable ()
{ {
long pe_offset = m_file.View.ReadUInt32 (0x3C); long pe_offset = GetHeaderOffset();
if (pe_offset >= m_file.MaxOffset-0x58 || !m_file.View.AsciiEqual (pe_offset, "PE\0\0")) int opt_header = View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader
throw new InvalidFormatException ("File is not a valid win32 executable.");
int opt_header = m_file.View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader
long offset = m_file.View.ReadUInt32 (pe_offset+0x54); // SizeOfHeaders
long section_table = pe_offset+opt_header+0x18; long section_table = pe_offset+opt_header+0x18;
int count = m_file.View.ReadUInt16 (pe_offset+6); // NumberOfSections long offset = View.ReadUInt32 (pe_offset+0x54); // SizeOfHeaders
int count = View.ReadUInt16 (pe_offset+6); // NumberOfSections
var table = new Dictionary<string, Section> (count); var table = new Dictionary<string, Section> (count);
var list = new List<ImageSection> (count);
if (section_table + 0x28*count < m_file.MaxOffset) if (section_table + 0x28*count < m_file.MaxOffset)
{ {
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
var name = m_file.View.ReadString (section_table, 8); var name = View.ReadString (section_table, 8);
var section = new Section { var img_section = new ImageSection {
Size = m_file.View.ReadUInt32 (section_table+0x10), Name = name,
Offset = m_file.View.ReadUInt32 (section_table+0x14) VirtualSize = View.ReadUInt32 (section_table+0x08),
VirtualAddress = View.ReadUInt32 (section_table+0x0C),
SizeOfRawData = View.ReadUInt32 (section_table+0x10),
PointerToRawData = View.ReadUInt32 (section_table+0x14),
Characteristics = View.ReadUInt32 (section_table+0x24),
}; };
var section = new Section {
Offset = img_section.PointerToRawData,
Size = img_section.SizeOfRawData
};
list.Add (img_section);
if (!table.ContainsKey (name)) if (!table.ContainsKey (name))
table.Add (name, section); table.Add (name, section);
if (0 != section.Size) if (0 != section.Size)
@ -183,6 +290,7 @@ namespace GameRes.Formats
m_overlay.Offset = offset; m_overlay.Offset = offset;
m_overlay.Size = (uint)(m_file.MaxOffset - offset); m_overlay.Size = (uint)(m_file.MaxOffset - offset);
m_section_table = table; m_section_table = table;
m_section_list = list;
} }
/// <summary> /// <summary>

View File

@ -112,6 +112,7 @@ namespace GameRes.Formats.FC01
{ {
var options = Query<McgOptions> (arcStrings.ArcImageEncrypted); var options = Query<McgOptions> (arcStrings.ArcImageEncrypted);
key = options.Key; key = options.Key;
LastKey = key;
} }
else else
key = LastKey.Value; key = LastKey.Value;

View File

@ -54,6 +54,11 @@ namespace GameRes.Formats.FrontWing
public override bool IsHierarchic { get { return true; } } public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
public FltOpener()
{
ContainedFormats = new[] { "FG/FWGI", "OGG", "DAT/GENERIC" };
}
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
if (!file.View.AsciiEqual (4, "PACKDATA0000")) if (!file.View.AsciiEqual (4, "PACKDATA0000"))
@ -130,4 +135,9 @@ namespace GameRes.Formats.FrontWing
0x22, 0x45, 0xF2, 0x65, 0x74, 0x34, 0x35, 0xDE, 0x59, 0x27, 0xA3, 0xFB, 0x0C, 0x80, 0x32, 0xFF, 0x22, 0x45, 0xF2, 0x65, 0x74, 0x34, 0x35, 0xDE, 0x59, 0x27, 0xA3, 0xFB, 0x0C, 0x80, 0x32, 0xFF,
}; };
} }
[Export(typeof(ResourceAlias))]
[ExportMetadata("Extension", "CSF")]
[ExportMetadata("Target", "DAT/GENERIC")]
public class CsfFormat : ResourceAlias { }
} }

View File

@ -23,9 +23,9 @@
// IN THE SOFTWARE. // IN THE SOFTWARE.
// //
using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Windows.Media.Imaging;
using GameRes.Compression; using GameRes.Compression;
namespace GameRes.Formats.FrontWing namespace GameRes.Formats.FrontWing
@ -94,7 +94,7 @@ namespace GameRes.Formats.FrontWing
{ {
var fge_name = Path.ChangeExtension (file.Name, ".fge"); var fge_name = Path.ChangeExtension (file.Name, ".fge");
if (!VFS.FileExists (fge_name)) if (!VFS.FileExists (fge_name))
return null; throw new OperationCanceledException("Required metadata file '"+Path.GetFileName(fge_name)+"' not found.");
using (var fg = OpenFg (file)) using (var fg = OpenFg (file))
{ {
return Fwgi.Value.ReadMetaData (fg); return Fwgi.Value.ReadMetaData (fg);

View File

@ -230,7 +230,7 @@ namespace GameRes.Formats.Gs
} }
[Export(typeof(ScriptFormat))] [Export(typeof(ScriptFormat))]
public class GsScriptFormat : ScriptFormat public class GsScriptFormat : GenericScriptFormat
{ {
public override string Tag { get { return "SCW"; } } public override string Tag { get { return "SCW"; } }
public override string Description { get { return "GsWin script file"; } } public override string Description { get { return "GsWin script file"; } }
@ -240,15 +240,5 @@ namespace GameRes.Formats.Gs
{ {
Signatures = new uint[] { 0x20574353, 0x35776353, 0x34776353 }; Signatures = new uint[] { 0x20574353, 0x35776353, 0x34776353 };
} }
public override ScriptData Read (string name, Stream file)
{
throw new NotImplementedException();
}
public override void Write (Stream file, ScriptData script)
{
throw new NotImplementedException();
}
} }
} }

View File

@ -42,7 +42,7 @@ namespace GameRes.Formats.HSP
public DpmOpener () public DpmOpener ()
{ {
Extensions = new string[] { "dpm", "bin" }; Extensions = new string[] { "dpm", "bin", "dat" };
Signatures = new uint[] { 0x584D5044, 0 }; Signatures = new uint[] { 0x584D5044, 0 };
} }

View File

@ -0,0 +1,239 @@
//! \file ArcPP.cs
//! \date 2022 Jun 01
//! \brief Illusion resource archive.
//
// Copyright (C) 2022 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using GameRes.Utility;
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel.Composition;
namespace GameRes.Formats.Illusion
{
[Serializable]
public class PpEncryptionScheme
{
public byte Method;
public uint[] Key;
}
[Serializable]
public class PpScheme : ResourceScheme
{
public IDictionary<string, PpEncryptionScheme> KnownKeys;
}
public class PpArchive : ArcFile
{
public readonly byte Method;
public readonly PpEncryptionScheme Scheme;
public PpArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, byte encMethod, PpEncryptionScheme scheme)
: base (arc, impl, dir)
{
Method = encMethod;
Scheme = scheme;
}
}
[Export(typeof(ArchiveFormat))]
public class PpOpener : ArchiveFormat
{
public override string Tag { get { return "PP/ILLUSION"; } }
public override string Description { get { return "Illusion resource archive"; } }
public override uint Signature { get { return 0x5650505B; } } // '[PPVER]'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
static readonly byte[][] DefaultIndexKey = new byte[][] {
new byte[] { 0xFA, 0x49, 0x7B, 0x1C, 0xF9, 0x4D, 0x83, 0x0A },
new byte[] { 0x3A, 0xE3, 0x87, 0xC2, 0xBD, 0x1E, 0xA6, 0xFE }
};
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (0, "[PPVER]\0"))
return null;
var buffer = file.View.ReadBytes (8, 9);
DecryptIndex (buffer, 0, 4);
int version = buffer.ToInt32 (0);
DecryptIndex (buffer, 4, 1);
byte encryption_method = buffer[4];
if (encryption_method > 4)
return null;
DecryptIndex (buffer, 5, 4);
int count = buffer.ToInt32 (5);
if (!IsSaneCount (count))
return null;
if (version < 0x6C)
return null;
var dir = new List<Entry> (count);
uint index_offset = 0x11;
uint index_size = (uint)(count * 0x120);
var index = file.View.ReadBytes (index_offset, index_size);
index_offset += index_size;
DecryptIndex (index, 0, index.Length);
int pos = 0;
for (int i = 0; i < count; ++i)
{
var name = Binary.GetCString (index, pos, 0x104);
pos += 0x104;
var entry = Create<Entry> (name);
entry.Size = index.ToUInt32 (pos);
entry.Offset = index.ToUInt32 (pos+4);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
pos += 0x1C;
}
var scheme = QueryEncryptionScheme (file);
if (null == scheme)
return null;
return new PpArchive (file, this, dir, encryption_method, scheme);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var parc = arc as PpArchive;
if (null == parc || 2 == parc.Method)
return base.OpenEntry (arc, entry);
var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
if (1 == parc.Method)
DecryptData1 (data, parc.Scheme);
else if (3 == parc.Method)
DecryptData3 (data, parc.Scheme);
else if (4 == parc.Method)
data = UnpackData (data);
return new BinMemoryStream (data, entry.Name);
}
byte[] UnpackData (byte[] input)
{
return input; // decompression not implemented
}
void DecryptData1 (byte[] data, PpEncryptionScheme scheme)
{
if (scheme.Method > 2 || scheme.Key.Length == 0)
return;
if (0 == scheme.Method)
{
for (int i = 0; i < data.Length; ++i)
{
data[i] ^= (byte)scheme.Key[i % scheme.Key.Length];
}
return;
}
unsafe
{
fixed (byte* data8 = data)
{
if (1 == scheme.Method)
{
ushort* data16 = (ushort*)data8;
int size = data.Length / 2;
for (int i = 0; i < size; ++i)
{
data16[i] ^= (ushort)scheme.Key[i % scheme.Key.Length];
}
}
else if (2 == scheme.Method)
{
uint* data32 = (uint*)data8;
int size = data.Length / 4;
for (int i = 0; i < size; ++i)
{
data32[i] ^= scheme.Key[i % scheme.Key.Length];
}
}
}
}
}
void DecryptData3 (byte[] data, PpEncryptionScheme scheme)
{
if (scheme.Key.Length < 8)
return;
var key0 = new ushort[] { (ushort)scheme.Key[0], (ushort)scheme.Key[1], (ushort)scheme.Key[2], (ushort)scheme.Key[3] };
var key1 = new ushort[] { (ushort)scheme.Key[4], (ushort)scheme.Key[5], (ushort)scheme.Key[6], (ushort)scheme.Key[7] };
unsafe
{
fixed (byte* data8 = data)
{
ushort* data16 = (ushort*)data8;
int size = data.Length / 2;
for (int i = 0; i < size; ++i)
{
int k = i & 3;
key0[k] += key1[k];
data16[i] ^= key0[k];
}
}
}
}
internal void DecryptIndex (byte[] data, int pos, int length)
{
var key0 = DefaultIndexKey[0].Clone() as byte[];
var key1 = DefaultIndexKey[1];
for (int i = 0; i < length; ++i)
{
int k = i & 7;
key0[k] += key1[k];
data[pos+i] ^= key0[k];
}
}
PpScheme DefaultScheme = new PpScheme
{
KnownKeys = new Dictionary<string, PpEncryptionScheme>()
};
internal IDictionary<string, PpEncryptionScheme> KnownKeys { get { return DefaultScheme.KnownKeys; } }
public override ResourceScheme Scheme
{
get { return DefaultScheme; }
set { DefaultScheme = (PpScheme)value; }
}
PpEncryptionScheme QueryEncryptionScheme (ArcView file)
{
var title = FormatCatalog.Instance.LookupGame (file.Name);
if (string.IsNullOrEmpty (title))
title = FormatCatalog.Instance.LookupGame (file.Name, @"..\*.exe");
if (string.IsNullOrEmpty (title))
return null;
PpEncryptionScheme key;
if (!KnownKeys.TryGetValue (title, out key))
return null;
return key;
}
}
}

View File

@ -43,7 +43,7 @@ namespace GameRes.Formats.CandySoft
public FpkOpener () public FpkOpener ()
{ {
Signatures = new uint[] { 0, 1 }; Signatures = new uint[] { 0, 1 };
ContainedFormats = new[] { "BMP", "KG", "OGG", "SCR", "TXT" }; ContainedFormats = new[] { "BMP", "KG", "OGG", "SCR", "TXT", "DAT/GENERIC" };
} }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
@ -134,19 +134,19 @@ namespace GameRes.Formats.CandySoft
public override Stream OpenEntry (ArcFile arc, Entry entry) public override Stream OpenEntry (ArcFile arc, Entry entry)
{ {
var input = arc.File.CreateStream (entry.Offset, entry.Size); IBinaryStream input = arc.File.CreateStream (entry.Offset, entry.Size);
if (entry.Size <= 8) while (input.Length > 8 && input.Signature == 0x32434C5A) // 'ZLC2'
return input;
var sign = input.Signature;
if (0x32434c5a != sign) // 'ZLC2'
return input;
using (input)
using (var reader = new Zlc2Reader (input, (int)entry.Size))
{ {
reader.Unpack(); IBinaryStream unpacked;
return new BinMemoryStream (reader.Data, entry.Name); using (input)
using (var reader = new Zlc2Reader (input, (int)input.Length))
{
reader.Unpack();
unpacked = new BinMemoryStream (reader.Data, entry.Name);
}
input = unpacked;
} }
return input.AsStream;
} }
} }

View File

@ -0,0 +1,111 @@
//! \file ImageBMP.cs
//! \date 2022 Jun 06
//! \brief Candy Soft RLE-compressed bitmap.
//
// Copyright (C) 2022 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media.Imaging;
// [980925][Candy Soft] Osananajimi
namespace GameRes.Formats.Interheart
{
internal class BmpRleMetaData : ImageMetaData
{
public int HeaderSize;
public int UnpackedSize;
}
[Export(typeof(ImageFormat))]
public class BmpRleFormat : ImageFormat
{
public override string Tag { get { return "BMP/RLE"; } }
public override string Description { get { return "Candy Soft RLE-compressed bitmap"; } }
public override uint Signature { get { return 0x32504D42; } } // 'BMP24'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x26);
if (!header.AsciiEqual ("BMP24RLE")) // also "BMP08RLE" and "BMP16RLE"
return null;
return new BmpRleMetaData {
Width = header.ToUInt32 (0x1A),
Height = header.ToUInt32 (0x1E),
BPP = header.ToUInt16 (0x24),
HeaderSize = header.ToInt32 (0x12),
UnpackedSize = header.ToInt32 (0x0A),
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var meta = (BmpRleMetaData)info;
var output = new byte[meta.UnpackedSize];
file.Position = 8;
file.Read (output, 0, meta.HeaderSize);
var buffer = new byte[3];
int dst = meta.HeaderSize;
// XXX BMMP08 and BMP16 bitmaps use exactly the same algorithm, just different pixel size
file.Read (buffer, 0, 3);
while (dst + 3 <= output.Length)
{
byte r = buffer[0];
byte g = buffer[1];
byte b = buffer[2];
output[dst++] = b;
output[dst++] = g;
output[dst++] = r;
file.Read (buffer, 0, 3);
if (r == buffer[0] && g == buffer[1] && b == buffer[2])
{
if (dst + 3 > output.Length)
break;
output[dst++] = b;
output[dst++] = g;
output[dst++] = r;
int count = file.ReadUInt16();
while (count --> 0)
{
output[dst++] = b;
output[dst++] = g;
output[dst++] = r;
}
file.Read (buffer, 0, 3);
}
}
using (var input = new BinMemoryStream (output))
{
var decoder = new BmpBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
var frame = decoder.Frames[0];
frame.Freeze();
return new ImageData (frame, info);
}
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("BmpRleFormat.Write not implemented");
}
}
}

View File

@ -0,0 +1,96 @@
//! \file ImageHMP.cs
//! \date 2022 Jun 16
//! \brief Candy Soft hover map.
//
// Copyright (C) 2022 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Interheart
{
[Export(typeof(ImageFormat))]
public class HmpFormat : ImageFormat
{
public override string Tag { get { return "HMP"; } }
public override string Description { get { return "Interheart hover map"; } }
public override uint Signature { get { return 0; } }
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".hmp"))
return null;
uint width = file.ReadUInt32();
uint height = file.ReadUInt32();
if (width == 0 || width > 0x7FFF || height == 0 || height > 0x7FFF)
return null;
return new ImageMetaData { Width = width, Height = height, BPP = 8 };
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 8;
var pixels = file.ReadBytes (info.iWidth * info.iHeight);
return ImageData.Create (info, PixelFormats.Indexed8, DefaultPalette, pixels);
// return ImageData.Create (info, PixelFormats.Gray8, null, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("HmpFormat.Write not implemented");
}
static readonly BitmapPalette DefaultPalette = new BitmapPalette (GenerateColors());
static Color[] GenerateColors ()
{
var colors = new Color[256];
for (int i = 8; i < 256; ++i)
{
colors[i] = Color.FromRgb ((byte)i, (byte)i, (byte)i);
}
colors[0] = Color.FromRgb (0x00, 0x00, 0x00);
colors[1] = Color.FromRgb (0x00, 0x00, 0x7F);
colors[2] = Color.FromRgb (0x00, 0x7F, 0x00);
colors[3] = Color.FromRgb (0x00, 0x7F, 0x7F);
colors[4] = Color.FromRgb (0x7F, 0x00, 0x00);
colors[5] = Color.FromRgb (0x7F, 0x00, 0x7F);
colors[6] = Color.FromRgb (0x7F, 0x7F, 0x00);
colors[7] = Color.FromRgb (0x7F, 0x7F, 0x7F);
colors[8] = Color.FromRgb (0x00, 0x00, 0xFF);
colors[9] = Color.FromRgb (0x00, 0xFF, 0x00);
colors[10] = Color.FromRgb (0x00, 0xFF, 0xFF);
colors[11] = Color.FromRgb (0xFF, 0x00, 0x00);
colors[12] = Color.FromRgb (0xFF, 0x00, 0xFF);
colors[13] = Color.FromRgb (0xFF, 0xFF, 0x00);
colors[14] = Color.FromRgb (0xFF, 0xFF, 0xFF);
colors[15] = Color.FromRgb (0xFF, 0x00, 0x7F);
colors[16] = Color.FromRgb (0xFF, 0x7F, 0x00);
colors[16] = Color.FromRgb (0xFF, 0x7F, 0x7F);
colors[17] = Color.FromRgb (0x7F, 0x00, 0xFF);
colors[18] = Color.FromRgb (0xFF, 0xFF, 0x7F);
return colors;
}
}
}

View File

@ -39,7 +39,7 @@ namespace GameRes.Formats.Kaguya
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
public class ArcOpener : ArchiveFormat public class ArcOpener : ArchiveFormat
{ {
public override string Tag { get { return "ARI"; } } public override string Tag { get { return "ARC/ARI"; } }
public override string Description { get { return "KaGuYa script engine resource archive"; } } public override string Description { get { return "KaGuYa script engine resource archive"; } }
public override uint Signature { get { return 0x314c4657; } } // 'WFL1' public override uint Signature { get { return 0x314c4657; } } // 'WFL1'
public override bool IsHierarchic { get { return true; } } public override bool IsHierarchic { get { return true; } }
@ -48,11 +48,12 @@ namespace GameRes.Formats.Kaguya
public ArcOpener () public ArcOpener ()
{ {
Extensions = new string[] { "arc" }; Extensions = new string[] { "arc" };
ContainedFormats = new[] { "AP", "APS3", "OGG", "DAT/GENERIC" };
} }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
var reader = new IndexReader(); var reader = new IndexReader (this);
var dir = reader.ReadIndex (file); var dir = reader.ReadIndex (file);
if (null == dir || 0 == dir.Count) if (null == dir || 0 == dir.Count)
return null; return null;
@ -77,9 +78,15 @@ namespace GameRes.Formats.Kaguya
internal class IndexReader internal class IndexReader
{ {
ArchiveFormat m_format;
byte[] m_name_buf = new byte[0x20]; byte[] m_name_buf = new byte[0x20];
List<Entry> m_dir = new List<Entry>(); List<Entry> m_dir = new List<Entry>();
public IndexReader (ArchiveFormat format)
{
m_format = format;
}
public List<Entry> ReadIndex (ArcView file) public List<Entry> ReadIndex (ArcView file)
{ {
string ari_name = Path.ChangeExtension (file.Name, "ari"); string ari_name = Path.ChangeExtension (file.Name, "ari");
@ -163,7 +170,7 @@ namespace GameRes.Formats.Kaguya
else if (1 == entry.Mode) else if (1 == entry.Mode)
entry.Type = "image"; entry.Type = "image";
else else
entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name); entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name, m_format.ContainedFormats);
} }
string ReadName (ArcView file, long offset, int name_len) string ReadName (ArcView file, long offset, int name_len)
@ -246,4 +253,9 @@ namespace GameRes.Formats.Kaguya
} }
#endregion #endregion
} }
[Export(typeof(ResourceAlias))]
[ExportMetadata("Extension", "TBL")]
[ExportMetadata("Target", "DAT/GENERIC")]
public class TblFormat : ResourceAlias { }
} }

View File

@ -228,8 +228,7 @@ namespace GameRes.Formats.Kaguya
public virtual LinkEncryption GetEncryption () public virtual LinkEncryption GetEncryption ()
{ {
var params_dir = VFS.GetDirectoryName (m_input.Name); var params_dat = VFS.ChangeFileName (m_input.Name, "params.dat");
var params_dat = VFS.CombinePath (params_dir, "params.dat");
if (!VFS.FileExists (params_dat)) if (!VFS.FileExists (params_dat))
return null; return null;
@ -555,8 +554,10 @@ namespace GameRes.Formats.Kaguya
m_input.Position = start; m_input.Position = start;
SkipChunk(); SkipChunk();
m_title = ReadString(); m_title = ReadString();
if (m_version.Major < 3) if (m_version.Major < 2)
m_input.ReadCString(); m_input.ReadCString();
// else
// SkipString();
SkipString(); SkipString();
SkipString(); SkipString();
m_input.ReadByte(); m_input.ReadByte();
@ -583,7 +584,7 @@ namespace GameRes.Formats.Kaguya
{ {
ReadHeader (0x17); ReadHeader (0x17);
if ("幼なじみと甘~くエッチに過ごす方法" == m_title) if ("幼なじみと甘~くエッチに過ごす方法" == m_title || "艶女医" == m_title)
{ {
int count = m_input.ReadUInt8(); int count = m_input.ReadUInt8();
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
@ -595,8 +596,15 @@ namespace GameRes.Formats.Kaguya
} }
SkipArray(); SkipArray();
SkipArray(); SkipArray();
m_input.ReadInt32(); if ("幼なじみと甘~くエッチに過ごす方法" == m_title)
return m_input.ReadBytes (240000); {
m_input.ReadInt32();
return m_input.ReadBytes (240000);
}
else
{
return ReadKey();
}
} }
else // 毎日がM! else // 毎日がM!
{ {

View File

@ -0,0 +1,166 @@
//! \file CroixCrypt.cs
//! \date 2017 Dec 27
//! \brief Croix encryption algorithm for KiriKiri.
//
// Copyright (C) 2017 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
namespace GameRes.Formats.KiriKiri
{
[Serializable]
public class CroixCrypt : ICrypt
{
// ulong sigdata_checksum = 0xA86704CF2727BE76;
ulong sigdata_checksum = 0x6C04B9AB66EF2EF0;
public override void Decrypt (Xp3Entry entry, long offset, byte[] data, int pos, int count)
{
uint v16 = 0x6EDC44A8;
uint v17 = 0x139E;
uint v19 = 0x4B93;
for (int i = 0; i < 0x3D; ++i)
{
v17 += byte_10014F64[i];
v19 += v17;
}
v17 %= 0xFFEFu;
v19 %= 0xFFEFu;
uint v21 = v17 ^ (v19 << 16);
uint v22 = (v21 & 0xF | ((v21 & 0xFFFFFFF0) << 14)) << 3
| ((v21 & 0x18000 | ((v21 & 0x1F00000
| ((v21 & 0xE0000 | (v21 >> 1) & 0x7F000000) >> 13)) >> 3)) >> 1);
uint v25 = ~(((v16 & 0x78 | (v16 >> 14) & 0x3FF80) >> 3) | (v16 & 0xC000 | (v16 & 0x1F0000
| ((v16 & 7 | 2 * (v16 & 0xFFFFFF80)) << 13)) << 3) << 1);
for (int i = 0; i < 0x3D; ++i)
{
v25 = dword_10014070[(v25 ^ byte_10014F64[i]) & 0xFF] ^ (v25 >> 8);
}
v25 = ~v25;
v25 = (v25 & 0xF | ((v25 & 0xFFFFFFF0) << 14)) << 3
| (v25 & 0x18000 | ((v25 & 0x1F00000 | ((v25 & 0xE0000 | (v25 >> 1) & 0x7F000000) >> 13)) >> 3)) >> 1;
uint v29 = 0x5793CE00;
uint v71 = v22 ^ (v29 << 21);
uint v75 = v25 ^ (v29 >> 11);
ulong v33 = sigdata_checksum ^ 0x5793CE00u ^ v71 ^ ((ulong)v75 << 32);
uint v55 = (uint)(v33 >> 17);
uint hash = entry.Hash ^ v55 ^ 0x15C3F972u;
for (int i = 0; i < count; ++i)
{
int shift = (((int)offset + i) & 3) << 3;
uint v40 = data[pos+i] ^ (hash >> shift);
v40 -= 0xE3AD9ACBu >> shift;
v40 ^= (byte)(offset >> ((((int)offset + i) & 7) << 3));
v40 += 0xFECAB9F2u >> shift;
data[pos+i] = (byte)v40;
}
hash = entry.Hash ^ 0x27D3BCA1;
for (int i = 0; i < count; ++i)
{
int shift = (((int)offset + i) & 3) << 3;
uint v40 = data[pos+i] ^ (hash >> shift);
v40 -= 0xE3779ACBu >> shift;
v40 ^= (byte)(offset >> ((((int)offset + i) & 7) << 3));
v40 += 0xFDCAB972u >> shift;
data[pos+i] = (byte)v40;
}
hash = entry.Hash & 0x7FFFFFFF;
var v83 = new byte[0x1F];
for (int i = 0; i < 0x1F; ++i)
{
v83[i] = (byte)hash;
hash = v55 ^ (hash >> 8 | (hash & 0xFF) << 23);
}
uint v58 = 0;
uint v59 = 0;
var v84 = new byte[0x3D];
for (int i = 0; i < 0x3D; ++i)
{
byte v61 = (byte)v58;
v84[i] = v61;
v58 = v55 ^ (uint)(v59 << 24 | v58 >> 8);
v59 = v59 >> 8 | (uint)v61 << 21;
}
for (int i = 0; i < count; ++i)
{
long v63 = offset + i;
data[pos+i] ^= v83[v63 % 0x1F];
data[pos+i] += (byte)(v55 ^ v84[v63 % 0x3D] ^ byte_10014F64[v63 % 0x3D]);
}
}
static ulong v160 = 0xA5665A5F061EC576;
static ulong CRC = 0xE51804DAE70D133E;
// static ulong v160 = 0xA6129778061EC576;
// static ulong CRC = 0xE4EFF66CE70D133E;
static byte[] byte_10014F64 = InitTable();
static byte[] InitTable ()
{
var key = new byte[0x3D];
uint v58 = (uint)(v160 + CRC);
uint v59 = (uint)((v160 + CRC) >> 32) & 0x1FFFFFFF;
for (int i = 0; i < 0x3D; ++i)
{
byte v61 = (byte)v58;
key[i] = v61;
v58 = v59 << 24 | v58 >> 8;
v59 = v59 >> 8 | (uint)v61 << 21;
}
return key;
}
static readonly uint[] dword_10014070 = {
0x00000000, 0x09073096, 0x120E612C, 0x1B0951BA, 0xFF6DC419, 0xF66AF48F, 0xED63A535, 0xE46495A3,
0xFEDB8832, 0xF7DCB8A4, 0xECD5E91E, 0xE5D2D988, 0x01B64C2B, 0x08B17CBD, 0x13B82D07, 0x1ABF1D91,
0xFDB71064, 0xF4B020F2, 0xEFB97148, 0xE6BE41DE, 0x02DAD47D, 0x0BDDE4EB, 0x10D4B551, 0x19D385C7,
0x036C9856, 0x0A6BA8C0, 0x1162F97A, 0x1865C9EC, 0xFC015C4F, 0xF5066CD9, 0xEE0F3D63, 0xE7080DF5,
0xFB6E20C8, 0xF269105E, 0xE96041E4, 0xE0677172, 0x0403E4D1, 0x0D04D447, 0x160D85FD, 0x1F0AB56B,
0x05B5A8FA, 0x0CB2986C, 0x17BBC9D6, 0x1EBCF940, 0xFAD86CE3, 0xF3DF5C75, 0xE8D60DCF, 0xE1D13D59,
0x06D930AC, 0x0FDE003A, 0x14D75180, 0x1DD06116, 0xF9B4F4B5, 0xF0B3C423, 0xEBBA9599, 0xE2BDA50F,
0xF802B89E, 0xF1058808, 0xEA0CD9B2, 0xE30BE924, 0x076F7C87, 0x0E684C11, 0x15611DAB, 0x1C662D3D,
0xF6DC4190, 0xFFDB7106, 0xE4D220BC, 0xEDD5102A, 0x09B18589, 0x00B6B51F, 0x1BBFE4A5, 0x12B8D433,
0x0807C9A2, 0x0100F934, 0x1A09A88E, 0x130E9818, 0xF76A0DBB, 0xFE6D3D2D, 0xE5646C97, 0xEC635C01,
0x0B6B51F4, 0x026C6162, 0x196530D8, 0x1062004E, 0xF40695ED, 0xFD01A57B, 0xE608F4C1, 0xEF0FC457,
0xF5B0D9C6, 0xFCB7E950, 0xE7BEB8EA, 0xEEB9887C, 0x0ADD1DDF, 0x03DA2D49, 0x18D37CF3, 0x11D44C65,
0x0DB26158, 0x04B551CE, 0x1FBC0074, 0x16BB30E2, 0xF2DFA541, 0xFBD895D7, 0xE0D1C46D, 0xE9D6F4FB,
0xF369E96A, 0xFA6ED9FC, 0xE1678846, 0xE860B8D0, 0x0C042D73, 0x05031DE5, 0x1E0A4C5F, 0x170D7CC9,
0xF005713C, 0xF90241AA, 0xE20B1010, 0xEB0C2086, 0x0F68B525, 0x066F85B3, 0x1D66D409, 0x1461E49F,
0x0EDEF90E, 0x07D9C998, 0x1CD09822, 0x15D7A8B4, 0xF1B33D17, 0xF8B40D81, 0xE3BD5C3B, 0xEABA6CAD,
0xEDB88320, 0xE4BFB3B6, 0xFFB6E20C, 0xF6B1D29A, 0x12D54739, 0x1BD277AF, 0x00DB2615, 0x09DC1683,
0x13630B12, 0x1A643B84, 0x016D6A3E, 0x086A5AA8, 0xEC0ECF0B, 0xE509FF9D, 0xFE00AE27, 0xF7079EB1,
0x100F9344, 0x1908A3D2, 0x0201F268, 0x0B06C2FE, 0xEF62575D, 0xE66567CB, 0xFD6C3671, 0xF46B06E7,
0xEED41B76, 0xE7D32BE0, 0xFCDA7A5A, 0xF5DD4ACC, 0x11B9DF6F, 0x18BEEFF9, 0x03B7BE43, 0x0AB08ED5,
0x16D6A3E8, 0x1FD1937E, 0x04D8C2C4, 0x0DDFF252, 0xE9BB67F1, 0xE0BC5767, 0xFBB506DD, 0xF2B2364B,
0xE80D2BDA, 0xE10A1B4C, 0xFA034AF6, 0xF3047A60, 0x1760EFC3, 0x1E67DF55, 0x056E8EEF, 0x0C69BE79,
0xEB61B38C, 0xE266831A, 0xF96FD2A0, 0xF068E236, 0x140C7795, 0x1D0B4703, 0x060216B9, 0x0F05262F,
0x15BA3BBE, 0x1CBD0B28, 0x07B45A92, 0x0EB36A04, 0xEAD7FFA7, 0xE3D0CF31, 0xF8D99E8B, 0xF1DEAE1D,
0x1B64C2B0, 0x1263F226, 0x096AA39C, 0x006D930A, 0xE40906A9, 0xED0E363F, 0xF6076785, 0xFF005713,
0xE5BF4A82, 0xECB87A14, 0xF7B12BAE, 0xFEB61B38, 0x1AD28E9B, 0x13D5BE0D, 0x08DCEFB7, 0x01DBDF21,
0xE6D3D2D4, 0xEFD4E242, 0xF4DDB3F8, 0xFDDA836E, 0x19BE16CD, 0x10B9265B, 0x0BB077E1, 0x02B74777,
0x18085AE6, 0x110F6A70, 0x0A063BCA, 0x03010B5C, 0xE7659EFF, 0xEE62AE69, 0xF56BFFD3, 0xFC6CCF45,
0xE00AE278, 0xE90DD2EE, 0xF2048354, 0xFB03B3C2, 0x1F672661, 0x166016F7, 0x0D69474D, 0x046E77DB,
0x1ED16A4A, 0x17D65ADC, 0x0CDF0B66, 0x05D83BF0, 0xE1BCAE53, 0xE8BB9EC5, 0xF3B2CF7F, 0xFAB5FFE9,
0x1DBDF21C, 0x14BAC28A, 0x0FB39330, 0x06B4A3A6, 0xE2D03605, 0xEBD70693, 0xF0DE5729, 0xF9D967BF,
0xE3667A2E, 0xEA614AB8, 0xF1681B02, 0xF86F2B94, 0x1C0BBE37, 0x150C8EA1, 0x0E05DF1B, 0x0702EF8D,
};
}
}

View File

@ -1406,4 +1406,43 @@ namespace GameRes.Formats.KiriKiri
return key; return key;
} }
} }
[Serializable]
public class GensouCrypt : ICrypt
{
byte[] Key1;
uint[] Key2;
public GensouCrypt (byte[] key1, uint[] key2)
{
Key1 = key1;
Key2 = key2;
StartupTjsNotEncrypted = true;
}
public override void Decrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count)
{
ushort off16 = (ushort)offset;
for (int i = 0; i < count; ++i)
{
buffer[pos+i] ^= (byte)(Key1[(Key2[off16 >> 3] >> ((off16 & 7) << 2)) & 0xF]
^ ((Key2[off16 >> 3] ^ entry.Hash) >> ((off16 & 3) << 3)));
off16++;
}
}
public override void Encrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count)
{
Decrypt (entry, offset, buffer, pos, count);
}
public override void Init (ArcFile arc)
{
foreach (Xp3Entry entry in arc.Dir.Where (e => VFS.IsPathEqualsToFileName (e.Name, "btext.dll")
|| VFS.IsPathEqualsToFileName (e.Name, "initialize.tjs")))
{
entry.Cipher = Xp3Opener.NoCryptAlgorithm;
}
}
}
} }

View File

@ -34,6 +34,7 @@ namespace GameRes.Formats.Lambda
internal class ClsMetaData : ImageMetaData internal class ClsMetaData : ImageMetaData
{ {
public int FrameOffset; public int FrameOffset;
public bool IsCompressed;
} }
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
@ -67,8 +68,7 @@ namespace GameRes.Formats.Lambda
int y = file.ReadInt32(); int y = file.ReadInt32();
file.Position = frame_offset+0x30; file.Position = frame_offset+0x30;
if (file.ReadByte() != 1) bool compressed = file.ReadByte() != 0;
return null;
int format = file.ReadByte(); int format = file.ReadByte();
if (format != 4 && format != 5 && format != 2) if (format != 4 && format != 5 && format != 2)
return null; return null;
@ -79,6 +79,7 @@ namespace GameRes.Formats.Lambda
OffsetY = y, OffsetY = y,
BPP = 8 * (format - 1), BPP = 8 * (format - 1),
FrameOffset = frame_offset, FrameOffset = frame_offset,
IsCompressed = compressed,
}; };
} }
@ -104,6 +105,7 @@ namespace GameRes.Formats.Lambda
int m_channels; int m_channels;
int m_base_offset; int m_base_offset;
int[] m_rows_sizes; int[] m_rows_sizes;
bool m_compressed;
public PixelFormat Format { get; private set; } public PixelFormat Format { get; private set; }
public BitmapPalette Palette { get; private set; } public BitmapPalette Palette { get; private set; }
@ -112,6 +114,7 @@ namespace GameRes.Formats.Lambda
{ {
m_input = input; m_input = input;
m_base_offset = info.FrameOffset; m_base_offset = info.FrameOffset;
m_compressed = info.IsCompressed;
m_width = (int)info.Width; m_width = (int)info.Width;
m_height = (int)info.Height; m_height = (int)info.Height;
m_channels = info.BPP / 8; m_channels = info.BPP / 8;
@ -120,7 +123,7 @@ namespace GameRes.Formats.Lambda
else if (4 == m_channels) else if (4 == m_channels)
Format = PixelFormats.Bgra32; Format = PixelFormats.Bgra32;
else else
Format = PixelFormats.Bgr32; Format = PixelFormats.Bgr24;
m_channel = new byte[m_width * m_height]; m_channel = new byte[m_width * m_height];
m_rows_sizes = new int[m_height]; m_rows_sizes = new int[m_height];
} }
@ -148,13 +151,20 @@ namespace GameRes.Formats.Lambda
UnpackChannel (sizes[0]); UnpackChannel (sizes[0]);
return m_channel; return m_channel;
} }
var output = new byte[m_width * m_height * 4]; else if (!m_compressed)
{
var pixels = new byte[sizes[0]];
SetPosition (offsets[0]);
m_input.Read (pixels, 0, pixels.Length);
return pixels;
}
var output = new byte[m_width * m_height * m_channels];
for (int i = 0; i < m_channels; ++i) for (int i = 0; i < m_channels; ++i)
{ {
SetPosition (offsets[i]); SetPosition (offsets[i]);
UnpackChannel (sizes[i]); UnpackChannel (sizes[i]);
int src = 0; int src = 0;
for (int dst = ChannelOrder[i]; dst < output.Length; dst += 4) for (int dst = ChannelOrder[i]; dst < output.Length; dst += m_channels)
{ {
output[dst] = m_channel[src++]; output[dst] = m_channel[src++];
} }
@ -169,6 +179,11 @@ namespace GameRes.Formats.Lambda
void UnpackChannel (int size) void UnpackChannel (int size)
{ {
if (!m_compressed)
{
ReadV0 (size);
return;
}
int method = Binary.BigEndian (m_input.ReadUInt16()); int method = Binary.BigEndian (m_input.ReadUInt16());
if (method > 1) if (method > 1)
throw new InvalidFormatException(); throw new InvalidFormatException();

View File

@ -181,7 +181,7 @@ namespace GameRes.Formats.Liar
} }
//[Export(typeof(ScriptFormat))] //[Export(typeof(ScriptFormat))]
public class GscFormat : ScriptFormat public class GscFormat : GenericScriptFormat
{ {
public override string Tag { get { return "GSC"; } } public override string Tag { get { return "GSC"; } }
public override string Description { get { return Strings.arcStrings.GSCDescription; } } public override string Description { get { return Strings.arcStrings.GSCDescription; } }

View File

@ -41,6 +41,11 @@ namespace GameRes.Formats.Lilim
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
public AosOpener ()
{
ContainedFormats = new[] { "BMP", "ABM", "IMG/BMP", "DAT/GENERIC", "OGG" };
}
static readonly byte[] IndexLink = Enumerable.Repeat<byte> (0xff, 0x10).ToArray(); static readonly byte[] IndexLink = Enumerable.Repeat<byte> (0xff, 0x10).ToArray();
static readonly byte[] IndexEnd = Enumerable.Repeat<byte> (0, 0x10).ToArray(); static readonly byte[] IndexEnd = Enumerable.Repeat<byte> (0, 0x10).ToArray();

View File

@ -32,13 +32,13 @@ using System.Text;
using GameRes.Compression; using GameRes.Compression;
using GameRes.Utility; using GameRes.Utility;
namespace GameRes.Formats.Selen namespace GameRes.Formats.Macromedia
{ {
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
public class CctOpener : ArchiveFormat public class CctOpener : ArchiveFormat
{ {
public override string Tag { get { return "CCT"; } } public override string Tag { get { return "CCT"; } }
public override string Description { get { return "Macromedia Director resource archive"; } } public override string Description { get { return "Macromedia Shockwave resource archive"; } }
public override uint Signature { get { return 0x52494658; } } // 'XFIR' public override uint Signature { get { return 0x52494658; } } // 'XFIR'
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }

View File

@ -0,0 +1,286 @@
//! \file ArcDXR.cs
//! \date 2023 Aug 17
//! \brief Macromedia Director presentation container.
//
// 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.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Macromedia
{
[Export(typeof(ArchiveFormat))]
public class DxrOpener : ArchiveFormat
{
public override string Tag { get => "DXR"; }
public override string Description { get => "Macromedia Director resource archive"; }
public override uint Signature { get => 0x52494658; } // 'XFIR'
public override bool IsHierarchic { get => false; }
public override bool CanWrite { get => false; }
public DxrOpener ()
{
Extensions = new[] { "dxr", "cxt" };
Signatures = new[] { 0x52494658u, 0x58464952u };
}
internal static readonly HashSet<string> RawChunks = new HashSet<string> {
"RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT",
};
internal bool ConvertText = true;
public override ArcFile TryOpen (ArcView file)
{
using (var input = file.CreateStream())
{
ByteOrder ord = input.Signature == 0x52494658u ? ByteOrder.LittleEndian : ByteOrder.BigEndian;
var reader = new Reader (input, ord);
reader.Position = 4;
uint length = reader.ReadU32();
var context = new SerializationContext();
var dir_file = new DirectorFile();
if (!dir_file.Deserialize (context, reader))
return null;
var dir = new List<Entry> ();
ImportMedia (dir_file, dir);
foreach (MemoryMapEntry entry in dir_file.MMap.Dir)
{
if (RawChunks.Contains (entry.FourCC))
{
entry.Name = string.Format ("{0:D6}.{1}", entry.Id, entry.FourCC.Trim());
dir.Add (entry);
}
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var snd = entry as SoundEntry;
if (snd != null)
return OpenSound (arc, snd);
var ment = entry as MemoryMapEntry;
if (!ConvertText || null == ment || ment.FourCC != "STXT")
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);
}
internal Stream OpenSound (ArcFile arc, SoundEntry entry)
{
var header = arc.File.View.ReadBytes (entry.Header.Offset, entry.Header.Size);
var format = entry.DeserializeHeader (header);
var riff = new MemoryStream (0x2C);
WaveAudio.WriteRiffHeader (riff, format, entry.Size);
if (format.BitsPerSample < 16)
{
using (riff)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new PrefixStream (riff.ToArray(), input);
}
}
// samples are stored in big-endian format
var samples = arc.File.View.ReadBytes (entry.Offset, entry.Size);
for (int i = 1; i < samples.Length; i += 2)
{
byte s = samples[i-1];
samples[i-1] = samples[i];
samples[i] = s;
}
riff.Write (samples, 0, samples.Length);
riff.Position = 0;
return riff;
}
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)
{
if (seen_ids.Contains (piece.Id))
continue;
seen_ids.Add (piece.Id);
Entry entry = null;
if (piece.Type == DataType.Bitmap)
entry = ImportBitmap (piece, dir_file, cast);
else if (piece.Type == DataType.Sound)
entry = ImportSound (piece, dir_file);
if (entry != null)
dir.Add (entry);
}
}
}
Entry ImportSound (CastMember sound, DirectorFile dir_file)
{
var sndHrec = dir_file.KeyTable.FindByCast (sound.Id, "sndH");
var sndSrec = dir_file.KeyTable.FindByCast (sound.Id, "sndS");
if (sndHrec == null || sndSrec == null)
return null;
var name = sound.Info.Name;
var sndH = dir_file.MMap[sndHrec.Id];
var sndS = dir_file.MMap[sndSrec.Id];
if (string.IsNullOrEmpty (name))
name = sndSrec.Id.ToString ("D6");
return new SoundEntry
{
Name = name + ".snd",
Type = "audio",
Offset = sndS.Offset,
Size = sndS.Size,
Header = sndH,
};
}
Entry ImportBitmap (CastMember bitmap, DirectorFile dir_file, Cast cast)
{
var bitd = dir_file.KeyTable.FindByCast (bitmap.Id, "BITD");
if (bitd == null)
return null;
var entry = new BitmapEntry();
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];
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];
var clut = dir_file.KeyTable.FindByCast (cast_id, "CLUT");
if (clut != null)
entry.PaletteRef = dir_file.MMap[clut.Id];
}
return entry;
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var bent = entry as BitmapEntry;
if (null == bent)
return base.OpenImage (arc, entry);
BitmapPalette palette = null;
if (bent.PaletteRef != null)
{
var pal_bytes = arc.File.View.ReadBytes (bent.PaletteRef.Offset, bent.PaletteRef.Size);
palette = ReadPalette (pal_bytes);
}
else if (bent.BitDepth <= 8)
{
switch (bent.Palette)
{
case 0: palette = Palettes.SystemMac; break;
case -1: palette = Palettes.Rainbow; break;
case -2: palette = Palettes.Grayscale; break;
case -100: palette = Palettes.WindowsDirector4; break;
default:
case -101: palette = Palettes.SystemWindows; break;
}
}
var input = arc.File.CreateStream (entry.Offset, entry.Size);
var info = new ImageMetaData {
Width = (uint)(bent.Right - bent.Left),
Height = (uint)(bent.Bottom - bent.Top),
BPP = bent.BitDepth
};
return new BitdDecoder (input.AsStream, info, palette);
}
BitmapPalette ReadPalette (byte[] data)
{
int num_colors = data.Length / 6;
var colors = new Color[num_colors];
for (int i = 0; i < data.Length; i += 6)
{
colors[i/6] = Color.FromRgb (data[i], data[i+2], data[i+4]);
}
return new BitmapPalette (colors);
}
}
internal class BitmapEntry : Entry
{
public byte Flags;
public byte DepthType;
public int Top;
public int Left;
public int Bottom;
public int Right;
public int BitDepth;
public int Palette;
public Entry PaletteRef;
public void DeserializeHeader (byte[] data)
{
using (var input = new MemoryStream (data, false))
{
var reader = new Reader (input, ByteOrder.BigEndian);
DepthType = reader.ReadU8();
Flags = reader.ReadU8();
Top = reader.ReadI16();
Left = reader.ReadI16();
Bottom = reader.ReadI16();
Right = reader.ReadI16();
reader.Skip (0x0C);
BitDepth = reader.ReadU16() & 0xFF; // ???
reader.Skip (2);
Palette = reader.ReadI16();
}
}
}
internal class SoundEntry : Entry
{
public Entry Header;
public WaveFormat DeserializeHeader (byte[] header)
{
// pure guesswork
return new WaveFormat {
FormatTag = 1,
Channels = (ushort)BigEndian.ToUInt32 (header, 0x4C),
SamplesPerSecond = BigEndian.ToUInt32 (header, 0x2C),
AverageBytesPerSecond = BigEndian.ToUInt32 (header, 0x30),
BlockAlign = (ushort)BigEndian.ToUInt32 (header, 0x50),
BitsPerSample = (ushort)BigEndian.ToUInt32 (header, 0x44),
};
}
}
}

View File

@ -0,0 +1,712 @@
//! \file DirectorFile.cs
//! \date 2023 Aug 21
//! \brief Macromedia Director container deserialization.
//
// 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.Utility;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace GameRes.Formats.Macromedia
{
internal enum ByteOrder
{
LittleEndian, BigEndian
}
internal enum DataType
{
Null = 0,
Bitmap = 1,
FilmLoop = 2,
Text = 3,
Palette = 4,
Picture = 5,
Sound = 6,
Button = 7,
Shape = 8,
Movie = 9,
DigitalVideo = 10,
Script = 11,
RTE = 12,
}
internal class SerializationContext
{
public int Version;
public Encoding Encoding;
public SerializationContext ()
{
Encoding = Encodings.cp932;
}
}
internal class DirectorFile
{
MemoryMap m_mmap = new MemoryMap();
KeyTable m_keyTable = new KeyTable();
DirectorConfig m_config = new DirectorConfig();
List<Cast> m_casts = new List<Cast>();
public MemoryMap MMap => m_mmap;
public KeyTable KeyTable => m_keyTable;
public DirectorConfig Config => m_config;
public List<Cast> Casts => m_casts;
public bool Deserialize (SerializationContext context, Reader reader)
{
reader.Position = 8;
string codec = reader.ReadFourCC();
if (codec != "MV93" && codec != "MC95")
return false;
return ReadMMap (context, reader)
&& ReadKeyTable (context, reader)
&& ReadConfig (context, reader)
&& ReadCasts (context, reader);
}
bool ReadMMap (SerializationContext context, Reader reader)
{
if (reader.ReadFourCC() != "imap")
return false;
reader.Skip (8);
uint mmap_pos = reader.ReadU32();
reader.Position = mmap_pos;
if (reader.ReadFourCC() != "mmap")
return false;
reader.Position = mmap_pos + 8;
MMap.Deserialize (context, reader);
return true;
}
bool ReadKeyTable (SerializationContext context, Reader reader)
{
var key_chunk = MMap.Find ("KEY*");
if (null == key_chunk)
return false;
reader.Position = key_chunk.Offset;
KeyTable.Deserialize (context, reader);
return true;
}
bool ReadConfig (SerializationContext context, Reader reader)
{
var config_chunk = MMap.Find ("VWCF") ?? MMap.Find ("DRCF");
if (null == config_chunk)
return false;
reader.Position = config_chunk.Offset;
Config.Deserialize (context, reader);
context.Version = Config.Version;
return true;
}
bool ReadCasts (SerializationContext context, Reader reader)
{
if (context.Version > 1200)
{
var mcsl = MMap.Find ("MCsL");
if (mcsl != null)
{
reader.Position = mcsl.Offset;
var cast_list = new CastList();
cast_list.Deserialize (context, 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);
if (!PopulateCast (cast, context, reader, entry))
return false;
Casts.Add (cast);
}
}
return true;
}
}
var cas_chunk = MMap.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);
if (!PopulateCast (new_cast, context, reader, new_entry))
return false;
Casts.Add (new_cast);
return true;
}
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 member = new CastMember();
member.Id = chunk_id;
reader.Position = chunk.Offset;
member.Deserialize (context, reader);
cast.Members[member.Id] = member;
}
}
return true;
}
}
internal class CastMember
{
public DataType Type;
public CastInfo Info = new CastInfo();
public byte[] SpecificData;
public byte Flags;
public int Id;
public void Deserialize (SerializationContext context, Reader reader)
{
reader = reader.CloneUnless (ByteOrder.BigEndian);
if (context.Version > 1200)
{
Type = (DataType)reader.ReadI32();
int info_length = reader.ReadI32();
int data_length = reader.ReadI32();
if (info_length > 0)
{
Info.Deserialize (context, reader);
}
SpecificData = reader.ReadBytes (data_length);
}
else
{
int data_length = reader.ReadU16();
int info_length = reader.ReadI32();
Type = (DataType)reader.ReadU8();
--data_length;
if (data_length > 0)
{
Flags = reader.ReadU8();
--data_length;
}
SpecificData = reader.ReadBytes (data_length);
if (info_length > 0)
{
Info.Deserialize (context, reader);
}
}
}
}
internal class CastInfo
{
public uint DataOffset;
public uint ScriptKey;
public uint Flags;
public int ScriptId;
public string Name;
public string SourceText;
public List<byte[]> Items = new List<byte[]>();
public void Deserialize (SerializationContext context, Reader reader)
{
long base_offset = reader.Position;
DataOffset = reader.ReadU32();
ScriptKey = reader.ReadU32();
reader.Skip (4);
Flags = reader.ReadU32();
ScriptId = reader.ReadI32();
reader.Position = base_offset + DataOffset;
int table_len = reader.ReadU16();
var offsets = new int[table_len];
for (int i = 0; i < table_len; ++i)
offsets[i] = reader.ReadI32();
int data_length = reader.ReadI32();
long list_offset = reader.Position;
Items.Clear();
Items.Capacity = offsets.Length;
for (int i = 0; i < offsets.Length; ++i)
{
int offset = offsets[i];
int next_offset = (i + 1 < offsets.Length) ? offsets[i+1] : data_length;
reader.Position = list_offset + offset;
Items.Add (reader.ReadBytes (next_offset - offset));
}
SourceText = Items.Count > 0 ? Binary.GetCString (Items[0], 0) : string.Empty;
Name = GetString (1, context.Encoding);
}
string GetString (int item_idx, Encoding enc)
{
if (item_idx >= Items.Count)
return string.Empty;
var src = Items[item_idx];
if (src.Length <= 1 || 0 == src[0])
return string.Empty;
int len = src[0];
return enc.GetString (src, 1, len);
}
}
internal class Cast
{
public int[] Index;
public string Name;
public Dictionary<int, CastMember> Members = new Dictionary<int, CastMember>();
public Cast (SerializationContext context, Reader reader, MemoryMapEntry entry)
{
int count = (int)(entry.Size / 4);
Index = new int[count];
reader.Position = entry.Offset;
Deserialize (context, reader);
}
public void Deserialize (SerializationContext context, Reader reader)
{
reader = reader.CloneUnless (ByteOrder.BigEndian);
for (int i = 0; i < Index.Length; ++i)
Index[i] = reader.ReadI32();
}
}
internal class CastList
{
public uint DataOffset;
public int OffsetCount;
public int[] OffsetTable;
public int ItemsLength;
public int CastCount;
public int ItemsPerCast;
public List<byte[]> Items = new List<byte[]>();
public readonly List<CastListEntry> Entries = new List<CastListEntry>();
public void Deserialize (SerializationContext context, Reader reader)
{
long base_offset = reader.Position;
reader = reader.CloneUnless (ByteOrder.BigEndian);
DataOffset = reader.ReadU32();
reader.Skip (2);
CastCount = reader.ReadU16();
ItemsPerCast = reader.ReadU16();
reader.Skip (2);
reader.Position = base_offset + DataOffset;
OffsetCount = reader.ReadU16();
OffsetTable = new int[OffsetCount];
for (int i = 0; i < OffsetCount; ++i)
{
OffsetTable[i] = reader.ReadI32();
}
ItemsLength = reader.ReadI32();
long items_offset = reader.Position;
Items.Clear();
Items.Capacity = OffsetCount;
for (int i = 0; i < OffsetCount; ++i)
{
int offset = OffsetTable[i];
int next_offset = (i + 1 < OffsetCount) ? OffsetTable[i + 1] : ItemsLength;
int item_size = next_offset - offset;
Items.Add (reader.ReadBytes (item_size));
}
Entries.Clear();
Entries.Capacity = CastCount;
int item_idx = 0;
for (int i = 0; i < CastCount; ++i)
{
var entry = new CastListEntry();
if (ItemsPerCast >= 1)
entry.Name = GetString (item_idx + 1, context.Encoding);
if (ItemsPerCast >= 2)
entry.Path = GetString (item_idx + 2, context.Encoding);
if (ItemsPerCast >= 3 && Items[item_idx + 3].Length >= 2)
entry.Flags = BigEndian.ToUInt16 (Items[item_idx + 3], 0);
if (ItemsPerCast >= 4 && Items[item_idx + 4].Length >= 8)
{
entry.MinMember = BigEndian.ToUInt16 (Items[item_idx + 4], 0);
entry.MaxMember = BigEndian.ToUInt16 (Items[item_idx + 4], 2);
entry.Id = BigEndian.ToInt32 (Items[item_idx + 4], 4);
}
Entries.Add (entry);
}
}
string GetString (int item_idx, Encoding enc)
{
var src = Items[item_idx];
if (src.Length <= 1 || 0 == src[0])
return string.Empty;
int len = src[0];
return enc.GetString (src, 1, len);
}
}
internal class CastListEntry
{
public string Name;
public string Path;
public ushort Flags;
public int MinMember;
public int MaxMember;
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;
public short FileVersion;
public short StageTop;
public short StageLeft;
public short StageBottom;
public short StageRight;
public short MinMember;
public short MaxMember;
public ushort StageColor;
public ushort BitDepth;
public int Version;
public int FrameRate;
public int Platform;
public int Protection;
public uint CheckSum;
public int DefaultPalette;
public void Deserialize (SerializationContext context, Reader reader)
{
long base_offset = reader.Position;
reader = reader.CloneUnless (ByteOrder.BigEndian);
reader.Position = base_offset + 0x24;
Version = reader.ReadU16();
reader.Position = base_offset;
Length = reader.ReadI16();
FileVersion = reader.ReadI16();
StageTop = reader.ReadI16();
StageLeft = reader.ReadI16();
StageBottom = reader.ReadI16();
StageRight = reader.ReadI16();
MinMember = reader.ReadI16();
MaxMember = reader.ReadI16();
reader.Skip (0x0A);
StageColor = reader.ReadU16();
BitDepth = reader.ReadU16();
reader.Skip (0x18);
FrameRate = reader.ReadU16();
Platform = reader.ReadI16();
Protection = reader.ReadI16();
reader.Skip (4);
CheckSum = reader.ReadU32();
if (Version > 1200)
{
reader.Position = base_offset + 0x4E;
}
else
{
reader.Position = base_offset + 0x46;
}
DefaultPalette = reader.ReadU16();
}
}
internal class KeyTable
{
public int EntrySize;
public int TotalCount;
public int UsedCount;
public readonly List<KeyTableEntry> Table = new List<KeyTableEntry>();
public KeyTableEntry this[int index] => Table[index];
public KeyTableEntry FindByCast (int cast_id, string four_cc)
{
return Table.Find (e => e.CastId == cast_id && e.FourCC == four_cc);
}
public void Deserialize (SerializationContext context, Reader reader)
{
EntrySize = reader.ReadU16();
reader.Skip(2);
TotalCount = reader.ReadI32();
UsedCount = reader.ReadI32();
Table.Clear();
Table.Capacity = TotalCount;
for (int i = 0; i < TotalCount; ++i)
{
var entry = new KeyTableEntry();
entry.Deserialize (context, reader);
Table.Add (entry);
}
}
}
internal class KeyTableEntry
{
public int Id;
public int CastId;
public string FourCC;
public void Deserialize (SerializationContext context, Reader input)
{
Id = input.ReadI32();
CastId = input.ReadI32();
FourCC = input.ReadFourCC();
}
}
internal class MemoryMap
{
public ushort HeaderLength;
public ushort EntryLength;
public int ChunkCountMax;
public int ChunkCountUsed;
public int FreeHead;
public readonly List<MemoryMapEntry> Dir = new List<MemoryMapEntry>();
public MemoryMapEntry 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)
{
long header_pos = reader.Position;
HeaderLength = reader.ReadU16();
if (HeaderLength < 0x18)
throw new InvalidFormatException ("Invalid <mmap> header length.");
EntryLength = reader.ReadU16();
if (EntryLength < 0x14)
throw new InvalidFormatException ("Invalid <mmap> entry length.");
ChunkCountMax = reader.ReadI32();
ChunkCountUsed = reader.ReadI32();
reader.Skip (8);
FreeHead = reader.ReadI32();
Dir.Clear();
Dir.Capacity = ChunkCountUsed;
long entry_pos = header_pos + HeaderLength;
for (int i = 0; i < ChunkCountUsed; ++i)
{
reader.Position = entry_pos;
var entry = new MemoryMapEntry (i);
entry.Deserialize (context, reader);
Dir.Add (entry);
entry_pos += EntryLength;
}
}
}
internal class MemoryMapEntry : Entry
{
public int Id;
public string FourCC;
public ushort Flags;
public MemoryMapEntry (int id = 0)
{
Id = id;
}
public void Deserialize (SerializationContext context, Reader reader)
{
FourCC = reader.ReadFourCC();
Size = reader.ReadU32();
Offset = reader.ReadU32() + 8;
Flags = reader.ReadU16();
int Next = reader.ReadI32();
}
}
internal class Reader
{
Stream m_input;
byte[] m_buffer = new byte[4];
public Reader (Stream input, ByteOrder e = ByteOrder.LittleEndian) : this (input, Encodings.cp932, e)
{
}
public Reader (Stream input, Encoding enc, ByteOrder e = ByteOrder.LittleEndian)
{
m_input = input;
Encoding = enc;
SetByteOrder (e);
}
public Stream Source { get => m_input; }
public ByteOrder ByteOrder { get; private set; }
public Encoding Encoding { get; set; }
public long Position
{
get => m_input.Position;
set => m_input.Position = value;
}
private Func<ushort> ToU16;
private Func<uint> ToU32;
public void SetByteOrder (ByteOrder e)
{
this.ByteOrder = e;
if (ByteOrder.LittleEndian == e)
{
ToU16 = () => LittleEndian.ToUInt16 (m_buffer, 0);
ToU32 = () => LittleEndian.ToUInt32 (m_buffer, 0);
}
else
{
ToU16 = () => BigEndian.ToUInt16 (m_buffer, 0);
ToU32 = () => BigEndian.ToUInt32 (m_buffer, 0);
}
}
static Dictionary<uint, string> KnownFourCC = new Dictionary<uint, string>();
public string ReadFourCC ()
{
uint signature = ReadU32();
string four_cc;
if (KnownFourCC.TryGetValue (signature, out four_cc))
return four_cc;
BigEndian.Pack (signature, m_buffer, 0);
return KnownFourCC[signature] = Encoding.GetString (m_buffer, 0, 4);
}
public void Skip (int amount) => m_input.Seek (amount, SeekOrigin.Current);
public byte ReadU8 ()
{
int b = m_input.ReadByte();
if (-1 == b)
throw new EndOfStreamException();
return (byte)b;
}
public sbyte ReadI8 () => (sbyte)ReadU8();
public ushort ReadU16 ()
{
if (m_input.Read (m_buffer, 0, 2) < 2)
throw new EndOfStreamException();
return ToU16();
}
public short ReadI16 () => (short)ReadU16();
public uint ReadU32 ()
{
if (m_input.Read (m_buffer, 0, 4) < 4)
throw new EndOfStreamException();
return ToU32();
}
public int ReadI32 () => (int)ReadU32();
public byte[] ReadBytes (int length)
{
if (0 == length)
return Array.Empty<byte>();
var buffer = new byte[length];
if (m_input.Read (buffer, 0, length) < length)
throw new EndOfStreamException();
return buffer;
}
public Reader CloneUnless (ByteOrder order)
{
if (this.ByteOrder != order)
return new Reader (this.Source, this.Encoding, order);
else
return this;
}
}
}

View File

@ -29,9 +29,10 @@ using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging;
using GameRes.Utility; using GameRes.Utility;
namespace GameRes.Formats.Selen namespace GameRes.Formats.Macromedia
{ {
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
public class BitdFormat : ImageFormat public class BitdFormat : ImageFormat
@ -202,4 +203,134 @@ namespace GameRes.Formats.Selen
} }
} }
} }
internal class BitdDecoder : IImageDecoder
{
Stream m_input;
byte[] m_output;
int m_width;
int m_height;
int m_stride;
ImageMetaData m_info;
ImageData m_image;
BitmapPalette m_palette;
public Stream Source { get => m_input; }
public ImageFormat SourceFormat { get => null; }
public ImageMetaData Info { get => m_info; }
public ImageData Image { get => m_image ?? (m_image = GetImageData()); }
public PixelFormat Format { get; private set; }
public BitdDecoder (Stream input, ImageMetaData info, BitmapPalette palette)
{
m_input = input;
m_info = info;
m_width = info.iWidth;
m_height = info.iHeight;
m_stride = (m_width * m_info.BPP + 7) / 8;
m_stride = (m_stride + 1) & ~1;
m_output = new byte[m_stride * m_height];
Format = info.BPP == 4 ? PixelFormats.Indexed4
: info.BPP == 8 ? PixelFormats.Indexed8
: info.BPP == 16 ? PixelFormats.Bgr555
: PixelFormats.Bgr32;
m_palette = palette;
}
protected ImageData GetImageData ()
{
if (Info.BPP <= 8)
Unpack8bpp();
else
UnpackChannels (Info.BPP / 8);
return ImageData.Create (m_info, Format, m_palette, m_output, m_stride);
}
void Unpack8bpp ()
{
for (int line = 0; line < m_output.Length; line += m_stride)
{
int x = 0;
while (x < m_stride)
{
int b = m_input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
int count = b;
if (b > 0x7f)
count = (byte)-(sbyte)b;
++count;
if (x + count > m_stride)
throw new InvalidFormatException();
if (b > 0x7f)
{
b = m_input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
for (int i = 0; i < count; ++i)
m_output[line + x++] = (byte)b;
}
else
{
m_input.Read (m_output, line + x, count);
x += count;
}
}
}
}
public void UnpackChannels (int channels)
{
var scan_line = new byte[m_stride];
for (int line = 0; line < m_output.Length; line += m_stride)
{
int x = 0;
while (x < m_stride)
{
int b = m_input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
int count = b;
if (b > 0x7f)
count = (byte)-(sbyte)b;
++count;
if (x + count > m_stride)
throw new InvalidFormatException();
if (b > 0x7f)
{
b = m_input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
for (int i = 0; i < count; ++i)
scan_line[x++] = (byte)b;
}
else
{
m_input.Read (scan_line, x, count);
x += count;
}
}
int dst = line;
for (int i = 0; i < m_width; ++i)
{
for (int src = m_width * (channels - 1); src >= 0; src -= m_width)
m_output[dst++] = scan_line[i + src];
}
}
}
#region IDisposable Members
bool m_disposed = false;
public void Dispose ()
{
if (!m_disposed)
{
m_input.Dispose();
m_disposed = true;
}
GC.SuppressFinalize (this);
}
#endregion
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -142,7 +142,7 @@ namespace GameRes.Formats.Maika
Stream input; Stream input;
// XXX scrambling might be applicable for 'E1' signatures only // XXX scrambling might be applicable for 'E1' signatures only
if (scheme.ScrambledSize > 0) if (0x3145 == signature && scheme.ScrambledSize > 0)
{ {
var prefix = arc.File.View.ReadBytes (entry.Offset+10, scheme.ScrambledSize); var prefix = arc.File.View.ReadBytes (entry.Offset+10, scheme.ScrambledSize);
foreach (var pair in scheme.ScrambleMap) foreach (var pair in scheme.ScrambleMap)

View File

@ -176,6 +176,7 @@ namespace GameRes.Formats.Marble
if (3 == b) if (3 == b)
{ {
length += 9; length += 9;
// length = Math.Min(m_output.Length - dst, length);
int read = m_input.Read (m_output, dst, length); int read = m_input.Read (m_output, dst, length);
if (read < length) if (read < length)
break; break;

View File

@ -75,9 +75,7 @@ namespace GameRes.Formats
} }
part_offset = part_end_offset; part_offset = part_end_offset;
} }
if (null == input) return input ?? Stream.Null;
return Stream.Null;
return input;
} }
catch catch
{ {

103
ArcFormats/MyAdv/ArcPAC.cs Normal file
View File

@ -0,0 +1,103 @@
//! \file ArcPAC.cs
//! \date 2022 Jun 01
//! \brief MyAdv engine resource archive.
//
// Copyright (C) 2022 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using GameRes.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression;
namespace GameRes.Formats.MyAdv
{
[Export(typeof(ArchiveFormat))]
public class PacOpener : ArchiveFormat
{
public override string Tag { get { return "PAC/MyAdv"; } }
public override string Description { get { return "MyAdv engine resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
public PacOpener ()
{
ContainedFormats = new[] { "DDS", "OGG", "TXT" };
}
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0);
if (!IsSaneCount (count))
return null;
var name_buffer = new byte[0x100];
var zlib_buffer = new byte[0x100];
long pos = 4;
var zlib = new Inflater();
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
int name_len = file.View.ReadInt32 (pos);
if (name_len <= 0 || name_len > name_buffer.Length)
return null;
int unpacked_size = file.View.ReadInt32 (pos+4);
if (unpacked_size < name_len || unpacked_size > name_buffer.Length)
return null;
int packed_size = file.View.ReadInt32 (pos+8);
if (packed_size <= 0 || packed_size > zlib_buffer.Length)
return null;
pos += 12;
file.View.Read (pos, zlib_buffer, 0, (uint)packed_size);
pos += packed_size;
zlib.Reset();
zlib.SetInput (zlib_buffer, 0, packed_size);
zlib.Inflate (name_buffer);
var name = Encodings.cp932.GetString (name_buffer, 0, name_len);
var entry = Create<PackedEntry> (name);
dir.Add (entry);
}
foreach (PackedEntry entry in dir)
{
entry.Offset = file.View.ReadUInt32 (pos);
entry.Size = file.View.ReadUInt32 (pos+4);
entry.UnpackedSize = file.View.ReadUInt32 (pos+8);
entry.IsPacked = true;
pos += 12;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = entry as PackedEntry;
var input = arc.File.CreateStream (entry.Offset, entry.Size);
if (null == pent || !pent.IsPacked)
return input;
return new ZLibStream (input, CompressionMode.Decompress);
}
}
}

View File

@ -0,0 +1,53 @@
//! \file Script.cs
//! \date 2023 Aug 23
//! \brief
//
// 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.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.NScripter
{
[Export(typeof(ScriptFormat))]
public class NSOpener : GenericScriptFormat
{
public override string Tag { get => "NScripter"; }
public override string Description { get => "NScripter engine script file"; }
public override uint Signature { get => 0; }
public override bool IsScript (IBinaryStream file)
{
return VFS.IsPathEqualsToFileName (file.Name, "nscript.dat");
}
public override Stream ConvertFrom (IBinaryStream file)
{
return new XoredStream (file.AsStream, 0x84);
}
public override Stream ConvertBack (IBinaryStream file)
{
return new XoredStream (file.AsStream, 0x84);
}
}
}

View File

@ -377,256 +377,4 @@ namespace GameRes.Formats.Neko
0x58, 0xC5, 0x74, 0xB7, 0x8E, 0x7D, 0x89, 0x8A, 0x56, 0x4D, 0x86, 0x94, 0x9A, 0x4C, 0x92, 0xB0, 0x58, 0xC5, 0x74, 0xB7, 0x8E, 0x7D, 0x89, 0x8A, 0x56, 0x4D, 0x86, 0x94, 0x9A, 0x4C, 0x92, 0xB0,
}; };
} }
[Export(typeof(ArchiveFormat))]
public class Pak2Opener : ArchiveFormat
{
public override string Tag { get { return "NEKOPACK/2"; } }
public override string Description { get { return "NekoPack resource archive"; } }
public override uint Signature { get { return 0x4F4B454E; } } // "NEKO"
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
public Pak2Opener ()
{
Extensions = new string[] { "dat" };
}
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (4, "PACK"))
return null;
uint init_key = file.View.ReadUInt32 (0xC);
var xdec = new NekoXCode (init_key);
uint seed = file.View.ReadUInt32 (0x10);
var buffer = file.View.ReadBytes (0x14, 8);
xdec.Decrypt (seed, buffer, 0, 8);
uint index_size = LittleEndian.ToUInt32 (buffer, 0);
if (index_size < 0x14 || index_size != LittleEndian.ToUInt32 (buffer, 4))
return null;
var index = new byte[(index_size + 7u) & ~7u];
if (file.View.Read (0x1C, index, 0, index_size) < index_size)
return null;
xdec.Decrypt (seed, index, 0, index.Length);
using (var reader = new IndexReader (file, xdec, index, (int)index_size))
{
var dir = reader.Parse (0x1C+index.Length);
if (null == dir)
return null;
reader.DetectTypes (dir, entry => {
uint key = file.View.ReadUInt32 (entry.Offset);
file.View.Read (entry.Offset+12, buffer, 0, 8);
xdec.Decrypt (key, buffer, 0, 8);
return LittleEndian.ToUInt32 (buffer, 0);
});
return new NekoArchive (file, this, dir, xdec);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var narc = arc as NekoArchive;
if (null == narc || entry.Size <= 12)
return base.OpenEntry (arc, entry);
uint key = arc.File.View.ReadUInt32 (entry.Offset);
var data = new byte[entry.Size];
arc.File.View.Read (entry.Offset+4, data, 0, 8);
narc.Decoder.Decrypt (key, data, 0, 8);
int size = LittleEndian.ToInt32 (data, 0);
if (size != LittleEndian.ToInt32 (data, 4))
{
Trace.WriteLine ("entry decryption failed", "[NEKOPACK]");
return base.OpenEntry (arc, entry);
}
int aligned_size = (size + 7) & ~7;
if (aligned_size > data.Length)
data = new byte[aligned_size];
arc.File.View.Read (entry.Offset+12, data, 0, (uint)size);
narc.Decoder.Decrypt (key, data, 0, aligned_size);
return new BinMemoryStream (data, 0, size, entry.Name);
}
}
internal class NekoXCode : INekoFormat
{
uint m_seed;
uint[] m_random;
SimdProgram m_program;
public NekoXCode (uint init_key)
{
m_seed = init_key;
m_random = InitTable (init_key);
m_program = new SimdProgram (init_key);
}
public void Decrypt (uint key, byte[] input, int offset, int length)
{
for (int i = 1; i < 7; ++i)
{
uint src = key % 0x28 * 2;
m_program.mm[i] = m_random[src] | (ulong)m_random[src+1] << 32;
key /= 0x28;
}
m_program.Execute (input, offset, length);
}
public uint HashFromName (byte[] str, int offset, int length)
{
uint hash = m_seed;
for (int i = 0; i < length; ++i)
{
hash = 0x100002A * (ShiftMap[str[offset+i] & 0xFF] ^ hash);
}
return hash;
}
public DirRecord ReadDir (IBinaryStream input)
{
uint hash = input.ReadUInt32();
int count = input.ReadInt32();
if (count != input.ReadInt32())
throw new InvalidFormatException();
return new DirRecord { Hash = hash, FileCount = count };
}
public long NextOffset (Entry entry)
{
return entry.Offset + entry.Size;
}
static readonly byte[] ShiftMap = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc9, 0xca, 0x00, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0x00, 0xd2, 0xd3, 0x27, 0x25, 0xc8,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x00, 0xd4, 0x00, 0xd5, 0x00, 0x00,
0xd6, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0xd7, 0xc8, 0xd8, 0xd9, 0x26,
0xda, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0xdb, 0x00, 0xdc, 0xdd, 0x00,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
};
class SimdProgram
{
public ulong[] mm = new ulong[7];
Action[] m_transform = new Action[4];
Action[] m_shuffle = new Action[6];
Action<int>[] TransformList;
Action[] ShuffleList;
public SimdProgram (uint key)
{
TransformList = new Action<int>[] {
pxor, paddb, paddw, paddd, psubb, psubw, psubd,
pxor, psubb, psubw, psubd, paddb, paddw, paddd,
};
ShuffleList = new Action[] {
paddq_1_2, paddq_2_3, paddq_3_4, paddq_4_5, paddq_5_6, paddq_6_1,
};
GenerateProgram (key);
}
void pxor (int i) { mm[0] ^= mm[i]; }
void paddb (int i) { mm[0] = MMX.PAddB (mm[0], mm[i]); }
void paddw (int i) { mm[0] = MMX.PAddW (mm[0], mm[i]); }
void paddd (int i) { mm[0] = MMX.PAddD (mm[0], mm[i]); }
void psubb (int i) { mm[0] = MMX.PSubB (mm[0], mm[i]); }
void psubw (int i) { mm[0] = MMX.PSubW (mm[0], mm[i]); }
void psubd (int i) { mm[0] = MMX.PSubD (mm[0], mm[i]); }
void paddq_1_2 () { mm[1] += mm[2]; }
void paddq_2_3 () { mm[2] += mm[3]; }
void paddq_3_4 () { mm[3] += mm[4]; }
void paddq_4_5 () { mm[4] += mm[5]; }
void paddq_5_6 () { mm[5] += mm[6]; }
void paddq_6_1 () { mm[6] += mm[1]; }
void GenerateProgram (uint key)
{
int t1 = 7 + (int)(key >> 28);
int cmd_base = (int)key & 0xffff;
int arg_base = (int)(key >> 16) & 0xfff;
for (int i = 3; i >= 0; --i)
{
int cmd = ((cmd_base >> (4 * i)) + t1) % TransformList.Length;
int arg = (arg_base >> (3 * i)) % 6 + 1;
m_transform[3-i] = () => TransformList[cmd] (arg);
}
for (uint i = 0; i < 6; ++i)
{
m_shuffle[i] = ShuffleList[(i + key) % (uint)ShuffleList.Length];
}
}
public unsafe void Execute (byte[] input, int offset, int length)
{
if (offset < 0 || offset > input.Length)
throw new ArgumentException ("offset");
int count = Math.Min (length, input.Length-offset) / 8;
if (0 == count)
return;
fixed (byte* data = &input[offset])
{
ulong* data64 = (ulong*)data;
for (;;)
{
mm[0] = *data64;
foreach (var cmd in m_transform)
cmd();
*data64++ = mm[0];
if (1 == count--)
break;
foreach (var cmd in m_shuffle)
cmd();
}
}
}
}
static uint[] InitTable (uint key)
{
uint a = 0;
uint b = 0;
do
{
a <<= 1;
b ^= 1;
a = ((a | b) << (int)(key & 1)) | b;
key >>= 1;
}
while (0 == (a & 0x80000000));
key = a << 1;
a = key + Binary.BigEndian (key);
byte count = (byte)key;
do
{
b = key ^ a;
a = (b << 4) ^ (b >> 4) ^ (b << 3) ^ (b >> 3) ^ b;
}
while (--count != 0);
var table = new uint[154];
for (int i = 0; i < table.Length; ++i)
{
b = key ^ a;
a = (b << 4) ^ (b >> 4) ^ (b << 3) ^ (b >> 3) ^ b;
table[i] = a;
}
return table;
}
}
} }

View File

@ -0,0 +1,285 @@
//! \file ArcNEKO2.cs
//! \date 2022 Jun 17
//! \brief Nekopack archive format implementation.
//
// Copyright (C) 2015-2016 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using GameRes.Utility;
using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
namespace GameRes.Formats.Neko
{
[Export(typeof(ArchiveFormat))]
public class Pak2Opener : ArchiveFormat
{
public override string Tag { get { return "NEKOPACK/2"; } }
public override string Description { get { return "NekoPack resource archive"; } }
public override uint Signature { get { return 0x4F4B454E; } } // "NEKO"
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
public Pak2Opener ()
{
Extensions = new string[] { "dat" };
}
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (4, "PACK"))
return null;
uint init_key = file.View.ReadUInt32 (0xC);
var xdec = new NekoXCode (init_key);
uint seed = file.View.ReadUInt32 (0x10);
var buffer = file.View.ReadBytes (0x14, 8);
xdec.Decrypt (seed, buffer, 0, 8);
uint index_size = LittleEndian.ToUInt32 (buffer, 0);
if (index_size < 0x14 || index_size != LittleEndian.ToUInt32 (buffer, 4))
return null;
var index = new byte[(index_size + 7u) & ~7u];
if (file.View.Read (0x1C, index, 0, index_size) < index_size)
return null;
xdec.Decrypt (seed, index, 0, index.Length);
using (var reader = new IndexReader (file, xdec, index, (int)index_size))
{
var dir = reader.Parse (0x1C+index.Length);
if (null == dir)
return null;
reader.DetectTypes (dir, entry => {
uint key = file.View.ReadUInt32 (entry.Offset);
file.View.Read (entry.Offset+12, buffer, 0, 8);
xdec.Decrypt (key, buffer, 0, 8);
return LittleEndian.ToUInt32 (buffer, 0);
});
return new NekoArchive (file, this, dir, xdec);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var narc = arc as NekoArchive;
if (null == narc || entry.Size <= 12)
return base.OpenEntry (arc, entry);
uint key = arc.File.View.ReadUInt32 (entry.Offset);
var data = new byte[entry.Size];
arc.File.View.Read (entry.Offset+4, data, 0, 8);
narc.Decoder.Decrypt (key, data, 0, 8);
int size = LittleEndian.ToInt32 (data, 0);
if (size != LittleEndian.ToInt32 (data, 4))
{
Trace.WriteLine ("entry decryption failed", "[NEKOPACK]");
return base.OpenEntry (arc, entry);
}
int aligned_size = (size + 7) & ~7;
if (aligned_size > data.Length)
data = new byte[aligned_size];
arc.File.View.Read (entry.Offset+12, data, 0, (uint)size);
narc.Decoder.Decrypt (key, data, 0, aligned_size);
return new BinMemoryStream (data, 0, size, entry.Name);
}
}
internal class NekoXCode : INekoFormat
{
uint m_seed;
uint[] m_random;
SimdProgram m_program;
public NekoXCode (uint init_key)
{
m_seed = init_key;
m_random = InitTable (init_key);
m_program = new SimdProgram (init_key);
}
public void Decrypt (uint key, byte[] input, int offset, int length)
{
for (int i = 1; i < 7; ++i)
{
uint src = key % 0x28 * 2;
m_program.mm[i] = m_random[src] | (ulong)m_random[src+1] << 32;
key /= 0x28;
}
m_program.Execute (input, offset, length);
}
public uint HashFromName (byte[] str, int offset, int length)
{
uint hash = m_seed;
for (int i = 0; i < length; ++i)
{
hash = 0x100002A * (ShiftMap[str[offset+i] & 0xFF] ^ hash);
}
return hash;
}
public DirRecord ReadDir (IBinaryStream input)
{
uint hash = input.ReadUInt32();
int count = input.ReadInt32();
if (count != input.ReadInt32())
throw new InvalidFormatException();
return new DirRecord { Hash = hash, FileCount = count };
}
public long NextOffset (Entry entry)
{
return entry.Offset + entry.Size;
}
static readonly byte[] ShiftMap = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc9, 0xca, 0x00, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0x00, 0xd2, 0xd3, 0x27, 0x25, 0xc8,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x00, 0xd4, 0x00, 0xd5, 0x00, 0x00,
0xd6, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0xd7, 0xc8, 0xd8, 0xd9, 0x26,
0xda, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0xdb, 0x00, 0xdc, 0xdd, 0x00,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
};
class SimdProgram
{
public ulong[] mm = new ulong[7];
Action[] m_transform = new Action[4];
Action[] m_shuffle = new Action[6];
Action<int>[] TransformList;
Action[] ShuffleList;
public SimdProgram (uint key)
{
TransformList = new Action<int>[] {
pxor, paddb, paddw, paddd, psubb, psubw, psubd,
pxor, psubb, psubw, psubd, paddb, paddw, paddd,
};
ShuffleList = new Action[] {
paddq_1_2, paddq_2_3, paddq_3_4, paddq_4_5, paddq_5_6, paddq_6_1,
};
GenerateProgram (key);
}
void pxor (int i) { mm[0] ^= mm[i]; }
void paddb (int i) { mm[0] = MMX.PAddB (mm[0], mm[i]); }
void paddw (int i) { mm[0] = MMX.PAddW (mm[0], mm[i]); }
void paddd (int i) { mm[0] = MMX.PAddD (mm[0], mm[i]); }
void psubb (int i) { mm[0] = MMX.PSubB (mm[0], mm[i]); }
void psubw (int i) { mm[0] = MMX.PSubW (mm[0], mm[i]); }
void psubd (int i) { mm[0] = MMX.PSubD (mm[0], mm[i]); }
void paddq_1_2 () { mm[1] += mm[2]; }
void paddq_2_3 () { mm[2] += mm[3]; }
void paddq_3_4 () { mm[3] += mm[4]; }
void paddq_4_5 () { mm[4] += mm[5]; }
void paddq_5_6 () { mm[5] += mm[6]; }
void paddq_6_1 () { mm[6] += mm[1]; }
void GenerateProgram (uint key)
{
int t1 = 7 + (int)(key >> 28);
int cmd_base = (int)key & 0xffff;
int arg_base = (int)(key >> 16) & 0xfff;
for (int i = 3; i >= 0; --i)
{
int cmd = ((cmd_base >> (4 * i)) + t1) % TransformList.Length;
int arg = (arg_base >> (3 * i)) % 6 + 1;
m_transform[3-i] = () => TransformList[cmd] (arg);
}
for (uint i = 0; i < 6; ++i)
{
m_shuffle[i] = ShuffleList[(i + key) % (uint)ShuffleList.Length];
}
}
public unsafe void Execute (byte[] input, int offset, int length)
{
if (offset < 0 || offset > input.Length)
throw new ArgumentException ("offset");
int count = Math.Min (length, input.Length-offset) / 8;
if (0 == count)
return;
fixed (byte* data = &input[offset])
{
ulong* data64 = (ulong*)data;
for (;;)
{
mm[0] = *data64;
foreach (var cmd in m_transform)
cmd();
*data64++ = mm[0];
if (1 == count--)
break;
foreach (var cmd in m_shuffle)
cmd();
}
}
}
}
static uint[] InitTable (uint key)
{
uint a = 0;
uint b = 0;
do
{
a <<= 1;
b ^= 1;
a = ((a | b) << (int)(key & 1)) | b;
key >>= 1;
}
while (0 == (a & 0x80000000));
key = a << 1;
a = key + Binary.BigEndian (key);
byte count = (byte)key;
do
{
b = key ^ a;
a = (b << 4) ^ (b >> 4) ^ (b << 3) ^ (b >> 3) ^ b;
}
while (--count != 0);
var table = new uint[154];
for (int i = 0; i < table.Length; ++i)
{
b = key ^ a;
a = (b << 4) ^ (b >> 4) ^ (b << 3) ^ (b >> 3) ^ b;
table[i] = a;
}
return table;
}
}
}

View File

@ -0,0 +1,157 @@
//! \file ArcNEKO3.cs
//! \date 2022 Jun 17
//! \brief Nekopack archive format implementation.
//
// Copyright (C) 2022 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using GameRes.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Neko
{
internal class Neko3Archive : ArcFile
{
public readonly byte[] Key;
public Neko3Archive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, byte[] key)
: base (arc, impl, dir)
{
Key = key;
}
}
internal class Neko3Entry : Entry
{
public ushort Seed;
}
[Export(typeof(ArchiveFormat))]
public class Pak3Opener : ArchiveFormat
{
public override string Tag { get { return "NEKOPACK/3"; } }
public override string Description { get { return "NekoPack resource archive"; } }
public override uint Signature { get { return 0x4F4B454E; } } // "NEKO"
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
public Pak3Opener ()
{
Extensions = new string[] { "dat" };
}
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (4, "PACK") || file.MaxOffset <= 0x410)
return null;
uint seed = file.View.ReadUInt32 (0xC);
int count = (int)(seed % 7u) + 3;
var key = file.View.ReadBytes (0x10, 0x400);
while (count --> 0)
{
Decrypt (key, 0x400, key, (ushort)seed, true);
}
seed = file.View.ReadUInt16 (0x410);
var index_info = file.View.ReadBytes (0x414, 8);
Decrypt (index_info, 8, key, (ushort)seed);
uint index_size = LittleEndian.ToUInt32 (index_info, 0);
long data_offset = 0x41CL + index_size;
if (data_offset >= file.MaxOffset)
return null;
var index = file.View.ReadBytes (0x41C, index_size);
Decrypt (index, index.Length, key, (ushort)seed);
var dir = new List<Entry>();
using (var input = new BinMemoryStream (index, file.Name))
{
int dir_count = input.ReadInt32();
if (!IsSaneCount (dir_count))
return null;
for (int d = 0; d < dir_count; ++d)
{
int name_len = input.ReadUInt8();
string dir_name = input.ReadCString (name_len);
if (string.IsNullOrEmpty (dir_name))
return null;
int file_count = input.ReadInt32();
if (!IsSaneCount (file_count))
return null;
for (int i = 0; i < file_count; ++i)
{
input.ReadByte();
name_len = input.ReadUInt8();
string name = input.ReadCString (name_len);
name = string.Join ("/", dir_name, name);
var entry = Create<Neko3Entry> (name);
entry.Offset = data_offset + input.ReadUInt32();
dir.Add (entry);
}
}
}
var buffer = new byte[12];
foreach (Neko3Entry entry in dir)
{
entry.Seed = file.View.ReadUInt16 (entry.Offset);
file.View.Read (entry.Offset+4, buffer, 0, 8);
Decrypt (buffer, 8, key, entry.Seed);
entry.Size = LittleEndian.ToUInt32 (buffer, 0);
entry.Offset += 12;
}
return new Neko3Archive (file, this, dir, key);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var narc = (Neko3Archive)arc;
var nent = (Neko3Entry)entry;
var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
Decrypt (data, data.Length, narc.Key, nent.Seed);
return new BinMemoryStream (data, entry.Name);
}
void Decrypt (byte[] data, int length, byte[] key, ushort seed, bool init = false)
{
int count = length / 4;
unsafe
{
fixed (byte* data8 = data)
{
uint* data32 = (uint*)data8;
while (count --> 0)
{
uint s = *data32;
seed = (ushort)((seed + 0xC3) & 0x1FF);
uint d = s ^ LittleEndian.ToUInt32 (key, seed);
if (init)
seed += (ushort)s;
else
seed += (ushort)d;
*data32++ = d;
}
}
}
}
}
}

View File

@ -34,6 +34,24 @@ namespace GameRes.Formats.Nonono
public int Seed; public int Seed;
} }
internal class NpfArchive : ArcFile
{
public readonly IRandomGenerator KeyGenerator;
public NpfArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, IRandomGenerator rnd)
: base (arc, impl, dir)
{
KeyGenerator = rnd;
}
}
internal interface IRandomGenerator
{
void SRand (int seed);
int Rand ();
}
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
public class NpfOpener : ArchiveFormat public class NpfOpener : ArchiveFormat
{ {
@ -49,8 +67,28 @@ namespace GameRes.Formats.Nonono
{ {
if (file.View.ReadInt32 (4) != 4 || file.View.ReadInt32 (8) != 1) if (file.View.ReadInt32 (4) != 4 || file.View.ReadInt32 (8) != 1)
return null; return null;
foreach (var rnd in GetGenerators())
{
var dir = ReadIndex (file, rnd);
if (dir != null)
{
return new NpfArchive (file, this, dir, rnd);
}
}
return null;
}
internal IEnumerable<IRandomGenerator> GetGenerators ()
{
yield return new RandomGenerator1 (DefaultSeed);
yield return new RandomGenerator2 (DefaultSeed);
}
List<Entry> ReadIndex (ArcView file, IRandomGenerator rnd)
{
var header = file.View.ReadBytes (12, 20); var header = file.View.ReadBytes (12, 20);
var rnd = new RandomGenerator (DefaultSeed); // rnd.SRand (DefaultSeed); // generator already seeded by GetGenerators()
Decrypt (header, 0, header.Length, rnd); Decrypt (header, 0, header.Length, rnd);
if (!header.AsciiEqual ("FAT ")) if (!header.AsciiEqual ("FAT "))
return null; return null;
@ -84,16 +122,16 @@ namespace GameRes.Formats.Nonono
pos += 20; pos += 20;
name_pos += name_length; name_pos += name_length;
} }
return new ArcFile (file, this, dir); return dir;
} }
public override Stream OpenEntry (ArcFile arc, Entry entry) public override Stream OpenEntry (ArcFile arc, Entry entry)
{ {
var nent = entry as NpfEntry; var narc = (NpfArchive)arc;
if (null == nent) var nent = (NpfEntry)entry;
return base.OpenEntry (arc, entry);
var data = arc.File.View.ReadBytes (entry.Offset, entry.Size); var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
var rnd = new RandomGenerator (nent.Seed); var rnd = narc.KeyGenerator;
rnd.SRand (nent.Seed);
Decrypt (data, 0, data.Length, rnd); Decrypt (data, 0, data.Length, rnd);
return new BinMemoryStream (data, entry.Name); return new BinMemoryStream (data, entry.Name);
} }
@ -106,20 +144,20 @@ namespace GameRes.Formats.Nonono
return new ImgXDecoder (input); return new ImgXDecoder (input);
} }
internal void Decrypt (byte[] data, int pos, int count, RandomGenerator rnd) internal void Decrypt (byte[] data, int pos, int count, IRandomGenerator rnd)
{ {
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
data[pos + i] ^= (byte)rnd.Rand(); data[pos + i] ^= (byte)rnd.Rand();
} }
} }
internal class RandomGenerator internal class RandomGenerator1 : IRandomGenerator
{ {
int m_seed; int m_seed;
const int DefaultSeed = 0x67895; const int DefaultSeed = 0x67895;
public RandomGenerator (int seed = DefaultSeed) public RandomGenerator1 (int seed = DefaultSeed)
{ {
SRand (seed); SRand (seed);
} }
@ -141,4 +179,30 @@ namespace GameRes.Formats.Nonono
return m_seed; return m_seed;
} }
} }
internal class RandomGenerator2 : IRandomGenerator
{
int m_seed1;
int m_seed2;
const int DefaultSeed = 0x67895;
public RandomGenerator2 (int seed = DefaultSeed)
{
SRand (seed);
}
public void SRand (int seed)
{
m_seed1 = seed;
m_seed2 = ((seed >> 12) ^ (seed << 18)) - 0x579E2B8D;
}
public int Rand ()
{
int n = m_seed2 + ((m_seed1 >> 10) ^ (m_seed1 << 14));
m_seed2 = n - 0x15633649 + ((m_seed2 >> 12) ^ (m_seed2 << 18));
return m_seed2;
}
}
} }

View File

@ -64,7 +64,12 @@ namespace GameRes.Formats.Nonono
using (var input = new MemoryStream (bitmap, header_size, bitmap.Length - header_size)) using (var input = new MemoryStream (bitmap, header_size, bitmap.Length - header_size))
{ {
if (8 == m_info.BPP) if (8 == m_info.BPP)
palette = ImageFormat.ReadPalette (input); {
int num_colors = bitmap.ToInt32 (0x20);
if (0 == num_colors)
num_colors = 0x100;
palette = ImageFormat.ReadPalette (input, num_colors);
}
input.Read (pixels, 0, pixels.Length); input.Read (pixels, 0, pixels.Length);
} }
PixelFormat format = 8 == m_info.BPP ? PixelFormats.Indexed8 PixelFormat format = 8 == m_info.BPP ? PixelFormats.Indexed8

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.2.48.2176")] [assembly: AssemblyVersion ("1.2.48.2200")]
[assembly: AssemblyFileVersion ("1.2.48.2176")] [assembly: AssemblyFileVersion ("1.2.48.2200")]

View File

@ -12,7 +12,7 @@ namespace GameRes.Formats.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")]
public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@ -801,5 +801,17 @@ namespace GameRes.Formats.Properties {
this["SJDatTitle"] = value; this["SJDatTitle"] = value;
} }
} }
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("932")]
public int AFAEncodingCP {
get {
return ((int)(this["AFAEncodingCP"]));
}
set {
this["AFAEncodingCP"] = value;
}
}
} }
} }

View File

@ -197,5 +197,8 @@
<Setting Name="SJDatTitle" Type="System.String" Scope="User"> <Setting Name="SJDatTitle" Type="System.String" Scope="User">
<Value Profile="(Default)" /> <Value Profile="(Default)" />
</Setting> </Setting>
<Setting Name="AFAEncodingCP" Type="System.Int32" Scope="User">
<Value Profile="(Default)">932</Value>
</Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>

View File

@ -84,6 +84,9 @@ namespace GameRes.Formats.Rpm
/// <summary>Minimum entry length across all possible archive schemes.</summary> /// <summary>Minimum entry length across all possible archive schemes.</summary>
const int MinEntryLength = 0x24; const int MinEntryLength = 0x24;
// largest size should be first
static readonly int[] PossibleNameSizes = new[] { 0x20, 0x18 };
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
int count = file.View.ReadInt32 (0); int count = file.View.ReadInt32 (0);
@ -94,7 +97,7 @@ namespace GameRes.Formats.Rpm
return null; return null;
var index_reader = new ArcIndexReader (file, count, is_compressed != 0); var index_reader = new ArcIndexReader (file, count, is_compressed != 0);
var scheme = index_reader.GuessScheme (8, new int[] { 0x20, 0x18 }); var scheme = index_reader.GuessScheme (8, PossibleNameSizes);
// additional checks to avoid dialog popup on false positives // additional checks to avoid dialog popup on false positives
if (null == scheme && KnownSchemes.Count > 0 && file.Name.HasExtension (".arc")) if (null == scheme && KnownSchemes.Count > 0 && file.Name.HasExtension (".arc"))
{ {

View File

@ -28,6 +28,9 @@ using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
// [960920][Petit] Trouble Outsiders
// [980220][Euphony Production] Happening Journey
namespace GameRes.Formats.ArchAngel namespace GameRes.Formats.ArchAngel
{ {
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
@ -44,7 +47,7 @@ namespace GameRes.Formats.ArchAngel
ContainedFormats = new[] { "CB" }; ContainedFormats = new[] { "CB" };
} }
static readonly string[] DefaultSections = { "image", "script", null }; static readonly string[] DefaultSections = { "image", "script", "" };
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
@ -54,13 +57,7 @@ namespace GameRes.Formats.ArchAngel
int file_count = file.View.ReadInt16 (0); int file_count = file.View.ReadInt16 (0);
if (!IsSaneCount (file_count)) if (!IsSaneCount (file_count))
return null; return null;
uint index_pos = 2; long index_pos = 2 + 4 * file_count;
var size_table = new uint[file_count];
for (int i = 0; i < file_count; ++i)
{
size_table[i] = file.View.ReadUInt32 (index_pos);
index_pos += 4;
}
var section_table = new SortedDictionary<int, uint>(); var section_table = new SortedDictionary<int, uint>();
uint min_offset = (uint)file.MaxOffset; uint min_offset = (uint)file.MaxOffset;
while (index_pos + 6 <= min_offset) while (index_pos + 6 <= min_offset)
@ -76,26 +73,34 @@ namespace GameRes.Formats.ArchAngel
} }
var dir = new List<Entry> (file_count); var dir = new List<Entry> (file_count);
int section_num = 0; int section_num = 0;
Func<string> get_type;
if (section_table.Count == DefaultSections.Length)
get_type = () => DefaultSections[section_num];
else
get_type = () => section_num > 0 ? "image" : "";
foreach (var section in section_table) foreach (var section in section_table)
{ {
int i = section.Key; int i = section.Key;
uint base_offset = section.Value; uint base_offset = section.Value;
do do
{ {
uint size = size_table[i]; uint size = file.View.ReadUInt32 (2 + i * 4);
var entry = new PackedEntry { if (size > 0)
Name = string.Format ("{0}-{1:D6}", section_num, i), {
Offset = base_offset, var entry = new PackedEntry
Size = size, {
}; Name = string.Format("{0}-{1:D6}", section_num, i),
if (!entry.CheckPlacement (file.MaxOffset)) Type = get_type(),
return null; Offset = base_offset,
if (section_num < DefaultSections.Length && DefaultSections[section_num] != null) Size = size,
entry.Type = DefaultSections[section_num]; };
if ("script" == entry.Type) if (!entry.CheckPlacement(file.MaxOffset))
entry.IsPacked = true; return null;
dir.Add (entry); if ("script" == entry.Type)
base_offset += size; entry.IsPacked = true;
dir.Add(entry);
base_offset += size;
}
++i; ++i;
} }
while (i < file_count && !section_table.ContainsKey (i)); while (i < file_count && !section_table.ContainsKey (i));

View File

@ -85,6 +85,14 @@ namespace GameRes.Formats.Seraphim
{ {
input = arc.File.CreateStream (entry.Offset+4, entry.Size-4); input = arc.File.CreateStream (entry.Offset+4, entry.Size-4);
return new ZLibStream (input.AsStream, CompressionMode.Decompress); return new ZLibStream (input.AsStream, CompressionMode.Decompress);
/*
using (var compr = new ZLibStream (input.AsStream, CompressionMode.Decompress))
using (var bin = new BinaryStream (compr, entry.Name))
{
var data = LzDecompress (bin);
return new BinMemoryStream (data, entry.Name);
}
*/
} }
input = arc.File.CreateStream (entry.Offset, entry.Size); input = arc.File.CreateStream (entry.Offset, entry.Size);
if (signature < 4 || 0 != (signature & 0xFF000000)) if (signature < 4 || 0 != (signature & 0xFF000000))
@ -141,4 +149,72 @@ namespace GameRes.Formats.Seraphim
return data; return data;
} }
} }
[Export(typeof(ArchiveFormat))]
public class Scn95Opener : ArchiveFormat
{
public override string Tag { get { return "SCN/ARCH"; } }
public override string Description { get { return "Archangel engine scripts archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public Scn95Opener ()
{
Extensions = new[] { "dat" };
}
public override ArcFile TryOpen (ArcView file)
{
if (!VFS.IsPathEqualsToFileName (file.Name, "SCNPAC.DAT"))
return null;
uint offset = file.View.ReadUInt32 (0);
int count = (int)offset / 4;
if (offset >= file.MaxOffset || !IsSaneCount (count))
return null;
int index_offset = 4;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
uint size = file.View.ReadUInt32 (index_offset);
if (0 == size)
return null;
var entry = new Entry {
Name = i.ToString ("D5"),
Type = "script",
Offset = offset + 4,
Size = size,
};
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
offset += size;
index_offset += 4;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
IBinaryStream input = arc.File.CreateStream (entry.Offset, entry.Size);
if (input.Signature < 4 || 0 != (input.Signature & 0xFF000000))
{
return input.AsStream;
}
try
{
var data = ScnOpener.LzDecompress (input);
return new BinMemoryStream (data, entry.Name);
}
catch
{
return arc.File.CreateStream (entry.Offset, entry.Size);
}
finally
{
input.Dispose();
}
}
}
} }

View File

@ -120,6 +120,8 @@ namespace GameRes.Formats.Seraphim
List<Entry> ReadIndex (ArcView file, long index_offset, long max_offset) List<Entry> ReadIndex (ArcView file, long index_offset, long max_offset)
{ {
if (index_offset >= max_offset)
return null;
int base_count = file.View.ReadInt32 (index_offset); int base_count = file.View.ReadInt32 (index_offset);
int file_count = file.View.ReadInt32 (index_offset + 4); int file_count = file.View.ReadInt32 (index_offset + 4);
index_offset += 8; index_offset += 8;
@ -220,7 +222,9 @@ namespace GameRes.Formats.Seraphim
{ {
uint width = input.ReadUInt16(); uint width = input.ReadUInt16();
uint height = input.ReadUInt16(); uint height = input.ReadUInt16();
if (width > 0x4100 || 0 == width || 0 == height || width * height * 3 + 4 != input.Length) uint plane_size = width * height;
uint total_size = (uint)(input.Length - 4);
if (width > 0x4100 || 0 == width || 0 == height || (total_size % plane_size) != 0)
{ {
input.Position = 0; input.Position = 0;
return new ImageFormatDecoder (input); return new ImageFormatDecoder (input);

View File

@ -47,7 +47,7 @@ namespace GameRes.Formats.Seraphim
Extensions = new string[] { "dat" }; Extensions = new string[] { "dat" };
} }
static readonly Regex VoiceRe = new Regex (@"^Voice\d\.dat$", RegexOptions.IgnoreCase); static readonly Regex VoiceRe = new Regex (@"^Voice(?:\d|pac)\.dat$", RegexOptions.IgnoreCase);
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {

View File

@ -47,12 +47,15 @@ namespace GameRes.Formats.Seraphim
public SeraphCfImage () public SeraphCfImage ()
{ {
Extensions = new string[] { "cts" }; Signatures = new [] { 0x4643u, 0x024643u, 0x044643u, 0x074643u, 0x094643u, 0x144643u, 0u };
Extensions = new [] { "cts" };
} }
public override ImageMetaData ReadMetaData (IBinaryStream stream) public override ImageMetaData ReadMetaData (IBinaryStream stream)
{ {
var header = stream.ReadHeader (0x10); var header = stream.ReadHeader (0x10);
if ('C' != header[0] || 'F' != header[1] || 0 != header[3])
return null;
int packed_size = header.ToInt32 (12); int packed_size = header.ToInt32 (12);
if (packed_size <= 0 || packed_size > stream.Length-0x10) if (packed_size <= 0 || packed_size > stream.Length-0x10)
return null; return null;

139
ArcFormats/Sohfu/ArcSKA.cs Normal file
View File

@ -0,0 +1,139 @@
//! \file ArcSKA.cs
//! \date 2022 Jun 12
//! \brief Sohfu resource archive.
//
// Copyright (C) 2022 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using GameRes.Utility;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Sohfu
{
[Export(typeof(ArchiveFormat))]
public class SkaOpener : ArchiveFormat
{
public override string Tag { get { return "SKA/SOHFU"; } }
public override string Description { get { return "Sohfu resource archive"; } }
public override uint Signature { get { return 0x32465049; } } // 'IPF2'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
uint index_pos = 8;
var name_buffer = new byte[0x10];
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
file.View.Read (index_pos, name_buffer, 0, 0x10);
int name_len = 0;
while (name_len < name_buffer.Length && name_buffer[name_len] != 0)
++name_len;
var name = Encodings.cp932.GetString (name_buffer, 0, name_len);
++name_len;
string ext = null;
if (name_len < 0x10)
ext = Binary.GetCString (name_buffer, name_len, 0x10 - name_len);
if (!string.IsNullOrEmpty (ext))
name = Path.ChangeExtension (name, ext);
var entry = Create<PackedEntry> (name);
entry.Offset = file.View.ReadUInt32 (index_pos+0x10);
entry.Size = file.View.ReadUInt32 (index_pos+0x14);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_pos += 0x18;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
var input = arc.File.CreateStream (entry.Offset, entry.Size);
if (!pent.IsPacked)
{
if (input.Signature != 0x4238534C) // 'LS8B'
return input;
pent.IsPacked = true;
pent.UnpackedSize = arc.File.View.ReadUInt32 (pent.Offset+4);
}
using (input)
{
var data = new byte[pent.UnpackedSize];
input.Position = 0xC;
LzssUnpack (input, data);
return new BinMemoryStream (data);
}
}
void LzssUnpack (IBinaryStream input, byte[] output)
{
byte[] frame = new byte[0x1000];
int frame_pos = 0xFFF;
const int frame_mask = 0xFFF;
int ctl = 1;
int dst = 0;
while (dst < output.Length)
{
if (1 == ctl)
{
ctl = input.ReadByte();
if (-1 == ctl)
break;
ctl |= 0x100;
}
if (0 == (ctl & 1))
{
int b = input.ReadByte();
if (-1 == b)
break;
frame[++frame_pos & frame_mask] = (byte)b;
output[dst++] = (byte)b;
}
else
{
int lo = input.ReadByte();
if (-1 == lo)
break;
int hi = input.ReadByte();
if (-1 == hi)
break;
int offset = hi << 4 | lo >> 4;
for (int count = 3 + (lo & 0xF); count != 0; --count)
{
byte v = frame[(offset + frame_pos++ - 0xFFF) & frame_mask];
frame[frame_pos & frame_mask] = v;
output[dst++] = v;
}
}
ctl >>= 1;
}
}
}
}

View File

@ -0,0 +1,146 @@
//! \file ImageDTL.cs
//! \date 2022 Jun 12
//! \brief Sohfu image format;
//
// Copyright (C) 2022 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
namespace GameRes.Formats.Sohfu
{
internal class DtlMetaData : ImageMetaData
{
public int Stride;
}
[Export(typeof(ImageFormat))]
public class DtlFormat : ImageFormat
{
public override string Tag { get { return "DTL/SOHFU"; } }
public override string Description { get { return "Sohfu image format"; } }
public override uint Signature { get { return 0x5F4C5444; } } // 'DTL_'
public DtlFormat ()
{
Extensions = new[] { "ls8b", "ls8" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x18);
int bpp = header.ToInt32 (16);
if (bpp != 24 && bpp != 32 && bpp != 8 && bpp != 4)
return null;
return new DtlMetaData
{
Width = header.ToUInt32 (8),
Height = header.ToUInt32 (12),
BPP = bpp,
Stride = header.ToInt32 (20),
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var meta = (DtlMetaData)info;
file.Position = 0x18;
var pixels = file.ReadBytes (meta.Stride * meta.iHeight);
PixelFormat format;
if (24 == meta.BPP)
format = PixelFormats.Bgr24;
else if (32 == meta.BPP)
format = PixelFormats.Bgra32;
else if (4 == meta.BPP)
format = PixelFormats.Gray4;
else
format = PixelFormats.Gray8;
return ImageData.Create (meta, format, null, pixels, meta.Stride);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("DtlFormat.Write not implemented");
}
}
[Export(typeof(ImageFormat))]
public class DtlcFormat : ImageFormat
{
public override string Tag { get { return "DTLC/SOHFU"; } }
public override string Description { get { return "Sohfu image format"; } }
public override uint Signature { get { return 0x434C5444; } } // 'DTLC'
public DtlcFormat ()
{
Extensions = new[] { "ls8b", "ls8" };
Signatures = new[] { 0x434C5444u, 0x414C5444u }; // 'DTLA'
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x18);
int bpp = header.ToInt32 (16);
if (bpp != 24 && bpp != 32)
return null;
return new DtlMetaData
{
Width = header.ToUInt32 (8),
Height = header.ToUInt32 (12),
BPP = bpp,
Stride = header.ToInt32 (20),
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var meta = (DtlMetaData)info;
// var lineBuffer = new int[meta.iHeight][];
file.Position = 0x18;
for (int y = 0; y < meta.iHeight; ++y)
{
int n = file.ReadInt32();
// lineBuffer[y] = new int[n * 2];
for (int i = 0; i < n; ++i)
{
// lineBuffer[y][i*2] = file.ReadInt32(); // x
// lineBuffer[y][i*2+1] = file.ReadInt32(); // number of pixels
file.ReadInt32();
file.ReadInt32();
}
}
var pixels = file.ReadBytes (meta.Stride * meta.iHeight);
PixelFormat format;
if (24 == meta.BPP)
format = PixelFormats.Bgr24;
else
format = PixelFormats.Bgra32;
return ImageData.Create (meta, format, null, pixels, meta.Stride);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("DtlcFormat.Write not implemented");
}
}
}

View File

@ -37,6 +37,11 @@ namespace GameRes.Formats.Will
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
public BmxOpener ()
{
ContainedFormats = new[] { "BC" };
}
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
uint total_size = file.View.ReadUInt32 (0); uint total_size = file.View.ReadUInt32 (0);
@ -48,34 +53,29 @@ namespace GameRes.Formats.Will
var dir = new List<Entry> (count); var dir = new List<Entry> (count);
uint index_offset = 0x10; uint index_offset = 0x10;
uint next_offset = file.View.ReadUInt32 (index_offset+0x1C);
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
var name = file.View.ReadString (index_offset, 0x1C); var name = file.View.ReadString (index_offset, 0x1C);
if (0 == name.Length) if (0 == name.Length)
break; break;
index_offset += 0x20;
var entry = FormatCatalog.Instance.Create<Entry> (name); var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = next_offset; entry.Offset = file.View.ReadUInt32 (index_offset+0x1C);
if (i+1 < count)
{
next_offset = file.View.ReadUInt32 (index_offset+0x1C);
if (0 == next_offset)
next_offset = (uint)file.MaxOffset;
}
else
{
next_offset = (uint)file.MaxOffset;
}
entry.Size = (uint)(next_offset - entry.Offset);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
if (string.IsNullOrEmpty (entry.Type)) if (string.IsNullOrEmpty (entry.Type))
entry.Type = "image"; entry.Type = "image";
dir.Add (entry); dir.Add (entry);
index_offset += 0x20;
} }
if (0 == dir.Count) if (0 == dir.Count)
return null; return null;
long last_offset = file.MaxOffset;
for (int i = dir.Count - 1; i >= 0; --i)
{
var entry = dir[i];
entry.Size = (uint)(last_offset - entry.Offset);
last_offset = entry.Offset;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
}
return new ArcFile (file, this, dir); return new ArcFile (file, this, dir);
} }
} }

View File

@ -40,6 +40,7 @@ namespace GameRes.Formats.Will
public MbfOpener () public MbfOpener ()
{ {
Signatures = new uint[] { 0x3046424D, 0x3146424D }; Signatures = new uint[] { 0x3046424D, 0x3146424D };
ContainedFormats = new[] { "BC" };
} }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)

View File

@ -43,6 +43,7 @@ namespace GameRes.Formats.Will
{ {
Extensions = new string[] { "vpk" }; Extensions = new string[] { "vpk" };
Signatures = new uint[] { 0x314B5056, 0x304B5056 }; Signatures = new uint[] { 0x314B5056, 0x304B5056 };
ContainedFormats = new[] { "WAV" };
} }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)

View File

@ -43,6 +43,7 @@ namespace GameRes.Formats.Will
{ {
Extensions = new string[] { "wsm" }; Extensions = new string[] { "wsm" };
Signatures = new uint[] { 0x324D5357, 0x334D5357 }; Signatures = new uint[] { 0x324D5357, 0x334D5357 };
ContainedFormats = new[] { "WAV" };
} }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
@ -71,6 +72,7 @@ namespace GameRes.Formats.Will
dir.Add (entry); dir.Add (entry);
} }
int index_offset = 0; int index_offset = 0;
var names = new HashSet<string>();
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
int entry_pos = index.ToInt32 (index_offset); int entry_pos = index.ToInt32 (index_offset);
@ -83,8 +85,20 @@ namespace GameRes.Formats.Will
int entry_idx = index[entry_pos+3]; int entry_idx = index[entry_pos+3];
if (entry_idx >= dir.Count) if (entry_idx >= dir.Count)
return null; return null;
if (0 == entry_idx)
entry_idx = i;
var entry = dir[entry_idx]; var entry = dir[entry_idx];
entry.Name = string.Format ("{0:D2}_{1}.wav", entry_idx, name); entry.Name = name + ".wav";
names.Add (entry.Name);
}
if (names.Count != dir.Count)
{
// make filenames unique by prepending index number
for (int i = 0; i < dir.Count; ++i)
{
var entry = dir[i];
entry.Name = string.Format("{0:D2}_{1}", i, entry.Name);
}
} }
return new ArcFile (file, this, dir); return new ArcFile (file, this, dir);
} }

View File

@ -0,0 +1,122 @@
//! \file ArcBIN.cs
//! \date 2023 Aug 09
//! \brief Tech Gian 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.IO;
using GameRes.Formats.Eagls;
using GameRes.Utility;
// [121102][Tech Gian Archive] Hanafuda Market Totsugeki! Tonari no Kanban Musume
namespace GameRes.Formats.TechGian
{
internal class RfilEntry : Entry
{
public int EncryptionMethod;
public bool IsEncrypted {
get { return EncryptionMethod == 1 || EncryptionMethod == 2 || EncryptionMethod == 4; }
}
}
[Export(typeof(ArchiveFormat))]
public class BinOpener : ArchiveFormat
{
public override string Tag { get { return "BIN/RFIL"; } }
public override string Description { get { return "Tech Gian archive"; } }
public override uint Signature { get { return 0x4C494652; } } // 'RFIL'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (8);
if (!IsSaneCount (count))
return null;
bool is_encrypted = file.View.ReadInt32 (12) == 1234;
long index_pos = 0x10;
var buffer = new byte[0x40];
var rnd = new CRuntimeRandomGenerator();
rnd.SRand (0);
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
file.View.Read (index_pos, buffer, 0, 0x40);
if (is_encrypted)
DecryptRand (buffer, 0, 0x40, rnd);
var name = Binary.GetCString (buffer, 0, 0x30);
var entry = Create<RfilEntry> (name);
entry.Offset = buffer.ToUInt32 (0x34);
entry.Size = buffer.ToUInt32 (0x38);
entry.EncryptionMethod = buffer.ToInt32 (0x3C);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_pos += 0x40;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var rent = (RfilEntry)entry;
if (!rent.IsEncrypted)
return base.OpenEntry (arc, entry);
var data = arc.File.View.ReadBytes (rent.Offset, rent.Size);
switch (rent.EncryptionMethod)
{
case 1:
DecryptData (data, 0, data.Length);
break;
case 2:
if (data.Length > 0)
DecryptData (data, 0, (data.Length - 1) / 100 + 1);
break;
case 4:
DecryptData (data, 0, Math.Min (1024, data.Length));
break;
}
return new BinMemoryStream (data, entry.Name);
}
internal static void DecryptData (byte[] data, int pos, int length)
{
while (length --> 0)
{
data[pos++] ^= 0x7F;
}
}
internal static void DecryptRand (byte[] data, int pos, int length, IRandomGenerator rnd)
{
while (length --> 0)
{
data[pos++] ^= (byte)rnd.Rand();
}
}
}
}

View File

@ -93,7 +93,7 @@ namespace GameRes.Formats.TechnoBrain
if (0x20706D62 != file.ReadInt32()) // 'bmp ' if (0x20706D62 != file.ReadInt32()) // 'bmp '
return false; return false;
int bmp_size = file.ReadInt32(); int bmp_size = file.ReadInt32();
if (bmp_size <= 0x20) if (bmp_size < 0x1C)
return false; return false;
info.BmpOffset = file.Position + 0x18; info.BmpOffset = file.Position + 0x18;
info.Width = file.ReadUInt16(); info.Width = file.ReadUInt16();

View File

@ -93,8 +93,9 @@ namespace GameRes.Formats.Unity
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
input.Align(); input.Align();
var file_id = input.ReadInt32();
var id = input.ReadId(); var id = input.ReadId();
m_adds[id] = input.ReadInt32(); m_adds[id] = file_id;
} }
} }
if (Format >= 6) if (Format >= 6)
@ -353,8 +354,11 @@ namespace GameRes.Formats.Unity
{ {
int count = reader.ReadInt32(); int count = reader.ReadInt32();
int buffer_bytes = reader.ReadInt32(); int buffer_bytes = reader.ReadInt32();
var node_data = reader.ReadBytes (24 * count); int node_size = m_format >= 18 ? 32 : 24;
var node_data = reader.ReadBytes (node_size * count);
m_data = reader.ReadBytes (buffer_bytes); m_data = reader.ReadBytes (buffer_bytes);
if (m_format >= 21)
reader.Skip (4);
var parents = new Stack<TypeTree>(); var parents = new Stack<TypeTree>();
parents.Push (this); parents.Push (this);
@ -384,6 +388,8 @@ namespace GameRes.Formats.Unity
current.Size = buf.ReadInt32(); current.Size = buf.ReadInt32();
current.Index = buf.ReadUInt32(); current.Index = buf.ReadUInt32();
current.Flags = buf.ReadInt32(); current.Flags = buf.ReadInt32();
if (m_format >= 18)
buf.ReadInt64();
} }
} }
} }

View File

@ -104,13 +104,42 @@ namespace GameRes.Formats.Unity
Load2021 (reader); Load2021 (reader);
return; return;
} }
if (type.Version != "2017.3.1f1") if (type.Version != "2017.3.1f1" && type.Version != "2019.3.0f1" && type.Version != "2017.4.3f1")
{ {
Load (reader); Load (reader);
if (0 == m_DataLength && type.Version.StartsWith ("2017.")) // "2017.2.0f3" || "2017.1.1p1" if (0 == m_DataLength && type.Version.StartsWith ("2017.")) // "2017.2.0f3" || "2017.1.1p1"
reader.ReadInt64(); reader.ReadInt64();
return; return;
} }
// type hash = [1E 87 D8 2D 4F D0 58 50 9A 3C 78 66 DB 0E 73 56]
m_Name = reader.ReadString();
reader.Align();
reader.ReadInt32(); // m_ForcedFallbackFormat
reader.ReadInt32(); // m_DownscaleFallback
m_Width = reader.ReadInt32();
m_Height = reader.ReadInt32();
m_CompleteImageSize = reader.ReadInt32();
m_TextureFormat = (TextureFormat)reader.ReadInt32();
m_MipCount = reader.ReadInt32();
m_IsReadable = reader.ReadBool();
reader.Align();
if ("2019.3.0f1" == type.Version) // type hash = [EE 6C 40 81 7D 29 51 92 9C DB 4F 5A 60 87 4F 5D]
reader.ReadInt32(); // m_StreamingMipmapsPriority
m_ImageCount = reader.ReadInt32();
m_TextureDimension = reader.ReadInt32();
m_FilterMode = reader.ReadInt32();
m_Aniso = reader.ReadInt32();
m_MipBias = reader.ReadFloat();
m_WrapMode = reader.ReadInt32(); // m_WrapU
reader.ReadInt32(); // m_WrapV
reader.ReadInt32(); // m_WrapW
reader.ReadInt32(); // m_LightmapFormat
m_ColorSpace = reader.ReadInt32();
m_DataLength = reader.ReadInt32();
}
public void Load2017 (AssetReader reader)
{
m_Name = reader.ReadString(); m_Name = reader.ReadString();
reader.Align(); reader.Align();
reader.ReadInt32(); // m_ForcedFallbackFormat reader.ReadInt32(); // m_ForcedFallbackFormat

View File

@ -0,0 +1,75 @@
//! \file ArcCFP.cs
//! \date 2022 May 17
//! \brief Winters resource archive.
//
// Copyright (C) 2022 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
// [110527][Winters] Kiss x 700 Kiss Tantei
namespace GameRes.Formats.Winters
{
[Export(typeof(ArchiveFormat))]
public class CfpOpener : ArchiveFormat
{
public override string Tag { get { return "CFP/CAPYBARA"; } }
public override string Description { get { return "Winters resource archive"; } }
public override uint Signature { get { return 0x59504143; } } // 'CAPYBARA DAT 002'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (0, "CAPYBARA DAT 002"))
return null;
uint names_offset = file.View.ReadUInt32 (0x14);
uint names_length = file.View.ReadUInt32 (0x18);
uint index_offset = 0x20;
var dir = new List<Entry>();
using (var names = file.CreateStream (names_offset, names_length))
using (var index = new StreamReader (names, Encodings.cp932))
{
string name;
while (index_offset < names_offset && (name = index.ReadLine()) != null)
{
if (name.Length > 0)
{
var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_offset);
entry.Size = file.View.ReadUInt32 (index_offset+4);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}
index_offset += 0xC;
}
}
if (0 == dir.Count)
return null;
return new ArcFile (file, this, dir);
}
}
}

View File

@ -57,6 +57,7 @@ namespace GameRes.Formats.Xuse
{ {
Extensions = new string[] { "wag", "4ag", "004" }; Extensions = new string[] { "wag", "4ag", "004" };
Signatures = new uint[] { 0x40474157, 0x34464147 }; // 'GAF4' Signatures = new uint[] { 0x40474157, 0x34464147 }; // 'GAF4'
// ContainedFormats = new [] { "PNG", "P/4AG" };
} }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
@ -284,7 +285,7 @@ namespace GameRes.Formats.Xuse
return entry; return entry;
} }
static readonly Regex DriveRe = new Regex (@"^(?:.+:)?\\+"); static readonly Regex DriveRe = new Regex (@"^(?:.+:|\.\.)?\\+");
} }
} }
} }

64
ArcFormats/Xuse/ImageP.cs Normal file
View File

@ -0,0 +1,64 @@
//! \file ImageP.cs
//! \date 2022 May 07
//! \brief Obfuscated PNG file.
//
// Copyright (C) 2022 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
namespace GameRes.Formats.Xuse
{
[Export(typeof(ImageFormat))]
public class P4AGFormat : ImageFormat
{
public override string Tag { get { return "P/4AG"; } }
public override string Description { get { return "Xuse/Eternal obfuscated PNG image"; } }
public override uint Signature { get { return 0x0A0D474E; } } // 'NG\n\r'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
using (var input = OpenAsPng (file))
return Png.ReadMetaData (input);
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
using (var input = OpenAsPng (file))
return Png.Read (input, info);
}
static readonly byte[] HeaderBytes = new byte[2] { PngFormat.HeaderBytes[0], PngFormat.HeaderBytes[1] };
internal IBinaryStream OpenAsPng (IBinaryStream file)
{
var input = new PrefixStream (HeaderBytes, file.AsStream, true);
return new BinaryStream (input, file.Name);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("P4AGFormat.Write not implemented");
}
}
}

142
ArcFormats/Zyx/ImageXMG.cs Normal file
View File

@ -0,0 +1,142 @@
//! \file ImageXMG.cs
//! \date 2022 Jun 19
//! \brief ZyX image format.
//
// Copyright (C) 2022 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using GameRes.Utility;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Ikura
{
[Export(typeof(ImageFormat))]
public class XmgFormat : ImageFormat
{
public override string Tag { get { return "XMG"; } }
public override string Description { get { return "ZyX image format"; } }
public override uint Signature { get { return 0; } }
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".xmg"))
return null;
var header = file.ReadBytes (12);
Decrypt (header);
if (header[2] != 0 || header[3] != 0)
return null;
int width = LittleEndian.ToInt16 (header, 4);
int height = LittleEndian.ToInt16 (header, 6);
if (width <= 0 || height <= 0)
return null;
return new ImageMetaData {
Width = (uint)width,
Height = (uint)height,
BPP = 8,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 12;
var palette_data = file.ReadBytes (0x300);
Decrypt (palette_data, (byte)(12 * 7));
var palette = ConvertPalette (palette_data);
var pixels = new byte[info.iWidth * info.iHeight];
int dst = 0;
for (int y = 0; y < info.iHeight; ++y)
{
int x = 0;
while (x < info.iWidth)
{
byte ctl = file.ReadUInt8();
int offset;
int count;
if ((ctl & 0xC0) != 0)
{
if ((ctl & 0x80) != 0)
{
offset = -((ctl << 8 | file.ReadUInt8()) & 0xFFF) - 1;
count = (ctl & 0x70) >> 4;
if (count != 0)
count += 2;
else
count = file.ReadUInt8() + 10;
Binary.CopyOverlapped (pixels, dst + offset, dst, count);
}
else
{
count = ctl & 0x3F;
if (0 == count)
count = 64 + file.ReadUInt8();
count += 1;
byte p = pixels[dst-1];
for (int i = 0; i < count; ++i)
pixels[dst+i] = p;
}
}
else
{
count = ctl & 0x3F;
if (0 == count)
count = 64 + file.ReadUInt8();
file.Read (pixels, dst, count);
}
x += count;
dst += count;
}
}
return ImageData.Create (info, PixelFormats.Indexed8, palette, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("xxxFormat.Write not implemented");
}
internal static BitmapPalette ConvertPalette (byte[] palette_data)
{
const int colors = 0x100;
var color_map = new Color[colors];
int src = 0;
for (int i = 0; i < colors; ++i)
{
color_map[i] = Color.FromRgb (palette_data[src+1], palette_data[src+2], palette_data[src]);
src += 3;
}
return new BitmapPalette (color_map);
}
internal static byte Decrypt (byte[] data, byte key = 0)
{
for (int i = 0; i < data.Length; ++i)
{
byte v = (byte)((data[i] - key) ^ 0xF3);
data[i] = v;
key += 7;
}
return key;
}
}
}

View File

@ -199,6 +199,9 @@
<setting name="SJDatTitle" serializeAs="String"> <setting name="SJDatTitle" serializeAs="String">
<value /> <value />
</setting> </setting>
<setting name="AFAEncodingCP" serializeAs="String">
<value>932</value>
</setting>
</GameRes.Formats.Properties.Settings> </GameRes.Formats.Properties.Settings>
</userSettings> </userSettings>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /></startup> <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /></startup>

View File

@ -40,7 +40,7 @@ namespace GameRes.Formats.Elf
public G24Format () public G24Format ()
{ {
Extensions = new string[] { "g24", "g16" }; Extensions = new string[] { "g24", "g16", "g32" };
} }
public override ImageMetaData ReadMetaData (IBinaryStream input) public override ImageMetaData ReadMetaData (IBinaryStream input)
@ -57,7 +57,8 @@ namespace GameRes.Formats.Elf
Height = (uint)h, Height = (uint)h,
OffsetX = x, OffsetX = x,
OffsetY = y, OffsetY = y,
BPP = input.Name.HasExtension (".G16") ? 16 : 24 BPP = input.Name.HasExtension (".G16") ? 16
: input.Name.HasExtension (".G32") ? 32 : 24
}; };
} }
@ -70,7 +71,9 @@ namespace GameRes.Formats.Elf
{ {
if (pixels.Length != reader.Read (pixels, 0, pixels.Length)) if (pixels.Length != reader.Read (pixels, 0, pixels.Length))
throw new InvalidFormatException(); throw new InvalidFormatException();
var format = 24 == info.BPP ? PixelFormats.Bgr24 : PixelFormats.Bgr555; var format = 24 == info.BPP ? PixelFormats.Bgr24
: 32 == info.BPP ? PixelFormats.Bgra32
: PixelFormats.Bgr555;
return ImageData.CreateFlipped (info, format, null, pixels, stride); return ImageData.CreateFlipped (info, format, null, pixels, stride);
} }
} }

View File

@ -77,7 +77,13 @@ namespace GameRes.Formats.Rugp
Size = node.Size Size = node.Size
}; };
if (!dir.Any()) if (!dir.Any())
return null; {
var box_ref = nodes.FirstOrDefault (n => n.ClassName == "CBoxOcean");
if (null == box_ref)
return null;
var box = reader.ReadObject (box_ref);
nodes = reader.Arc.LoadArray.OfType<COceanNode>();
}
return new ArcFile (file, this, dir.ToList()); return new ArcFile (file, this, dir.ToList());
} }
} }
@ -307,7 +313,7 @@ namespace GameRes.Formats.Rugp
public int GetObjectSchema () public int GetObjectSchema ()
{ {
int schema = m_objectSchema; int schema = m_objectSchema;
m_objectSchema = -1; // m_objectSchema = -1;
return schema; return schema;
} }
@ -329,9 +335,12 @@ namespace GameRes.Formats.Rugp
public CObject ReadObject (COceanNode node) public CObject ReadObject (COceanNode node)
{ {
m_field_60 = false;
var obj = CreateObject (node.Name); var obj = CreateObject (node.Name);
PopulateLoadArray(); PopulateLoadArray();
m_input.Position = ((long)node.Offset << m_shift); m_input.Position = ((long)node.Offset << m_shift);
// if (node.Name != "CrelicUnitedGameProject")
// m_input.Seek (3, SeekOrigin.Current);
int f1 = ReadByte() & 3; int f1 = ReadByte() & 3;
int f2 = ReadByte(); int f2 = ReadByte();
int f3 = ReadByte(); int f3 = ReadByte();
@ -700,7 +709,7 @@ namespace GameRes.Formats.Rugp
class_ref = LoadRuntimeClass (out schema); class_ref = LoadRuntimeClass (out schema);
if (null == class_ref) if (null == class_ref)
throw new InvalidFormatException(); throw new InvalidFormatException();
// m_objectSchema = (int)schema; m_objectSchema = (int)schema;
m_LoadArray.Add (class_ref); m_LoadArray.Add (class_ref);
} }
@ -957,6 +966,7 @@ namespace GameRes.Formats.Rugp
{ "CrelicUnitedGameProject", new CObjectFactory<CrelicUnitedGameProject>() }, { "CrelicUnitedGameProject", new CObjectFactory<CrelicUnitedGameProject>() },
{ "CStdb", new CObjectFactory<CStdb>() }, { "CStdb", new CObjectFactory<CStdb>() },
{ "CObjectOcean", new CObjectFactory<CObjectOcean>() }, { "CObjectOcean", new CObjectFactory<CObjectOcean>() },
{ "CBoxOcean", new CObjectFactory<CBoxOcean>() },
}; };
} }
@ -1351,11 +1361,74 @@ namespace GameRes.Formats.Rugp
internal class CBoxOcean : CObject internal class CBoxOcean : CObject
{ {
public CObject field_10; public CObject field_10;
public CObject field_14;
public List<CObject> field_18 = new List<CObject>();
public List<string> field_118 = new List<string>();
public CObject field_198;
public List<CObject> m_box_list = new List<CObject>();
static CObject static_cui = null;
public override void Deserialize (CRioArchive arc) public override void Deserialize (CRioArchive arc)
{ {
field_10 = arc.ReadRioReference ("CFrameBuffer"); field_10 = arc.ReadRioReference ("CFrameBuffer");
int schema = arc.GetObjectSchema();
if (schema < 3)
{
for (int i = 0; i < 32; ++i)
{
var box = arc.ReadRioReference ("CBox");
m_box_list.Add (box);
}
}
if (schema >= 2)
{
field_14 = arc.ReadRioReference ("CSbm");
if (schema < 6)
{
field_18.Add (arc.ReadRioReference ("CSbm"));
}
else
{
int ref_count = 15;
int str_count = 0;
if (schema >= 7)
{
ref_count = 32;
str_count = 32;
}
for (int i = 0; i < ref_count; ++i)
{
field_18.Add (arc.ReadRioReference ("CSbm"));
}
for (int i = 0; i < str_count; ++i)
{
field_118.Add (arc.ReadString());
}
}
field_198 = arc.ReadRioReference ("CUnitedMenu");
if (schema >= 4)
{
ReadCui (arc);
if (schema >= 5)
{
var cui = arc.ReadRioReference ("CUI");
if (cui != static_cui)
{
ReadCui (arc);
}
}
}
}
}
void ReadCui (CRioArchive arc)
{
while (arc.ReadByte() != 0)
{
var cui = arc.ReadRioReference ("CUI");
}
} }
} }

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.0.16.40")] [assembly: AssemblyVersion ("1.0.16.42")]
[assembly: AssemblyFileVersion ("1.0.16.40")] [assembly: AssemblyFileVersion ("1.0.16.42")]

View File

@ -35,6 +35,11 @@ namespace GameRes.Formats.RPGMaker
public override uint Signature { get { return 0x4D475052; } } // 'RPGMV' public override uint Signature { get { return 0x4D475052; } } // 'RPGMV'
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
public RpgmvoAudio ()
{
Extensions = new[] { "rpgmvo", "ogg_" };
}
public override SoundInput TryOpen (IBinaryStream file) public override SoundInput TryOpen (IBinaryStream file)
{ {
var header = file.ReadHeader (0x14); var header = file.ReadHeader (0x14);

View File

@ -44,6 +44,11 @@ namespace GameRes.Formats.RPGMaker
public override string Description { get { return "RPG Maker engine image format"; } } public override string Description { get { return "RPG Maker engine image format"; } }
public override uint Signature { get { return 0x4D475052; } } // 'RPGMV' public override uint Signature { get { return 0x4D475052; } } // 'RPGMV'
public RpgmvpFormat ()
{
Extensions = new string[] { "rpgmvp", "png_" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file) public override ImageMetaData ReadMetaData (IBinaryStream file)
{ {
var header = file.ReadHeader (0x14); var header = file.ReadHeader (0x14);
@ -149,6 +154,8 @@ namespace GameRes.Formats.RPGMaker
var dir_name = Path.GetDirectoryName (filename); var dir_name = Path.GetDirectoryName (filename);
yield return Path.Combine (dir_name, @"..\..\data\System.json"); yield return Path.Combine (dir_name, @"..\..\data\System.json");
yield return Path.Combine (dir_name, @"..\..\..\www\data\System.json"); yield return Path.Combine (dir_name, @"..\..\..\www\data\System.json");
yield return Path.Combine (dir_name, @"..\..\..\data\System.json");
yield return Path.Combine (dir_name, @"..\..\..\..\data\System.json");
yield return Path.Combine (dir_name, @"..\data\System.json"); yield return Path.Combine (dir_name, @"..\data\System.json");
yield return Path.Combine (dir_name, @"data\System.json"); yield return Path.Combine (dir_name, @"data\System.json");
} }

View File

@ -34,6 +34,7 @@ using System.Windows.Media;
namespace GameRes namespace GameRes
{ {
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
[ExportMetadata("Priority", 100)] // makes PNG first format in list
public class PngFormat : ImageFormat public class PngFormat : ImageFormat
{ {
public override string Tag { get { return "PNG"; } } public override string Tag { get { return "PNG"; } }

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.5.44.320")] [assembly: AssemblyVersion ("1.5.44.321")]
[assembly: AssemblyFileVersion ("1.5.44.320")] [assembly: AssemblyFileVersion ("1.5.44.321")]

View File

@ -30,12 +30,51 @@ namespace GameRes
{ {
public override string Type { get { return "script"; } } public override string Type { get { return "script"; } }
public abstract bool IsScript (IBinaryStream file);
public abstract Stream ConvertFrom (IBinaryStream file);
public abstract Stream ConvertBack (IBinaryStream file);
public abstract ScriptData Read (string name, Stream file); public abstract ScriptData Read (string name, Stream file);
public abstract void Write (Stream file, ScriptData script); public abstract void Write (Stream file, ScriptData script);
public static ScriptFormat FindFormat (IBinaryStream file)
{
foreach (var impl in FormatCatalog.Instance.FindFormats<ScriptFormat> (file.Name, file.Signature))
{
try
{
file.Position = 0;
if (impl.IsScript (file))
return impl;
}
catch (System.OperationCanceledException)
{
throw;
}
catch { }
}
return null;
}
} }
public abstract class GenericScriptFormat : ScriptFormat public abstract class GenericScriptFormat : ScriptFormat
{ {
public override bool IsScript (IBinaryStream file)
{
return false;
}
public override Stream ConvertFrom (IBinaryStream file)
{
return file.AsStream;
}
public override Stream ConvertBack (IBinaryStream file)
{
return file.AsStream;
}
public override ScriptData Read (string name, Stream file) public override ScriptData Read (string name, Stream file)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();

View File

@ -38,12 +38,23 @@ namespace GameRes.Formats.Airyu
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
static readonly uint[] ImageSizes = new[] { 0x96000u, 0x4B000u, 0x19000u };
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
if (!file.Name.HasExtension (".chr")) if (!file.Name.HasExtension (".chr"))
return null; return null;
int count = (int)(file.MaxOffset / 0x96000); int count = 0;
if (!IsSaneCount (count) || count * 0x96000 != file.MaxOffset) uint image_size = 1;
for (int i = 0; i < ImageSizes.Length; ++i)
{
image_size = ImageSizes[i];
count = (int)(file.MaxOffset / image_size);
if (IsSaneCount (count) && count * image_size == file.MaxOffset)
break;
count = 0;
}
if (0 == count)
return null; return null;
uint offset = 0; uint offset = 0;
@ -54,10 +65,10 @@ namespace GameRes.Formats.Airyu
Name = i.ToString ("D5"), Name = i.ToString ("D5"),
Type = "image", Type = "image",
Offset = offset, Offset = offset,
Size = 0x96000, Size = image_size,
}; };
dir.Add (entry); dir.Add (entry);
offset += 0x96000; offset += image_size;
} }
return new ArcFile (file, this, dir); return new ArcFile (file, this, dir);
} }
@ -65,21 +76,32 @@ namespace GameRes.Formats.Airyu
public override IImageDecoder OpenImage (ArcFile arc, Entry entry) public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{ {
var input = arc.File.CreateStream (entry.Offset, entry.Size); var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new ChrImageDecoder (input); return new ChrImageDecoder (input, entry.Size);
} }
} }
internal class ChrImageDecoder : BinaryImageDecoder internal class ChrImageDecoder : BinaryImageDecoder
{ {
public ChrImageDecoder (IBinaryStream input) : base (input) int m_image_size;
int m_stride;
public ChrImageDecoder (IBinaryStream input, uint size) : base (input)
{ {
Info = new ImageMetaData { Width = 640, Height = 480, BPP = 16 }; m_image_size = (int)size;
switch (size)
{
case 0x19000: Info = new ImageMetaData { Width = 200, Height = 256, BPP = 16 }; break;
case 0x4B000: Info = new ImageMetaData { Width = 320, Height = 480, BPP = 16 }; break;
case 0x96000: Info = new ImageMetaData { Width = 640, Height = 480, BPP = 16 }; break;
default: throw new InvalidFormatException ("Invalid image size.");
}
m_stride = Info.iWidth * 2;
} }
protected override ImageData GetImageData () protected override ImageData GetImageData ()
{ {
var pixels = m_input.ReadBytes (0x96000); var pixels = m_input.ReadBytes (m_image_size);
return ImageData.CreateFlipped (Info, PixelFormats.Bgr555, null, pixels, 640*2); return ImageData.CreateFlipped (Info, PixelFormats.Bgr555, null, pixels, m_stride);
} }
} }
} }

View File

@ -0,0 +1,142 @@
//! \file ArcDAT.cs
//! \date 2022 Nov 09
//! \brief Black Butterfly resource archive.
//
// Copyright (C) 2022 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using GameRes.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.BlackButterfly
{
[Export(typeof(ArchiveFormat))]
public class DatOpener : ArchiveFormat
{
public override string Tag { get { return "DAT/PITA"; } }
public override string Description { get { return "Black Butterfly resource archive"; } }
public override uint Signature { get { return 0x41544950; } } // 'PITA'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
uint index_pos = 0x10;
uint next_offset = file.View.ReadUInt32 (index_pos);
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
index_pos += 4;
var entry = new PackedEntry {
Name = string.Format ("{0:D5}.bmp", i),
Type = "image",
Offset = next_offset,
};
next_offset = file.View.ReadUInt32 (index_pos);
entry.Size = (uint)(next_offset - entry.Offset);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
if (!pent.IsPacked)
{
pent.IsPacked = true;
pent.UnpackedSize = arc.File.View.ReadUInt32 (pent.Offset);
}
using (var input = arc.File.CreateStream (pent.Offset+4, pent.Size-4))
{
var data = new byte[pent.UnpackedSize];
Unpack (input, data);
return new BinMemoryStream (data, entry.Name);
}
}
void Unpack (IBinaryStream input, byte[] output)
{
int dst = 0;
while (input.PeekByte() != -1)
{
byte ctl = input.ReadUInt8();
if (0x7F == ctl)
{
if (input.PeekByte() == 0xFF)
break;
}
int count;
if (ctl <= 0x7F)
{
count = (ctl >> 2) + 2;
int offset = (ctl & 3) << 8 | input.ReadUInt8();
offset = (offset ^ 0x3FF) + 1;
Binary.CopyOverlapped (output, dst - offset, dst, count);
dst += count;
}
else if (ctl > 0xFE)
{
count = input.ReadUInt8() + 32;
while (count --> 0)
output[dst++] = 0;
}
else if (ctl > 0xDF)
{
count = (ctl & 0x1F) + 1;
while (count --> 0)
output[dst++] = 0;
}
else if (ctl > 0xBF)
{
count = (ctl & 0x1F) + 2;
byte fill = input.ReadUInt8();
while (count --> 0)
output[dst++] = fill;
}
else if (ctl > 0x9F)
{
count = (ctl & 0x1F) + 1;
while (count --> 0)
{
output[dst++] = 0;
output[dst++] = input.ReadUInt8();
}
}
else
{
count = (ctl & 0x1F) + 1;
input.Read (output, dst, count);
dst += count;
}
}
}
}
}

View File

@ -0,0 +1,204 @@
//! \file ImageLMG.cs
//! \date 2023 Aug 07
//! \brief Cotton Club encrypted image.
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [071231][Cotton Club] Kureuta
namespace GameRes.Formats.CottonClub
{
public class LmgMetaData : ImageMetaData
{
public byte Method;
}
[Export(typeof(ImageFormat))]
public class LmgFormat : ImageFormat
{
public override string Tag { get { return "LMG"; } }
public override string Description { get { return "Cotton Club encrypted image"; } }
public override uint Signature { get { return 0x03474D4C; } } // 'LMG'
public LmgFormat ()
{
Signatures = new uint[] { 0x03474D4C, 0x02474D4C, 0x01474D4C };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (12);
return new LmgMetaData {
Width = header.ToUInt32 (4),
Height = header.ToUInt32 (8),
BPP = header[3] == 2 ? 32 : 24,
Method = header[3],
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var meta = (LmgMetaData)info;
var data = new byte[file.Length - 12];
file.Position = 12;
file.Read (data, 0, data.Length);
DecryptData (data, file.Name);
if (3 == meta.Method)
{
using (var input = new BinMemoryStream (data))
{
var decoder = new JpegBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
var frame = decoder.Frames[0];
frame.Freeze();
return new ImageData (frame, info);
}
}
else if (2 == meta.Method)
{
var reader = new LmgReader (info, data);
data = reader.Unpack();
return ImageData.Create (info, PixelFormats.Bgra32, null, data, reader.Stride);
}
else
{
return ImageData.Create (info, PixelFormats.Bgr24, null, data, info.iWidth * 3);
}
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("LmgFormat.Write not implemented");
}
internal void DecryptData (byte[] data, string filename)
{
filename = Path.GetFileName (filename).ToLowerInvariant();
byte key = 0;
for (int i = 0; i < filename.Length; ++i)
{
key ^= (byte)filename[i];
}
for (int i = 0; i < data.Length; ++i)
{
byte x = data[i];
data[i] ^= key;
key = x;
}
}
}
internal class LmgReader
{
byte[] m_data;
byte[] m_output;
int m_stride;
public int Stride { get { return m_stride; } }
public LmgReader (ImageMetaData info, byte[] data)
{
m_data = data;
m_stride = info.iWidth * 4;
m_output = new byte[m_stride * info.iHeight];
}
int m_src;
int m_dst;
public byte[] Unpack ()
{
m_src = 0;
m_dst = 0;
while (m_src+1 < m_data.Length)
{
byte alpha = m_data[m_src++];
if (0xFF == alpha)
{
int length = GetLength16();
for (int i = 0; i < length; ++i)
{
m_output[m_dst++] = m_data[m_src++];
m_output[m_dst++] = m_data[m_src++];
m_output[m_dst++] = m_data[m_src++];
m_output[m_dst++] = 0xFF;
}
}
else if (0 == alpha)
{
int length = GetLength16();
m_dst += length * 4;
}
else
{
ReadARGB (alpha);
}
}
return m_output;
}
void ReadARGB (byte alpha)
{
int length = GetLength8();
m_output[m_dst++] = m_data[m_src++];
m_output[m_dst++] = m_data[m_src++];
m_output[m_dst++] = m_data[m_src++];
m_output[m_dst++] = alpha;
while (--length > 0)
{
alpha = m_data[m_src++];
m_output[m_dst++] = m_data[m_src++];
m_output[m_dst++] = m_data[m_src++];
m_output[m_dst++] = m_data[m_src++];
m_output[m_dst++] = alpha;
}
}
int GetLength8 ()
{
int i = 0;
while (m_data[m_src] == 0)
{
i += 0xFF;
++m_src;
}
return i + m_data[m_src++];
}
int GetLength16 ()
{
int i = 0;
while (m_data[m_src] == 0 && m_data[m_src+1] == 0)
{
i += 0xFFFF;
m_src += 2;
}
i += m_data.ToUInt16 (m_src);
m_src += 2;
return i;
}
}
}

View File

@ -28,6 +28,7 @@ using System.ComponentModel.Composition;
using System.IO; using System.IO;
// [000623][Marimo] Setsunai // [000623][Marimo] Setsunai
// [010316][Evolution] Bukkake Tenshi Silky & Milky
namespace GameRes.Formats.Dice namespace GameRes.Formats.Dice
{ {

View File

@ -30,6 +30,7 @@ using GameRes.Compression;
// [030725][Digital Monkey] Kono Sora ga Tsuieru Toki ni // [030725][Digital Monkey] Kono Sora ga Tsuieru Toki ni
// [041112][Yumesta] Seikon ~Kiba to Nie to Kyouki no Yakata~ // [041112][Yumesta] Seikon ~Kiba to Nie to Kyouki no Yakata~
// [050325][Supple Entertainment] Dark: Shitsuraku no Shimai Ningyou
namespace GameRes.Formats.DigitalMonkey namespace GameRes.Formats.DigitalMonkey
{ {

2090
Legacy/Giga/ArcALL.cs Normal file

File diff suppressed because it is too large Load Diff

73
Legacy/Herb/ArcPAK.cs Normal file
View File

@ -0,0 +1,73 @@
//! \file ArcPAK.cs
//! \date 2023 Aug 16
//! \brief Herb Soft 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.IO;
namespace GameRes.Formats.Herb
{
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
{
public override string Tag { get { return "PAK/HERB"; } }
public override string Description { get { return "Herb Soft resource archive"; } }
public override uint Signature { get { return 0x2E; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public PakOpener ()
{
ContainedFormats = new[] { "GRP/HERB", "WAV", "TXT" };
}
public override ArcFile TryOpen (ArcView file)
{
long base_offset = 0x20000;
if (file.MaxOffset <= base_offset || !file.View.AsciiEqual (0x40, "..\0"))
return null;
long index_pos = 0x80;
var dir = new List<Entry>();
while (index_pos < base_offset && file.View.ReadByte (index_pos) != 0)
{
var name = file.View.ReadString (index_pos, 0x30);
var entry = Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_pos + 0x30) + base_offset;
entry.Size = file.View.ReadUInt32 (index_pos + 0x38);
if (entry.Size != 0)
{
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}
index_pos += 0x40;
}
if (dir.Count == 0)
return null;
return new ArcFile (file, this, dir);
}
}
}

91
Legacy/Herb/ImageGRP.cs Normal file
View File

@ -0,0 +1,91 @@
//! \file ImageGRP.cs
//! \date 2023 Aug 16
//! \brief Herb Soft compressed image format.
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Herb
{
internal class GrpMetaData : ImageMetaData
{
public int Stride;
}
[Export(typeof(ImageFormat))]
public class GrpFormat : ImageFormat
{
public override string Tag { get { return "GRP/HERB"; } }
public override string Description { get { return "Herb Soft image format"; } }
public override uint Signature { get { return 0x08; } }
public GrpFormat ()
{
Signatures = new[] { 0x20u, 0x18u, 0x08u };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x28);
if (header.ToInt32 (4) != 0 || header.ToInt32 (8) != 1)
return null;
return new GrpMetaData {
Width = header.ToUInt32 (0x20),
Height = header.ToUInt32 (0x24),
BPP = header[0] == 0x08 ? 8 : header[0] == 0x18 ? 16 : 24,
Stride = header.ToInt32 (0x0C),
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var meta = (GrpMetaData)info;
BitmapPalette palette = null;
PixelFormat format = 8 == info.BPP ? PixelFormats.Indexed8
: 16 == info.BPP ? PixelFormats.Bgr555
: PixelFormats.Bgr24;
if (8 == info.BPP)
{
file.Position = 0x28;
palette = ReadPalette (file.AsStream, 0x100, PaletteFormat.RgbX);
}
int stride = meta.Stride;
var pixels = new byte[stride * info.iHeight];
file.Position = 0x428;
using (var input = new ZLibStream (file.AsStream, CompressionMode.Decompress, true))
{
input.Read (pixels, 0, pixels.Length);
}
return ImageData.Create (info, format, palette, pixels, stride);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GrpFormat.Write not implemented");
}
}
}

View File

@ -0,0 +1,71 @@
//! \file ArcPAK.cs
//! \date 2022 May 28
//! \brief HyperWorks resource archive.
//
// Copyright (C) 2022 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.HyperWorks
{
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
{
public override string Tag { get { return "PAK/ACE"; } }
public override string Description { get { return "HyperWorks resource archive"; } }
public override uint Signature { get { return 0x4B434150; } } // 'PACK'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int index_size = file.View.ReadInt32 (4);
if (index_size >= file.MaxOffset - 4)
return null;
int count = index_size / 0x18;
if (!IsSaneCount (count))
return null;
uint pos = 8;
file.View.Reserve (pos, (uint)index_size);
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
uint name_length = file.View.ReadByte (pos+8);
if (name_length > 15)
return null;
var name = file.View.ReadString (pos+9, name_length);
var entry = Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (pos);
entry.Size = file.View.ReadUInt32 (pos+4);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
pos += 0x18;
}
return new ArcFile (file, this, dir);
}
}
}

View File

@ -23,10 +23,14 @@
// IN THE SOFTWARE. // IN THE SOFTWARE.
// //
using GameRes.Utility;
using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Windows.Media; using System.Windows.Media;
// [980626][Love Gun] ACE OF SPADES 2
namespace GameRes.Formats.HyperWorks namespace GameRes.Formats.HyperWorks
{ {
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
@ -51,7 +55,8 @@ namespace GameRes.Formats.HyperWorks
public override ImageData Read (IBinaryStream file, ImageMetaData info) public override ImageData Read (IBinaryStream file, ImageMetaData info)
{ {
return ImageData.Create (info, format, palette, pixels); var reader = new I24Decoder (file, info);
return reader.Unpack();
} }
public override void Write (Stream file, ImageData image) public override void Write (Stream file, ImageData image)
@ -59,4 +64,439 @@ namespace GameRes.Formats.HyperWorks
throw new System.NotImplementedException ("I24Format.Write not implemented"); throw new System.NotImplementedException ("I24Format.Write not implemented");
} }
} }
internal class I24Decoder
{
IBinaryStream m_input;
ImageMetaData m_info;
public I24Decoder (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
}
static readonly short[] shiftTable = new short[] {
-1, 0, 0, 1, 1, 1, -1, 1, 2, 1, -2, 1, -2, 0, 0, 2, 1, 2, -1, 2, -3, 0
};
bool cacheEmpty = true;
int byteCount = 0;
int bits = 0;
int bitCount = 0;
class Node
{
public Node next;
public int depth;
public int token;
}
struct DictRec
{
public Link link;
public int bitSize;
public int token;
}
class Link
{
public Link[] children = new Link[2];
public int token;
}
DictRec[] dTable_1 = new DictRec[256];
DictRec[] dTable_2 = new DictRec[256];
DictRec[] dTable_3 = new DictRec[256];
Link[] tBuffer_1 = InitNodeList<Link> (684);
Link[] tBuffer_2 = InitNodeList<Link> (22);
Link[] tBuffer_3 = InitNodeList<Link> (502);
Node[] bTable_1 = InitNodeList<Node> (342);
Node[] bTable_2 = InitNodeList<Node> (11);
Node[] bTable_3 = InitNodeList<Node> (251);
static T[] InitNodeList<T>(int count) where T : new()
{
var list = new T[count];
for (int i = 0; i < count; ++i)
list[i] = new T();
return list;
}
public ImageData Unpack ()
{
m_input.Position = 0x18;
int stride = m_info.iWidth * 4;
var pixels = new byte[stride * m_info.iHeight];
byte[][] line_buffer = new[] { new byte[stride], new byte[stride], new byte[stride] };
int dst = 0;
var shift_table = shiftTable.Clone() as short[];
for (int y = 0; y < m_info.iHeight; ++y)
{
var line = line_buffer[2];
line_buffer[2] = line_buffer[1];
line_buffer[1] = line_buffer[0];
line_buffer[0] = line;
int x = 0;
int p = 0;
while (x < m_info.iWidth)
{
if (byteCount-- == 0)
{
if (cacheEmpty)
{
cacheEmpty = false;
int val = ReadUInt8() << 8;
val |= ReadUInt8();
bits = val;
bitCount = 8;
}
InitTree (bTable_1, 342);
InitTree (bTable_2, 11);
InitTree (bTable_3, 251);
RebuildTree(bTable_1, dTable_1, tBuffer_1, 342);
RebuildTree(bTable_2, dTable_2, tBuffer_2, 11);
RebuildTree(bTable_3, dTable_3, tBuffer_3, 251);
byteCount = 0x3FFF;
}
int color_token = GetToken (dTable_1);
int shift_token = GetToken (dTable_2);
int shift_idx = 2 * shift_token;
short s1 = shift_table[shift_idx];
short s2 = shift_table[shift_idx + 1];
if (shift_token != 0)
{
while (shift_token --> 0)
{
shift_table[shift_idx] = shift_table[shift_idx - 2];
shift_table[shift_idx+1] = shift_table[shift_idx - 1];
shift_idx -= 2;
}
shift_table[0] = s1;
shift_table[1] = s2;
}
int src = 4 * (x + s1);
if (color_token >= 216)
{
int count = color_token - 214;
x += count;
while (count --> 0)
{
line[p++] = line_buffer[s2][src++];
line[p++] = line_buffer[s2][src++];
line[p++] = line_buffer[s2][src++];
line[p++] = line_buffer[s2][src++];
}
}
else
{
sbyte r = shift_R[color_token];
if (r == -3)
r = (sbyte)(GetToken (dTable_3) + 3);
line[p+2] = (byte)(line_buffer[s2][src+2] - r);
sbyte g = shift_G[color_token];
if (g == -3)
g = (sbyte)(GetToken(dTable_3) + 3);
line[p+1] = (byte)(line_buffer[s2][src+1] - g);
sbyte b = shift_B[color_token];
if (b == -3)
b = (sbyte)(GetToken(dTable_3) + 3);
line[p] = (byte)(line_buffer[s2][src] - b);
line[p+3] = 0;
p += 4;
++x;
}
}
Buffer.BlockCopy (line_buffer[0], 0, pixels, dst, stride);
dst += stride;
}
return ImageData.Create (m_info, PixelFormats.Bgr32, null, pixels, stride);
}
private void InitTree (Node[] tree, int count) // sub_408FB0
{
for (int i = 0; i < count; ++i)
{
tree[i].next = null;
tree[i].depth = 0;
tree[i].token = i;
}
int length = GetBitLength();
if (length <= 1)
return;
length -= 1;
int fieldWidth = GetBits (3);
int tIdx = 0;
while (length > 0)
{
if (GetNextBit() != 0)
{
tree[tIdx++].depth = GetBits (fieldWidth);
--length;
}
else
{
int step = GetBitLength();
if (0 == step)
tIdx++;
else
tIdx += step;
}
}
}
private void RebuildTree (Node[] tree, DictRec[] dict, Link[] links, int count)
{
for (int i = 0; i < 256; ++i)
{
dict[i].token = 0;
dict[i].link = null;
}
int next_idx = 0; // var node = tree;
count -= 1;
while (0 == tree[next_idx].depth)
{
if (--count <= 0)
break;
++next_idx;
}
if (0 == count)
return;
var node = tree[next_idx++];
while (count --> 0)
{
int depth = tree[next_idx].depth;
if (depth != 0)
{
if (node != null)
{
if (node.depth <= depth)
{
var prev = node;
var ptr = node.next;
while (ptr != null)
{
if (ptr.depth > depth)
break;
prev = ptr;
ptr = ptr.next;
}
prev.next = tree[next_idx];
tree[next_idx].next = ptr;
}
else
{
tree[next_idx].next = node;
node = tree[next_idx];
}
}
}
++next_idx;
}
int bit_size = 0;
int t3_i = 0;
int dict_idx = 0;
while (node != null)
{
if (node.depth > bit_size)
{
dict_idx <<= node.depth - bit_size;
bit_size = node.depth;
}
if (bit_size >= 8)
{
if (bit_size == 8)
{
dict[dict_idx].bitSize = 8;
dict[dict_idx].token = node.token;
dict[dict_idx].link = null;
}
else
{
int d1 = bit_size - 8;
int d2 = dict_idx >> d1;
int d3 = dict_idx << (32 - (bit_size - 8));
dict[d2].bitSize = 0;
var ptr_t2 = d2; // &dict[d2];
var link = dict[ptr_t2].link;
if (null == link)
{
var t3_ptr = links[t3_i];
t3_ptr.children[0] = null;
t3_ptr.children[1] = null;
t3_ptr.token = 0;
link = t3_ptr;
dict[ptr_t2].link = t3_ptr;
t3_i++;
}
while (d1 --> 0)
{
int v26 = (d3 >> 31) & 1;
d3 <<= 1;
if (null == link.children[v26])
{
var t3_ptr = links[t3_i];
t3_ptr.children[0] = null;
t3_ptr.children[1] = null;
t3_ptr.token = 0;
link.children[v26] = t3_ptr;
t3_i++;
}
link = link.children[v26];
}
link.token = node.token;
}
}
else
{
int d1 = dict_idx << (8 - bit_size);
int d2 = 1 << (8 - bit_size);
while (d2 --> 0)
{
dict[d1].bitSize = bit_size;
dict[d1].token = node.token;
d1++;
}
}
++dict_idx;
node = node.next;
}
}
int GetToken (DictRec[] table)
{
var table_ptr = table[(bits >> 8) & 0xFF];
int count = table_ptr.bitSize;
if (count != 0)
{
if (count >= bitCount)
{
bits <<= bitCount;
count -= bitCount;
bits |= ReadUInt8();
bitCount = 8;
}
bits <<= count;
bitCount -= count;
return table_ptr.token;
}
else
{
bits = ReadUInt8() | bits << bitCount;
bits <<= 8 - bitCount;
Link link = table_ptr.link;
do
{
int path = GetNextBit() & 1;
link = link.children[path];
if (null == link)
throw new InvalidFormatException ("Invalid tree path");
}
while (link.children[0] != null);
return link.token;
}
}
private byte ReadUInt8()
{
return (byte)m_input.ReadByte();
}
private int GetNextBit ()
{
bits <<= 1;
if (0 == --bitCount)
{
bits |= ReadUInt8();
bitCount = 8;
}
return (bits >> 16) & 1;
}
private int GetBitLength ()
{
if (GetNextBit() != 0)
return 0;
int i = 0;
do
{
++i;
}
while (0 == GetNextBit());
return (1 << i) | GetBits (i);
}
private int GetBits (int count)
{
int n = count;
if (count >= bitCount)
{
bits <<= bitCount;
n = count - bitCount;
bits |= ReadUInt8();
bitCount = 8;
}
if (n >= 8)
{
bits <<= 8;
bits |= ReadUInt8();
n -= 8;
}
bits <<= n;
bitCount -= n;
return (bits >> 16) & bitMask[count];
}
static readonly int[] bitMask = new[] {
0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F,
0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF,
0x0000FFFF, 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 0x000FFFFF, 0x001FFFFF, 0x003FFFFF, 0x007FFFFF,
0x00FFFFFF, 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF
};
static readonly sbyte[] shift_R = new sbyte[] {
-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,
-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
};
static readonly sbyte[] shift_G = new sbyte[] {
-3, -3, -3, -3, -3, -3, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, -3, -3, -3, -3, -3, -3, -2, -2, -2,
-2, -2, -2, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,
2, 2, 2, 2, -3, -3, -3, -3, -3, -3, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1,
-1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, -3, -3, -3, -3,
-3, -3, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, -3, -3, -3, -3, -3, -3, -2, -2, -2, -2, -2,
-2, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
2, 2, -3, -3, -3, -3, -3, -3, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
};
static readonly sbyte[] shift_B = new sbyte[] {
-3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2,
-3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0,
1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2,
-1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2,
-3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0,
1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2,
-1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2,
-3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0,
1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2,
-1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2,
};
}
} }

133
Legacy/James/ImageJMG.cs Normal file
View File

@ -0,0 +1,133 @@
//! \file ImageJMG.cs
//! \date 2022 Jun 11
//! \brief JAMES engine encrypted image.
//
// Copyright (C) 2022 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using GameRes.Utility;
using System.ComponentModel.Composition;
using System.IO;
// [981105][Berserker] Situation
// [991109][Berserker] Situation 2
namespace GameRes.Formats.James
{
public enum Obfuscation
{
None,
RotateWords,
ReverseBits
}
public class JmgMetaData : ImageMetaData
{
public Obfuscation Method;
public ImageMetaData BmpInfo;
}
[Export(typeof(ImageFormat))]
public class JmgFormat : ImageFormat
{
public override string Tag { get { return "JMG"; } }
public override string Description { get { return "JAMES engine obfuscated bitmap"; } }
public override uint Signature { get { return 0; } }
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (2);
Obfuscation method;
if (header[0] == 0xD4 && header[1] == 0x24)
method = Obfuscation.RotateWords;
else if (header[0] == 0xB2 && header[1] == 0x42)
method = Obfuscation.ReverseBits;
else
return null;
using (var input = OpenAsBitmap (file, 0x40, method))
{
var info = Bmp.ReadMetaData (input);
if (null == info)
return null;
return new JmgMetaData
{
Width = info.Width,
Height = info.Height,
BPP = info.BPP,
Method = method,
BmpInfo = info,
};
}
}
IBinaryStream OpenAsBitmap (IBinaryStream input, int length, Obfuscation method)
{
input.Position = 0;
var data = input.ReadBytes (length);
Deobfuscate (data, 0, length, method);
return new BinMemoryStream (data, input.Name);
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var meta = (JmgMetaData)info;
using (var bmp = OpenAsBitmap (file, (int)file.Length, meta.Method))
return Bmp.Read (bmp, meta.BmpInfo);
}
void Deobfuscate (byte[] data, int pos, int length, Obfuscation method)
{
switch (method)
{
case Obfuscation.RotateWords: RotateWords (data, pos, length); break;
case Obfuscation.ReverseBits: ReverseBits (data, pos, length); break;
}
}
void RotateWords (byte[] data, int pos, int length)
{
for (int i = 0; i < length; i += 2)
{
ushort w = LittleEndian.ToUInt16 (data, pos+i);
w = (ushort)(w >> 12 | w << 4);
LittleEndian.Pack (w, data, pos+i);
}
}
void ReverseBits (byte[] data, int pos, int length)
{
for (int i = 0; i < length; i += 2)
{
int v = LittleEndian.ToUInt16 (data, pos+i);
v = (v & 0xAAAA) >> 1 | (v & 0x5555) << 1;
v = (v & 0xCCCC) >> 2 | (v & 0x3333) << 2;
v = (v & 0xF0F0) >> 4 | (v & 0x0F0F) << 4;
v = (v & 0xFF00) >> 8 | (v & 0x00FF) << 8;
LittleEndian.Pack ((ushort)v, data, pos+i);
}
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("JmgFormat.Write not implemented");
}
}
}

Some files were not shown because too many files have changed in this diff Show More