This commit is contained in:
Crsky 2023-08-25 09:03:13 +08:00
commit 902b5381f3
119 changed files with 11077 additions and 617 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("Target", "DAT/GENERIC")]
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.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using GameRes.Compression;
@ -50,14 +49,16 @@ namespace GameRes.Formats.Abel
public GpsFormat ()
{
Extensions = new string[] { "gps", "cmp" };
Extensions = new[] { "gps", "gp2", "cmp" };
Signatures = new[] { 0x535047u, 0x325047u }; // 'GPS', 'GP2'
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x29);
bool is_gp2 = header.AsciiEqual ("GP2");
var gps = new GpsMetaData();
if (header.ToUInt32 (4) == 0xCCCCCCCC)
if (!is_gp2 && header.ToUInt32 (4) == 0xCCCCCCCC)
{
gps.HeaderSize = 0x19;
gps.Compression = 2;
@ -69,6 +70,8 @@ namespace GameRes.Formats.Abel
gps.HeaderSize = 0x29;
gps.Compression = header[0x10];
gps.UnpackedSize = header.ToInt32 (0x11);
if (is_gp2)
gps.UnpackedSize = - 1 - gps.UnpackedSize;
gps.PackedSize = header.ToInt32 (0x15);
gps.Width = header.ToUInt32 (0x19);
gps.Height = header.ToUInt32 (0x1D);

View File

@ -53,6 +53,7 @@ namespace GameRes.Formats.Abogado
{ "PCM1", "ADP" },
{ "PCM2", "ADP" },
{ "PCM", "ADP" },
{ "ADPCM", "ADP" },
{ "GRAPHIC", "KG" },
{ "GRPFILE", "KG" },
{ "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.ComponentModel.Composition;
using System.IO;
using System.Text;
using GameRes.Compression;
namespace GameRes.Formats.AliceSoft
@ -40,6 +41,16 @@ namespace GameRes.Formats.AliceSoft
public override bool IsHierarchic { get { return true; } }
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)
{
if (!file.View.AsciiEqual (8, "AlicArch"))
@ -54,6 +65,7 @@ namespace GameRes.Formats.AliceSoft
if (!IsSaneCount (count))
return null;
var default_enc = NameEncoding;
var dir = new List<Entry> (count);
var name_buf = new byte[0x40];
using (var input = file.CreateStream (0x2C, packed_size))
@ -70,7 +82,7 @@ namespace GameRes.Formats.AliceSoft
name_buf = new byte[index_step];
if (index_step != index.Read (name_buf, 0, index_step))
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);
index.ReadInt32();
index.ReadInt32();

View File

@ -2,7 +2,7 @@
//! \date Fri Jul 29 14:07:15 2016
//! \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
// of this software and associated documentation files (the "Software"), to
@ -27,6 +27,7 @@ using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows.Media;
using GameRes.Compression;
using GameRes.Utility;
@ -37,6 +38,15 @@ namespace GameRes.Formats.AliceSoft
{
public string BaseName;
public long DataOffset;
public bool IsPcf;
}
internal interface IBaseImageReader
{
int BPP { get; }
byte[] Data { get; }
void Unpack ();
}
[Export(typeof(ImageFormat))]
@ -46,17 +56,25 @@ namespace GameRes.Formats.AliceSoft
public override string Description { get { return "AliceSoft System incremental image"; } }
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)
{
stream.Seek (4, SeekOrigin.Current);
uint header_size = stream.ReadUInt32();
long data_pos = stream.Position + header_size;
if (stream.ReadInt32() != 1)
var header = stream.ReadHeader (0x1C);
uint header_size = header.ToUInt32 (4);
long data_pos = 8 + header_size;
if (header.ToInt32 (8) != 1)
return null;
uint width = stream.ReadUInt32();
uint height = stream.ReadUInt32();
int bpp = stream.ReadInt32();
int name_length = stream.ReadInt32();
uint width = header.ToUInt32 (0x0C);
uint height = header.ToUInt32 (0x10);
int bpp = header.ToInt32 (0x14);
int name_length = header.ToInt32 (0x18);
if (name_length <= 0)
return null;
int shift = (name_length % 7) + 1;
@ -70,18 +88,17 @@ namespace GameRes.Formats.AliceSoft
Width = width,
Height = height,
BPP = bpp,
BaseName = Encodings.cp932.GetString (name_bits),
BaseName = Afa.Value.NameEncoding.GetString (name_bits),
DataOffset = data_pos,
IsPcf = stream.Signature == 0x20666370u,
};
}
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();
return ImageData.Create (info, reader.Format, null, reader.Data, reader.Stride);
}
return ImageData.Create (reader.Info, reader.Format, null, reader.Data, reader.Stride);
}
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;
DcfMetaData m_info;
@ -100,10 +117,14 @@ namespace GameRes.Formats.AliceSoft
int m_overlay_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 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 PixelFormat Format { get; private set; }
public int Stride { get; private set; }
@ -112,10 +133,13 @@ namespace GameRes.Formats.AliceSoft
{
m_input = input;
m_info = info;
Info = info;
}
public void Unpack ()
{
int pt_x = 0;
int pt_y = 0;
long next_pos = m_info.DataOffset;
for (;;)
{
@ -131,7 +155,12 @@ namespace GameRes.Formats.AliceSoft
using (var input = new ZLibStream (m_input.AsStream, CompressionMode.Decompress, true))
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;
}
long qnt_pos = m_input.Position;
@ -147,18 +176,28 @@ namespace GameRes.Formats.AliceSoft
var overlay = new QntFormat.Reader (reg, qnt_info);
overlay.Unpack();
m_overlay_bpp = overlay.BPP;
if (m_mask != null)
if (m_mask != null || m_info.IsPcf)
ReadBaseImage();
if (m_base != null)
if (m_info.IsPcf)
{
m_output = ApplyOverlay (overlay.Data);
SetFormat ((int)m_info.Width, m_overlay_bpp);
if (null == m_base)
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
{
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);
}
byte[] ApplyOverlay (byte[] overlay)
void SetEmptyBase ()
{
int blocks_x = (int)m_info.Width / 0x10;
int blocks_y = (int)m_info.Height / 0x10;
m_base_bpp = 32;
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 overlay_step = m_overlay_bpp / 8;
int base_stride = (int)m_info.Width * base_step;
int overlay_stride = (int)m_info.Width * overlay_step;
int base_stride = m_info.iWidth * base_step;
int overlay_stride = m_info.iWidth * overlay_step;
int mask_pos = 4;
for (int y = 0; y < blocks_y; ++y)
{
@ -208,6 +253,58 @@ namespace GameRes.Formats.AliceSoft
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 ()
{
try
@ -215,13 +312,29 @@ namespace GameRes.Formats.AliceSoft
string dir_name = VFS.GetDirectoryName (m_info.FileName);
string base_name = Path.ChangeExtension (m_info.BaseName, "qnt");
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))
{
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)
{
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();
m_base_bpp = reader.BPP;
m_base = reader.Data;
@ -233,11 +346,5 @@ namespace GameRes.Formats.AliceSoft
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);
}
internal class Reader
internal class Reader : IBaseImageReader
{
byte[] m_input;
byte[] m_alpha;

View File

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

View File

@ -83,61 +83,6 @@ namespace GameRes.Formats.Apricot
dir.Add (entry);
}
}
return new Mpf2Archive (file, this, dir, arc_list);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var mpf = (Mpf2Archive)arc;
var input = mpf.ViewChain.CreateStream (entry.Offset, entry.Size);
var pent = entry as PackedEntry;
if (pent != null && pent.IsPacked)
input = new ZLibStream (input, CompressionMode.Decompress);
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
{
@ -153,56 +98,18 @@ namespace GameRes.Formats.Apricot
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;
return new MultiFileArchive (file, this, dir, parts);
}
}
bool m_disposed = false;
public void Dispose ()
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
if (m_disposed)
return;
if (m_parts != null)
{
foreach (var arc in m_parts)
arc.Dispose();
}
m_disposed = true;
var mpf = (MultiFileArchive)arc;
var input = mpf.OpenStream (entry);
var pent = entry as PackedEntry;
if (pent != null && pent.IsPacked)
input = new ZLibStream (input, CompressionMode.Decompress);
return input;
}
}
}

View File

@ -350,4 +350,10 @@ namespace GameRes.Formats
public override string Description { get { return "Unidentified data file"; } }
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

@ -115,15 +115,22 @@
<Compile Include="Abogado\ImageKG.cs" />
<Compile Include="Actgs\ArcCG.cs" />
<Compile Include="Actgs\ArcDAT.cs" />
<Compile Include="Adobe\ArcAIR.cs" />
<Compile Include="Ads\ArcPAC.cs" />
<Compile Include="AdvDx\ArcPKD.cs" />
<Compile Include="AdvScripter\ArcPAK.cs" />
<Compile Include="AdvSys\ArcAdvSys3.cs" />
<Compile Include="AdvSys\ImageGWD.cs" />
<Compile Include="Ail\ArcLNK2.cs" />
<Compile Include="AIRNovel\ArcAIR.cs" />
<Compile Include="BlueGale\ImageBBM.cs" />
<Compile Include="ClickTeam\ArcMF.cs" />
<Compile Include="DxLib\ArcDX8.cs" />
<Compile Include="Hypatia\ArcLPK.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\ImageDPO.cs" />
<Compile Include="AliceSoft\ArcAAR.cs" />
@ -147,6 +154,12 @@
<Compile Include="Apricot\ArcDAT.cs" />
<Compile Include="ArcARCX.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" />
<Compile Include="Basil\ArcMIF.cs" />
<Compile Include="Basil\AudioWHC.cs" />
@ -273,6 +286,9 @@
<Compile Include="Mnp\ArcMMA.cs" />
<Compile Include="MultiFileArchive.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\ArcMPK.cs" />
<Compile Include="NitroPlus\ArcNPP.cs" />
@ -357,6 +373,8 @@
<Compile Include="Software House Parsley\ArcPAC.cs" />
<Compile Include="Software House Parsley\ArcUCG.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="Stack\ArcGPK.cs" />
<Compile Include="Strikes\ArcPCK.cs" />
@ -766,7 +784,7 @@
<Compile Include="MangaGamer\ArcMGPK.cs" />
<Compile Include="MnoViolet\ArcMnoViolet.cs" />
<Compile Include="FC01\ArcMRG.cs" />
<Compile Include="ArcNEKO.cs" />
<Compile Include="Nekopack\ArcNEKO.cs" />
<Compile Include="Nexas\ArcPAC.cs" />
<Compile Include="NitroPlus\ArcNitro.cs" />
<Compile Include="Entis\ArcNOA.cs" />
@ -846,6 +864,7 @@
<Compile Include="Will\ArcPNA.cs" />
<Compile Include="Will\ArcPulltop.cs" />
<Compile Include="Will\ArcWIP.cs" />
<Compile Include="Winters\ArcCFP.cs" />
<Compile Include="Winters\ArcDAT.cs" />
<Compile Include="Winters\ArcIFP.cs" />
<Compile Include="Winters\ArcIFX.cs" />
@ -854,6 +873,7 @@
<Compile Include="Xuse\ArcWAG.cs" />
<Compile Include="Xuse\ArcXARC.cs" />
<Compile Include="Xuse\ArcXuse.cs" />
<Compile Include="Xuse\ImageP.cs" />
<Compile Include="YaneSDK\ArcDAT.cs" />
<Compile Include="YaneSDK\ArcHibiki.cs" />
<Compile Include="Yatagarasu\ArcPKG.cs" />

View File

@ -27,11 +27,13 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using GameRes.Compression;
using GameRes.Formats.Maika;
using GameRes.Utility;
namespace GameRes.Formats.BellDa
{
[Export(typeof(ArchiveFormat))]
public sealed class BldOpener : ArchiveFormat
public sealed class BldOpener : Mk2Opener
{
public override string Tag { get { return "DAT/BLD"; } }
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 CanWrite { get { return false; } }
public BldOpener ()
{
Signatures = new[] { this.Signature };
Settings = null;
Scheme = null;
}
public override ArcFile TryOpen (ArcView file)
{
var version_str = file.View.ReadString (4, 4).TrimEnd ('\x1A');
@ -66,26 +75,5 @@ namespace GameRes.Formats.BellDa
}
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))
{
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)
{
data[dst++] = v;
}
}
else
{
data[dst++] = m_input.ReadUInt8();
}
if (dst >= data.Length)
break;
bits[i >> 3] >>= 1;
}
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.IO;
using System.Linq;
using GameRes.Formats.Cmvs;
using GameRes.Utility;
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 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);
if (null != arc)
return arc;
// both CmvsMd5 and index was altered by ReadIndex in decryption attempt
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();
}
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)
{
var cmvs_md5 = Cmvs.MD5.Create (scheme.Md5Variant);

View File

@ -27,7 +27,7 @@ using GameRes.Utility;
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
{
@ -46,6 +46,7 @@ namespace GameRes.Formats.Cmvs
case Md5Variant.Memoria: return new Md5Memoria();
case Md5Variant.Natsu: return new Md5Natsu();
case Md5Variant.Aoi: return new Md5Aoi();
case Md5Variant.Mirai: return new Md5Mirai();
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
{
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
// of this software and associated documentation files (the "Software"), to
@ -29,11 +29,11 @@ namespace GameRes.Formats.??????
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
{
public override string Tag { get { return "ARC"; } }
public override string Description { get { return "?????? engine resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override string Tag { get => "ARC"; }
public override string Description { get => "?????? engine resource archive"; }
public override uint Signature { get => 0; }
public override bool IsHierarchic { get => false; }
public override bool CanWrite { get => false; }
public override 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
// of this software and associated documentation files (the "Software"), to
@ -26,10 +26,10 @@ namespace GameRes.Formats.??????
[Export(typeof(AudioFormat))]
public class xxxAudio : AudioFormat
{
public override string Tag { get { return "xxx"; } }
public override string Description { get { return "?????? audio resource"; } }
public override uint Signature { get { return 0; } }
public override bool CanWrite { get { return false; } }
public override string Tag { get => "xxx"; }
public override string Description { get => "?????? audio resource"; }
public override uint Signature { get => 0; }
public override bool CanWrite { get => false; }
public override 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
// of this software and associated documentation files (the "Software"), to
@ -28,9 +28,9 @@ namespace GameRes.Formats.??????
[Export(typeof(ImageFormat))]
public class xxxFormat : ImageFormat
{
public override string Tag { get { return "xxx"; } }
public override string Description { get { return "?????? image format"; } }
public override uint Signature { get { return 0; } }
public override string Tag { get => "xxx"; }
public override string Description { get => "?????? image format"; }
public override uint Signature { get => 0; }
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; } }
static Lazy<ImageFormat> s_TlgFormat = new Lazy<ImageFormat> (() => ImageFormat.FindByTag ("TLG"));
static ResourceInstance<ImageFormat> s_TlgFormat = new ResourceInstance<ImageFormat> ("TLG");
}
/// <summary>

View File

@ -56,6 +56,7 @@ namespace GameRes.Formats.Entis
float[] m_ptrMatrixBuf;
float[] m_ptrIQParamBuf;
byte[] m_ptrIQParamTable;
// float[] m_ptrBuffer2;
sbyte[] m_ptrBlockLineBuf;
sbyte[] m_ptrNextBlockBuf;
@ -769,6 +770,7 @@ namespace GameRes.Formats.Entis
{
throw new NotImplementedException();
/*
if (m_nChannelCount < 3)
throw new InvalidFormatException();
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 ()
{
int ptrDstLine = m_ptrDstBlock;

View File

@ -35,6 +35,7 @@ namespace GameRes.Formats.Eternity
public bool HasAlpha;
public int BlockSize;
public uint DataOffset;
public uint AlphaOffset;
}
[Export(typeof(ImageFormat))]
@ -64,6 +65,7 @@ namespace GameRes.Formats.Eternity
HasAlpha = header.ToInt32 (8) != 0,
BlockSize = header.ToUInt16 (0xC),
DataOffset = header.ToUInt32 (0x14),
AlphaOffset = header.ToUInt32 (0x1C),
};
}
@ -87,13 +89,14 @@ namespace GameRes.Formats.Eternity
byte[] 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)
{
m_input = input;
m_info = info;
m_output = new byte[3 * info.Width * info.Height];
Format = PixelFormats.Bgr24;
}
public byte[] Unpack ()
@ -128,9 +131,79 @@ namespace GameRes.Formats.Eternity
g = m_output[start_pos+1];
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;
}
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 mask2;
uint mask3;

View File

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

View File

@ -37,6 +37,8 @@ namespace GameRes.Formats
ArcView m_file;
Dictionary<string, Section> m_section_table;
Section m_overlay;
uint m_image_base = 0;
List<ImageSection> m_section_list;
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>
/// Structure representing section of executable file in the form of its offset and size.
/// </summary>
@ -88,6 +100,16 @@ namespace GameRes.Formats
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>
/// Returns true if executable file contains section <paramref name="name"/>.
/// </summary>
@ -140,6 +162,16 @@ namespace GameRes.Formats
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)
{
return FindString (section, Encoding.ASCII.GetBytes (seq), step);
@ -152,26 +184,101 @@ namespace GameRes.Formats
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 ()
{
long pe_offset = m_file.View.ReadUInt32 (0x3C);
if (pe_offset >= m_file.MaxOffset-0x58 || !m_file.View.AsciiEqual (pe_offset, "PE\0\0"))
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 pe_offset = GetHeaderOffset();
int opt_header = View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader
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 list = new List<ImageSection> (count);
if (section_table + 0x28*count < m_file.MaxOffset)
{
for (int i = 0; i < count; ++i)
{
var name = m_file.View.ReadString (section_table, 8);
var section = new Section {
Size = m_file.View.ReadUInt32 (section_table+0x10),
Offset = m_file.View.ReadUInt32 (section_table+0x14)
var name = View.ReadString (section_table, 8);
var img_section = new ImageSection {
Name = name,
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))
table.Add (name, section);
if (0 != section.Size)
@ -183,6 +290,7 @@ namespace GameRes.Formats
m_overlay.Offset = offset;
m_overlay.Size = (uint)(m_file.MaxOffset - offset);
m_section_table = table;
m_section_list = list;
}
/// <summary>

View File

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

View File

@ -54,6 +54,11 @@ namespace GameRes.Formats.FrontWing
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
public FltOpener()
{
ContainedFormats = new[] { "FG/FWGI", "OGG", "DAT/GENERIC" };
}
public override ArcFile TryOpen (ArcView file)
{
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,
};
}
[Export(typeof(ResourceAlias))]
[ExportMetadata("Extension", "CSF")]
[ExportMetadata("Target", "DAT/GENERIC")]
public class CsfFormat : ResourceAlias { }
}

View File

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

View File

@ -230,7 +230,7 @@ namespace GameRes.Formats.Gs
}
[Export(typeof(ScriptFormat))]
public class GsScriptFormat : ScriptFormat
public class GsScriptFormat : GenericScriptFormat
{
public override string Tag { get { return "SCW"; } }
public override string Description { get { return "GsWin script file"; } }
@ -240,15 +240,5 @@ namespace GameRes.Formats.Gs
{
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 ()
{
Extensions = new string[] { "dpm", "bin" };
Extensions = new string[] { "dpm", "bin", "dat" };
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 ()
{
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)
@ -134,19 +134,19 @@ namespace GameRes.Formats.CandySoft
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
if (entry.Size <= 8)
return input;
var sign = input.Signature;
if (0x32434c5a != sign) // 'ZLC2'
return input;
IBinaryStream input = arc.File.CreateStream (entry.Offset, entry.Size);
while (input.Length > 8 && input.Signature == 0x32434C5A) // 'ZLC2'
{
IBinaryStream unpacked;
using (input)
using (var reader = new Zlc2Reader (input, (int)entry.Size))
using (var reader = new Zlc2Reader (input, (int)input.Length))
{
reader.Unpack();
return new BinMemoryStream (reader.Data, entry.Name);
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))]
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 uint Signature { get { return 0x314c4657; } } // 'WFL1'
public override bool IsHierarchic { get { return true; } }
@ -48,11 +48,12 @@ namespace GameRes.Formats.Kaguya
public ArcOpener ()
{
Extensions = new string[] { "arc" };
ContainedFormats = new[] { "AP", "APS3", "OGG", "DAT/GENERIC" };
}
public override ArcFile TryOpen (ArcView file)
{
var reader = new IndexReader();
var reader = new IndexReader (this);
var dir = reader.ReadIndex (file);
if (null == dir || 0 == dir.Count)
return null;
@ -77,9 +78,15 @@ namespace GameRes.Formats.Kaguya
internal class IndexReader
{
ArchiveFormat m_format;
byte[] m_name_buf = new byte[0x20];
List<Entry> m_dir = new List<Entry>();
public IndexReader (ArchiveFormat format)
{
m_format = format;
}
public List<Entry> ReadIndex (ArcView file)
{
string ari_name = Path.ChangeExtension (file.Name, "ari");
@ -163,7 +170,7 @@ namespace GameRes.Formats.Kaguya
else if (1 == entry.Mode)
entry.Type = "image";
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)
@ -246,4 +253,9 @@ namespace GameRes.Formats.Kaguya
}
#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 ()
{
var params_dir = VFS.GetDirectoryName (m_input.Name);
var params_dat = VFS.CombinePath (params_dir, "params.dat");
var params_dat = VFS.ChangeFileName (m_input.Name, "params.dat");
if (!VFS.FileExists (params_dat))
return null;
@ -555,8 +554,10 @@ namespace GameRes.Formats.Kaguya
m_input.Position = start;
SkipChunk();
m_title = ReadString();
if (m_version.Major < 3)
if (m_version.Major < 2)
m_input.ReadCString();
// else
// SkipString();
SkipString();
SkipString();
m_input.ReadByte();
@ -583,7 +584,7 @@ namespace GameRes.Formats.Kaguya
{
ReadHeader (0x17);
if ("幼なじみと甘~くエッチに過ごす方法" == m_title)
if ("幼なじみと甘~くエッチに過ごす方法" == m_title || "艶女医" == m_title)
{
int count = m_input.ReadUInt8();
for (int i = 0; i < count; ++i)
@ -595,9 +596,16 @@ namespace GameRes.Formats.Kaguya
}
SkipArray();
SkipArray();
if ("幼なじみと甘~くエッチに過ごす方法" == m_title)
{
m_input.ReadInt32();
return m_input.ReadBytes (240000);
}
else
{
return ReadKey();
}
}
else // 毎日がM!
{
int count = m_input.ReadUInt8();

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

@ -1555,4 +1555,43 @@ namespace GameRes.Formats.KiriKiri
Decrypt (entry, offset, buffer, pos, count);
}
}
[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
{
public int FrameOffset;
public bool IsCompressed;
}
[Export(typeof(ImageFormat))]
@ -67,8 +68,7 @@ namespace GameRes.Formats.Lambda
int y = file.ReadInt32();
file.Position = frame_offset+0x30;
if (file.ReadByte() != 1)
return null;
bool compressed = file.ReadByte() != 0;
int format = file.ReadByte();
if (format != 4 && format != 5 && format != 2)
return null;
@ -79,6 +79,7 @@ namespace GameRes.Formats.Lambda
OffsetY = y,
BPP = 8 * (format - 1),
FrameOffset = frame_offset,
IsCompressed = compressed,
};
}
@ -104,6 +105,7 @@ namespace GameRes.Formats.Lambda
int m_channels;
int m_base_offset;
int[] m_rows_sizes;
bool m_compressed;
public PixelFormat Format { get; private set; }
public BitmapPalette Palette { get; private set; }
@ -112,6 +114,7 @@ namespace GameRes.Formats.Lambda
{
m_input = input;
m_base_offset = info.FrameOffset;
m_compressed = info.IsCompressed;
m_width = (int)info.Width;
m_height = (int)info.Height;
m_channels = info.BPP / 8;
@ -120,7 +123,7 @@ namespace GameRes.Formats.Lambda
else if (4 == m_channels)
Format = PixelFormats.Bgra32;
else
Format = PixelFormats.Bgr32;
Format = PixelFormats.Bgr24;
m_channel = new byte[m_width * m_height];
m_rows_sizes = new int[m_height];
}
@ -148,13 +151,20 @@ namespace GameRes.Formats.Lambda
UnpackChannel (sizes[0]);
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)
{
SetPosition (offsets[i]);
UnpackChannel (sizes[i]);
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++];
}
@ -169,6 +179,11 @@ namespace GameRes.Formats.Lambda
void UnpackChannel (int size)
{
if (!m_compressed)
{
ReadV0 (size);
return;
}
int method = Binary.BigEndian (m_input.ReadUInt16());
if (method > 1)
throw new InvalidFormatException();

View File

@ -181,7 +181,7 @@ namespace GameRes.Formats.Liar
}
//[Export(typeof(ScriptFormat))]
public class GscFormat : ScriptFormat
public class GscFormat : GenericScriptFormat
{
public override string Tag { get { return "GSC"; } }
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 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[] IndexEnd = Enumerable.Repeat<byte> (0, 0x10).ToArray();

View File

@ -32,13 +32,13 @@ using System.Text;
using GameRes.Compression;
using GameRes.Utility;
namespace GameRes.Formats.Selen
namespace GameRes.Formats.Macromedia
{
[Export(typeof(ArchiveFormat))]
public class CctOpener : ArchiveFormat
{
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 bool IsHierarchic { 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.Linq;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GameRes.Utility;
namespace GameRes.Formats.Selen
namespace GameRes.Formats.Macromedia
{
[Export(typeof(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;
// 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);
foreach (var pair in scheme.ScrambleMap)

View File

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

View File

@ -75,9 +75,7 @@ namespace GameRes.Formats
}
part_offset = part_end_offset;
}
if (null == input)
return Stream.Null;
return input;
return input ?? Stream.Null;
}
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,
};
}
[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;
}
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))]
public class NpfOpener : ArchiveFormat
{
@ -49,8 +67,28 @@ namespace GameRes.Formats.Nonono
{
if (file.View.ReadInt32 (4) != 4 || file.View.ReadInt32 (8) != 1)
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 rnd = new RandomGenerator (DefaultSeed);
// rnd.SRand (DefaultSeed); // generator already seeded by GetGenerators()
Decrypt (header, 0, header.Length, rnd);
if (!header.AsciiEqual ("FAT "))
return null;
@ -84,16 +122,16 @@ namespace GameRes.Formats.Nonono
pos += 20;
name_pos += name_length;
}
return new ArcFile (file, this, dir);
return dir;
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var nent = entry as NpfEntry;
if (null == nent)
return base.OpenEntry (arc, entry);
var narc = (NpfArchive)arc;
var nent = (NpfEntry)entry;
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);
return new BinMemoryStream (data, entry.Name);
}
@ -106,20 +144,20 @@ namespace GameRes.Formats.Nonono
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)
data[pos + i] ^= (byte)rnd.Rand();
}
}
internal class RandomGenerator
internal class RandomGenerator1 : IRandomGenerator
{
int m_seed;
const int DefaultSeed = 0x67895;
public RandomGenerator (int seed = DefaultSeed)
public RandomGenerator1 (int seed = DefaultSeed)
{
SRand (seed);
}
@ -141,4 +179,30 @@ namespace GameRes.Formats.Nonono
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))
{
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);
}
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
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.2.48.2266")]
[assembly: AssemblyFileVersion ("1.2.48.2266")]
[assembly: AssemblyVersion ("1.2.48.2200")]
[assembly: AssemblyFileVersion ("1.2.48.2200")]

View File

@ -801,5 +801,17 @@ namespace GameRes.Formats.Properties {
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">
<Value Profile="(Default)" />
</Setting>
<Setting Name="AFAEncodingCP" Type="System.Int32" Scope="User">
<Value Profile="(Default)">932</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@ -84,6 +84,9 @@ namespace GameRes.Formats.Rpm
/// <summary>Minimum entry length across all possible archive schemes.</summary>
const int MinEntryLength = 0x24;
// largest size should be first
static readonly int[] PossibleNameSizes = new[] { 0x20, 0x18 };
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0);
@ -94,7 +97,7 @@ namespace GameRes.Formats.Rpm
return null;
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
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.IO;
// [960920][Petit] Trouble Outsiders
// [980220][Euphony Production] Happening Journey
namespace GameRes.Formats.ArchAngel
{
[Export(typeof(ArchiveFormat))]
@ -44,7 +47,7 @@ namespace GameRes.Formats.ArchAngel
ContainedFormats = new[] { "CB" };
}
static readonly string[] DefaultSections = { "image", "script", null };
static readonly string[] DefaultSections = { "image", "script", "" };
public override ArcFile TryOpen (ArcView file)
{
@ -54,13 +57,7 @@ namespace GameRes.Formats.ArchAngel
int file_count = file.View.ReadInt16 (0);
if (!IsSaneCount (file_count))
return null;
uint index_pos = 2;
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;
}
long index_pos = 2 + 4 * file_count;
var section_table = new SortedDictionary<int, uint>();
uint min_offset = (uint)file.MaxOffset;
while (index_pos + 6 <= min_offset)
@ -76,26 +73,34 @@ namespace GameRes.Formats.ArchAngel
}
var dir = new List<Entry> (file_count);
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)
{
int i = section.Key;
uint base_offset = section.Value;
do
{
uint size = size_table[i];
var entry = new PackedEntry {
uint size = file.View.ReadUInt32 (2 + i * 4);
if (size > 0)
{
var entry = new PackedEntry
{
Name = string.Format("{0}-{1:D6}", section_num, i),
Type = get_type(),
Offset = base_offset,
Size = size,
};
if (!entry.CheckPlacement(file.MaxOffset))
return null;
if (section_num < DefaultSections.Length && DefaultSections[section_num] != null)
entry.Type = DefaultSections[section_num];
if ("script" == entry.Type)
entry.IsPacked = true;
dir.Add(entry);
base_offset += size;
}
++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);
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);
if (signature < 4 || 0 != (signature & 0xFF000000))
@ -141,4 +149,72 @@ namespace GameRes.Formats.Seraphim
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)
{
if (index_offset >= max_offset)
return null;
int base_count = file.View.ReadInt32 (index_offset);
int file_count = file.View.ReadInt32 (index_offset + 4);
index_offset += 8;
@ -220,7 +222,9 @@ namespace GameRes.Formats.Seraphim
{
uint width = 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;
return new ImageFormatDecoder (input);

View File

@ -47,7 +47,7 @@ namespace GameRes.Formats.Seraphim
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)
{

View File

@ -47,12 +47,15 @@ namespace GameRes.Formats.Seraphim
public SeraphCfImage ()
{
Extensions = new string[] { "cts" };
Signatures = new [] { 0x4643u, 0x024643u, 0x044643u, 0x074643u, 0x094643u, 0x144643u, 0u };
Extensions = new [] { "cts" };
}
public override ImageMetaData ReadMetaData (IBinaryStream stream)
{
var header = stream.ReadHeader (0x10);
if ('C' != header[0] || 'F' != header[1] || 0 != header[3])
return null;
int packed_size = header.ToInt32 (12);
if (packed_size <= 0 || packed_size > stream.Length-0x10)
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 CanWrite { get { return false; } }
public BmxOpener ()
{
ContainedFormats = new[] { "BC" };
}
public override ArcFile TryOpen (ArcView file)
{
uint total_size = file.View.ReadUInt32 (0);
@ -48,34 +53,29 @@ namespace GameRes.Formats.Will
var dir = new List<Entry> (count);
uint index_offset = 0x10;
uint next_offset = file.View.ReadUInt32 (index_offset+0x1C);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index_offset, 0x1C);
if (0 == name.Length)
break;
index_offset += 0x20;
var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = next_offset;
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;
entry.Offset = file.View.ReadUInt32 (index_offset+0x1C);
if (string.IsNullOrEmpty (entry.Type))
entry.Type = "image";
dir.Add (entry);
index_offset += 0x20;
}
if (0 == dir.Count)
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);
}
}

View File

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

View File

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

View File

@ -43,6 +43,7 @@ namespace GameRes.Formats.Will
{
Extensions = new string[] { "wsm" };
Signatures = new uint[] { 0x324D5357, 0x334D5357 };
ContainedFormats = new[] { "WAV" };
}
public override ArcFile TryOpen (ArcView file)
@ -71,6 +72,7 @@ namespace GameRes.Formats.Will
dir.Add (entry);
}
int index_offset = 0;
var names = new HashSet<string>();
for (int i = 0; i < count; ++i)
{
int entry_pos = index.ToInt32 (index_offset);
@ -83,8 +85,20 @@ namespace GameRes.Formats.Will
int entry_idx = index[entry_pos+3];
if (entry_idx >= dir.Count)
return null;
if (0 == entry_idx)
entry_idx = i;
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);
}

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 '
return false;
int bmp_size = file.ReadInt32();
if (bmp_size <= 0x20)
if (bmp_size < 0x1C)
return false;
info.BmpOffset = file.Position + 0x18;
info.Width = file.ReadUInt16();

View File

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

View File

@ -104,13 +104,42 @@ namespace GameRes.Formats.Unity
Load2021 (reader);
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);
if (0 == m_DataLength && type.Version.StartsWith ("2017.")) // "2017.2.0f3" || "2017.1.1p1"
reader.ReadInt64();
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();
reader.Align();
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" };
Signatures = new uint[] { 0x40474157, 0x34464147 }; // 'GAF4'
// ContainedFormats = new [] { "PNG", "P/4AG" };
}
public override ArcFile TryOpen (ArcView file)
@ -284,7 +285,7 @@ namespace GameRes.Formats.Xuse
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">
<value />
</setting>
<setting name="AFAEncodingCP" serializeAs="String">
<value>932</value>
</setting>
</GameRes.Formats.Properties.Settings>
</userSettings>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /></startup>

View File

@ -40,7 +40,7 @@ namespace GameRes.Formats.Elf
public G24Format ()
{
Extensions = new string[] { "g24", "g16" };
Extensions = new string[] { "g24", "g16", "g32" };
}
public override ImageMetaData ReadMetaData (IBinaryStream input)
@ -57,7 +57,8 @@ namespace GameRes.Formats.Elf
Height = (uint)h,
OffsetX = x,
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))
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);
}
}

View File

@ -77,7 +77,13 @@ namespace GameRes.Formats.Rugp
Size = node.Size
};
if (!dir.Any())
{
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());
}
}
@ -307,7 +313,7 @@ namespace GameRes.Formats.Rugp
public int GetObjectSchema ()
{
int schema = m_objectSchema;
m_objectSchema = -1;
// m_objectSchema = -1;
return schema;
}
@ -329,9 +335,12 @@ namespace GameRes.Formats.Rugp
public CObject ReadObject (COceanNode node)
{
m_field_60 = false;
var obj = CreateObject (node.Name);
PopulateLoadArray();
m_input.Position = ((long)node.Offset << m_shift);
// if (node.Name != "CrelicUnitedGameProject")
// m_input.Seek (3, SeekOrigin.Current);
int f1 = ReadByte() & 3;
int f2 = ReadByte();
int f3 = ReadByte();
@ -700,7 +709,7 @@ namespace GameRes.Formats.Rugp
class_ref = LoadRuntimeClass (out schema);
if (null == class_ref)
throw new InvalidFormatException();
// m_objectSchema = (int)schema;
m_objectSchema = (int)schema;
m_LoadArray.Add (class_ref);
}
@ -957,6 +966,7 @@ namespace GameRes.Formats.Rugp
{ "CrelicUnitedGameProject", new CObjectFactory<CrelicUnitedGameProject>() },
{ "CStdb", new CObjectFactory<CStdb>() },
{ "CObjectOcean", new CObjectFactory<CObjectOcean>() },
{ "CBoxOcean", new CObjectFactory<CBoxOcean>() },
};
}
@ -1352,10 +1362,73 @@ namespace GameRes.Formats.Rugp
internal class CBoxOcean : CObject
{
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)
{
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
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.0.16.46")]
[assembly: AssemblyFileVersion ("1.0.16.46")]
[assembly: AssemblyVersion ("1.0.16.42")]
[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 bool CanWrite { get { return false; } }
public RpgmvoAudio ()
{
Extensions = new[] { "rpgmvo", "ogg_" };
}
public override SoundInput TryOpen (IBinaryStream file)
{
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 uint Signature { get { return 0x4D475052; } } // 'RPGMV'
public RpgmvpFormat ()
{
Extensions = new string[] { "rpgmvp", "png_" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x14);
@ -149,6 +154,8 @@ namespace GameRes.Formats.RPGMaker
var dir_name = Path.GetDirectoryName (filename);
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, @"..\..\..\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
{
[Export(typeof(ImageFormat))]
[ExportMetadata("Priority", 100)] // makes PNG first format in list
public class PngFormat : ImageFormat
{
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
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.5.44.324")]
[assembly: AssemblyFileVersion ("1.5.44.324")]
[assembly: AssemblyVersion ("1.5.44.321")]
[assembly: AssemblyFileVersion ("1.5.44.321")]

View File

@ -30,12 +30,51 @@ namespace GameRes
{
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 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 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)
{
throw new System.NotImplementedException();

View File

@ -38,12 +38,23 @@ namespace GameRes.Formats.Airyu
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
static readonly uint[] ImageSizes = new[] { 0x96000u, 0x4B000u, 0x19000u };
public override ArcFile TryOpen (ArcView file)
{
if (!file.Name.HasExtension (".chr"))
return null;
int count = (int)(file.MaxOffset / 0x96000);
if (!IsSaneCount (count) || count * 0x96000 != file.MaxOffset)
int count = 0;
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;
uint offset = 0;
@ -54,10 +65,10 @@ namespace GameRes.Formats.Airyu
Name = i.ToString ("D5"),
Type = "image",
Offset = offset,
Size = 0x96000,
Size = image_size,
};
dir.Add (entry);
offset += 0x96000;
offset += image_size;
}
return new ArcFile (file, this, dir);
}
@ -65,21 +76,32 @@ namespace GameRes.Formats.Airyu
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new ChrImageDecoder (input);
return new ChrImageDecoder (input, entry.Size);
}
}
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 ()
{
var pixels = m_input.ReadBytes (0x96000);
return ImageData.CreateFlipped (Info, PixelFormats.Bgr555, null, pixels, 640*2);
var pixels = m_input.ReadBytes (m_image_size);
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;
// [000623][Marimo] Setsunai
// [010316][Evolution] Bukkake Tenshi Silky & Milky
namespace GameRes.Formats.Dice
{

View File

@ -30,6 +30,7 @@ using GameRes.Compression;
// [030725][Digital Monkey] Kono Sora ga Tsuieru Toki ni
// [041112][Yumesta] Seikon ~Kiba to Nie to Kyouki no Yakata~
// [050325][Supple Entertainment] Dark: Shitsuraku no Shimai Ningyou
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.
//
using GameRes.Utility;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
// [980626][Love Gun] ACE OF SPADES 2
namespace GameRes.Formats.HyperWorks
{
[Export(typeof(ImageFormat))]
@ -51,7 +55,8 @@ namespace GameRes.Formats.HyperWorks
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)
@ -59,4 +64,439 @@ namespace GameRes.Formats.HyperWorks
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