diff --git a/ArcFormats/Ankh/ArcGRP.cs b/ArcFormats/Ankh/ArcGRP.cs
index 4dc09bed..a594f541 100644
--- a/ArcFormats/Ankh/ArcGRP.cs
+++ b/ArcFormats/Ankh/ArcGRP.cs
@@ -118,6 +118,12 @@ namespace GameRes.Formats.Ankh
entry.UnpackedSize = header.ToUInt32 (0);
entry.IsPacked = true;
}
+ else if (header.AsciiEqual (0, "zfd "))
+ {
+ entry.ChangeType (ImageFormat.Tga);
+ entry.UnpackedSize = header.ToUInt32 (4);
+ entry.IsPacked = true;
+ }
else if (header.AsciiEqual (4, "OggS"))
{
entry.ChangeType (OggAudio.Instance);
@@ -177,6 +183,8 @@ namespace GameRes.Formats.Ankh
return OpenTpw (arc, pent);
if (arc.File.View.AsciiEqual (entry.Offset+4, "HDJ\0"))
return OpenImage (arc, pent);
+ if (arc.File.View.AsciiEqual (entry.Offset, "zfd "))
+ return OpenZfd (arc, pent);
if (entry.Size > 12)
{
byte type = arc.File.View.ReadByte (entry.Offset+4);
@@ -243,6 +251,12 @@ namespace GameRes.Formats.Ankh
}
}
+ Stream OpenZfd (ArcFile arc, PackedEntry entry)
+ {
+ var input = arc.File.CreateStream (entry.Offset+8, entry.Size-8);
+ return new ZLibStream (input, CompressionMode.Decompress);
+ }
+
internal static void UnpackTpw (IBinaryStream input, byte[] output)
{
input.Position = 8;
diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index 2e54d131..63f3500b 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -123,6 +123,8 @@
+
+
@@ -170,9 +172,13 @@
+
+
+
+
@@ -751,7 +757,6 @@
-
diff --git a/ArcFormats/CsWare/AudioWAV.cs b/ArcFormats/CsWare/AudioWAV.cs
new file mode 100644
index 00000000..5cff2cf5
--- /dev/null
+++ b/ArcFormats/CsWare/AudioWAV.cs
@@ -0,0 +1,92 @@
+//! \file AudioWAV.cs
+//! \date 2023 Oct 26
+//! \brief Obscure C's ware WAVE file encoding.
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+
+// [960405][C's Ware] GLO-RI-A ~Kindan no Ketsuzoku~
+
+namespace GameRes.Formats.CsWare
+{
+ [Export(typeof(AudioFormat))]
+ [ExportMetadata("Priority", 1)] // should be tried before generic WAVE format
+ public class WavAudio : AudioFormat
+ {
+ public override string Tag => "WAV/CSWARE";
+ public override string Description => "C's ware encoded audio";
+ public override uint Signature => 0x46464952; // 'RIFF'
+ public override bool CanWrite => false;
+
+ public override SoundInput TryOpen (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x2E);
+ if (header[0x14] != 1 || header[0x15] != 0xFF
+ || !header.AsciiEqual (8, "WAVEfmt ")
+ || !header.AsciiEqual (0x26, "data"))
+ return null;
+ var format = new WaveFormat {
+ FormatTag = 1,
+ Channels = header.ToUInt16 (0x16),
+ SamplesPerSecond = header.ToUInt32 (0x18),
+ AverageBytesPerSecond = header.ToUInt32 (0x1C) * 2,
+ BlockAlign = (ushort)(header.ToUInt16 (0x20) * 2),
+ BitsPerSample = 16,
+ };
+ uint input_size = header.ToUInt32 (0x2A);
+ var samples = new byte[input_size * 2];
+ Decode (file, samples);
+ var decoded = new BinMemoryStream (samples, file.Name);
+ file.Dispose();
+ return new RawPcmInput (decoded, format);
+ }
+
+ void Decode (IBinaryStream input, byte[] output)
+ {
+ int dst = 0;
+ while (input.PeekByte() != -1)
+ {
+ sbyte sample = input.ReadInt8();
+ LittleEndian.Pack (SampleMap[sample + 128], output, dst);
+ dst += 2;
+ }
+ }
+
+ static readonly short[] SampleMap = InitSampleMap();
+
+ static short[] InitSampleMap ()
+ {
+ var map = new short[256];
+ for (int i = 1; i <= 127; ++i)
+ {
+ map[128 + i] = (short)(Math.Pow (10.0, ((double)i + 44.8637) / 38.0597) - 14.5342);
+ map[128 - i] = (short)-map[i + 128];
+ }
+ map[0] = -0x8000;
+ return map;
+ }
+ }
+}
diff --git a/ArcFormats/CsWare/ImageGDT.cs b/ArcFormats/CsWare/ImageGDT.cs
new file mode 100644
index 00000000..e184874f
--- /dev/null
+++ b/ArcFormats/CsWare/ImageGDT.cs
@@ -0,0 +1,569 @@
+//! \file ImageGDT.cs
+//! \date 2023 Sep 29
+//! \brief AGS engine image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace GameRes.Formats.CsWare
+{
+ internal class GdtMetaData : ImageMetaData
+ {
+ public byte Flags;
+
+ public bool HasPalette => (Flags & 0x80) != 0;
+ public bool IsDouble => (Flags & 0x40) == 0;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class GdtFormat : ImageFormat
+ {
+ public override string Tag => "GDT";
+ public override string Description => "AGS engine image format";
+ public override uint Signature => 0x314144; // 'DA1'
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (16);
+ var info = new GdtMetaData {
+ OffsetX = header[8] << 3,
+ OffsetY = header.ToUInt16 (0xA),
+ Width = (uint)header[9] << 3,
+ Height = header.ToUInt16 (0xC),
+ BPP = 4,
+ Flags = header[0xF],
+ };
+ return info;
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new GdtReader (file, (GdtMetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("GdtFormat.Write not implemented");
+ }
+ }
+
+ internal class GdtReader
+ {
+ IBinaryStream m_input;
+ GdtMetaData m_info;
+ int m_stride;
+ int m_output_stride;
+
+ public BitmapPalette Palette { get; private set; }
+
+ public GdtReader (IBinaryStream file, GdtMetaData info)
+ {
+ m_input = file;
+ m_info = info;
+ m_stride = info.iWidth >> 3;
+ m_output_stride = info.iWidth >> 1;
+ }
+
+ byte[][] m_planes;
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 0x10;
+ if (m_info.HasPalette)
+ {
+ Palette = ReadPalette();
+ }
+ var packed_sizes = new ushort[4];
+ for (int i = 0; i < 4; ++i)
+ packed_sizes[i] = m_input.ReadUInt16();
+ long plane_pos = m_input.Position;
+ int plane_size = m_stride * m_info.iHeight;
+ m_planes = new byte[][] {
+ new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size],
+ };
+ Action UnpackPlane = UnpackSingle;
+ if (m_info.IsDouble)
+ UnpackPlane = UnpackDouble;
+ for (int i = 0; i < 4; ++i)
+ {
+ m_input.Position = plane_pos;
+ plane_pos += packed_sizes[i];
+ UnpackPlane (i);
+ }
+ var pixels = new byte[m_output_stride * m_info.iHeight];
+ FlattenPlanes (pixels);
+ PixelFormat format;
+ if (null == Palette)
+ format = PixelFormats.Gray4;
+ else
+ format = PixelFormats.Indexed4;
+ return ImageData.Create (m_info, format, Palette, pixels, m_output_stride);
+ }
+
+ void UnpackSingle (int plane_index)
+ {
+ int h = m_info.iHeight;
+ int w = m_stride;
+ int dst = 0;
+ while (w --> 0)
+ {
+ Unpack8Line (plane_index, dst);
+ dst += h;
+ }
+ }
+
+ void UnpackDouble (int plane_index)
+ {
+ var output = m_planes[plane_index];
+ int h = m_info.iHeight;
+ int width = m_stride;
+ int dst = 0;
+ if ((m_info.OffsetX & 8) != 0)
+ {
+ Unpack8Line (plane_index, dst);
+ --width;
+ dst += h;
+ }
+ if ((m_input.Position & 1) != 0)
+ m_input.Seek (1, SeekOrigin.Current);
+ if (1 == width)
+ {
+ Unpack8Line (plane_index, dst);
+ return;
+ }
+ while (m_input.PeekByte() != -1)
+ {
+ byte op = m_input.ReadUInt8();
+ byte ctl = m_input.ReadUInt8();
+ if (ctl < 0x80)
+ {
+ if (0 == ctl)
+ continue;
+ ushort w = (ushort)(((op & 0xF) << 8 | (op & 0xF0) >> 4) * 0x11);
+ int count = ctl;
+ Fill (output, dst , count, w);
+ Fill (output, dst+h, count, w);
+ dst += count * 2;
+ }
+ else if (ctl < 0xC0)
+ {
+ int count = ctl & 0x3F;
+ int w = op & 0xF | (op & 0xF0) << 4;
+ uint d = (uint)(w | (w & 0x0303) << 18 | (w & 0x0C0C) << 14);
+ d = Binary.BigEndian (d | d << 4);
+ Fill (output, dst , count, d);
+ Fill (output, dst+h, count, d);
+ dst += count * 4;
+ }
+ else if (ctl < 0xD0)
+ {
+ byte b = (byte)((ctl & 0xF) | ctl << 4);
+ int count = op;
+ Fill (output, dst , count, b);
+ Fill (output, dst+h, count, b);
+ dst += count;
+ }
+ else if (ctl < 0xD2)
+ {
+ int count = (ctl & 1) << 8 | op;
+ while (count --> 0)
+ {
+ output[dst ] = m_input.ReadUInt8();
+ output[dst+h] = m_input.ReadUInt8();
+ }
+ }
+ else if (0xD2 == ctl)
+ {
+ dst += op;
+ }
+ else if (ctl < 0xF3)
+ {
+ int count = op;
+ int off = 0;
+ switch (ctl)
+ {
+ case 0xD3: off = 16; break;
+ case 0xD4: off = 12; break;
+ case 0xD5: off = 8; break;
+ case 0xD6: off = 4; break;
+ case 0xD7: off = 2; break;
+ case 0xD8: off = 1; break;
+ case 0xD9: off = h * 2 + 8; break;
+ case 0xDA: off = h * 2 + 4; break;
+ case 0xDB: off = h * 2 + 2; break;
+ case 0xDC: off = h * 2 + 1; break;
+ case 0xDD: off = h * 2; break;
+ case 0xDE: off = h * 2 - 1; break;
+ case 0xDF: off = h * 2 - 2; break;
+ case 0xE0: off = h * 2 - 4; break;
+ case 0xE1: off = h * 2 - 8; break;
+ case 0xE2: off = h * 4 + 8; break;
+ case 0xE3: off = h * 4 + 4; break;
+ case 0xE4: off = h * 4 + 2; break;
+ case 0xE5: off = h * 4 + 1; break;
+ case 0xE6: off = h * 4; break;
+ case 0xE7: off = h * 4 - 1; break;
+ case 0xE8: off = h * 4 - 2; break;
+ case 0xE9: off = h * 4 - 4; break;
+ case 0xEA: off = h * 4 - 8; break;
+ case 0xEB: off = h * 6 + 4; break;
+ case 0xEC: off = h * 6 + 2; break;
+ case 0xED: off = h * 6 + 1; break;
+ case 0xEE: off = h * 6; break;
+ case 0xEF: off = h * 6 - 1; break;
+ case 0xF0: off = h * 6 - 2; break;
+ case 0xF1: off = h * 6 - 4; break;
+ case 0xF2: off = h * 8; break;
+ }
+ Binary.CopyOverlapped (output, dst-off, dst, count);
+ Binary.CopyOverlapped (output, dst-off+h, dst+h, count);
+ dst += count;
+ }
+ else if (ctl < 0xFC)
+ {
+ int count = op;
+ var source = m_planes[(ctl - 0xF3) % 3];
+ if (ctl < 0xF6)
+ {
+ Buffer.BlockCopy (source, dst, output, dst, count);
+ Buffer.BlockCopy (source, dst+h, output, dst+h, count);
+ dst += count;
+ }
+ else if (ctl > 0xF8)
+ {
+ var source1 = m_planes[ctl & 1];
+ var source2 = m_planes[ctl & 2];
+ while (count --> 0)
+ {
+ output[dst] = (byte)(source1[dst] & source2[dst]);
+ output[dst+h] = (byte)(source1[dst+h] & source2[dst+h]);
+ ++dst;
+ }
+ }
+ else
+ {
+ while (count --> 0)
+ {
+ output[dst] = (byte)~source[dst];
+ output[dst+h] = (byte)~source[dst+h];
+ ++dst;
+ }
+ }
+ }
+ else if (0xFC == ctl)
+ {
+ int count = op;
+ byte b = m_input.ReadUInt8();
+ Fill (output, dst , count, b);
+ Fill (output, dst+h, count, b);
+ dst += count;
+ }
+ else if (0xFD == ctl)
+ {
+ if (op < 0x80)
+ {
+ int count = op;
+ ushort w = m_input.ReadUInt16();
+ Fill (output, dst , count, w);
+ Fill (output, dst+h, count, w);
+ dst += count * 2;
+ }
+ else
+ {
+ int count = op & 0x7F;
+ ushort w1 = m_input.ReadUInt16();
+ ushort w2 = m_input.ReadUInt16();
+ Fill (output, dst , count, (ushort)(w1 << 8 | w2 & 0xFF));
+ Fill (output, dst+h, count, (ushort)(w1 & 0xFF | w2 >> 8));
+ dst += count * 2;
+ }
+ }
+ else if (0xFE == ctl)
+ {
+ int count = op & 0x3F;
+ if (op < 0x80)
+ {
+ byte b0 = m_input.ReadUInt8();
+ byte b1 = m_input.ReadUInt8();
+ int d = b1 | b0 << 16;
+ d = d & 0x0F000F | (d & 0xF000F0) << 4;
+ d *= 0x11;
+ d = Binary.BigEndian (d);
+ Fill (output, dst , count, (uint)d);
+ Fill (output, dst+h, count, (uint)d);
+ }
+ else
+ {
+ uint d0 = m_input.ReadUInt32();
+ uint d1 = m_input.ReadUInt32();
+ uint p0 = d0 << 24 | d0 & 0xFF0000 | (d1 & 0xFF) << 8 | (d1 & 0xFF0000) >> 16;
+ uint p1 = (d0 & 0xFF00) << 16 | (d0 & 0xFF000000) >> 8 | d1 & 0xFF00 | (d1 & 0xFF000000) >> 24;
+ Fill (output, dst , count, p0);
+ Fill (output, dst+h, count, p1);
+ }
+ dst += count * 4;
+ }
+ else // 0xFF
+ {
+ dst += h;
+ width -= 2;
+ if (0 == width)
+ break;
+ if (1 == width)
+ {
+ Unpack8Line (plane_index, dst);
+ break;
+ }
+ }
+ }
+ }
+
+ void Unpack8Line (int plane_index, int dst)
+ {
+ var output = m_planes[plane_index];
+ int h = m_info.iHeight;
+ int end_pos = dst + h;
+ while (m_input.PeekByte() != -1)
+ {
+ byte ctl = m_input.ReadUInt8();
+ if (ctl < 0x40)
+ {
+ byte b = 0;
+ if (ctl >= 0x20)
+ b = 0xFF;
+ int count = ctl & 0x1F;
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ Fill (output, dst, count, b);
+ dst += count;
+ }
+ else if (ctl < 0xA0)
+ {
+ int count = ctl & 0x1F;
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ int src_plane = (ctl - 0x40) >> 5;
+ Buffer.BlockCopy (m_planes[src_plane], dst, output, dst, count);
+ dst += count;
+ }
+ else if (ctl < 0xF0)
+ {
+ int count = ctl & 0xF;
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ switch (ctl & 0xF0)
+ {
+ case 0xA0: Binary.CopyOverlapped (output, dst-16, dst, count); break;
+ case 0xB0: Binary.CopyOverlapped (output, dst-8, dst, count); break;
+ case 0xC0: Binary.CopyOverlapped (output, dst-4, dst, count); break;
+ case 0xD0: Binary.CopyOverlapped (output, dst-2, dst, count); break;
+ case 0xE0: Binary.CopyOverlapped (output, dst-h*2, dst, count); break;
+ }
+ dst += count;
+ }
+ else if (ctl < 0xF9)
+ {
+ int count = ctl & 0xF;
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ m_input.Read (output, dst, count);
+ dst += count;
+ }
+ else if (0xF9 == ctl)
+ {
+ dst += m_input.ReadUInt8();
+ }
+ else if (0xFA == ctl)
+ {
+ int count = m_input.ReadUInt8();
+ byte b = m_input.ReadUInt8();
+ Fill (output, dst, count, b);
+ dst += count;
+ }
+ else if (0xFB == ctl)
+ {
+ int count = m_input.ReadUInt8();
+ int b = count >> 7;
+ count &= 0x7F;
+ while (count --> 0)
+ {
+ output[dst] = (byte)~m_planes[b][dst];
+ ++dst;
+ }
+ }
+ else if (0xFC == ctl)
+ {
+ int count = m_input.ReadUInt8();
+ if ((count & 0x80) != 0)
+ {
+ count &= 0x7F;
+ byte b = m_input.ReadUInt8();
+ ushort d = (ushort)(b & 0xF | (b & 0xF0) << 4);
+ d |= (ushort)(d << 4);
+ Fill (output, dst, count, d);
+ dst += count * 2;
+ }
+ else
+ {
+ while (count --> 0)
+ {
+ output[dst] = (byte)~m_planes[2][dst];
+ ++dst;
+ }
+ }
+ }
+ else if (0xFD == ctl)
+ {
+ int count = m_input.ReadUInt8();
+ if ((count & 0x80) != 0)
+ {
+ byte b = m_input.ReadUInt8();
+ uint d = (uint)(b & 0xF | b << 4 | (b & 0xF0) << 8);
+ if ((count & 0x40) != 0)
+ {
+ b = m_input.ReadUInt8();
+ d |= (uint)((b & 0xF) << 16 | b << 20 | (b & 0xF0) << 24);
+ }
+ else
+ {
+ d |= (d & 0x3F3F) << 18 | (d & 0xC0C0) << 10;
+ }
+ count &= 0x3F;
+ Fill (output, dst, count, d);
+ dst += count * 4;
+ }
+ else
+ {
+ ushort w = m_input.ReadUInt16();
+ Fill (output, dst, count, w);
+ dst += count * 2;
+ }
+ }
+ else if (0xFE == ctl)
+ {
+ int count = m_input.ReadUInt8();
+ int b = count & 0xC0;
+ if (b != 0)
+ {
+ count &= 0x3F;
+ b >>= 6;
+ while (count --> 0)
+ {
+ output[dst] = (byte)(m_planes[b & 1][dst] & m_planes[b & 2][dst]);
+ ++dst;
+ }
+ }
+ else
+ {
+ uint u = m_input.ReadUInt32();
+ Fill (output, dst, count, u);
+ dst += count * 4;
+ }
+ }
+ else // 0xFF
+ {
+ break;
+ }
+ }
+ }
+
+ void FlattenPlanes (byte[] output)
+ {
+ int plane_size = m_planes[0].Length;
+ int src = 0;
+ for (int x = 0; x < m_output_stride; x += 4)
+ {
+ int dst = x;
+ for (int y = 0; y < m_info.iHeight; ++y)
+ {
+ byte b0 = m_planes[0][src];
+ byte b1 = m_planes[1][src];
+ byte b2 = m_planes[2][src];
+ byte b3 = m_planes[3][src];
+ ++src;
+ for (int j = 0; j < 8; j += 2)
+ {
+ byte px = (byte)((((b0 << j) & 0x80) >> 3)
+ | (((b1 << j) & 0x80) >> 2)
+ | (((b2 << j) & 0x80) >> 1)
+ | (((b3 << j) & 0x80) >> 0));
+ px |= (byte)((((b0 << j) & 0x40) >> 6)
+ | (((b1 << j) & 0x40) >> 5)
+ | (((b2 << j) & 0x40) >> 4)
+ | (((b3 << j) & 0x40) >> 3));
+ output[dst+j/2] = px;
+ }
+ dst += m_output_stride;
+ }
+ }
+ }
+
+ static void Fill (byte[] output, int dst, int count, byte pixel)
+ {
+ while (count --> 0)
+ {
+ output[dst++] = pixel;
+ }
+ }
+
+ static void Fill (byte[] output, int dst, int count, ushort pixel)
+ {
+ count <<= 1;
+ for (int i = 0; i < count; i += 2)
+ {
+ LittleEndian.Pack (pixel, output, dst+i);
+ }
+ }
+
+ static void Fill (byte[] output, int dst, int count, uint pixel)
+ {
+ count <<= 2;
+ for (int i = 0; i < count; i += 4)
+ {
+ LittleEndian.Pack (pixel, output, dst+i);
+ }
+ }
+
+ BitmapPalette ReadPalette ()
+ {
+ using (var bits = new MsbBitStream (m_input.AsStream, true))
+ {
+ var colors = new Color[16];
+ for (int i = 0; i < 16; ++i)
+ {
+ int b = bits.GetBits (4) * 0x11;
+ int r = bits.GetBits (4) * 0x11;
+ int g = bits.GetBits (4) * 0x11;
+ colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+ }
+}
diff --git a/ArcFormats/Cyberworks/ArcP8.cs b/ArcFormats/Cyberworks/ArcP8.cs
index f968e81f..83342d86 100644
--- a/ArcFormats/Cyberworks/ArcP8.cs
+++ b/ArcFormats/Cyberworks/ArcP8.cs
@@ -55,7 +55,7 @@ namespace GameRes.Formats.TinkerBell
var entry = FormatCatalog.Instance.Create (name);
entry.Offset = file.View.ReadUInt32 (index_offset+0x18);
entry.Size = file.View.ReadUInt32 (index_offset+0x10);
- if (!entry.CheckPlacement (file.MaxOffset))
+ if (entry.Offset <= index_offset || !entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}
diff --git a/ArcFormats/ExeFile.cs b/ArcFormats/ExeFile.cs
index 8c73f48f..b57f9b63 100644
--- a/ArcFormats/ExeFile.cs
+++ b/ArcFormats/ExeFile.cs
@@ -40,6 +40,7 @@ namespace GameRes.Formats
Section m_overlay;
uint m_image_base = 0;
List m_section_list;
+ bool? m_is_NE;
public ExeFile (ArcView file)
{
@@ -56,9 +57,18 @@ namespace GameRes.Formats
///
public Section Whole { get; private set; }
+ public bool IsWin16 => m_is_NE ?? (m_is_NE = IsNe()).Value;
+
+ private bool IsNe ()
+ {
+ uint ne_offset = View.ReadUInt32 (0x3C);
+ return ne_offset < m_file.MaxOffset-2 && View.AsciiEqual (ne_offset, "NE");
+ }
+
///
/// Dictionary of executable file sections.
///
+ ///
public IReadOnlyDictionary Sections
{
get
@@ -255,6 +265,11 @@ namespace GameRes.Formats
private void InitSectionTable ()
{
+ if (IsWin16)
+ {
+ InitNe();
+ return;
+ }
long pe_offset = GetHeaderOffset();
int opt_header = View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader
long section_table = pe_offset+opt_header+0x18;
@@ -294,6 +309,26 @@ namespace GameRes.Formats
m_section_list = list;
}
+ void InitNe ()
+ {
+ uint ne_offset = m_file.View.ReadUInt32 (0x3C);
+ int segment_count = m_file.View.ReadUInt16 (ne_offset + 0x1C);
+ uint seg_table = m_file.View.ReadUInt16 (ne_offset + 0x22) + ne_offset;
+ int shift = m_file.View.ReadUInt16 (ne_offset + 0x32);
+ uint last_seg_end = 0;
+ for (int i = 0; i < segment_count; ++i)
+ {
+ uint offset = (uint)m_file.View.ReadUInt16 (seg_table) << shift;
+ uint size = m_file.View.ReadUInt16 (seg_table+2);
+ if (offset + size > last_seg_end)
+ last_seg_end = offset + size;
+ }
+ m_overlay.Offset = last_seg_end;
+ m_overlay.Size = (uint)(m_file.MaxOffset - last_seg_end);
+ m_section_table = new Dictionary(); // these are empty for 16-bit executables
+ m_section_list = new List(); //
+ }
+
///
/// Helper class for executable file resources access.
///
diff --git a/ArcFormats/ImageMB.cs b/ArcFormats/ImageMB.cs
index 39925bdc..70dc4117 100644
--- a/ArcFormats/ImageMB.cs
+++ b/ArcFormats/ImageMB.cs
@@ -38,23 +38,30 @@ namespace GameRes.Formats
public MbImageFormat ()
{
- Extensions = new[] { "bmp", "gra" };
+ Extensions = new[] { "bmp", "gra", "xxx" };
}
public override ImageMetaData ReadMetaData (IBinaryStream stream)
{
int c1 = stream.ReadByte();
int c2 = stream.ReadByte();
+ // MB/MC/MK/CL/XX
switch (c1)
{
case 'M':
- if ('B' != c2 && 'C' != c2)
+ if ('B' != c2 && 'C' != c2 && 'K' != c2)
return null;
break;
case 'C':
if ('L' != c2)
return null;
break;
+ case 'X':
+ if ('X' != c2)
+ return null;
+ break;
+ default:
+ return null;
}
using (var bmp = OpenAsBitmap (stream))
return Bmp.ReadMetaData (bmp);
diff --git a/ArcFormats/KiriKiri/ArcXP3.cs b/ArcFormats/KiriKiri/ArcXP3.cs
index b96df0fc..6e06bd2f 100644
--- a/ArcFormats/KiriKiri/ArcXP3.cs
+++ b/ArcFormats/KiriKiri/ArcXP3.cs
@@ -89,8 +89,8 @@ namespace GameRes.Formats.KiriKiri
public Xp3Opener ()
{
- Signatures = new uint[] { 0x0d335058, 0 };
- Extensions = new[] { "XP3", "EXE" };
+ Signatures = new uint[] { 0x0d335058, 0x00905A4D, 0 };
+ Extensions = new[] { "xp3", "exe" };
ContainedFormats = new[] { "TLG", "BMP", "PNG", "JPEG", "OGG", "WAV", "TXT" };
}
diff --git a/ArcFormats/Lilim/ArcAOS.cs b/ArcFormats/Lilim/ArcAOS.cs
index 5c4e030d..64cf39a4 100644
--- a/ArcFormats/Lilim/ArcAOS.cs
+++ b/ArcFormats/Lilim/ArcAOS.cs
@@ -60,6 +60,7 @@ namespace GameRes.Formats.Lilim
if (!name_buf.SequenceEqual (IndexLink) && !name_buf.SequenceEqual (IndexEnd))
return null;
+ string last_name = null;
long current_offset = 0;
var dir = new List (0x3E);
while (current_offset < file.MaxOffset)
@@ -79,6 +80,9 @@ namespace GameRes.Formats.Lilim
if (-1 == name_length)
name_length = name_buf.Length;
var name = Encodings.cp932.GetString (name_buf, 0, name_length);
+ if (last_name == name || string.IsNullOrWhiteSpace (name))
+ return null;
+ last_name = name;
var entry = FormatCatalog.Instance.Create (name);
entry.Offset = file.View.ReadUInt32 (current_offset+0x10);
entry.Size = file.View.ReadUInt32 (current_offset+0x14);
diff --git a/ArcFormats/LiveMaker/ArcVF.cs b/ArcFormats/LiveMaker/ArcVF.cs
index ee40f60f..fd8e505d 100644
--- a/ArcFormats/LiveMaker/ArcVF.cs
+++ b/ArcFormats/LiveMaker/ArcVF.cs
@@ -45,7 +45,7 @@ namespace GameRes.Formats.LiveMaker
public VffOpener ()
{
Extensions = new string[] { "dat", "exe" };
- Signatures = new uint[] { 0x666676, 0 };
+ Signatures = new uint[] { 0x666676, 0x00905A4D, 0 };
}
public override ArcFile TryOpen (ArcView file)
@@ -66,6 +66,8 @@ namespace GameRes.Formats.LiveMaker
&& (0x5A4D == (signature & 0xFFFF))) // 'MZ'
{
base_offset = SkipExeData (index_file);
+ if (base_offset >= file.MaxOffset)
+ return null;
signature = index_file.View.ReadUInt32 (base_offset);
}
else if (!file.Name.HasExtension (".dat"))
diff --git a/ArcFormats/MAI/ImageMAI.cs b/ArcFormats/MAI/ImageMAI.cs
index 2263d9fb..c636667a 100644
--- a/ArcFormats/MAI/ImageMAI.cs
+++ b/ArcFormats/MAI/ImageMAI.cs
@@ -50,7 +50,7 @@ namespace GameRes.Formats.MAI
public CmFormat ()
{
- Extensions = new string[] { "cm" };
+ Extensions = new string[] { "cmp" };
}
public override void Write (Stream file, ImageData image)
@@ -60,24 +60,22 @@ namespace GameRes.Formats.MAI
public override ImageMetaData ReadMetaData (IBinaryStream stream)
{
- if ('C' != stream.ReadByte() || 'M' != stream.ReadByte())
+ var header = stream.ReadHeader (0x20);
+ if ('C' != header[0] || 'M' != header[1])
return null;
- var header = stream.ReadBytes (0x1e);
- if (header.Length != 0x1e)
+ if (1 != header[0x0E])
return null;
- if (1 != header[0x0c])
- return null;
- uint size = LittleEndian.ToUInt32 (header, 0);
+ uint size = LittleEndian.ToUInt32 (header, 2);
if (size != stream.Length)
return null;
var info = new CmMetaData();
- info.Width = LittleEndian.ToUInt16 (header, 4);
- info.Height = LittleEndian.ToUInt16 (header, 6);
- info.Colors = LittleEndian.ToUInt16 (header, 8);
- info.BPP = header[0x0a];
- info.IsCompressed = 0 != header[0x0b];
- info.DataOffset = LittleEndian.ToUInt32 (header, 0x0e);
- info.DataLength = LittleEndian.ToUInt32 (header, 0x12);
+ info.Width = LittleEndian.ToUInt16 (header, 6);
+ info.Height = LittleEndian.ToUInt16 (header, 8);
+ info.Colors = LittleEndian.ToUInt16 (header, 0x0A);
+ info.BPP = header[0x0C];
+ info.IsCompressed = 0 != header[0x0D];
+ info.DataOffset = LittleEndian.ToUInt32 (header, 0x10);
+ info.DataLength = LittleEndian.ToUInt32 (header, 0x14);
if (info.DataLength > size)
return null;
return info;
@@ -158,7 +156,7 @@ namespace GameRes.Formats.MAI
public AmFormat ()
{
- Extensions = new string[] { "am", "ami" };
+ Extensions = new string[] { "amp", "ami" };
}
public override void Write (Stream file, ImageData image)
@@ -235,6 +233,8 @@ namespace GameRes.Formats.MAI
m_pixels = new byte[m_width*m_height*4];
}
+ static readonly Color Default8bppTransparencyColor = Color.FromRgb (0, 0xFE, 0);
+
public void Unpack ()
{
if (m_info.Colors > 0)
@@ -262,13 +262,23 @@ namespace GameRes.Formats.MAI
m_pixels[dst+3] = alpha;
};
else
+ {
+ const int alphaScale = 0x11;
+ var alphaColor = Color.FromRgb (0, 0xFE, 0);
copy_pixel = (src, dst, alpha) => {
var color = Palette.Colors[m_output[src]];
+ if (Default8bppTransparencyColor == color)
+ alpha = 0;
+ else if (0 == alpha)
+ alpha = 0xFF;
+ else
+ alpha *= alphaScale;
m_pixels[dst] = color.B;
m_pixels[dst+1] = color.G;
m_pixels[dst+2] = color.R;
m_pixels[dst+3] = alpha;
};
+ }
int src_stride = m_width * m_pixel_size;
for (int y = 0; y < m_height; ++y)
{
diff --git a/ArcFormats/Macintosh/ImagePICT.cs b/ArcFormats/Macintosh/ImagePICT.cs
index d332afa1..95c9c55c 100644
--- a/ArcFormats/Macintosh/ImagePICT.cs
+++ b/ArcFormats/Macintosh/ImagePICT.cs
@@ -190,6 +190,9 @@ namespace GameRes.Formats.Apple
break;
}
+ case 0x001E: // DefHilite
+ break;
+
case 0x0090:
case 0x0091:
case 0x0098:
@@ -291,8 +294,10 @@ namespace GameRes.Formats.Apple
byte[] RepackPixels (Pixmap pixmap)
{
int bpp = m_info.BPP;
- if (bpp <= 16)
+ if (bpp < 16)
return m_buffer;
+ else if (16 == bpp)
+ return Repack16bpp();
int bytes_per_pixel = bpp / 8;
int stride = m_info.iWidth * bytes_per_pixel;
var pixels = new byte[stride * m_info.iHeight];
@@ -323,6 +328,17 @@ namespace GameRes.Formats.Apple
return pixels;
}
+ byte[] Repack16bpp () // swap 16bit pixels to little-endian order
+ {
+ for (int p = 1; p < m_buffer.Length; p += 2)
+ {
+ byte b = m_buffer[p-1];
+ m_buffer[p-1] = m_buffer[p];
+ m_buffer[p] = b;
+ }
+ return m_buffer;
+ }
+
void SetFormat (Pixmap pixmap)
{
int bpp = null == pixmap ? 8 : pixmap.BPP;
diff --git a/ArcFormats/Macromedia/ArcCCT.cs b/ArcFormats/Macromedia/ArcCCT.cs
deleted file mode 100644
index 0f31f25c..00000000
--- a/ArcFormats/Macromedia/ArcCCT.cs
+++ /dev/null
@@ -1,256 +0,0 @@
-//! \file ArcCCT.cs
-//! \date Fri Jun 26 01:15:26 2015
-//! \brief Macromedia Director archive access implementation.
-//
-// Copyright (C) 2015 by morkt
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.Composition;
-using System.IO;
-using System.Linq;
-using System.Text;
-using GameRes.Compression;
-using GameRes.Utility;
-
-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 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; } }
-
- public CctOpener ()
- {
- Extensions = new string[] { "cct", "dcr" };
- }
-
- public override ArcFile TryOpen (ArcView file)
- {
- uint id = file.View.ReadUInt32 (8);
- if (id != 0x46474443 && id != 0x4647444D) // 'CDGF' || 'MDGF'
- return null;
-
- var reader = new CctReader (file);
- var dir = reader.ReadIndex();
- if (null != dir)
- {
- var arc = new ArcFile (file, this, dir);
- SetEntryTypes (arc);
- return arc;
- }
- return null;
- }
-
- public override Stream OpenEntry (ArcFile arc, Entry entry)
- {
- var input = arc.File.CreateStream (entry.Offset, entry.Size);
- var packed_entry = entry as PackedEntry;
- if (null == packed_entry || !packed_entry.IsPacked)
- return input;
- else
- return new ZLibStream (input, CompressionMode.Decompress);
- }
-
- private void SetEntryTypes (ArcFile arc)
- {
- foreach (var entry in arc.Dir.OrderBy (x => x.Offset))
- {
- if (entry.Name.EndsWith (".edim"))
- entry.Type = DetectEdimType (arc, entry);
- else if (entry.Name.EndsWith (".bitd"))
- entry.Type = "image";
- }
- }
-
- private string DetectEdimType (ArcFile arc, Entry entry)
- {
- using (var input = OpenEntry (arc, entry))
- {
- uint signature = (uint)input.ReadByte() << 24;
- signature |= (uint)input.ReadByte() << 16;
- signature |= (uint)input.ReadByte() << 8;
- signature |= (byte)input.ReadByte();
- if (0xffd8ffe0 == signature)
- return "image";
- uint real_size = (entry as PackedEntry).UnpackedSize;
- if (signature > 0xffff || signature+4 > real_size)
- return "";
- var header = new byte[signature+0x10];
- if (header.Length != input.Read (header, 0, header.Length))
- return "";
- if (0xff == header[signature])
- return "audio";
- return "";
- }
- }
- }
-
- internal class CctReader
- {
- ArcView m_file;
- long m_offset;
-
- public CctReader (ArcView file)
- {
- m_file = file;
- m_offset = 0x0C;
- }
-
- byte[] m_size_buffer = new byte[10];
-
- public List ReadIndex ()
- {
- uint section_size = ReadSectionSize ("Fver");
- m_offset += section_size;
- section_size = ReadSectionSize ("Fcdr");
- /*
- int Mcdr_size;
- var Mcdr = ZlibUnpack (m_offset, section_size, out Mcdr_size);
- */
- m_offset += section_size;
- uint abmp_size = ReadSectionSize ("ABMP");
- int max_count = m_file.View.Read (m_offset, m_size_buffer, 0, Math.Min (10, abmp_size));
- int size_offset = 0;
- ReadValue (m_size_buffer, ref size_offset, max_count);
- max_count -= size_offset;
-
- int bmp_unpacked_size = (int)ReadValue (m_size_buffer, ref size_offset, max_count);
- m_offset += size_offset;
- abmp_size -= (uint)size_offset;
- int index_size;
- var index = ZlibUnpack (m_offset, abmp_size, out index_size, bmp_unpacked_size);
- m_offset += abmp_size;
- section_size = ReadSectionSize ("FGEI");
- if (0 != section_size)
- throw new NotSupportedException();
-
- int index_offset = 0;
- ReadValue (index, ref index_offset, index_size-index_offset);
- ReadValue (index, ref index_offset, index_size-index_offset);
- int entry_count = (int)ReadValue (index, ref index_offset, index_size-index_offset);
- if (entry_count <= 0 || entry_count > 0xfffff)
- return null;
-
- var type_buf = new char[4];
- var dir = new List (entry_count);
- for (int i = 0; i < entry_count; ++i)
- {
- uint id = ReadValue (index, ref index_offset, index_size-index_offset);
- uint offset = ReadValue (index, ref index_offset, index_size-index_offset);
- uint size = ReadValue (index, ref index_offset, index_size-index_offset);
- uint unpacked_size = ReadValue (index, ref index_offset, index_size-index_offset);
- uint flag = ReadValue (index, ref index_offset, index_size-index_offset);
-
- if (index_size-index_offset < 4)
- return null;
- uint type_id = LittleEndian.ToUInt32 (index, index_offset);
- index_offset += 4;
- if (0 == type_id || uint.MaxValue == offset)
- continue;
-
- Encoding.ASCII.GetChars (index, index_offset-4, 4, type_buf, 0);
- var entry = new PackedEntry
- {
- Name = CreateName (id, type_buf),
- Offset = (long)m_offset + offset,
- Size = size,
- UnpackedSize = unpacked_size,
- IsPacked = 0 == flag,
- };
- if (entry.CheckPlacement (m_file.MaxOffset))
- {
- dir.Add (entry);
- }
- }
- return dir;
- }
-
- string CreateName (uint id, char[] type_buf)
- {
- Array.Reverse (type_buf);
- int t = 3;
- while (t >= 0 && ' ' == type_buf[t])
- t--;
- if (t >= 0)
- {
- string ext = new string (type_buf, 0, t+1);
- return string.Format ("{0:D8}.{1}", id, ext.ToLowerInvariant());
- }
- else
- return id.ToString ("D8");
- }
-
- byte[] ZlibUnpack (long offset, uint size, out int actual_size, int unpacked_size_hint = 0)
- {
- using (var input = m_file.CreateStream (offset, size))
- using (var zstream = new ZLibStream (input, CompressionMode.Decompress))
- using (var mem = new MemoryStream (unpacked_size_hint))
- {
- zstream.CopyTo (mem);
- actual_size = (int)mem.Length;
- return mem.GetBuffer();
- }
- }
-
- uint ReadSectionSize (string id_str)
- {
- uint id = ConvertId (id_str);
- if (id != m_file.View.ReadUInt32 (m_offset))
- throw new InvalidFormatException();
- m_offset += 4;
- if (5 != m_file.View.Read (m_offset, m_size_buffer, 0, 5))
- throw new InvalidFormatException();
- int off_count = 0;
- uint size = ReadValue (m_size_buffer, ref off_count, 5);
- m_offset += off_count;
- return size;
- }
-
- static uint ReadValue (byte[] buffer, ref int offset, int length)
- {
- uint n = 0;
- for (int off_count = 0; off_count < length; ++off_count)
- {
- uint bits = buffer[offset++];
- n = (n << 7) | (bits & 0x7F);
- if (0 == (bits & 0x80))
- return n;
- }
- throw new InvalidFormatException();
- }
-
- static uint ConvertId (string id)
- {
- if (id.Length != 4)
- throw new ArgumentException ("Invalid section id");
- uint n = 0;
- for (int i = 0; i < 4; ++i)
- n = (n << 8) | (byte)id[i];
- return n;
- }
- }
-}
diff --git a/ArcFormats/Macromedia/ArcDXR.cs b/ArcFormats/Macromedia/ArcDXR.cs
index ed6eabc5..1fa780da 100644
--- a/ArcFormats/Macromedia/ArcDXR.cs
+++ b/ArcFormats/Macromedia/ArcDXR.cs
@@ -38,46 +38,58 @@ 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 override string Tag => "DXR";
+ public override string Description => "Macromedia Director resource archive";
+ public override uint Signature => SignatureXFIR; // 'XFIR'
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public const uint SignatureXFIR = 0x52494658u;
+ public const uint SignatureRIFX = 0x58464952u;
public DxrOpener ()
{
- Extensions = new[] { "dxr", "cxt", "cct", "dcr" };
- Signatures = new[] { 0x52494658u, 0x58464952u };
+ Extensions = new[] { "dxr", "cxt", "cct", "dcr", "dir", "exe" };
+ Signatures = new[] { SignatureXFIR, SignatureRIFX, 0x00905A4Du, 0u };
}
internal static readonly HashSet RawChunks = new HashSet {
- "RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", //"snd "
+ "RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", "File"
};
internal bool ConvertText = true;
public override ArcFile TryOpen (ArcView file)
{
+ long base_offset = 0;
+ if (file.View.AsciiEqual (0, "MZ"))
+ base_offset = LookForXfir (file);
+ uint signature = file.View.ReadUInt32 (base_offset);
+ if (signature != SignatureXFIR && signature != SignatureRIFX)
+ return null;
using (var input = file.CreateStream())
{
- ByteOrder ord = input.Signature == 0x52494658u ? ByteOrder.LittleEndian : ByteOrder.BigEndian;
+ ByteOrder ord = signature == SignatureXFIR ? ByteOrder.LittleEndian : ByteOrder.BigEndian;
var reader = new Reader (input, ord);
- reader.Position = 4;
- uint length = reader.ReadU32();
+ reader.Position = base_offset;
var context = new SerializationContext();
var dir_file = new DirectorFile();
if (!dir_file.Deserialize (context, reader))
return null;
var dir = new List ();
- ImportMedia (dir_file, dir);
+ if (dir_file.Codec != "APPL")
+ ImportMedia (dir_file, dir);
foreach (DirectorEntry entry in dir_file.Directory)
{
if (entry.Size != 0 && entry.Offset != -1 && RawChunks.Contains (entry.FourCC))
{
entry.Name = string.Format ("{0:D6}.{1}", entry.Id, entry.FourCC.Trim());
- if ("snd " == entry.FourCC)
- entry.Type = "audio";
+ if ("File" == entry.FourCC)
+ {
+ entry.Offset -= 8;
+ entry.Size += 8;
+ }
dir.Add (entry);
}
}
@@ -155,7 +167,7 @@ namespace GameRes.Formats.Macromedia
entry = ImportBitmap (piece, dir_file, cast);
else if (piece.Type == DataType.Sound)
entry = ImportSound (piece, dir_file);
- if (entry != null)
+ if (entry != null && entry.Size > 0)
dir.Add (entry);
}
}
@@ -381,6 +393,57 @@ namespace GameRes.Formats.Macromedia
zstream.Read (data, 0, data.Length);
return new BinMemoryStream (data, entry.Name);
}
+
+ static readonly byte[] s_xfir = { (byte)'X', (byte)'F', (byte)'I', (byte)'R' };
+
+ long LookForXfir (ArcView file)
+ {
+ var exe = new ExeFile (file);
+ long pos;
+ if (exe.IsWin16)
+ {
+ pos = exe.FindString (exe.Overlay, s_xfir);
+ if (pos < 0)
+ return 0;
+ }
+ else
+ {
+ pos = exe.Overlay.Offset;
+ if (pos >= file.MaxOffset)
+ return 0;
+ if (file.View.AsciiEqual (pos, "10JP") || file.View.AsciiEqual (pos, "59JP"))
+ {
+ pos = file.View.ReadUInt32 (pos+4);
+ }
+ }
+ if (pos >= file.MaxOffset || !file.View.AsciiEqual (pos, "XFIR"))
+ return 0;
+ // TODO threat 'LPPA' archives the normal way, like archives that contain entries.
+ // the problem is, DXR archives contained within 'LPPA' have their offsets relative to executable file,
+ // so have to figure out way to handle it.
+ if (!file.View.AsciiEqual (pos+8, "LPPA"))
+ return pos;
+ var appl = new DirectorFile();
+ var context = new SerializationContext();
+ using (var input = file.CreateStream())
+ {
+ var reader = new Reader (input, ByteOrder.LittleEndian);
+ input.Position = pos + 12;
+ if (!appl.ReadMMap (context, reader))
+ return 0;
+ foreach (var entry in appl.Directory)
+ {
+ // only the first XFIR entry is matched here, but archive may contain multiple sub-archives.
+ if (entry.FourCC == "File")
+ {
+ if (file.View.AsciiEqual (entry.Offset-8, "XFIR")
+ && !file.View.AsciiEqual (entry.Offset, "artX"))
+ return entry.Offset-8;
+ }
+ }
+ return 0;
+ }
+ }
}
internal class BitmapEntry : PackedEntry
@@ -407,10 +470,16 @@ namespace GameRes.Formats.Macromedia
Left = reader.ReadI16();
Bottom = reader.ReadI16();
Right = reader.ReadI16();
- reader.Skip (0x0C);
- BitDepth = reader.ReadU16() & 0xFF; // ???
- reader.Skip (2);
- Palette = reader.ReadI16();
+ if (data.Length > 0x16)
+ {
+ reader.Skip (0x0C);
+ BitDepth = reader.ReadU16() & 0xFF; // ???
+ if (data.Length >= 0x1C)
+ {
+ reader.Skip (2);
+ Palette = reader.ReadI16();
+ }
+ }
}
}
}
diff --git a/ArcFormats/Macromedia/AudioEDIM.cs b/ArcFormats/Macromedia/AudioEDIM.cs
index 18925d59..aab5eaca 100644
--- a/ArcFormats/Macromedia/AudioEDIM.cs
+++ b/ArcFormats/Macromedia/AudioEDIM.cs
@@ -1,6 +1,6 @@
//! \file AudioEDIM.cs
//! \date Fri Jun 26 06:52:33 2015
-//! \brief Selen wrapper around MP3 stream.
+//! \brief Macromedia Director wrapper around MP3 stream.
//
// Copyright (C) 2015 by morkt
//
@@ -33,7 +33,7 @@ namespace GameRes.Formats.Selen
public class EdimAudio : Mp3Audio
{
public override string Tag { get { return "EDIM"; } }
- public override string Description { get { return "Selen audio format (MP3)"; } }
+ public override string Description { get { return "Macromedia Director audio format (MP3)"; } }
public override uint Signature { get { return 0x40010000; } }
public override bool CanWrite { get { return false; } }
diff --git a/ArcFormats/Macromedia/DirectorFile.cs b/ArcFormats/Macromedia/DirectorFile.cs
index cca399d5..a1b2232f 100644
--- a/ArcFormats/Macromedia/DirectorFile.cs
+++ b/ArcFormats/Macromedia/DirectorFile.cs
@@ -75,6 +75,9 @@ namespace GameRes.Formats.Macromedia
DirectorConfig m_config = new DirectorConfig();
List m_casts = new List();
Dictionary m_ilsMap = new Dictionary();
+ string m_codec;
+
+ public string Codec => m_codec;
public bool IsAfterBurned { get; private set; }
@@ -96,14 +99,14 @@ namespace GameRes.Formats.Macromedia
public bool Deserialize (SerializationContext context, Reader reader)
{
- reader.Position = 8;
- string codec = reader.ReadFourCC();
- if (codec == "MV93" || codec == "MC95")
+ reader.Skip (8);
+ m_codec = reader.ReadFourCC();
+ if (m_codec == "MV93" || m_codec == "MC95")
{
if (!ReadMMap (context, reader))
return false;
}
- else if (codec == "FGDC" || codec == "FGDM")
+ else if (m_codec == "FGDC" || m_codec == "FGDM")
{
IsAfterBurned = true;
if (!ReadAfterBurner (context, reader))
@@ -111,7 +114,7 @@ namespace GameRes.Formats.Macromedia
}
else
{
- Trace.WriteLine (string.Format ("Unknown codec '{0}'", codec), "DXR");
+ Trace.WriteLine (string.Format ("Unknown m_codec '{0}'", m_codec), "DXR");
return false;
}
return ReadKeyTable (context, reader)
@@ -119,7 +122,7 @@ namespace GameRes.Formats.Macromedia
&& ReadCasts (context, reader);
}
- bool ReadMMap (SerializationContext context, Reader reader)
+ internal bool ReadMMap (SerializationContext context, Reader reader)
{
if (reader.ReadFourCC() != "imap")
return false;
diff --git a/ArcFormats/Macromedia/ImageBITD.cs b/ArcFormats/Macromedia/ImageBITD.cs
index fb571e6a..0e1795a6 100644
--- a/ArcFormats/Macromedia/ImageBITD.cs
+++ b/ArcFormats/Macromedia/ImageBITD.cs
@@ -71,7 +71,7 @@ namespace GameRes.Formats.Macromedia
: info.BPP == 8 ? PixelFormats.Indexed8
: info.BPP == 16 ? PixelFormats.Bgr555
: info.DepthType == 0x87 // i have no clue what this is
- || info.DepthType == 0x8A ? PixelFormats.Bgra32 // depth type 0x87/0x8A
+ || info.DepthType == 0x8A ? PixelFormats.Bgra32 // depth type 0x87/0x8A
: PixelFormats.Bgra32; // depth type 0x82/84/85/86/8C
m_palette = palette;
}
@@ -151,32 +151,7 @@ namespace GameRes.Formats.Macromedia
{
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;
- }
- }
+ UnpackScanLine (m_output, line);
}
return m_output;
}
@@ -186,33 +161,7 @@ namespace GameRes.Formats.Macromedia
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)
- break; // one in 5000 images somehow stumbles here
-// throw new InvalidFormatException ("Unexpected end of file");
- int count = b;
- if (b > 0x7f)
- count = (byte)-(sbyte)b;
- ++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;
- }
- }
+ UnpackScanLine (scan_line, 0);
int dst = line;
for (int i = 0; i < m_width; ++i)
{
@@ -222,6 +171,36 @@ namespace GameRes.Formats.Macromedia
}
}
+ void UnpackScanLine (byte[] scan_line, int pos)
+ {
+ int x = 0;
+ while (x < m_stride)
+ {
+ int b = m_input.ReadByte();
+ if (-1 == b)
+ break; // one in 5000 images somehow stumbles here
+ 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[pos + x++] = (byte)b;
+ }
+ else
+ {
+ m_input.Read (scan_line, pos+x, count);
+ x += count;
+ }
+ }
+ }
+
#region IDisposable Members
bool m_disposed = false;
diff --git a/ArcFormats/NScripter/ArcNSA.cs b/ArcFormats/NScripter/ArcNSA.cs
index 1e95d140..f1d4431a 100644
--- a/ArcFormats/NScripter/ArcNSA.cs
+++ b/ArcFormats/NScripter/ArcNSA.cs
@@ -107,6 +107,9 @@ namespace GameRes.Formats.NScripter
catch { /* ignore parse errors */ }
if (zero_signature || !file.Name.HasExtension (".nsa"))
return null;
+ uint signature = file.View.ReadUInt32 (0);
+ if ((signature & 0xFFFFFF) == 0x90FBFF) // looks like mp3 file
+ return new WrapSingleFileArchive (file, Path.GetFileNameWithoutExtension (file.Name)+".mp3");
var password = QueryPassword();
if (string.IsNullOrEmpty (password))
diff --git a/ArcFormats/Otemoto/ArcTLZ.cs b/ArcFormats/Otemoto/ArcTLZ.cs
index 585bc48d..bbc2b8ff 100644
--- a/ArcFormats/Otemoto/ArcTLZ.cs
+++ b/ArcFormats/Otemoto/ArcTLZ.cs
@@ -39,6 +39,11 @@ namespace GameRes.Formats.Otemoto
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
+ public TlzOpener ()
+ {
+ ContainedFormats = new[] { "BMP", "SCR" };
+ }
+
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0xC);
@@ -61,7 +66,7 @@ namespace GameRes.Formats.Otemoto
if (0 == name_length || name_length > 0x100)
return null;
entry.Name = file.View.ReadString (index_offset+0x10, name_length);
- entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name);
+ entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name, ContainedFormats);
entry.IsPacked = entry.UnpackedSize != entry.Size;
dir.Add (entry);
index_offset += 0x10 + name_length;
@@ -78,4 +83,9 @@ namespace GameRes.Formats.Otemoto
return new LzssStream (input);
}
}
+
+ [Export(typeof(ResourceAlias))]
+ [ExportMetadata("Extension", "SNR")]
+ [ExportMetadata("Target", "SCR")]
+ internal class SnrFormat : ResourceAlias { }
}
diff --git a/ArcFormats/Otemoto/ImageMAG.cs b/ArcFormats/Otemoto/ImageMAG.cs
index 8d222fa5..e68f3f21 100644
--- a/ArcFormats/Otemoto/ImageMAG.cs
+++ b/ArcFormats/Otemoto/ImageMAG.cs
@@ -45,7 +45,7 @@ namespace GameRes.Formats.Otemoto
[Export(typeof(ImageFormat))]
public class MagFormat : ImageFormat
{
- public override string Tag { get { return "MAG"; } }
+ public override string Tag { get { return "MAG/MAKI02"; } }
public override string Description { get { return "Otemoto image format"; } }
public override uint Signature { get { return 0x494B414D; } } // 'MAKI02'
diff --git a/ArcFormats/RealLive/ArcSEEN.cs b/ArcFormats/RealLive/ArcSEEN.cs
index 772f1ff6..791c0742 100644
--- a/ArcFormats/RealLive/ArcSEEN.cs
+++ b/ArcFormats/RealLive/ArcSEEN.cs
@@ -111,7 +111,7 @@ namespace GameRes.Formats.RealLive
int offset = input.ReadUInt16();
int count = (offset & 0xF) + 2;
offset >>= 4;
- Binary.CopyOverlapped (output, dst-offset, dst, count);
+ Binary.CopyOverlapped (output, dst-offset-1, dst, count);
dst += count;
}
}
diff --git a/ArcFormats/SingleFileArchive.cs b/ArcFormats/SingleFileArchive.cs
new file mode 100644
index 00000000..a070b9ed
--- /dev/null
+++ b/ArcFormats/SingleFileArchive.cs
@@ -0,0 +1,67 @@
+//! \file SingleFileArchive.cs
+//! \date 2023 Oct 05
+//! \brief represent single file as an archive for convenience.
+//
+// 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.Collections.Generic;
+
+namespace GameRes.Formats
+{
+ public class WrapSingleFileArchive : ArcFile
+ {
+ internal static readonly ArchiveFormat Format = new SingleFileArchiveFormat();
+
+ public WrapSingleFileArchive (ArcView file, Entry entry)
+ : base (file, Format, new List { entry })
+ {
+ }
+
+ public WrapSingleFileArchive (ArcView file, string entry_name)
+ : base (file, Format, new List { CreateEntry (file, entry_name) })
+ {
+ }
+
+ private static Entry CreateEntry (ArcView file, string name)
+ {
+ var entry = FormatCatalog.Instance.Create (name);
+ entry.Offset = 0;
+ entry.Size = (uint)file.MaxOffset;
+ return entry;
+ }
+
+ /// this format is not registered in catalog and only accessible via WrapSingleFileArchive.Format singleton.
+ private class SingleFileArchiveFormat : ArchiveFormat
+ {
+ public override string Tag => "DAT/BOGUS";
+ public override string Description => "Not an archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ return new WrapSingleFileArchive (file, System.IO.Path.GetFileName (file.Name));
+ }
+ }
+ }
+}
diff --git a/ArcFormats/Software House Parsley/ArcCG3.cs b/ArcFormats/Software House Parsley/ArcCG3.cs
new file mode 100644
index 00000000..43a5aa45
--- /dev/null
+++ b/ArcFormats/Software House Parsley/ArcCG3.cs
@@ -0,0 +1,170 @@
+//! \file ArcCG3.cs
+//! \date 2023 Oct 10
+//! \brief Software House Parsley CG 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 System.Windows.Media;
+
+// [050610][Software House Parsley] Desert Time Mugen no Meikyuu PE
+
+namespace GameRes.Formats.Parsley
+{
+ [Export(typeof(ArchiveFormat))]
+ public class DesertCgOpener : ArchiveFormat
+ {
+ public override string Tag { get { return "CG/DESERT"; } }
+ public override string Description { get { return "Software House Parsley CG archive"; } }
+ public override uint Signature { get { return 0; } }
+ public override bool IsHierarchic { get { return false; } }
+ public override bool CanWrite { get { return false; } }
+
+ public DesertCgOpener ()
+ {
+ Extensions = new string[] { "" };
+ }
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!VFS.IsPathEqualsToFileName (file.Name, "CG"))
+ return null;
+ int count = file.View.ReadInt32 (0);
+ if (!IsSaneCount (count))
+ return null;
+ uint index_pos = 4;
+ var filename_table = LookupFileNameTable (file, count);
+ Func get_entry_name;
+ if (filename_table != null)
+ get_entry_name = n => filename_table[n];
+ else
+ get_entry_name = n => string.Format ("CG#{0:D4}");
+ long last_offset = count * 4 + 4;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++ i)
+ {
+ uint offset = file.View.ReadUInt32 (index_pos);
+ if (0 == offset)
+ break;
+ if (offset <= last_offset || offset >= file.MaxOffset)
+ return null;
+ var entry = new Entry {
+ Name = get_entry_name (i),
+ Type = "image",
+ Offset = offset,
+ };
+ dir.Add (entry);
+ last_offset = offset;
+ index_pos += 4;
+ }
+ if (0 == dir.Count)
+ return null;
+ last_offset = file.MaxOffset;
+ for (int i = dir.Count-1; i >= 0; --i)
+ {
+ dir[i].Size = (uint)(last_offset - dir[i].Offset);
+ last_offset = dir[i].Offset;
+ }
+ return new ArcFile (file, this, dir);
+ }
+
+ public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
+ {
+ var input = arc.File.CreateStream (entry.Offset, entry.Size);
+ try
+ {
+ return new DesertCgDecoder (input);
+ }
+ catch
+ {
+ input.Dispose();
+ throw;
+ }
+ }
+
+ internal static Dictionary FileNameTableMap = new Dictionary {
+ { @"..\DTime.exe", 0x49E348 },
+ };
+
+ List LookupFileNameTable (ArcView file, int count)
+ {
+ try
+ {
+ var dir_name = Path.GetDirectoryName (file.Name);
+ foreach (var source in FileNameTableMap.Keys)
+ {
+ var src_name = Path.Combine (dir_name, source);
+ if (File.Exists (src_name))
+ {
+ using (var src = new ArcView (src_name))
+ {
+ var exe = new ExeFile (src);
+ long offset = exe.GetAddressOffset (FileNameTableMap[source]);
+ if (offset >= src.MaxOffset || offset + 0x104 * count > src.MaxOffset)
+ return null;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ var name = src.View.ReadString (offset, 0x104);
+ dir.Add (name);
+ offset += 0x104;
+ }
+ return dir;
+ }
+ }
+ }
+ }
+ catch { } // ignore errors
+ return null;
+ }
+ }
+
+ internal class DesertCgDecoder : BinaryImageDecoder
+ {
+ public DesertCgDecoder (IBinaryStream input) : base (input, ReadMetaData (input))
+ {
+ }
+
+ static ImageMetaData ReadMetaData (IBinaryStream input)
+ {
+ return new ImageMetaData {
+ Width = input.ReadUInt32(),
+ Height = input.ReadUInt32(),
+ BPP = 8,
+ };
+ }
+
+ protected override ImageData GetImageData ()
+ {
+ m_input.Position = 8;
+ var palette = ImageFormat.ReadPalette (m_input.AsStream, 0x100, PaletteFormat.RgbX);
+ int stride = (Info.iWidth + 3) & ~3;
+ var pixels = m_input.ReadBytes (stride * Info.iHeight);
+ return ImageData.Create (Info, PixelFormats.Indexed8, palette, pixels, stride);
+ }
+ }
+}
+
+
diff --git a/ArcFormats/Triangle/ImageIAF.cs b/ArcFormats/Triangle/ImageIAF.cs
index 486c1dd7..df16d275 100644
--- a/ArcFormats/Triangle/ImageIAF.cs
+++ b/ArcFormats/Triangle/ImageIAF.cs
@@ -100,7 +100,7 @@ namespace GameRes.Formats.Triangle
return null;
unpacked_size &= (int)~0xC0000000;
stream.Position = data_offset;
- byte[] bmp = UnpackBitmap (stream.AsStream, pack_type, packed_size, 0x26);
+ byte[] bmp = UnpackBitmap (stream, pack_type, packed_size, 0x26);
if (bmp[0] != 'B' && bmp[0] != 'C' || bmp[1] != 'M')
return null;
return new IafMetaData
@@ -121,7 +121,7 @@ namespace GameRes.Formats.Triangle
{
var meta = (IafMetaData)info;
stream.Position = meta.DataOffset;
- var bitmap = UnpackBitmap (stream.AsStream, meta.PackType, meta.PackedSize, meta.UnpackedSize);
+ var bitmap = UnpackBitmap (stream, meta.PackType, meta.PackedSize, meta.UnpackedSize);
if ('C' == bitmap[0])
{
bitmap[0] = (byte)'B';
@@ -155,19 +155,24 @@ namespace GameRes.Formats.Triangle
return Bmp.Read (bmp, info);
}
- internal static byte[] UnpackBitmap (Stream stream, int pack_type, int packed_size, int unpacked_size)
+ internal static byte[] UnpackBitmap (IBinaryStream stream, int pack_type, int packed_size, int unpacked_size)
{
if (2 == pack_type)
{
+ uint signature = stream.ReadUInt32();
+ stream.Seek (-4, SeekOrigin.Current);
using (var reader = new RleReader (stream, packed_size, unpacked_size))
{
- reader.Unpack();
+ if (0x014D0142 == signature)
+ reader.UnpackV2();
+ else
+ reader.Unpack();
return reader.Data;
}
}
else if (0 == pack_type)
{
- using (var reader = new LzssReader (stream, packed_size, unpacked_size))
+ using (var reader = new LzssReader (stream.AsStream, packed_size, unpacked_size))
{
reader.Unpack();
return reader.Data;
@@ -261,15 +266,15 @@ namespace GameRes.Formats.Triangle
internal class RleReader : IDataUnpacker, IDisposable
{
- BinaryReader m_input;
+ IBinaryStream m_input;
byte[] m_output;
int m_size;
public byte[] Data { get { return m_output; } }
- public RleReader (Stream input, int input_length, int output_length)
+ public RleReader (IBinaryStream input, int input_length, int output_length)
{
- m_input = new ArcView.Reader (input);
+ m_input = input;
m_output = new byte[output_length];
m_size = input_length;
}
@@ -280,8 +285,8 @@ namespace GameRes.Formats.Triangle
int dst = 0;
while (dst < m_output.Length && src < m_size)
{
- byte b = m_input.ReadByte();
- int count = m_input.ReadByte();
+ byte b = m_input.ReadUInt8();
+ int count = m_input.ReadUInt8();
src += 2;
count = Math.Min (count, m_output.Length - dst);
for (int i = 0; i < count; i++)
@@ -295,11 +300,11 @@ namespace GameRes.Formats.Triangle
int dst = 0;
while (dst < m_output.Length && src < m_size)
{
- byte ctl = m_input.ReadByte();
+ byte ctl = m_input.ReadUInt8();
++src;
if (0 == ctl)
{
- int count = m_input.ReadByte();
+ int count = m_input.ReadUInt8();
++src;
count = Math.Min (count, m_output.Length - dst);
int read = m_input.Read (m_output, dst, count);
@@ -309,7 +314,7 @@ namespace GameRes.Formats.Triangle
else
{
int count = ctl;
- byte b = m_input.ReadByte();
+ byte b = m_input.ReadUInt8();
++src;
count = Math.Min (count, m_output.Length - dst);
@@ -320,24 +325,8 @@ namespace GameRes.Formats.Triangle
}
#region IDisposable Members
- bool disposed = false;
-
public void Dispose ()
{
- Dispose (true);
- GC.SuppressFinalize (this);
- }
-
- protected virtual void Dispose (bool disposing)
- {
- if (!disposed)
- {
- if (disposing)
- {
- m_input.Dispose();
- }
- disposed = true;
- }
}
#endregion
}
diff --git a/ArcFormats/Xuse/ArcNT.cs b/ArcFormats/Xuse/ArcNT.cs
new file mode 100644
index 00000000..06990a91
--- /dev/null
+++ b/ArcFormats/Xuse/ArcNT.cs
@@ -0,0 +1,189 @@
+//! \file ArcNT.cs
+//! \date 2023 Oct 17
+//! \brief Xuse 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;
+using System.Windows.Media;
+
+namespace GameRes.Formats.Xuse
+{
+ [Export(typeof(ArchiveFormat))]
+ public class BgOpener : ArchiveFormat
+ {
+ public override string Tag => "BG/Xuse";
+ public override string Description => "Xuse bitmap archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public BgOpener ()
+ {
+ Extensions = new[] { "" };
+ }
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ var arc_name = Path.GetFileName (file.Name);
+ if (!arc_name.StartsWith ("bg00", StringComparison.OrdinalIgnoreCase) &&
+ !arc_name.StartsWith ("sbg", StringComparison.OrdinalIgnoreCase))
+ return null;
+ long rem;
+ uint entry_size = arc_name[0] == 's' ? 0x96400u : 0x4B400u;
+ int count = (int)Math.DivRem (file.MaxOffset, entry_size, out rem);
+ if (rem != 0 || !IsSaneCount (count))
+ return null;
+ uint offset = 0;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ var entry = new Entry {
+ Name = string.Format ("{0}#{1:D4}", arc_name, i),
+ Type = "image",
+ Offset = offset,
+ Size = entry_size,
+ };
+ dir.Add (entry);
+ offset += entry_size;
+ }
+ return new ArcFile (file, this, dir);
+ }
+
+ public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
+ {
+ var input = arc.File.CreateStream (entry.Offset, entry.Size);
+ uint height = entry.Size == 0x4B400u ? 480u : 960u;
+ var info = new ImageMetaData { Width = 640, Height = height, BPP = 8 };
+ return new BgImageDecoder (input, info);
+ }
+ }
+
+ internal class BgImageDecoder : BinaryImageDecoder
+ {
+ public BgImageDecoder (IBinaryStream input, ImageMetaData info) : base (input, info)
+ {
+ }
+
+ protected override ImageData GetImageData ()
+ {
+ m_input.Position = 0;
+ var pixels = m_input.ReadBytes (Info.iWidth * Info.iHeight);
+ var palette = ImageFormat.ReadPalette (m_input.AsStream);
+ return ImageData.CreateFlipped (Info, PixelFormats.Indexed8, palette, pixels, Info.iWidth);
+ }
+ }
+
+ [Export(typeof(ArchiveFormat))]
+ public class HOpener : ArchiveFormat
+ {
+ public override string Tag => "H/Xuse";
+ public override string Description => "Xuse bitmap archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public HOpener ()
+ {
+ Extensions = new[] { "" };
+ }
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ var arc_name = Path.GetFileName (file.Name);
+ if (!arc_name.StartsWith ("H", StringComparison.OrdinalIgnoreCase))
+ return null;
+ long rem;
+ uint entry_size = arc_name.EndsWith ("W") ? 0x2A700u : 0x25480u;
+ int count = (int)Math.DivRem (file.MaxOffset, entry_size, out rem);
+ if (!IsSaneCount (count) || rem != 0)
+ return null;
+ uint offset = 0;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ var entry = new Entry {
+ Name = string.Format ("{0}#{1:D4}", arc_name, i),
+ Type = "image",
+ Offset = offset,
+ Size = entry_size,
+ };
+ dir.Add (entry);
+ offset += entry_size;
+ }
+ return new ArcFile (file, this, dir);
+ }
+
+ public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
+ {
+ var input = arc.File.CreateStream (entry.Offset, entry.Size);
+ uint width = entry.Size == 0x25480u ? 280u : 320u;
+ var info = new ImageMetaData { Width = width, Height = 480, BPP = 8 };
+ return new HImageDecoder (input, info);
+ }
+ }
+
+ internal class HImageDecoder : BinaryImageDecoder
+ {
+ public HImageDecoder (IBinaryStream input, ImageMetaData info) : base (input, info)
+ {
+ }
+
+ protected override ImageData GetImageData ()
+ {
+ m_input.Position = 0;
+ int stride8bpp = Info.iWidth;
+ int alpha_stride = (Info.iWidth / 8 + 1) & -2;
+ var pixels = m_input.ReadBytes (stride8bpp * Info.iHeight);
+ var alpha = m_input.ReadBytes (alpha_stride * Info.iHeight);
+ m_input.Seek (-0x400, SeekOrigin.End);
+ var palette = ImageFormat.ReadPalette (m_input.AsStream);
+ int stride32bpp = Info.iWidth * 4;
+ var bgra = new byte[stride32bpp * Info.iHeight];
+ var colors = palette.Colors;
+ int src = pixels.Length - stride8bpp;
+ int asrc = alpha.Length - alpha_stride;
+ int dst_row = 0;
+ for (int y = 0; y < Info.iHeight; ++y)
+ {
+ int dst = dst_row;
+ for (int x = 0; x < Info.iWidth; ++x)
+ {
+ var c = colors[pixels[src+x]];
+ bgra[dst ] = c.B;
+ bgra[dst+1] = c.G;
+ bgra[dst+2] = c.R;
+ int A = (alpha[asrc + (x >> 3)] << (x & 7)) & 0x80;
+ bgra[dst+3] = (byte)(A == 0 ? 0xFF : 0);
+ dst += 4;
+ }
+ src -= stride8bpp;
+ asrc -= alpha_stride;
+ dst_row += stride32bpp;
+ }
+ return ImageData.Create (Info, PixelFormats.Bgra32, null, bgra, stride32bpp);
+ }
+ }
+}
diff --git a/ArcFormats/Xuse/ArcWVB.cs b/ArcFormats/Xuse/ArcWVB.cs
new file mode 100644
index 00000000..1bc5e894
--- /dev/null
+++ b/ArcFormats/Xuse/ArcWVB.cs
@@ -0,0 +1,91 @@
+//! \file ArcWVB.cs
+//! \date 2023 Oct 17
+//! \brief Xuse audio 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;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+
+namespace GameRes.Formats.Xuse
+{
+ [Export(typeof(ArchiveFormat))]
+ public class WvbOpener : ArchiveFormat
+ {
+ public override string Tag => "WVB";
+ public override string Description => "Xuse audio resource archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ int first_offset = file.View.ReadInt32 (4) - 1;
+ if (first_offset < 8 || first_offset >= file.MaxOffset)
+ return null;
+ int count = first_offset / 8;
+ if ((first_offset & 7) != 0 || !IsSaneCount (count))
+ return null;
+ uint fmt_size = file.View.ReadUInt32 (first_offset);
+ if (!file.View.AsciiEqual (first_offset+fmt_size+4, "data"))
+ return null;
+
+ var base_name = Path.GetFileNameWithoutExtension (file.Name);
+ uint index_pos = 0;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ uint offset = file.View.ReadUInt32 (index_pos+4);
+ if (0 == offset)
+ break;
+ var entry = new Entry {
+ Name = string.Format ("{0}#{1:D2}", base_name, i),
+ Type = "audio",
+ Size = file.View.ReadUInt32 (index_pos) - 8,
+ Offset = offset - 1,
+ };
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ entry.Size += 16;
+ dir.Add (entry);
+ index_pos += 8;
+ }
+ if (0 == dir.Count)
+ return null;
+ return new ArcFile (file, this, dir);
+ }
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ var header = new byte[0x10];
+ LittleEndian.Pack (AudioFormat.Wav.Signature, header, 0);
+ LittleEndian.Pack (entry.Size - 8u, header, 4);
+ LittleEndian.Pack (0x45564157, header, 8); // 'WAVE'
+ LittleEndian.Pack (0x20746d66, header, 12); // 'fmt '
+ var data = arc.File.CreateStream (entry.Offset, entry.Size - 16);
+ return new PrefixStream (header, data);
+ }
+ }
+}
diff --git a/ArcFormats/YuRis/ArcYPF.cs b/ArcFormats/YuRis/ArcYPF.cs
index 36336593..08a2f11e 100644
--- a/ArcFormats/YuRis/ArcYPF.cs
+++ b/ArcFormats/YuRis/ArcYPF.cs
@@ -105,7 +105,7 @@ namespace GameRes.Formats.YuRis
public YpfOpener ()
{
- Signatures = new uint[] { 0x00465059, 0 };
+ Signatures = new uint[] { 0x00465059, 0x00905A4D, 0 };
}
static public Dictionary KnownSchemes { get { return DefaultScheme.KnownSchemes; } }
diff --git a/ArcFormats/Zyx/ImageSPL.cs b/ArcFormats/Zyx/ImageSPL.cs
index bd26100b..3d28aa0a 100644
--- a/ArcFormats/Zyx/ImageSPL.cs
+++ b/ArcFormats/Zyx/ImageSPL.cs
@@ -52,30 +52,37 @@ namespace GameRes.Formats.Zyx
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
int count = file.ReadInt16();
- if (count <= 0)
+ if (count < 0 || count > 0x100)
return null;
- var tiles = new Tile[count];
- for (int i = 0; i < count; ++i)
+ Tile[] tiles = null;
+ if (count > 0)
{
- var tile = new Tile();
- tile.Left = file.ReadInt16();
- tile.Top = file.ReadInt16();
- if (tile.Left < 0 || tile.Top < 0)
- return null;
- tile.Right = file.ReadInt16();
- tile.Bottom = file.ReadInt16();
- if (tile.Right <= tile.Left || tile.Bottom <= tile.Top)
- return null;
- tiles[i] = tile;
+ tiles = new Tile[count];
+ for (int i = 0; i < count; ++i)
+ {
+ var tile = new Tile();
+ tile.Left = file.ReadInt16();
+ tile.Top = file.ReadInt16();
+ if (tile.Left < 0 || tile.Top < 0)
+ return null;
+ tile.Right = file.ReadInt16();
+ tile.Bottom = file.ReadInt16();
+ if (tile.Right <= tile.Left || tile.Bottom <= tile.Top)
+ return null;
+ tiles[i] = tile;
+ }
}
int width = file.ReadInt16();
int height = file.ReadInt16();
if (width <= 0 || height <= 0)
return null;
- foreach (var tile in tiles)
+ if (tiles != null)
{
- if (tile.Right > width || tile.Bottom > height)
- return null;
+ foreach (var tile in tiles)
+ {
+ if (tile.Right > width || tile.Bottom > height)
+ return null;
+ }
}
return new SplMetaData
{
diff --git a/Experimental/Microsoft/ArcEXE.cs b/Experimental/Microsoft/ArcEXE.cs
index 56ca88ad..3555c950 100644
--- a/Experimental/Microsoft/ArcEXE.cs
+++ b/Experimental/Microsoft/ArcEXE.cs
@@ -37,11 +37,11 @@ namespace GameRes.Formats.Microsoft
[ExportMetadata("Priority", -1)]
public class ExeOpener : ArchiveFormat
{
- public override string Tag { get => "EXE"; }
- public override string Description { get => "Windows executable resources"; }
- public override uint Signature { get => 0; }
- public override bool IsHierarchic { get => true; }
- public override bool CanWrite { get => false; }
+ public override string Tag => "EXE";
+ public override string Description => "Windows executable resources";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => true;
+ public override bool CanWrite => false;
public ExeOpener ()
{
@@ -158,8 +158,6 @@ namespace GameRes.Formats.Microsoft
return id;
}
- static readonly byte[] VS_VERSION_INFO = Encoding.Unicode.GetBytes ("VS_VERSION_INFO");
-
Stream OpenVersion (byte[] data, string name)
{
var input = new BinMemoryStream (data, name);
@@ -177,17 +175,23 @@ namespace GameRes.Formats.Microsoft
input.Position = pos;
if (input.ReadUInt32() != 0xFEEF04BDu)
break;
- input.Position = pos + value_length;
- int str_info_length = input.ReadUInt16();
- value_length = input.ReadUInt16();
- type = input.ReadUInt16();
- if (value_length != 0)
- break;
- if (input.ReadCString (Encoding.Unicode) != "StringFileInfo")
+ int info_length = value_length;
+ bool found_string_info = false;
+ do
+ {
+ pos += info_length;
+ input.Position = pos;
+ info_length = input.ReadUInt16();
+ value_length = input.ReadUInt16();
+ type = input.ReadUInt16();
+ found_string_info = input.ReadCString (Encoding.Unicode) == "StringFileInfo";
+ }
+ while (!found_string_info && input.PeekByte() != -1);
+ if (!found_string_info)
break;
pos = (input.Position + 3) & -4L;
input.Position = pos;
- int info_length = input.ReadUInt16();
+ info_length = input.ReadUInt16();
long end_pos = pos + info_length;
value_length = input.ReadUInt16();
type = input.ReadUInt16();
@@ -197,7 +201,7 @@ namespace GameRes.Formats.Microsoft
using (var text = new StreamWriter (output, new UTF8Encoding (false), 512, true))
{
string block_name = input.ReadCString (Encoding.Unicode);
- text.WriteLine ("BLOCK \"{0}\"\n{{", block_name);
+ text.WriteLine ("BLOCK \"{0}\"\r\n{{", block_name);
long next_pos = (input.Position + 3) & -4L;
while (next_pos < end_pos)
{
diff --git a/Experimental/Microsoft/ArcNE.cs b/Experimental/Microsoft/ArcNE.cs
index ba795e57..88a8b599 100644
--- a/Experimental/Microsoft/ArcNE.cs
+++ b/Experimental/Microsoft/ArcNE.cs
@@ -27,17 +27,19 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
+using System.Text;
namespace GameRes.Formats.Microsoft
{
[Export(typeof(ArchiveFormat))]
- public class PakOpener : ArchiveFormat
+ [ExportMetadata("Priority", -2)]
+ public class NeExeOpener : ArchiveFormat
{
- public override string Tag { get => "EXE/NE"; }
- public override string Description { get => "Windows 16-bit executable resources"; }
- public override uint Signature { get => 0; }
- public override bool IsHierarchic { get => true; }
- public override bool CanWrite { get => false; }
+ public override string Tag => "EXE/NE";
+ public override string Description => "Windows 16-bit executable resources";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => true;
+ public override bool CanWrite => false;
static readonly Dictionary TypeMap = new Dictionary {
{ 1, "RT_CURSOR" },
@@ -53,10 +55,10 @@ namespace GameRes.Formats.Microsoft
public override ArcFile TryOpen (ArcView file)
{
- if (!file.View.AsciiEqual (0, "MZ"))
+ if (!file.View.AsciiEqual (0, "MZ") || file.MaxOffset < 0x40)
return null;
uint ne_offset = file.View.ReadUInt32 (0x3C);
- if (!file.View.AsciiEqual (ne_offset, "NE"))
+ if (ne_offset > file.MaxOffset-2 || !file.View.AsciiEqual (ne_offset, "NE"))
return null;
uint res_table_offset = file.View.ReadUInt16 (ne_offset+0x24) + ne_offset;
if (res_table_offset <= ne_offset || res_table_offset >= file.MaxOffset)
@@ -84,13 +86,17 @@ namespace GameRes.Formats.Microsoft
int offset = file.View.ReadUInt16 (res_table_offset) << shift;
uint size = (uint)file.View.ReadUInt16 (res_table_offset+2) << shift;
int res_id = file.View.ReadUInt16 (res_table_offset+6);
+ if ((res_id & 0x8000) != 0)
+ res_id &= 0x7FFF;
res_table_offset += 12;
string name = res_id.ToString ("D5");
name = string.Join ("/", dir_name, name);
- var entry = new Entry {
+ var entry = new NeResourceEntry {
Name = name,
Offset = offset,
Size = size,
+ NativeName = res_id,
+ NativeType = type_id,
};
dir.Add (entry);
}
@@ -99,5 +105,79 @@ namespace GameRes.Formats.Microsoft
return null;
return new ArcFile (file, this, dir);
}
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ var rent = (NeResourceEntry)entry;
+ if (rent.NativeType == 16)
+ return OpenVersion (arc, rent);
+ return base.OpenEntry (arc, entry);
+ }
+
+ Encoding DefaultEncoding = Encodings.cp932;
+
+ Stream OpenVersion (ArcFile arc, NeResourceEntry entry)
+ {
+ uint data_length = arc.File.View.ReadUInt16 (entry.Offset);
+ var input = arc.File.CreateStream (entry.Offset, data_length);
+ for (;;)
+ {
+ input.ReadUInt16();
+ int value_length = input.ReadUInt16();
+ if (0 == value_length)
+ break;
+ if (input.ReadCString (DefaultEncoding) != "VS_VERSION_INFO")
+ break;
+ long pos = (input.Position + 3) & -4L;
+ input.Position = pos;
+ if (input.ReadUInt32() != 0xFEEF04BDu)
+ break;
+ input.Position = pos + value_length;
+ int str_info_length = input.ReadUInt16();
+ value_length = input.ReadUInt16();
+ if (value_length != 0)
+ break;
+ if (input.ReadCString (DefaultEncoding) != "StringFileInfo")
+ break;
+ pos = (input.Position + 3) & -4L;
+ input.Position = pos;
+ int info_length = input.ReadUInt16();
+ long end_pos = pos + info_length;
+ value_length = input.ReadUInt16();
+ if (value_length != 0)
+ break;
+ var output = new MemoryStream();
+ using (var text = new StreamWriter (output, DefaultEncoding, 512, true))
+ {
+ string block_name = input.ReadCString (DefaultEncoding);
+ text.WriteLine ("BLOCK \"{0}\"\n{{", block_name);
+ long next_pos = (input.Position + 3) & -4L;
+ while (next_pos < end_pos)
+ {
+ input.Position = next_pos;
+ info_length = input.ReadUInt16();
+ value_length = input.ReadUInt16();
+ next_pos = (next_pos + info_length + 3) & -4L;
+ string key = input.ReadCString (DefaultEncoding);
+ input.Position = (input.Position + 3) & -4L;
+ string value = value_length != 0 ? input.ReadCString (value_length, DefaultEncoding)
+ : String.Empty;
+ text.WriteLine ("\tVALUE \"{0}\", \"{1}\"", key, value);
+ }
+ text.WriteLine ("}");
+ }
+ input.Dispose();
+ output.Position = 0;
+ return output;
+ }
+ input.Position = 0;
+ return input;
+ }
+ }
+
+ internal class NeResourceEntry : Entry
+ {
+ public int NativeType;
+ public int NativeName;
}
}
diff --git a/GameRes/Audio.cs b/GameRes/Audio.cs
index 00f59f9d..25ae4e3f 100644
--- a/GameRes/Audio.cs
+++ b/GameRes/Audio.cs
@@ -197,8 +197,8 @@ namespace GameRes
return null;
}
- public static AudioFormat Wav { get { return s_WavFormat.Value; } }
+ public static AudioFormat Wav => s_WavFormat.Value;
- static readonly Lazy s_WavFormat = new Lazy (() => FormatCatalog.Instance.AudioFormats.FirstOrDefault (x => x.Tag == "WAV"));
+ static readonly ResourceInstance s_WavFormat = new ResourceInstance ("WAV");
}
}
diff --git a/GameRes/FileSystem.cs b/GameRes/FileSystem.cs
index 0fa33e6d..d65ea995 100644
--- a/GameRes/FileSystem.cs
+++ b/GameRes/FileSystem.cs
@@ -833,10 +833,7 @@ namespace GameRes
return false;
// now, compare length of filename portion of the path
int filename_index = path.LastIndexOfAny (PathSeparatorChars);
- if (-1 == filename_index)
- filename_index = 0;
- else
- filename_index++;
+ filename_index++;
int filename_portion_length = path.Length - filename_index;
return filename.Length == filename_portion_length;
}
diff --git a/GameRes/Image.cs b/GameRes/Image.cs
index aab3b796..f52ce64c 100644
--- a/GameRes/Image.cs
+++ b/GameRes/Image.cs
@@ -189,15 +189,15 @@ namespace GameRes
return FormatCatalog.Instance.ImageFormats.FirstOrDefault (x => x.Tag == tag);
}
- static readonly Lazy s_JpegFormat = new Lazy (() => FindByTag ("JPEG"));
- static readonly Lazy s_PngFormat = new Lazy (() => FindByTag ("PNG"));
- static readonly Lazy s_BmpFormat = new Lazy (() => FindByTag ("BMP"));
- static readonly Lazy s_TgaFormat = new Lazy (() => FindByTag ("TGA"));
+ static readonly ResourceInstance s_JpegFormat = new ResourceInstance ("JPEG");
+ static readonly ResourceInstance s_PngFormat = new ResourceInstance ("PNG");
+ static readonly ResourceInstance s_BmpFormat = new ResourceInstance ("BMP");
+ static readonly ResourceInstance s_TgaFormat = new ResourceInstance ("TGA");
- public static ImageFormat Jpeg { get { return s_JpegFormat.Value; } }
- public static ImageFormat Png { get { return s_PngFormat.Value; } }
- public static ImageFormat Bmp { get { return s_BmpFormat.Value; } }
- public static ImageFormat Tga { get { return s_TgaFormat.Value; } }
+ public static ImageFormat Jpeg => s_JpegFormat.Value;
+ public static ImageFormat Png => s_PngFormat.Value;
+ public static ImageFormat Bmp => s_BmpFormat.Value;
+ public static ImageFormat Tga => s_TgaFormat.Value;
///
/// Desereialize color map from stream, consisting of specified number of
diff --git a/GameRes/ImageBMP.cs b/GameRes/ImageBMP.cs
index 8ee0c054..0fc5416c 100644
--- a/GameRes/ImageBMP.cs
+++ b/GameRes/ImageBMP.cs
@@ -47,6 +47,7 @@ namespace GameRes
}
[Export(typeof(ImageFormat))]
+ [ExportMetadata("Priority", 10)]
public sealed class BmpFormat : ImageFormat
{
public override string Tag { get { return "BMP"; } }
diff --git a/GameRes/ImageJPEG.cs b/GameRes/ImageJPEG.cs
index 3de37066..41ddd25f 100644
--- a/GameRes/ImageJPEG.cs
+++ b/GameRes/ImageJPEG.cs
@@ -34,6 +34,7 @@ using GameRes.Utility;
namespace GameRes
{
[Export(typeof(ImageFormat))]
+ [ExportMetadata("Priority", 10)]
public class JpegFormat : ImageFormat
{
public override string Tag { get { return "JPEG"; } }
diff --git a/Legacy/Adv98/ImageGPC.cs b/Legacy/Adv98/ImageGPC.cs
new file mode 100644
index 00000000..7af92cba
--- /dev/null
+++ b/Legacy/Adv98/ImageGPC.cs
@@ -0,0 +1,239 @@
+//! \file ImageGPC.cs
+//! \date 2023 Sep 22
+//! \brief Adv98 engine image format (PC-98).
+//
+// 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;
+
+namespace GameRes.Formats.Adv98
+{
+ internal class GpcMetaData : ImageMetaData
+ {
+ public long PaletteOffset;
+ public long DataOffset;
+ public int Interleaving;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class GpcFormat : ImageFormat
+ {
+ public override string Tag => "GPC/PC98";
+ public override string Description => "Adv98 engine image format";
+ public override uint Signature => 0x38394350; // 'PC98'
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x20);
+ if (!header.AsciiEqual (4, ")GPCFILE \0"))
+ return null;
+ uint info_pos = header.ToUInt32 (0x18);
+ var info = new GpcMetaData
+ {
+ Interleaving = header.ToUInt16 (0x10),
+ PaletteOffset = header.ToUInt32 (0x14),
+ DataOffset = info_pos + 0x10,
+ BPP = 4,
+ };
+ file.Position = info_pos;
+ info.Width = file.ReadUInt16();
+ info.Height = file.ReadUInt16();
+ file.Position = info_pos + 0xA;
+ info.OffsetX = file.ReadInt16();
+ info.OffsetY = file.ReadInt16();
+ return info;
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new GpcReader (file, (GpcMetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("GpcFormat.Write not implemented");
+ }
+ }
+
+ internal class GpcReader
+ {
+ IBinaryStream m_input;
+ GpcMetaData m_info;
+ int m_stride;
+
+ public BitmapPalette Palette { get; private set; }
+
+ public GpcReader (IBinaryStream input, GpcMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = m_info.PaletteOffset;
+ Palette = ReadPalette();
+ int plane_stride = (m_info.iWidth + 7) >> 3;
+ int row_size = plane_stride * 4 + 1;
+ var data = new byte[row_size * m_info.iHeight];
+ m_input.Position = m_info.DataOffset;
+ UnpackData (data);
+ RestoreData (data, row_size);
+ m_stride = plane_stride * 4;
+ var pixels = new byte[m_stride * m_info.iHeight];
+ ConvertTo8bpp (data, pixels, plane_stride);
+ return ImageData.Create (m_info, PixelFormats.Indexed4, Palette, pixels, m_stride);
+ }
+
+ void ConvertTo8bpp (byte[] input, byte[] output, int plane_stride)
+ {
+ int interleaving_step = m_stride * m_info.Interleaving;
+ int src_row = 1;
+ int dst_row = 0;
+ int i = 0;
+ for (int y = 0; y < m_info.iHeight; ++y)
+ {
+ if (dst_row >= output.Length)
+ {
+ dst_row = m_stride * ++i;
+ }
+ int p0 = src_row;
+ int p1 = p0 + plane_stride;
+ int p2 = p1 + plane_stride;
+ int p3 = p2 + plane_stride;
+ src_row = p3 + plane_stride + 1;
+ int dst = dst_row;
+ for (int x = plane_stride; x > 0; --x)
+ {
+ byte b0 = input[p0++];
+ byte b1 = input[p1++];
+ byte b2 = input[p2++];
+ byte b3 = input[p3++];
+ for (int j = 0; j < 8; j += 2)
+ {
+ byte px = (byte)((((b0 << j) & 0x80) >> 3)
+ | (((b1 << j) & 0x80) >> 2)
+ | (((b2 << j) & 0x80) >> 1)
+ | (((b3 << j) & 0x80) ));
+ px |= (byte)((((b0 << j) & 0x40) >> 6)
+ | (((b1 << j) & 0x40) >> 5)
+ | (((b2 << j) & 0x40) >> 4)
+ | (((b3 << j) & 0x40) >> 3));
+ output[dst++] = px;
+ }
+ }
+ dst_row += interleaving_step;
+ }
+ }
+
+ void UnpackData (byte[] output)
+ {
+ int dst = 0;
+ int ctl = 0;
+ int ctl_mask = 0;
+ while (dst < output.Length)
+ {
+ if (0 == ctl_mask)
+ {
+ ctl = m_input.ReadByte();
+ if (-1 == ctl)
+ break;
+ ctl_mask = 0x80;
+ }
+ if ((ctl & ctl_mask) != 0)
+ {
+ int cmd = m_input.ReadByte();
+ for (int cmd_mask = 0x80; cmd_mask != 0; cmd_mask >>= 1)
+ {
+ if ((cmd & cmd_mask) != 0)
+ output[dst++] = m_input.ReadUInt8();
+ else
+ ++dst;
+ }
+ }
+ else
+ {
+ dst += 8;
+ }
+ ctl_mask >>= 1;
+ }
+ }
+
+ void RestoreData (byte[] data, int stride)
+ {
+ int src = 0;
+ for (int y = 0; y < m_info.iHeight; ++y)
+ {
+ int interleave = data[src];
+ if (interleave != 0)
+ {
+ byte lastValue = 0;
+ for (int i = 0; i < interleave; ++i)
+ {
+ int pos = 1 + i;
+ while (pos < stride)
+ {
+ data[src + pos] ^= lastValue;
+ lastValue = data[src + pos];
+ pos += interleave;
+ }
+ }
+
+ }
+ if (y > 0)
+ {
+ int prev = src - stride;
+ int length = (stride - 1) & -4;
+ for (int x = 1; x <= length; ++x)
+ {
+ data[src + x] ^= data[prev + x];
+
+ }
+ }
+ src += stride;
+ }
+ }
+
+ BitmapPalette ReadPalette ()
+ {
+ int count = m_input.ReadUInt16();
+ int elem_size = m_input.ReadUInt16();
+ if (elem_size != 2)
+ throw new InvalidFormatException (string.Format ("Invalid palette element size {0}", elem_size));
+ var colors = new Color[count];
+ for (int i = 0; i < count; ++i)
+ {
+ int v = m_input.ReadUInt16();
+ int r = (v >> 4) & 0xF;
+ int g = (v >> 8) & 0xF;
+ int b = (v ) & 0xF;
+ colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
+ }
+// colors[0].A = 0; // force transparency
+ return new BitmapPalette (colors);
+ }
+ }
+}
diff --git a/Legacy/Adviz/ImageBIZ.cs b/Legacy/Adviz/ImageBIZ.cs
new file mode 100644
index 00000000..ed6c8bae
--- /dev/null
+++ b/Legacy/Adviz/ImageBIZ.cs
@@ -0,0 +1,366 @@
+//! \file ImageBIZ.cs
+//! \date 2023 Sep 30
+//! \brief ADVIZ engine 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 System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [970829][Ange] Coin
+
+namespace GameRes.Formats.Adviz
+{
+ [Export(typeof(ImageFormat))]
+ public class BizFormat : ImageFormat
+ {
+ public override string Tag => "BIZ";
+ public override string Description => "ADVIZ engine image format";
+ public override uint Signature => 0;
+
+ const byte DefaultKey = 0x39;
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ if (!file.Name.HasExtension (".BIZ"))
+ return null;
+ var header = file.ReadHeader (4);
+ uint width = header.ToUInt16 (0);
+ uint height = header.ToUInt16 (2);
+ if (width * height + 4 != file.Length)
+ return null;
+ return new ImageMetaData {
+ Width = width,
+ Height = height,
+ BPP = 8,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var palette = ReadPalette (file.Name, 0x300, (pal, off) => ReadPalette (pal, off, 0x100, PaletteFormat.Rgb));
+ if (null == palette)
+ throw new FileNotFoundException ("Unable to retrieve palette.");
+ file.Position = 4;
+ var pixels = file.ReadBytes (info.iWidth * info.iHeight);
+ byte key = DefaultKey;
+ for (int i = 0; i < pixels.Length; ++i)
+ {
+ pixels[i] ^= key;
+ key += pixels[i];
+ }
+ return ImageData.CreateFlipped (info, PixelFormats.Indexed8, palette, pixels, info.iWidth);
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("BizFormat.Write not implemented");
+ }
+
+ internal delegate BitmapPalette PaletteReader (ArcView file, int offset);
+
+ static readonly Regex TachieRe = new Regex (@"^(T[^._]+_)[2-9][^.]*\.GIZ$", RegexOptions.Compiled);
+
+ internal static BitmapPalette ReadPalette (string base_name, int pal_size, PaletteReader read_pal)
+ {
+ var dir_name = Path.GetDirectoryName (base_name);
+ var grp_tbl_name = Path.Combine (dir_name, @"..\GRP_TBL.SYS");
+ var plt_tbl_name = Path.Combine (dir_name, @"..\PLT_TBL.SYS");
+ if (!File.Exists (grp_tbl_name) || !File.Exists (plt_tbl_name))
+ return null;
+ int index = 0;
+ uint grp_size = 0;
+ base_name = Path.GetFileName (base_name).ToUpperInvariant();
+ var name = base_name;
+ var ext = Path.GetExtension (name).TrimStart('.');
+ var match = TachieRe.Match (name);
+ if (match.Success)
+ name = match.Groups[1].Value + "1";
+ else
+ name = Path.GetFileNameWithoutExtension (name);
+ if (name.Length < 8)
+ name += ' ';
+ using (var grp = new ArcView (grp_tbl_name))
+ {
+ grp_size = (uint)grp.MaxOffset;
+ int pos = 0;
+ while (pos + 12 <= grp.MaxOffset)
+ {
+ if (grp.View.AsciiEqual (pos, name) &&
+ grp.View.AsciiEqual (pos+8, ext))
+ {
+ break;
+ }
+ ++index;
+ pos += 12;
+ }
+ if (pos >= grp.MaxOffset)
+ return null;
+ }
+ using (var pal = new ArcView (plt_tbl_name))
+ {
+ uint plt_size = (uint)pal.MaxOffset;
+ var id = new GrpIdentifier (grp_size, plt_size);
+ IGrpMapper mapper;
+ if (!GrpMap.TryGetValue (id, out mapper))
+ mapper = new DirectMapper();
+ index = mapper.GetPaletteIndex (index, base_name);
+ int pal_offset = index * pal_size;
+ if (pal_offset + pal_size > pal.MaxOffset)
+ {
+ int count = (int)(pal.MaxOffset / pal_size) - 1;
+ pal_offset = count * pal_size;
+ }
+ return read_pal (pal, pal_offset);
+ }
+ }
+
+ static readonly Dictionary GrpMap = new Dictionary {
+ { new GrpIdentifier (1584, 139008), new GrpShiftMapper (52) },
+ { new GrpIdentifier (2160, 12288),
+ new GrpNameMapper { NameMap = new Dictionary {
+ { "BG01.BIZ", 14 },
+ { "BG02.BIZ", 8 },
+ { "BG03.BIZ", 8 },
+ { "BG04.BIZ", 8 },
+ { "BG05.BIZ", 8 },
+ { "BG06.BIZ", 8 },
+ { "BG07.BIZ", 8 },
+ { "BG08.BIZ", 8 },
+ { "BG09.BIZ", 8 },
+ { "BG10.BIZ", 8 },
+ { "BG11.BIZ", 8 },
+ { "BG12.BIZ", 8 },
+ { "BG13.BIZ", 8 },
+ { "BG14.BIZ", 8 },
+ { "BG15.BIZ", 8 },
+ { "BG16.BIZ", 8 },
+ { "BG17.BIZ", 8 },
+ { "BG18.BIZ", 8 },
+ { "BG19.BIZ", 8 },
+ { "CA01.BIZ", 12 },
+ { "CA02.BIZ", 12 },
+ { "CA03.BIZ", 4 },
+ { "CA04.BIZ", 8 },
+ { "CA05.BIZ", 8 },
+ { "CA06.BIZ", 8 },
+ { "CA07.BIZ", 8 },
+ { "CA08.BIZ", 8 },
+ { "CA09.BIZ", 8 },
+ { "CA10.BIZ", 8 },
+ { "CA11.BIZ", 8 },
+ { "CA12.BIZ", 8 },
+ { "CA13.BIZ", 8 },
+ { "CA14.BIZ", 8 },
+ { "CA15.BIZ", 8 },
+ { "CA16.BIZ", 8 },
+ { "CA17.BIZ", 8 },
+ { "CA18.BIZ", 8 },
+ { "CA19.BIZ", 8 },
+ { "CA20.BIZ", 8 },
+ { "CA21.BIZ", 8 },
+ { "CA22.BIZ", 8 },
+ { "CA23.BIZ", 8 },
+ { "CA24.BIZ", 8 },
+ { "CA25.BIZ", 8 },
+ { "CA26.BIZ", 8 },
+ { "CA27.BIZ", 8 },
+ { "CA28.BIZ", 8 },
+ { "CA29.BIZ", 8 },
+ { "CA30.BIZ", 8 },
+ { "CA31.BIZ", 8 },
+ { "CA32.BIZ", 8 },
+ { "CA33.BIZ", 8 },
+ { "CA34.BIZ", 8 },
+ { "CA35.BIZ", 8 },
+ { "CA36.BIZ", 8 },
+ { "CA37.BIZ", 8 },
+ { "CA38.BIZ", 8 },
+ { "CA39.BIZ", 8 },
+ { "CA40.BIZ", 8 },
+ { "CA41.BIZ", 8 },
+ { "CA42.BIZ", 8 },
+ { "CA43.BIZ", 8 },
+ { "CA44.BIZ", 8 },
+ { "CA45.BIZ", 8 },
+ { "CA46.BIZ", 8 },
+ { "CA47.BIZ", 8 },
+ { "CA48.BIZ", 8 },
+ { "CA49.BIZ", 8 },
+ { "CA50.BIZ", 8 },
+ { "CA51.BIZ", 8 },
+ { "CA52.BIZ", 8 },
+ { "CA53.BIZ", 8 },
+ { "CA54.BIZ", 8 },
+ { "CA55.BIZ", 8 },
+ { "CA56.BIZ", 8 },
+ { "CA57.BIZ", 8 },
+ { "CA58.BIZ", 8 },
+ { "CA59.BIZ", 8 },
+ { "CA60.BIZ", 8 },
+ { "E02.BIZ", 8 },
+ { "E03.BIZ", 8 },
+ { "E04.BIZ", 8 },
+ { "E05.BIZ", 8 },
+ { "E06.BIZ", 8 },
+ { "E07.BIZ", 8 },
+ { "E08.BIZ", 8 },
+ { "E09.BIZ", 8 },
+ { "E10.BIZ", 8 },
+ { "E11.BIZ", 8 },
+ { "E12.BIZ", 8 },
+ { "E13.BIZ", 8 },
+ { "E14.BIZ", 8 },
+ { "E15.BIZ", 8 },
+ { "E16.BIZ", 8 },
+ { "E17.BIZ", 8 },
+ { "E18.BIZ", 8 },
+ { "END.BIZ", 6 },
+ { "IPL.BIZ", 12 },
+ { "S01.BIZ", 8 },
+ { "S02.BIZ", 8 },
+ { "S03.BIZ", 8 },
+ { "S04.BIZ", 8 },
+ { "S05.BIZ", 8 },
+ { "S06.BIZ", 8 },
+ { "S07.BIZ", 8 },
+ { "S08.BIZ", 8 },
+ { "S09.BIZ", 8 },
+ { "S10.BIZ", 8 },
+ { "S11.BIZ", 8 },
+ { "S12.BIZ", 8 },
+ { "S13.BIZ", 8 },
+ { "S14.BIZ", 8 },
+ { "S15.BIZ", 8 },
+ { "S16.BIZ", 8 },
+ { "S17.BIZ", 8 },
+ { "S18.BIZ", 8 },
+ { "S19.BIZ", 8 },
+ { "S20.BIZ", 8 },
+ { "S21.BIZ", 8 },
+ { "S22.BIZ", 8 },
+ { "S23.BIZ", 8 },
+ { "S24.BIZ", 8 },
+ { "S25.BIZ", 8 },
+ { "S26.BIZ", 8 },
+ { "S27.BIZ", 8 },
+ { "S28.BIZ", 8 },
+ { "S29.BIZ", 8 },
+ { "S30.BIZ", 8 },
+ { "S31.BIZ", 8 },
+ { "S32.BIZ", 8 },
+ { "S33.BIZ", 8 },
+ { "S34.BIZ", 8 },
+ { "S35.BIZ", 8 },
+ { "S36.BIZ", 8 },
+ { "S37.BIZ", 8 },
+ { "S38.BIZ", 8 },
+ { "S39.BIZ", 8 },
+ { "S40.BIZ", 8 },
+ { "S41.BIZ", 8 },
+ { "S42.BIZ", 8 },
+ { "S43.BIZ", 8 },
+ { "S44.BIZ", 8 },
+ { "S45.BIZ", 8 },
+ { "S46.BIZ", 8 },
+ { "S47.BIZ", 8 },
+ { "S48.BIZ", 8 },
+ { "S49.BIZ", 8 },
+ { "T01.BIZ", 8 },
+ { "T02.BIZ", 8 },
+ { "T03.BIZ", 8 },
+ { "WAKU1.BIZ", 8 },
+ { "WAKU2.BIZ", 8 },
+ } } },
+ };
+ }
+
+ public struct GrpIdentifier
+ {
+ public uint GrpSize;
+ public uint PltSize;
+
+ public GrpIdentifier (uint grp_size, uint plt_size)
+ {
+ GrpSize = grp_size;
+ PltSize = plt_size;
+ }
+
+ public override int GetHashCode ()
+ {
+ return (int)((GrpSize + 1) * (PltSize + 1));
+ }
+
+ public override bool Equals (object obj)
+ {
+ if (null == obj)
+ return false;
+ var other = (GrpIdentifier)obj;
+ return this.GrpSize == other.GrpSize && this.PltSize == other.PltSize;
+ }
+ }
+
+ internal interface IGrpMapper
+ {
+ int GetPaletteIndex (int id, string name);
+ }
+
+ internal class DirectMapper : IGrpMapper
+ {
+ public int GetPaletteIndex (int id, string name)
+ {
+ return id;
+ }
+ }
+
+ internal class GrpShiftMapper : IGrpMapper
+ {
+ int m_shift;
+
+ public GrpShiftMapper (int shift)
+ {
+ m_shift = shift;
+ }
+
+ public int GetPaletteIndex (int id, string name)
+ {
+ return id + m_shift;
+ }
+ }
+
+ internal class GrpNameMapper : IGrpMapper
+ {
+ public Dictionary NameMap;
+
+ public int GetPaletteIndex (int id, string name)
+ {
+ int index;
+ if (NameMap.TryGetValue (name, out index))
+ return index;
+ return id;
+ }
+ }
+}
diff --git a/Legacy/Sorciere/ImageBIZ.cs b/Legacy/Adviz/ImageBIZ2.cs
similarity index 56%
rename from Legacy/Sorciere/ImageBIZ.cs
rename to Legacy/Adviz/ImageBIZ2.cs
index 83260b74..a6a86fcc 100644
--- a/Legacy/Sorciere/ImageBIZ.cs
+++ b/Legacy/Adviz/ImageBIZ2.cs
@@ -1,6 +1,6 @@
-//! \file ImageBIZ.cs
+//! \file ImageBIZ2.cs
//! \date 2018 Feb 11
-//! \brief Sorciere compressed image.
+//! \brief ADVIZ engine compressed image.
//
// Copyright (C) 2018 by morkt
//
@@ -29,14 +29,15 @@ using System.Windows.Media;
using GameRes.Compression;
// [000225][Sorciere] Karei
+// [011012][Ange] Nyuunyuu
-namespace GameRes.Formats.Sorciere
+namespace GameRes.Formats.Adviz
{
[Export(typeof(ImageFormat))]
- public class BizFormat : ImageFormat
+ public class Biz2Format : ImageFormat
{
- public override string Tag { get { return "BIZ"; } }
- public override string Description { get { return "Sorciere compressed image"; } }
+ public override string Tag { get { return "BIZ/2"; } }
+ public override string Description { get { return "ADVIZ engine compressed image"; } }
public override uint Signature { get { return 0x325A4942; } } // 'BIZ2'
public override ImageMetaData ReadMetaData (IBinaryStream file)
@@ -52,13 +53,34 @@ namespace GameRes.Formats.Sorciere
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 8;
- using (var input = new LzssStream (file.AsStream, LzssMode.Decompress, true))
+ using (var lzss = new LzssStream (file.AsStream, LzssMode.Decompress, true))
+ using (var input = new BinaryStream (lzss, file.Name))
{
- int stride = (int)info.Width * 3;
- var pixels = new byte[stride * (int)info.Height];
- if (pixels.Length != input.Read (pixels, 0, pixels.Length))
+ int stride = info.iWidth * 3;
+ var rgb = new byte[stride * info.Height];
+ if (rgb.Length != input.Read (rgb, 0, rgb.Length))
throw new InvalidFormatException();
- return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, pixels, stride);
+ if (input.PeekByte() != -1) // possible alpha channel
+ {
+ var alpha = input.ReadBytes (rgb.Length);
+ if (alpha.Length == rgb.Length)
+ {
+ int stride32bpp = info.iWidth * 4;
+ var rgba = new byte[stride32bpp * info.iHeight];
+ int src = 0;
+ int dst = 0;
+ while (src < rgb.Length)
+ {
+ rgba[dst++] = rgb[src ];
+ rgba[dst++] = rgb[src+1];
+ rgba[dst++] = rgb[src+2];
+ rgba[dst++] = alpha[src]; // presumably it's grayscale and R/G/B values are equal
+ src += 3;
+ }
+ return ImageData.CreateFlipped (info, PixelFormats.Bgra32, null, rgba, stride32bpp);
+ }
+ }
+ return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, rgb, stride);
}
}
diff --git a/Legacy/Adviz/ImageGIZ.cs b/Legacy/Adviz/ImageGIZ.cs
new file mode 100644
index 00000000..8e4da430
--- /dev/null
+++ b/Legacy/Adviz/ImageGIZ.cs
@@ -0,0 +1,309 @@
+//! \file ImageGIZ.cs
+//! \date 2023 Oct 02
+//! \brief ADVIZ engine image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [960830][Ange] Coin
+
+namespace GameRes.Formats.Adviz
+{
+ internal class GizMetaData : ImageMetaData
+ {
+ public byte RleCode;
+ public byte PlaneMap;
+ public bool HasPalette;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class Giz3Format : ImageFormat
+ {
+ public override string Tag => "GIZ";
+ public override string Description => "ADVIZ engine image format";
+ public override uint Signature => 0x335A4947; // 'GIZ3'
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x10);
+ int xy = header.ToUInt16 (4);
+ return new GizMetaData {
+ Width = (uint)header.ToUInt16 (6) << 3,
+ Height = header.ToUInt16 (8),
+ OffsetX = (xy % 0x50) << 3,
+ OffsetY = xy / 0x50,
+ HasPalette = header[0xC] != 0,
+ PlaneMap = header[0xE],
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new Giz3Reader (file, (GizMetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("Giz3Format.Write not implemented");
+ }
+ }
+
+ internal class Giz3Reader
+ {
+ IBinaryStream m_input;
+ GizMetaData m_info;
+ BitmapPalette m_palette;
+ int m_stride;
+ int m_output_stride;
+
+ public Giz3Reader (IBinaryStream input, GizMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ byte[] m_buffer;
+ byte[] m_output;
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 0x10;
+ if (m_info.HasPalette)
+ m_palette = ReadPalette();
+ else
+ m_palette = BitmapPalettes.Gray16; // palette is stored somewhere else
+ long data_pos = m_input.Position;
+ ReadHuffmanTree();
+ data_pos += m_dataOffset;
+
+ m_bitCount = 1;
+ m_stride = m_info.iWidth >> 3;
+ m_buffer = new byte[0x2000];
+ m_output_stride = m_info.iWidth >> 1;
+ m_output = new byte[m_output_stride * m_info.iHeight];
+ m_input.Position = data_pos;
+ UnpackBits();
+ return ImageData.Create (m_info, PixelFormats.Indexed4, m_palette, m_output, m_output_stride);
+ }
+
+ int m_dataOffset;
+ int m_gizColumn;
+ int m_outputPos1;
+ int m_outputPos2;
+
+ void UnpackBits () // sub_15426
+ {
+ m_gizColumn = 0;
+ m_outputPos1 = 0;
+ m_outputPos2 = 0;
+ int dst = 0;
+ for (int x = 0; x < m_stride; ++x)
+ {
+ int src1 = m_outputPos1;
+ int src2 = 0;
+ for (int j = 0; j < 2; ++j)
+ {
+ src2 = m_outputPos1;
+ int plane_mask = 1;
+ for (int i = 0; i < 4; ++i)
+ {
+ if ((m_info.PlaneMap & plane_mask) == 0)
+ {
+ UnpackPlane (m_outputPos1);
+ }
+ plane_mask <<= 1;
+ m_outputPos1 += 0x800;
+ m_outputPos2 += 0x800;
+ }
+ m_gizColumn = (m_gizColumn + 1) & 3;
+ m_outputPos1 = m_gizColumn << 9;
+ m_outputPos2 = 0;
+ }
+ CopyPlanes (src1, src2, dst);
+ dst += 4;
+ }
+ }
+
+ void CopyPlanes (int src1, int src2, int dst)
+ {
+ for (int y = 0; y < m_info.iHeight; ++y)
+ {
+ int b0 = m_buffer[src1+y ] << 4 | m_buffer[src2+y ];
+ int b1 = m_buffer[src1+y+0x0800] << 4 | m_buffer[src2+y+0x0800];
+ int b2 = m_buffer[src1+y+0x1000] << 4 | m_buffer[src2+y+0x1000];
+ int b3 = m_buffer[src1+y+0x1800] << 4 | m_buffer[src2+y+0x1800];
+ for (int j = 0; j < 8; j += 2)
+ {
+ byte px = (byte)((((b0 << j) & 0x80) >> 3)
+ | (((b1 << j) & 0x80) >> 2)
+ | (((b2 << j) & 0x80) >> 1)
+ | (((b3 << j) & 0x80) ));
+ px |= (byte)((((b0 << j) & 0x40) >> 6)
+ | (((b1 << j) & 0x40) >> 5)
+ | (((b2 << j) & 0x40) >> 4)
+ | (((b3 << j) & 0x40) >> 3));
+ m_output[dst+j/2] = px;
+ }
+ dst += m_output_stride;
+ }
+ }
+
+ int m_root;
+ ushort[] m_treeTable;
+
+ void ReadHuffmanTree ()
+ {
+ m_dataOffset = m_input.ReadUInt16();
+ m_treeTable = new ushort[(m_dataOffset-2) * 2 / 3 + 1];
+ int di = 0;
+ for (int si = 2; si + 2 < m_dataOffset; si += 3)
+ {
+ ushort bx = m_input.ReadUInt16();
+ int ax = bx & 0xFFF;
+ if ((ax & 0x800) == 0)
+ ax = (ax - 2) >> 1;
+ m_treeTable[di++] = (ushort)ax;
+ ax = m_input.ReadUInt8() << 4;
+ ax |= bx >> 12;
+ if ((ax & 0x800) == 0)
+ ax = (ax - 2) >> 1;
+ m_treeTable[di++] = (ushort)ax;
+ }
+ m_root = di - 2;
+ }
+
+ byte ReadToken ()
+ {
+ int token = m_root;
+ do
+ {
+ if (GetNextBit())
+ ++token;
+ token = m_treeTable[token];
+ }
+ while ((token & 0x800) == 0);
+ return (byte)token;
+ }
+
+ void UnpackPlane (int dst)
+ {
+ int y = 0;
+ while (y < m_info.iHeight)
+ {
+ byte ctl = ReadToken();
+ if (ctl < 0x10)
+ {
+ m_buffer[dst++] = ctl;
+ ++y;
+ }
+ else
+ {
+ int count = ReadToken() + 2;
+ ctl -= 0x10;
+ switch (ctl)
+ {
+ case 0:
+ for (int i = 0; i < count; ++i)
+ m_buffer[dst+i] = 0;
+ break;
+
+ case 1:
+ for (int i = 0; i < count; ++i)
+ m_buffer[dst+i] = 0xF;
+ break;
+
+ case 2:
+ Binary.CopyOverlapped (m_buffer, dst-1, dst, count);
+ break;
+
+ case 3:
+ Binary.CopyOverlapped (m_buffer, dst-2, dst, count);
+ break;
+
+ case 4:
+ case 5:
+ case 6:
+ {
+ int off = (ctl - 3) << 11;
+ Binary.CopyOverlapped (m_buffer, dst-off, dst, count);
+ break;
+ }
+ case 7:
+ {
+ int src = dst - m_outputPos1;
+ int ax = (m_gizColumn - 1) & 3;
+ src += (ax << 9) + m_outputPos2;
+ Binary.CopyOverlapped (m_buffer, src, dst, count);
+ break;
+ }
+ case 8:
+ {
+ int src = dst - m_outputPos1;
+ int ax = (m_gizColumn - 2) & 3;
+ src += (ax << 9) + m_outputPos2;
+ Binary.CopyOverlapped (m_buffer, src, dst, count);
+ break;
+ }
+ }
+ dst += count;
+ y += count;
+ }
+ }
+ }
+
+ BitmapPalette ReadPalette ()
+ {
+ const int count = 16;
+ var colors = new Color[count];
+ for (int i = 0; i < count; ++i)
+ {
+ byte b = m_input.ReadUInt8();
+ byte r = m_input.ReadUInt8();
+ byte g = m_input.ReadUInt8();
+ colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
+ }
+ return new BitmapPalette (colors);
+ }
+
+ int m_bitCount;
+ int m_bits;
+
+ bool GetNextBit ()
+ {
+ if (--m_bitCount == 0)
+ {
+ m_bits = m_input.ReadUInt16();
+ m_bitCount = 16;
+ }
+ bool bit = (m_bits & 0x8000) != 0;
+ m_bits <<= 1;
+ return bit;
+ }
+ }
+}
diff --git a/Legacy/Adviz/ImageGIZ2.cs b/Legacy/Adviz/ImageGIZ2.cs
new file mode 100644
index 00000000..7ceb287c
--- /dev/null
+++ b/Legacy/Adviz/ImageGIZ2.cs
@@ -0,0 +1,231 @@
+//! \file ImageGIZ2.cs
+//! \date 2023 Oct 02
+//! \brief ADVIZ engine image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [951027][Ange] Leap Toki ni Sarawareta Shoujo
+
+namespace GameRes.Formats.Adviz
+{
+ [Export(typeof(ImageFormat))]
+ public class GizFormat : ImageFormat
+ {
+ public override string Tag => "GIZ/2";
+ public override string Description => "ADVIZ engine image format";
+ public override uint Signature => 0x325A4947; // 'GIZ2'
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x10);
+ int xy = header.ToUInt16 (4);
+ return new GizMetaData {
+ Width = (uint)header.ToUInt16 (6) << 3,
+ Height = header.ToUInt16 (8),
+ OffsetX = (xy % 0x50) << 3,
+ OffsetY = xy / 0x50,
+ RleCode = header[0xC],
+ PlaneMap = header[0xE],
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new Giz2Reader (file, (GizMetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("GizFormat.Write not implemented");
+ }
+ }
+
+ internal class Giz2Reader
+ {
+ IBinaryStream m_input;
+ GizMetaData m_info;
+ BitmapPalette m_palette;
+
+ public BitmapPalette Palette => m_palette;
+
+ public Giz2Reader (IBinaryStream input, GizMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ int m_stride;
+ byte[][] m_planes;
+ int m_output_stride;
+ byte[] m_output;
+
+ public ImageData Unpack ()
+ {
+ m_palette = BizFormat.ReadPalette (m_input.Name, 0x30, (pal, off) => ReadPalette (pal, off));
+ if (null == m_palette)
+ {
+// m_palette = BitmapPalettes.Gray16;
+ throw new FileNotFoundException ("Unable to retrieve palette.");
+ }
+ m_input.Position = 0x10;
+ m_stride = m_info.iWidth >> 3;
+ int plane_size = m_info.iHeight;
+ m_planes = new byte[][] {
+ new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size],
+ };
+ m_output_stride = m_info.iWidth >> 1;
+ m_output = new byte[m_output_stride * m_info.iHeight];
+ int dst = 0;
+ for (int x = 0; x < m_stride; ++x)
+ {
+ int plane_mask = 1;
+ for (int i = 0; i < 4; ++i)
+ {
+ if ((m_info.PlaneMap & plane_mask) == 0)
+ UnpackPlane (m_planes[i], 0);
+ plane_mask <<= 1;
+ }
+ CopyPlanes (dst);
+ dst += 4;
+ }
+ return ImageData.Create (m_info, PixelFormats.Indexed4, Palette, m_output, m_output_stride);
+ }
+
+ bool UnpackPlane (byte[] output, int dst)
+ {
+ for (int y = 0; y < m_info.iHeight; )
+ {
+ byte b = m_input.ReadUInt8();
+ int ctl = (b - m_info.RleCode) & 0xFF;
+ if (2 == ctl)
+ {
+ output[dst++] = m_input.ReadUInt8();
+ }
+ else if (ctl < 4)
+ {
+ if (0 == ctl)
+ b = 0;
+ else if (1 == ctl)
+ b = 0xFF;
+ else
+ b = m_input.ReadUInt8();
+ int count = ((m_input.ReadUInt8() - 1) & 0xFF) + 1;
+ y += count;
+ while (count --> 0)
+ {
+ output[dst++] = b;
+ }
+ continue;
+ }
+ else if (ctl < 7)
+ {
+ byte b0 = m_input.ReadUInt8();
+ byte b1 = m_input.ReadUInt8();
+ int count;
+ if (4 == ctl)
+ {
+ count = ((b1 - 1) & 0x7F) + 1;
+ if (b1 < 0x80)
+ b1 = Binary.RotByteL (b0, 1);
+ else
+ b1 = Binary.RotByteR (b0, 1);
+ }
+ else if (5 == ctl)
+ {
+ count = ((b1 - 1) & 0x7F) + 1;
+ if (b1 < 0x80)
+ b1 = Binary.RotByteL (b0, 2);
+ else
+ b1 = Binary.RotByteR (b0, 2);
+ }
+ else
+ {
+ count = ((m_input.ReadUInt8() - 1) & 0xFF) + 1;
+ count *= 2;
+ }
+ y += count;
+ do
+ {
+ output[dst++] = b0;
+ if (--count <= 0)
+ break;
+ output[dst++] = b1;
+ }
+ while (--count > 0);
+ continue;
+ }
+ else
+ {
+ output[dst++] = b;
+ }
+ ++y;
+ }
+ return true;
+ }
+
+ void CopyPlanes (int dst)
+ {
+ for (int y = 0; y < m_info.iHeight; ++y)
+ {
+ int b0 = m_planes[0][y];
+ int b1 = m_planes[1][y];
+ int b2 = m_planes[2][y];
+ int b3 = m_planes[3][y];
+ for (int j = 0; j < 8; j += 2)
+ {
+ byte px = (byte)((((b0 << j) & 0x80) >> 3)
+ | (((b1 << j) & 0x80) >> 2)
+ | (((b2 << j) & 0x80) >> 1)
+ | (((b3 << j) & 0x80) ));
+ px |= (byte)((((b0 << j) & 0x40) >> 6)
+ | (((b1 << j) & 0x40) >> 5)
+ | (((b2 << j) & 0x40) >> 4)
+ | (((b3 << j) & 0x40) >> 3));
+ m_output[dst+j/2] = px;
+ }
+ dst += m_output_stride;
+ }
+ }
+
+ BitmapPalette ReadPalette (ArcView file, int offset)
+ {
+ const int count = 16;
+ var colors = new Color[count];
+ for (int i = 0; i < count; ++i)
+ {
+ byte b = file.View.ReadByte (offset++);
+ byte r = file.View.ReadByte (offset++);
+ byte g = file.View.ReadByte (offset++);
+ colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+}
diff --git a/Legacy/AyPio/ArcDLB.cs b/Legacy/AyPio/ArcDLB.cs
new file mode 100644
index 00000000..3bbe2450
--- /dev/null
+++ b/Legacy/AyPio/ArcDLB.cs
@@ -0,0 +1,99 @@
+//! \file ArcDLB.cs
+//! \date 2023 Oct 13
+//! \brief AyPio resource archive (PC-98).
+//
+// 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.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace GameRes.Formats.AyPio
+{
+ [Export(typeof(ArchiveFormat))]
+ public class DlbOpener : ArchiveFormat
+ {
+ public override string Tag => "DLB";
+ public override string Description => "UK2 engine resource archive";
+ public override uint Signature => 0x64203C3C; // '<< d'
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!file.View.AsciiEqual (3, "dlb file Ver1.00>>\0"))
+ return null;
+ int count = file.View.ReadInt16 (0x16);
+ if (!IsSaneCount (count))
+ return null;
+ using (var index = file.CreateStream())
+ {
+ index.Position = 0x18;
+ var dir = ReadIndex (index, count);
+ return new ArcFile (file, this, dir);
+ }
+ }
+
+ internal List ReadIndex (IBinaryStream index, int count)
+ {
+ var max_offset = index.Length;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ var name = index.ReadCString (0xD);
+ var entry = Create (name);
+ entry.Offset = index.ReadUInt32();
+ entry.Size = index.ReadUInt32();
+ if (!entry.CheckPlacement (max_offset))
+ return null;
+ dir.Add (entry);
+ }
+ return dir;
+ }
+ }
+
+ [Export(typeof(ArchiveFormat))]
+ public class Dlb0Opener : DlbOpener
+ {
+ public override string Tag => "DLB/V0";
+ public override string Description => "UK2 engine resource archive";
+ public override uint Signature => 0;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!file.Name.HasExtension (".DLB"))
+ return null;
+ int count = file.View.ReadInt16 (0);
+ if (!IsSaneCount (count))
+ return null;
+ uint first_offset = file.View.ReadUInt32 (0xF);
+ if (first_offset != count * 0x15 + 2)
+ return null;
+ using (var index = file.CreateStream())
+ {
+ index.Position = 2;
+ var dir = ReadIndex (index, count);
+ return new ArcFile (file, this, dir);
+ }
+ }
+ }
+}
diff --git a/Legacy/AyPio/AudioVOC.cs b/Legacy/AyPio/AudioVOC.cs
new file mode 100644
index 00000000..090f70df
--- /dev/null
+++ b/Legacy/AyPio/AudioVOC.cs
@@ -0,0 +1,192 @@
+//! \file AudioVOC.cs
+//! \date 2023 Oct 19
+//! \brief AyPio ADPCM-compressed audio.
+//
+// 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.ComponentModel.Composition;
+
+namespace GameRes.Formats.AyPio
+{
+ [Export(typeof(AudioFormat))]
+ public class VocAudio : AudioFormat
+ {
+ public override string Tag => "VOC/UK2";
+ public override string Description => "UK2 engine compressed audio";
+ public override uint Signature => 0x81564157; // 'WAV\x81'
+ public override bool CanWrite => false;
+
+ public override SoundInput TryOpen (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x3C);
+ if (!header.AsciiEqual (0x38, "RIFF"))
+ return null;
+ var decoder = new VocDecoder (file);
+ var samples = decoder.Decode();
+ var stream = new BinMemoryStream (samples, file.Name);
+ file.Dispose();
+ return new RawPcmInput (stream, decoder.Format);
+ }
+ }
+
+ internal sealed class VocDecoder
+ {
+ IBinaryStream m_input;
+
+ int m_sample_count;
+ byte m_channels;
+ byte m_bits_per_sample;
+ byte[] m_prev_sample = new byte[2];
+ long m_start_pos;
+
+ public WaveFormat Format { get; private set; }
+
+ public VocDecoder (IBinaryStream input)
+ {
+ m_input = input;
+ var header = input.ReadHeader (0x38);
+ Format = new WaveFormat {
+ FormatTag = header.ToUInt16 (0x21),
+ Channels = header.ToUInt16 (0x23),
+ SamplesPerSecond = header.ToUInt32 (0x25),
+ AverageBytesPerSecond = header.ToUInt32 (0x29),
+ BlockAlign = header.ToUInt16 (0x2D),
+ BitsPerSample = header.ToUInt16 (0x2F),
+ };
+ m_sample_count = header.ToInt32 (0x18);
+ m_channels = header[8];
+ m_prev_sample[0] = header[0xC];
+ m_prev_sample[1] = header[0x10];
+ m_bits_per_sample = header[0x20];
+ m_output = new byte[m_sample_count << 1];
+ m_output[0] = header[0xA];
+ m_output[1] = header[0xB];
+ m_output[2] = header[0xE];
+ m_output[3] = header[0xF];
+ m_start_pos = header.ToUInt32 (4) + header.ToUInt32 (0x14);
+ }
+
+ byte[] m_output;
+ int[] m_samples;
+ int m_src;
+
+ public byte[] Decode ()
+ {
+ m_input.Position = m_start_pos;
+ m_src = 0;
+ BuildSamples();
+ int count = m_sample_count - m_channels;
+ int src = 0;
+ int pos = 0;
+ while (src < count)
+ {
+ byte sample = GetSample();
+ int v7 = sample & 7;
+ int channel;
+ if (m_channels == 1 || (src & 1) == 0)
+ channel = 0;
+ else
+ channel = 1;
+ byte prev = m_prev_sample[channel];
+ int s = m_samples[89 * v7 + prev];
+ if ((sample & 8) != 0)
+ s = -s;
+ s += m_output.ToInt16 (pos);
+ pos += 2;
+ LittleEndian.Pack (Clamp (s), m_output, pos);
+ int p = IndexTable[v7] + prev;
+ if (p < 0)
+ p = 0;
+ else if (p > 88)
+ p = 88;
+ m_prev_sample[channel] = (byte)p;
+ ++src;
+ }
+ return m_output;
+ }
+
+ byte m_current_sample;
+
+ byte GetSample ()
+ {
+ if (0 == (m_src & 1))
+ m_current_sample = m_input.ReadUInt8();
+ ++m_src;
+ byte sample = m_current_sample;
+ m_current_sample >>= 4;
+ return sample &= 0xF;
+ }
+
+ short Clamp (int sample)
+ {
+ if (sample > 0x7FFF)
+ sample = 0x7FFF;
+ else if (sample < -0x8000)
+ sample = -0x8000;
+ return (short)sample;
+ }
+
+ void BuildSamples ()
+ {
+ int b = 1 << (m_bits_per_sample - 1);
+ int i = 0;
+ m_samples = new int[89 * b];
+ while (i < 89)
+ {
+ int ii = i;
+ int j = 0;
+ while (j < b)
+ {
+ double d = 0.0;
+ int a = 1;
+ int c = b;
+ do
+ {
+ if (j % a >= a / 2)
+ {
+ d += (double)StepTable[ii] / (double)c;
+ }
+ a <<= 1;
+ c >>= 1;
+ }
+ while (a <= b);
+ ++j;
+ m_samples[i] = (int)d;
+ i += 89;
+ }
+ i = ii + 1;
+ }
+ }
+
+ static readonly short[] StepTable = {
+ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x10, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1C, 0x1F,
+ 0x22, 0x25, 0x29, 0x2D, 0x32, 0x37, 0x3C, 0x42, 0x49, 0x50, 0x58, 0x61, 0x6B, 0x76, 0x82, 0x8F,
+ 0x9D, 0x0AD, 0x0BE, 0x0D1, 0x0E6, 0x0FD, 0x117, 0x133, 0x151, 0x173, 0x198, 0x1C1, 0x1EE, 0x220,
+ 0x256, 0x292, 0x2D4, 0x31C, 0x36C, 0x3C3, 0x424, 0x48E, 0x502, 0x583, 0x610, 0x6AB, 0x756, 0x812,
+ 0x8E0, 0x9C3, 0x0ABD, 0x0BD0, 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
+ 0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, 0x3BB9, 0x41B2, 0x4844, 0x4F7E,
+ 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF,
+ };
+ static readonly sbyte[] IndexTable = { -1, -1, -1, -1, 1, 2, 3, 4 };
+ }
+}
diff --git a/Legacy/AyPio/ImagePDT.cs b/Legacy/AyPio/ImagePDT.cs
new file mode 100644
index 00000000..07756e87
--- /dev/null
+++ b/Legacy/AyPio/ImagePDT.cs
@@ -0,0 +1,204 @@
+//! \file ImagePDT.cs
+//! \date 2023 Oct 13
+//! \brief AyPio image format (PC-98).
+//
+// 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;
+
+// [960726][AyPio] Chuushaki
+// [970314][AyPio] Stars
+
+namespace GameRes.Formats.AyPio
+{
+ internal class PdtMetaData : ImageMetaData
+ {
+ public byte Rle1;
+ public byte Rle2;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class PdtFormat : ImageFormat
+ {
+ public override string Tag => "PDT/UK2";
+ public override string Description => "UK2 engine image format";
+ public override uint Signature => 0;
+
+ public PdtFormat ()
+ {
+ Extensions = new[] { "pdt", "anm" };
+ }
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ if (file.ReadByte() != 0x34)
+ return null;
+ file.Position = 0x21;
+ byte rle1 = file.ReadUInt8();
+ byte rle2 = file.ReadUInt8();
+ int left = file.ReadUInt16();
+ int top = file.ReadUInt16();
+ int right = file.ReadUInt16();
+ int bottom = file.ReadUInt16();
+ int width = (right - left + 1) << 3;
+ int height = (((bottom - top) >> 1) + 1) << 1;
+ if (width <= 0 || height <= 0 || width > 2048 || height > 512)
+ return null;
+ return new PdtMetaData {
+ Width = (uint)width,
+ Height = (uint)height,
+ OffsetX = left << 3,
+ OffsetY = top,
+ BPP = 4,
+ Rle1 = rle1,
+ Rle2 = rle2,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new Pdt4Reader (file, (PdtMetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("PdtFormat.Write not implemented");
+ }
+ }
+
+ internal class Pdt4Reader
+ {
+ IBinaryStream m_input;
+ PdtMetaData m_info;
+
+ public Pdt4Reader (IBinaryStream input, PdtMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ int m_stride;
+ int m_rows;
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 1;
+ var palette = ReadPalette();
+ m_input.Position = 0x2B;
+ int output_stride = m_info.iWidth >> 1;
+ int height = m_info.iHeight;
+ m_stride = m_info.iWidth >> 3;
+ m_rows = height >> 1;
+ int plane_size = m_stride * height;
+ var planes = new byte[][] {
+ new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size]
+ };
+ for (int i = 0; i < 4; ++i)
+ UnpackPlane (planes[i]);
+ var pixels = new byte[output_stride * height];
+ FlattenPlanes (planes, pixels);
+ return ImageData.Create (m_info, PixelFormats.Indexed4, palette, pixels, output_stride);
+ }
+
+ void UnpackPlane (byte[] output)
+ {
+ for (int x = 0; x < m_stride; ++x)
+ {
+ int dst = x;
+ int y = 0;
+ while (y < m_rows)
+ {
+ int count = 1;
+ byte p0, p1;
+ byte ctl = m_input.ReadUInt8();
+ if (ctl == m_info.Rle1)
+ {
+ count = m_input.ReadUInt8();
+ p0 = m_input.ReadUInt8();
+ p1 = m_input.ReadUInt8();
+ }
+ else if (ctl == m_info.Rle2)
+ {
+ count = m_input.ReadUInt8();
+ p1 = p0 = m_input.ReadUInt8();
+ }
+ else
+ {
+ p0 = ctl;
+ p1 = m_input.ReadUInt8();
+ }
+ while (count --> 0)
+ {
+ output[dst] = p0;
+ dst += m_stride;
+ output[dst] = p1;
+ dst += m_stride;
+ ++y;
+ }
+ }
+ }
+ }
+
+ void FlattenPlanes (byte[][] planes, byte[] output)
+ {
+ int plane_size = planes[0].Length;
+ int dst = 0;
+ for (int src = 0; src < plane_size; ++src)
+ {
+ int b0 = planes[0][src];
+ int b1 = planes[1][src];
+ int b2 = planes[2][src];
+ int b3 = planes[3][src];
+ for (int j = 0; j < 8; j += 2)
+ {
+ byte px = (byte)((((b0 << j) & 0x80) >> 3)
+ | (((b1 << j) & 0x80) >> 2)
+ | (((b2 << j) & 0x80) >> 1)
+ | (((b3 << j) & 0x80) ));
+ px |= (byte)((((b0 << j) & 0x40) >> 6)
+ | (((b1 << j) & 0x40) >> 5)
+ | (((b2 << j) & 0x40) >> 4)
+ | (((b3 << j) & 0x40) >> 3));
+ output[dst++] = px;
+ }
+ }
+ }
+
+ BitmapPalette ReadPalette ()
+ {
+ var colors = new Color[16];
+ for (int i = 0; i < 16; ++i)
+ {
+ ushort rgb = m_input.ReadUInt16();
+ int b = (rgb & 0xF) * 0x11;
+ int r = ((rgb >> 4) & 0xF) * 0x11;
+ int g = ((rgb >> 8) & 0xF) * 0x11;
+ colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+}
diff --git a/Legacy/AyPio/ImagePDT5.cs b/Legacy/AyPio/ImagePDT5.cs
new file mode 100644
index 00000000..d2857805
--- /dev/null
+++ b/Legacy/AyPio/ImagePDT5.cs
@@ -0,0 +1,252 @@
+//! \file ImagePDT5.cs
+//! \date 2023 Oct 16
+//! \brief AyPio 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.Utility;
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [960726][AyPio] Chuushaki
+
+namespace GameRes.Formats.AyPio
+{
+ [Export(typeof(ImageFormat))]
+ public class Pdt5Format : ImageFormat
+ {
+ public override string Tag => "PDT/5";
+ public override string Description => "UK2 engine image format";
+ public override uint Signature => 0;
+
+ public Pdt5Format ()
+ {
+ Extensions = new[] { "pdt", "anm" };
+ }
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ if (file.ReadByte() != 0x35)
+ return null;
+ file.Position = 0x21;
+ int left = file.ReadUInt16();
+ int top = file.ReadUInt16();
+ int right = file.ReadUInt16();
+ int bottom = file.ReadUInt16();
+ int width = (right - left + 1) << 3;
+ int height = bottom - top + 1;
+ if (width <= 0 || height <= 0 || width > 640 || height > 1024)
+ return null;
+ return new ImageMetaData {
+ Width = (uint)width,
+ Height = (uint)height,
+ OffsetX = left << 3,
+ OffsetY = top,
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new Pdt5Reader (file, info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("Pdt5Format.Write not implemented");
+ }
+ }
+
+ internal class Pdt5Reader
+ {
+ IBinaryStream m_input;
+ ImageMetaData m_info;
+
+ public Pdt5Reader (IBinaryStream input, ImageMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ byte[] m_buffer;
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 1;
+ var palette = ReadPalette();
+ m_input.Position = 0x29;
+ int width = m_info.iWidth;
+ int height = m_info.iHeight;
+ int output_stride = m_info.iWidth;
+ var pixels = new byte[output_stride * height];
+ InitFrame();
+ InitBitReader();
+ byte px = 0;
+ m_buffer = new byte[1932];
+ int output_pos = 0;
+ for (int y = 0; y < height; ++y)
+ {
+ for (int x = 0; x < width; ++x)
+ {
+ if (GetNextBit() != 0)
+ {
+ if (GetNextBit() != 0)
+ {
+ if (GetNextBit() != 0)
+ {
+ int count = GetCount() + 2;
+ int pos = 1290 + x;
+ x += count - 1;
+ while (count --> 0)
+ {
+ m_buffer[pos++] = px;
+ }
+ }
+ else
+ {
+ int count = GetCount() + 1;
+ px = m_buffer[1289 + x];
+ int src = x + 1288;
+ int dst = x + 1290;
+ Binary.CopyOverlapped (m_buffer, src, dst, count * 2);
+ x += count * 2 - 1;
+ }
+ }
+ else
+ {
+ px = GetPixel (x);
+ m_buffer[x + 1290] = px;
+ }
+ }
+ else
+ {
+ int count = 0;
+ byte b = GetPixel (x);
+ while (GetNextBit() != 1)
+ ++count;
+ int src = 0x10 * b + count;
+ px = m_frame[src];
+ m_buffer[x + 1290] = px;
+ while (count --> 0)
+ {
+ m_frame[src] = m_frame[src-1];
+ --src;
+ }
+ m_frame[src] = px;
+ }
+ }
+ Buffer.BlockCopy (m_buffer, 1290, pixels, output_pos, width);
+ output_pos += output_stride;
+ Buffer.BlockCopy (m_buffer, 644, m_buffer, 0, 1288);
+ }
+ return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, output_stride);
+ }
+
+ byte GetPixel (int src)
+ {
+ byte px = m_buffer[src + 647];
+ if (m_buffer[src + 4] != px)
+ {
+ byte v = m_buffer[src + 2];
+ if (v != px)
+ {
+ px = m_buffer[src + 645];
+ if (px != v && m_buffer[src] != px)
+ return m_buffer[src + 2];
+ }
+ }
+ return px;
+ }
+
+ byte[] m_frame;
+
+ void InitFrame ()
+ {
+ m_frame = new byte[0x110];
+ for (int j = 0; j < 0x110; j += 0x10)
+ {
+ for (byte i = 0; i < 0x10; ++i)
+ m_frame[j + i] = i;
+ }
+ }
+
+ int GetCount ()
+ {
+ int count = 0;
+ int bits = 1;
+ while (GetNextBit() != 1)
+ {
+ count += bits;
+ bits <<= 1;
+ }
+ if (bits > 1)
+ {
+ do
+ {
+ if (GetNextBit() != 0)
+ count += bits;
+ bits >>= 1;
+ }
+ while (bits != 0);
+ }
+ return count;
+ }
+
+ uint m_bits;
+ int m_bit_count;
+
+ void InitBitReader ()
+ {
+ m_bit_count = 1;
+ }
+
+ byte GetNextBit ()
+ {
+ if (--m_bit_count <= 0)
+ {
+ m_bits = m_input.ReadUInt8();
+ m_bit_count = 8;
+ }
+ uint bit = m_bits & 1;
+ m_bits >>= 1;
+ return (byte)bit;
+ }
+
+ BitmapPalette ReadPalette ()
+ {
+ var colors = new Color[16];
+ for (int i = 0; i < 16; ++i)
+ {
+ ushort rgb = m_input.ReadUInt16();
+ int b = (rgb & 0xF) * 0x11;
+ int r = ((rgb >> 4) & 0xF) * 0x11;
+ int g = ((rgb >> 8) & 0xF) * 0x11;
+ colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+}
diff --git a/Legacy/AyPio/PdtBitmap.cs b/Legacy/AyPio/PdtBitmap.cs
new file mode 100644
index 00000000..a56f94a6
--- /dev/null
+++ b/Legacy/AyPio/PdtBitmap.cs
@@ -0,0 +1,221 @@
+//! \file PdtBitmap.cs
+//! \date 2023 Oct 19
+//! \brief UK2 engine compressed bitmap.
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [971031][AyPio] Satyr 95
+
+namespace GameRes.Formats.AyPio
+{
+ [Export(typeof(ImageFormat))]
+ public class PdtBmpFormat : ImageFormat
+ {
+ public override string Tag => "PDT/BMP";
+ public override string Description => "UK2 engine compressed bitmap";
+ public override uint Signature => 0x544450; // 'PDT'
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (8);
+ if (header.ToInt32 (4) != 0x118)
+ return null;
+ return new ImageMetaData { Width = 640, Height = 480, BPP = 32 };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var decoder = new PdtBmpDecoder (file, info);
+ return decoder.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("PdtFormat.Write not implemented");
+ }
+ }
+
+ internal sealed class PdtBmpDecoder
+ {
+ IBinaryStream m_input;
+ ImageMetaData m_info;
+
+ public PdtBmpDecoder (IBinaryStream input, ImageMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ int m_unpacked_size;
+ int m_packed_size;
+
+ public ImageData Unpack ()
+ {
+ long offset = 0;
+ var bitmap = UnpackBitmap (offset);
+ m_info.Width = (uint)bitmap.PixelWidth;
+ m_info.Height = (uint)bitmap.PixelHeight;
+ m_info.BPP = bitmap.Format.BitsPerPixel;
+ offset += m_packed_size;
+ var signature = m_input.ReadBytes (4);
+ if (signature.Length != 4 || !signature.AsciiEqual ("PDT\0"))
+ return new ImageData (bitmap, m_info);
+ var alpha = UnpackBitmap (offset);
+ if (alpha.Format != PixelFormats.Gray8)
+ alpha = new FormatConvertedBitmap (alpha, PixelFormats.Gray8, null, 0);
+ if (m_info.BPP != 32)
+ bitmap = new FormatConvertedBitmap (bitmap, PixelFormats.Bgr32, null, 0);
+
+ int stride = m_info.iWidth * 4;
+ var pixels = new byte[stride * m_info.iHeight];
+ bitmap.CopyPixels (pixels, stride, 0);
+ var rect = new Int32Rect (0, 0, Math.Min (m_info.iWidth, alpha.PixelWidth),
+ Math.Min (m_info.iHeight, alpha.PixelHeight));
+ var a = new byte[m_info.iWidth * m_info.iHeight];
+ alpha.CopyPixels (rect, a, m_info.iWidth, 0);
+ int src = 0;
+ for (int dst = 3; dst < pixels.Length; dst += 4)
+ {
+ pixels[dst] = a[src++];
+ }
+ return ImageData.Create (m_info, PixelFormats.Bgra32, null, pixels, stride);
+ }
+
+ byte[] m_bits;
+ byte[] m_output;
+
+ BitmapSource UnpackBitmap (long offset)
+ {
+ m_input.Position = offset+8;
+ m_unpacked_size = m_input.ReadInt32();
+ m_packed_size = m_input.ReadInt32();
+ long data_offset = m_input.ReadUInt32() + offset;
+ long bits_offset = m_input.ReadUInt32() + offset;
+ string name = m_input.ReadCString (0x100);
+
+ if (null == m_output || m_unpacked_size > m_output.Length)
+ m_output = new byte[m_unpacked_size];
+ int bits_length = (int)(data_offset - bits_offset);
+ if (null == m_bits || bits_length > m_bits.Length)
+ m_bits = new byte[bits_length+4];
+
+ m_input.Position = bits_offset;
+ m_input.Read (m_bits, 0, bits_length);
+
+ m_input.Position = data_offset;
+ UnpackBits();
+
+ using (var bmp_input = new BinMemoryStream (m_output, name))
+ {
+ var decoder = new BmpBitmapDecoder (bmp_input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
+ return decoder.Frames[0];
+ }
+ }
+
+ public void UnpackBits ()
+ {
+ InitBitReader();
+ int dst = 0;
+ byte last_byte = 0;
+ while (dst < m_unpacked_size)
+ {
+ int ctl = 0;
+ while (GetNextBit() != 0)
+ ++ctl;
+ switch (ctl)
+ {
+ case 0:
+ last_byte = m_output[dst++] = m_input.ReadUInt8();
+ break;
+ case 1:
+ {
+ int off = GetInteger();
+ int count = GetInteger();
+ Binary.CopyOverlapped (m_output, dst - off, dst, count);
+ dst += count;
+ break;
+ }
+ case 2:
+ {
+ int count = GetInteger();
+ int step = GetInteger();
+ int pos = 0;
+ for (int i = 0; i < step; i += count)
+ {
+ Binary.CopyOverlapped (m_output, dst - count, dst + pos, count);
+ pos += count * count;
+ }
+ dst += count * step;
+ break;
+ }
+ case 3:
+ m_output[dst++] = last_byte;
+ break;
+ }
+ }
+ }
+
+ int GetInteger ()
+ {
+ int i = 0;
+ while (GetNextBit() != 0)
+ ++i;
+ int n = 0;
+ for (int j = i; j > 0; --j)
+ {
+ n = n << 1 | GetNextBit();
+ }
+ return n + (1 << i);
+ }
+
+ uint m_current_bits;
+ int m_bit_count;
+ int m_bit_pos;
+
+ void InitBitReader ()
+ {
+ m_bit_pos = 0;
+ m_bit_count = 0;
+ }
+
+ byte GetNextBit ()
+ {
+ if (0 == m_bit_count--)
+ {
+ m_current_bits = m_bits.ToUInt32 (m_bit_pos);
+ m_bit_pos += 4;
+ m_bit_count = 31;
+ }
+ uint bit = m_current_bits >> 31;
+ m_current_bits <<= 1;
+ return (byte)bit;
+ }
+ }
+}
diff --git a/Legacy/Blucky/Aliases.cs b/Legacy/Blucky/Aliases.cs
new file mode 100644
index 00000000..39d738cb
--- /dev/null
+++ b/Legacy/Blucky/Aliases.cs
@@ -0,0 +1,41 @@
+//! \file Aliases.cs
+//! \date 2023 Sep 17
+//! \brief Blucky formats aliases.
+//
+// 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;
+
+// [970627][Blucky] Rekiai
+
+namespace GameRes.Formats.Blucky
+{
+ [Export(typeof(ResourceAlias))]
+ [ExportMetadata("Extension", "OSA")]
+ [ExportMetadata("Target", "BMP")]
+ public class OsaFormat : ResourceAlias { }
+
+ [Export(typeof(ResourceAlias))]
+ [ExportMetadata("Extension", "WF")]
+ [ExportMetadata("Target", "WAV")]
+ public class WfFormat : ResourceAlias { }
+}
diff --git a/Legacy/DMotion/ArcDM.cs b/Legacy/DMotion/ArcDM.cs
new file mode 100644
index 00000000..8788e64b
--- /dev/null
+++ b/Legacy/DMotion/ArcDM.cs
@@ -0,0 +1,89 @@
+//! \file ArcDM.cs
+//! \date 2023 Oct 24
+//! \brief D-Motion engine 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.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace GameRes.Formats.DMotion
+{
+ internal class ExtEntry : Entry
+ {
+ public int Count;
+ }
+
+ [Export(typeof(ArchiveFormat))]
+ public class PakOpener : ArchiveFormat
+ {
+ public override string Tag => "256/DMOTION";
+ public override string Description => "D-Motion engine resource archive";
+ public override uint Signature => 0x4B434150; // 'PACK'
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!file.View.AsciiEqual (4, "FILE100DATA"))
+ return null;
+ if (!file.View.AsciiEqual (0x10, @".\\\"))
+ return null;
+ int ext_count = file.View.ReadUInt16 (0x16);
+ long index_pos = file.View.ReadUInt32 (0x18);
+ int total_count = 0;
+ var ext_dir = new List (ext_count);
+ for (int i = 0; i < ext_count; ++i)
+ {
+ var ext = new ExtEntry {
+ Name = file.View.ReadString (index_pos, 4),
+ Count = file.View.ReadUInt16 (index_pos+6),
+ Offset = file.View.ReadUInt32 (index_pos+8),
+ Size = file.View.ReadUInt32 (index_pos+12),
+ };
+ ext_dir.Add (ext);
+ total_count += ext.Count;
+ index_pos += 0x10;
+ }
+ if (!IsSaneCount (total_count))
+ return null;
+
+ var dir = new List (total_count);
+ foreach (var ext in ext_dir)
+ {
+ index_pos = ext.Offset;
+ for (int i = 0; i < ext.Count; ++i)
+ {
+ var name = file.View.ReadString (index_pos, 8).TrimEnd() + ext.Name;
+ var entry = Create (name);
+ entry.Offset = file.View.ReadUInt32 (index_pos+8);
+ entry.Size = file.View.ReadUInt32 (index_pos+12);
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ dir.Add (entry);
+ index_pos += 0x10;
+ }
+ }
+ return new ArcFile (file, this, dir);
+ }
+ }
+}
diff --git a/Legacy/Desire/ArcDSV.cs b/Legacy/Desire/ArcDSV.cs
new file mode 100644
index 00000000..7075df32
--- /dev/null
+++ b/Legacy/Desire/ArcDSV.cs
@@ -0,0 +1,85 @@
+//! \file ArcDSV.cs
+//! \date 2023 Oct 15
+//! \brief Desire resource archive (PC-98).
+//
+// 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.Desire
+{
+ [Export(typeof(ArchiveFormat))]
+ public class D000Opener : ArchiveFormat
+ {
+ public override string Tag => "000/DESIRE";
+ public override string Description => "Desire resource archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!file.Name.HasAnyOfExtensions (".000", ".001", ".002", ".003"))
+ return null;
+ if (!IsAscii (file.View.ReadByte (0)))
+ return null;
+ uint index_pos = 0;
+ var dir = new List();
+ while (index_pos < file.MaxOffset)
+ {
+ byte b = file.View.ReadByte (index_pos);
+ if (0 == b)
+ break;
+ if (!IsAscii (b))
+ return null;
+ var name = file.View.ReadString (index_pos, 0xC);
+ var entry = Create (name);
+ entry.Size = file.View.ReadUInt32 (index_pos+0xC);
+ if (entry.Size >= file.MaxOffset || 0 == entry.Size)
+ return null;
+ dir.Add (entry);
+ index_pos += 0x10;
+ }
+ if (index_pos >= file.MaxOffset || file.View.ReadUInt32 (index_pos+0xC) != 0)
+ return null;
+ uint offset = index_pos + 0x10;
+ foreach (var entry in dir)
+ {
+ entry.Offset = offset;
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ offset += entry.Size;
+ }
+ if (offset != file.MaxOffset)
+ return null;
+ return new ArcFile (file, this, dir);
+ }
+
+ static internal bool IsAscii (byte b)
+ {
+ return b >= 0x20 && b < 0x7F;
+ }
+ }
+}
diff --git a/Legacy/Desire/ImageDES.cs b/Legacy/Desire/ImageDES.cs
new file mode 100644
index 00000000..a2d4baeb
--- /dev/null
+++ b/Legacy/Desire/ImageDES.cs
@@ -0,0 +1,74 @@
+//! \file ImageDES.cs
+//! \date 2023 Oct 15
+//! \brief DES98 engine image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+
+// [940720][Desire] H+
+
+namespace GameRes.Formats.Desire
+{
+ [Export(typeof(ImageFormat))]
+ public class DesFormat : ImageFormat
+ {
+ public override string Tag => "DES98";
+ public override string Description => "Des98 engine image format";
+ public override uint Signature => 0;
+
+ public DesFormat ()
+ {
+ Extensions = new[] { "" };
+ }
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ ushort width = Binary.BigEndian (file.ReadUInt16());
+ ushort height = Binary.BigEndian (file.ReadUInt16());
+ if (0 == width || 0 == height || (width & 7) != 0 || width > 640 || height > 400)
+ return null;
+ return new ImageMetaData {
+ Width = width,
+ Height = height,
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ file.Position = 4;
+ var palette = ReadPalette (file.AsStream, 16, PaletteFormat.Rgb);
+ var reader = new System98.GraBaseReader (file, info);
+ reader.UnpackBits();
+ return ImageData.Create (info, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride);
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("DesFormat.Write not implemented");
+ }
+ }
+}
diff --git a/Legacy/Desire/ImageDPC.cs b/Legacy/Desire/ImageDPC.cs
new file mode 100644
index 00000000..02cba3c7
--- /dev/null
+++ b/Legacy/Desire/ImageDPC.cs
@@ -0,0 +1,93 @@
+//! \file ImageDPC.cs
+//! \date 2023 Oct 15
+//! \brief Desire image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [970801][Desire] Yuugiri ~Ningyoushi no Isan~
+
+namespace GameRes.Formats.Desire
+{
+ [Export(typeof(ImageFormat))]
+ public class DpcFormat : ImageFormat
+ {
+ public override string Tag => "DPC";
+ public override string Description => "Desire image format";
+ public override uint Signature => 0;
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ if (!file.Name.HasExtension (".DPC"))
+ return null;
+ file.Position = 0x20;
+ short left = file.ReadInt16();
+ short top = file.ReadInt16();
+ ushort width = file.ReadUInt16();
+ ushort height = file.ReadUInt16();
+ if (0 == width || 0 == height || left < 0 || left + width > 2048 || top < 0 || top + height > 2048)
+ return null;
+ return new ImageMetaData {
+ Width = width,
+ Height = height,
+ OffsetX = left,
+ OffsetY = top,
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var palette = ReadPalette (file);
+ file.Position = 0x28;
+ var reader = new System98.GraBaseReader (file, info);
+ reader.UnpackBits();
+ return ImageData.Create (info, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride);
+ }
+
+ BitmapPalette ReadPalette (IBinaryStream input)
+ {
+ var colors = new Color[16];
+ for (int i = 0; i < 16; ++i)
+ {
+ ushort w = input.ReadUInt16();
+ int alpha = (w & 1) == 0 ? 0xFF : 0;
+ int g = (w >> 12) * 0x11;
+ int r = ((w >> 7) & 0xF) * 0x11;
+ int b = ((w >> 2) & 0xF) * 0x11;
+
+ colors[i] = Color.FromArgb ((byte)alpha, (byte)r, (byte)g, (byte)b);
+ }
+ return new BitmapPalette (colors);
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("DpcFormat.Write not implemented");
+ }
+ }
+}
diff --git a/Legacy/Discovery/ImageAN1.cs b/Legacy/Discovery/ImageAN1.cs
new file mode 100644
index 00000000..ff6673c7
--- /dev/null
+++ b/Legacy/Discovery/ImageAN1.cs
@@ -0,0 +1,80 @@
+//! \file ImageAN1.cs
+//! \date 2023 Oct 05
+//! \brief Discovery animation resource (PC-98).
+//
+// 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;
+
+namespace GameRes.Formats.Discovery
+{
+ //[Export(typeof(ImageFormat))]
+ public class An1Format : Pr1Format
+ {
+ public override string Tag => "AN1";
+ public override string Description => "Discovery animation resource";
+ public override uint Signature => 0;
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ if (!file.Name.HasExtension (".AN1"))
+ return null;
+ return base.ReadMetaData (file);
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new AnReader (file, (PrMetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("An1Format.Write not implemented");
+ }
+ }
+
+ internal class AnReader : PrReader
+ {
+ public AnReader (IBinaryStream file, PrMetaData info) : base (file, info)
+ {
+ }
+
+ public new ImageData Unpack ()
+ {
+ UnpackPlanes();
+ int frame_count = m_planes[0].ToUInt16 (2);
+ int frame_width = 0x20;
+ int frame_height = frame_count * 0x20;
+ int output_stride = frame_width >> 1;
+ var output = new byte[output_stride * frame_height];
+ int src = frame_count * 0x16 + 6;
+ m_plane_size = (output_stride >> 2) * frame_height;
+ FlattenPlanes (src, output);
+ Info.Width = (uint)frame_width;
+ Info.Height = (uint)frame_height;
+ return ImageData.Create (Info, PixelFormats.Indexed4, m_palette, output, output_stride);
+ }
+ }
+}
diff --git a/Legacy/Discovery/ImagePR1.cs b/Legacy/Discovery/ImagePR1.cs
new file mode 100644
index 00000000..027689a2
--- /dev/null
+++ b/Legacy/Discovery/ImagePR1.cs
@@ -0,0 +1,343 @@
+//! \file ImagePR1.cs
+//! \date 2023 Oct 04
+//! \brief Discovery image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace GameRes.Formats.Discovery
+{
+ internal class PrMetaData : ImageMetaData
+ {
+ public byte Flags;
+ public byte Mask;
+
+ public bool IsLeftToRight => (Flags & 1) != 0;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class Pr1Format : ImageFormat
+ {
+ public override string Tag => "PR1";
+ public override string Description => "Discovery image format";
+ public override uint Signature => 0;
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ if (!file.Name.HasAnyOfExtensions (".PR1", ".AN1"))
+ return null;
+ var header = file.ReadHeader (12);
+ return new PrMetaData {
+ Width = (uint)header.ToUInt16 (8) << 3,
+ Height = header.ToUInt16 (0xA),
+ OffsetX = header.ToUInt16 (2),
+ OffsetY = header.ToUInt16 (4),
+ Flags = header[0],
+ Mask = header[1],
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new PrReader (file, (PrMetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("Pr1Format.Write not implemented");
+ }
+ }
+
+ internal class PrReader
+ {
+ IBinaryStream m_input;
+ PrMetaData m_info;
+
+ Action IncrementDest;
+ Func IsDone;
+
+ public PrMetaData Info => m_info;
+
+ public PrReader (IBinaryStream file, PrMetaData info)
+ {
+ m_input = file;
+ m_info = info;
+ if (m_info.IsLeftToRight)
+ {
+ IncrementDest = IncLeftToRight;
+ IsDone = () => m_dst >= m_plane_size;
+ }
+ else
+ {
+ IncrementDest = IncTopToBottom;
+ IsDone = () => m_x >= m_stride;
+ }
+ }
+
+ protected BitmapPalette m_palette;
+ protected int m_stride;
+ protected int m_plane_size;
+ protected byte[][] m_planes;
+ int m_dst;
+ int m_x;
+
+ protected void UnpackPlanes ()
+ {
+ const int buffer_slice = 0x410;
+ m_input.Position = 0xC;
+ m_palette = ReadPalette();
+ m_stride = m_info.iWidth >> 3;
+ m_plane_size = m_stride * m_info.iHeight;
+ m_planes = new byte[][] {
+ new byte[m_plane_size], new byte[m_plane_size], new byte[m_plane_size], new byte[m_plane_size],
+ };
+ var buffer = new byte[buffer_slice * 4];
+ var buf_count = new byte[4];
+ var offsets = new int[] { 0, buffer_slice, buffer_slice*2, buffer_slice*3 };
+ m_dst = 0;
+ m_x = 0;
+ while (!IsDone())
+ {
+ int ctl = m_input.ReadByte();
+ if (-1 == ctl)
+ break;
+ int count = (ctl & 0x1F) + 1;
+ bool bit = (ctl & 0x20) != 0;
+ ctl >>= 6;
+ if (!bit)
+ {
+ if (ctl != 0)
+ {
+ int src_pos = ctl;
+ int src_count2 = 1 << (ctl - 1);
+ int pos = offsets[ctl];
+ int count2 = src_count2;
+ do
+ {
+ byte p0 = m_input.ReadUInt8();
+ byte p1 = m_input.ReadUInt8();
+ byte p2 = m_input.ReadUInt8();
+ byte p3 = m_input.ReadUInt8();
+ PutPixels (p0, p1, p2, p3);
+ buffer[pos++] = p0;
+ buffer[pos++] = p1;
+ buffer[pos++] = p2;
+ buffer[pos++] = p3;
+ }
+ while (--count > 0 && --count2 > 0);
+ while (count > 0)
+ {
+ int si = offsets[src_pos];
+ for (int i = 0; i < src_count2; ++i)
+ {
+ byte p0 = buffer[si++];
+ byte p1 = buffer[si++];
+ byte p2 = buffer[si++];
+ byte p3 = buffer[si++];
+ PutPixels (p0, p1, p2, p3);
+ if (--count <= 0)
+ break;
+ }
+ }
+ offsets[src_pos] += src_count2 * 4;
+ buf_count[src_pos] += (byte)src_count2;
+ if (buf_count[src_pos] == 0)
+ offsets[src_pos] = src_pos * buffer_slice;
+ }
+ else
+ {
+ while (count --> 0)
+ {
+ byte p0 = m_input.ReadUInt8();
+ byte p1 = m_input.ReadUInt8();
+ byte p2 = m_input.ReadUInt8();
+ byte p3 = m_input.ReadUInt8();
+ PutPixels (p0, p1, p2, p3);
+ int pos = offsets[0];
+ buffer[pos++] = p0;
+ buffer[pos++] = p1;
+ buffer[pos++] = p2;
+ buffer[pos++] = p3;
+ offsets[0] += 4;
+ buf_count[0]++;
+ if (0 == buf_count[0])
+ offsets[0] = 0;
+ }
+ }
+ }
+ else if (ctl != 0)
+ {
+ int count2 = 1 << (ctl - 1);
+ int off_diff = count2 << 2;
+ int off_mask = off_diff - 1;
+ int off = m_input.ReadUInt8() << 2;;
+ int base_pos = ctl * buffer_slice;
+ off += base_pos;
+ int src = off;
+ while (count > 0)
+ {
+ off = src;
+ for (int i = 0; i < count2; ++i)
+ {
+ byte p0 = buffer[off];
+ byte p1 = buffer[off+1];
+ byte p2 = buffer[off+2];
+ byte p3 = buffer[off+3];
+ PutPixels (p0, p1, p2, p3);
+ off += 4;
+ int pos = off - base_pos;
+ if ((pos & off_mask) == 0)
+ off -= off_diff;
+ if (--count <= 0)
+ break;
+ }
+ }
+ }
+ else
+ {
+ while (count --> 0)
+ {
+ int off = m_input.ReadUInt8() << 2;
+ byte p0 = buffer[off];
+ byte p1 = buffer[off+1];
+ byte p2 = buffer[off+2];
+ byte p3 = buffer[off+3];
+ PutPixels (p0, p1, p2, p3);
+ }
+ }
+ }
+ }
+
+ public ImageData Unpack ()
+ {
+ UnpackPlanes();
+ int output_stride = m_info.iWidth >> 1;
+ var output = new byte[output_stride * m_info.iHeight];
+ FlattenPlanes (0, output);
+ return ImageData.Create (m_info, PixelFormats.Indexed4, m_palette, output, output_stride);
+ }
+
+ void PutPixels (byte p0, byte p1, byte p2, byte p3)
+ {
+ if (0xFF == m_info.Mask || true) // we don't do overlaying here, just single image decoding
+ {
+ m_planes[0][m_dst] = p0;
+ m_planes[1][m_dst] = p1;
+ m_planes[2][m_dst] = p2;
+ m_planes[3][m_dst] = p3;
+ }
+ else
+ {
+ byte v = m_info.Mask;
+ byte mask = p0;
+ if ((v & 1) != 0)
+ mask = (byte)~mask;
+ if ((v & 2) != 0)
+ mask |= (byte)~p1;
+ else
+ mask |= p1;
+ if ((v & 4) != 0)
+ mask |= (byte)~p2;
+ else
+ mask |= p2;
+ if ((v & 8) != 0)
+ mask |= (byte)~p3;
+ else
+ mask |= p3;
+ p0 &= mask;
+ p1 &= mask;
+ p2 &= mask;
+ p3 &= mask;
+ mask = (byte)~mask;
+ m_planes[0][m_dst] &= mask;
+ m_planes[0][m_dst] |= p0;
+ m_planes[1][m_dst] &= mask;
+ m_planes[1][m_dst] |= p1;
+ m_planes[2][m_dst] &= mask;
+ m_planes[2][m_dst] |= p2;
+ m_planes[3][m_dst] &= mask;
+ m_planes[3][m_dst] |= p3;
+ }
+ IncrementDest();
+ }
+
+ void IncLeftToRight ()
+ {
+ ++m_dst;
+ ++m_x;
+ if (m_x > m_info.iWidth)
+ m_x = 0;
+ }
+
+ void IncTopToBottom ()
+ {
+ m_dst += m_stride;
+ if (m_dst >= m_plane_size)
+ m_dst = ++m_x;
+ }
+
+ internal void FlattenPlanes (int src, byte[] output)
+ {
+ int m_dst = 0;
+ for (; src < m_plane_size; ++src)
+ {
+ int b0 = m_planes[0][src];
+ int b1 = m_planes[1][src];
+ int b2 = m_planes[2][src];
+ int b3 = m_planes[3][src];
+ for (int j = 0; j < 8; j += 2)
+ {
+ byte px = (byte)((((b0 << j) & 0x80) >> 3)
+ | (((b1 << j) & 0x80) >> 2)
+ | (((b2 << j) & 0x80) >> 1)
+ | (((b3 << j) & 0x80) ));
+ px |= (byte)((((b0 << j) & 0x40) >> 6)
+ | (((b1 << j) & 0x40) >> 5)
+ | (((b2 << j) & 0x40) >> 4)
+ | (((b3 << j) & 0x40) >> 3));
+ output[m_dst++] = px;
+ }
+ }
+ }
+
+ BitmapPalette ReadPalette ()
+ {
+ const int count = 16;
+ var colors = new Color[count];
+ for (int i = 0; i < count; ++i)
+ {
+ byte g = m_input.ReadUInt8();
+ byte r = m_input.ReadUInt8();
+ byte b = m_input.ReadUInt8();
+ colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+}
diff --git a/Legacy/Grocer/ImagePIC.cs b/Legacy/Grocer/ImagePIC.cs
new file mode 100644
index 00000000..4da613d0
--- /dev/null
+++ b/Legacy/Grocer/ImagePIC.cs
@@ -0,0 +1,193 @@
+//! \file ImagePIC.cs
+//! \date 2023 Sep 25
+//! \brief Grocer image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [941209][Grocer] Wedding Errantry -Gyakutama Ou-
+
+namespace GameRes.Formats.Grocer
+{
+ [Export(typeof(ImageFormat))]
+ public class PicFormat : ImageFormat
+ {
+ public override string Tag => "PIC/GROCER";
+ public override string Description => "Grocer image format";
+ public override uint Signature => 1;
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x57);
+ if (!header.AsciiEqual (0x10, "Actor98"))
+ return null;
+ uint width = (uint)header.ToUInt16 (0x53) << 3;
+ if (width > 640)
+ return null;
+ return new ImageMetaData
+ {
+ Width = width,
+ Height = header.ToUInt16 (0x55),
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new PicReader (file, info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("PicFormat.Write not implemented");
+ }
+ }
+
+ internal class PicReader
+ {
+ IBinaryStream m_input;
+ ImageMetaData m_info;
+
+ public PicReader (IBinaryStream input, ImageMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 0x21;
+ var palette = ReadPalette();
+ m_input.Position = 0x57;
+ int stride = m_info.iWidth / 8;
+ var pixels = new byte[m_info.iWidth * m_info.iHeight];
+ var buffer = new byte[0x3C0];
+ int output_pos = 0;
+ for (int y = 0; y < m_info.iHeight; ++y)
+ {
+ int x;
+ for (int plane = 0; plane < 4; ++plane)
+ {
+ x = 0;
+ while (x < stride)
+ {
+ byte cur_byte = m_input.ReadUInt8();
+ if (cur_byte > 0 && cur_byte < 6)
+ {
+ int count = m_input.ReadUInt8();
+ switch (cur_byte)
+ {
+ case 1:
+ {
+ cur_byte = m_input.ReadUInt8();
+ int dst = plane * 0x50 + x + 0x280;
+ for (int i = 0; i < count; ++i)
+ {
+ buffer[dst+i] = cur_byte;
+ }
+ break;
+ }
+ case 2:
+ {
+ int src = plane * 0x50 + x;
+ int dst = src + 0x280;
+ Buffer.BlockCopy (buffer, src, buffer, dst, count);
+ break;
+ }
+ case 3:
+ {
+ int src = x + 0x280;
+ int dst = plane * 0x50 + src;
+ Buffer.BlockCopy (buffer, src, buffer, dst, count);
+ break;
+ }
+ case 4:
+ {
+ int src = x + 0x2D0;
+ int dst = plane * 0x50 + x + 0x280;
+ Buffer.BlockCopy (buffer, src, buffer, dst, count);
+ break;
+ }
+ case 5:
+ {
+ int src = x + 0x320;
+ int dst = plane * 0x50 + x + 0x280;
+ Buffer.BlockCopy (buffer, src, buffer, dst, count);
+ break;
+ }
+ }
+ x += count;
+ }
+ else
+ {
+ if (6 == cur_byte)
+ {
+ cur_byte = m_input.ReadUInt8();
+ }
+ int dst = plane * 0x50 + x + 0x280;
+ buffer[dst] = cur_byte;
+ ++x;
+ }
+ }
+ }
+ for (x = 0; x < stride; ++x)
+ {
+ byte mask = 0x80;
+ for (int i = 0; i < 8; ++i)
+ {
+ byte px = 0;
+ if ((buffer[x + 0x280] & mask) != 0) px |= 0x01;
+ if ((buffer[x + 0x2D0] & mask) != 0) px |= 0x02;
+ if ((buffer[x + 0x320] & mask) != 0) px |= 0x04;
+ if ((buffer[x + 0x370] & mask) != 0) px |= 0x08;
+ pixels[output_pos + (x << 3) + i] = px;
+ mask >>= 1;
+ }
+ }
+ Buffer.BlockCopy (buffer, 0x140, buffer, 0, 0x280);
+ output_pos += m_info.iWidth;
+ }
+ return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels);
+ }
+
+ BitmapPalette ReadPalette ()
+ {
+ const int count = 16;
+ var colors = new Color[count];
+ for (int i = 0; i < count; ++i)
+ {
+ byte g = m_input.ReadUInt8();
+ byte r = m_input.ReadUInt8();
+ byte b = m_input.ReadUInt8();
+ colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+}
diff --git a/Legacy/Harvest/ArcDAT.cs b/Legacy/Harvest/ArcDAT.cs
new file mode 100644
index 00000000..a9908fd5
--- /dev/null
+++ b/Legacy/Harvest/ArcDAT.cs
@@ -0,0 +1,65 @@
+//! \file ArcDAT.cs
+//! \date 2023 Sep 26
+//! \brief MyHarvest 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.MyHarvest
+{
+ [Export(typeof(ArchiveFormat))]
+ public class DatOpener : ArchiveFormat
+ {
+ public override string Tag => "DAT/UNA";
+ public override string Description => "MyHarvest resource archive";
+ public override uint Signature => 0x414E55; // 'UNA'
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!file.View.AsciiEqual (4, "001\0"))
+ return null;
+ int count = file.View.ReadInt32 (8);
+ if (!IsSaneCount (count))
+ return null;
+ uint index = 0x20;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ var name = file.View.ReadString (index, 0x20);
+ var entry = Create (name);
+ entry.Offset = file.View.ReadUInt32 (index+0x20);
+ entry.Size = file.View.ReadUInt32 (index+0x24);
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ dir.Add (entry);
+ index += 0x30;
+ }
+ return new ArcFile (file, this, dir);
+ }
+ }
+}
diff --git a/Legacy/Harvest/AudioBGM.cs b/Legacy/Harvest/AudioBGM.cs
new file mode 100644
index 00000000..f3221782
--- /dev/null
+++ b/Legacy/Harvest/AudioBGM.cs
@@ -0,0 +1,56 @@
+//! \file AudioBGM.cs
+//! \date 2023 Sep 26
+//! \brief MyHarvest audio 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 System.ComponentModel.Composition;
+
+namespace GameRes.Formats.MyHarvest
+{
+ [Export(typeof(AudioFormat))]
+ public class BgmAudio : AudioFormat
+ {
+ public override string Tag => "BGM/HARVEST";
+ public override string Description => "MyHarvest audio resource";
+ public override uint Signature => 0x304D4742; // 'BMG0'
+ public override bool CanWrite => false;
+
+ public override SoundInput TryOpen (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x1C);
+ if (!header.AsciiEqual (0x14, "dar\0"))
+ return null;
+ var format = new WaveFormat {
+ FormatTag = header.ToUInt16 (4),
+ Channels = header.ToUInt16 (6),
+ SamplesPerSecond = header.ToUInt32 (8),
+ AverageBytesPerSecond = header.ToUInt32 (0xC),
+ BlockAlign = header.ToUInt16 (0x10),
+ BitsPerSample = header.ToUInt16 (0x12),
+ };
+ uint pcm_size = header.ToUInt32 (0x18);
+ var region = new StreamRegion (file.AsStream, 0x1C, pcm_size);
+ return new RawPcmInput (region, format);
+ }
+ }
+}
diff --git a/Legacy/Harvest/AudioSED.cs b/Legacy/Harvest/AudioSED.cs
new file mode 100644
index 00000000..5543d225
--- /dev/null
+++ b/Legacy/Harvest/AudioSED.cs
@@ -0,0 +1,61 @@
+//! \file AudioSED.cs
+//! \date 2023 Sep 26
+//! \brief MyHarvest audio 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 System.ComponentModel.Composition;
+
+namespace GameRes.Formats.MyHarvest
+{
+ [Export(typeof(AudioFormat))]
+ public class SedAudio : AudioFormat
+ {
+ public override string Tag => "SED/HARVEST";
+ public override string Description => "MyHarvest audio resource";
+ public override uint Signature => 0x14553; // 'SE'
+ public override bool CanWrite => false;
+
+ public SedAudio ()
+ {
+ Signatures = new[] { 0x14553u, 0u };
+ }
+
+ public override SoundInput TryOpen (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x18);
+ if (!header.AsciiEqual (0, "SE") || !header.AsciiEqual (0x12, "da"))
+ return null;
+ var format = new WaveFormat {
+ FormatTag = header.ToUInt16 (2),
+ Channels = header.ToUInt16 (4),
+ SamplesPerSecond = header.ToUInt32 (6),
+ AverageBytesPerSecond = header.ToUInt32 (0xA),
+ BlockAlign = header.ToUInt16 (0xE),
+ BitsPerSample = header.ToUInt16 (0x10),
+ };
+ uint pcm_size = header.ToUInt32 (0x14);
+ var region = new StreamRegion (file.AsStream, 0x18, pcm_size);
+ return new RawPcmInput (region, format);
+ }
+ }
+}
diff --git a/Legacy/Harvest/ImageUNH.cs b/Legacy/Harvest/ImageUNH.cs
new file mode 100644
index 00000000..993adcbf
--- /dev/null
+++ b/Legacy/Harvest/ImageUNH.cs
@@ -0,0 +1,96 @@
+//! \file ImageUNH.cs
+//! \date 2023 Sep 26
+//! \brief MyHarvest 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 System.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+
+// [021206][MyHarvest] Idol Mahjong Final Romance 4
+
+namespace GameRes.Formats.MyHarvest
+{
+ [Export(typeof(ImageFormat))]
+ public class UnhFormat : ImageFormat
+ {
+ public override string Tag => "UNH";
+ public override string Description => "MyHarvest image format";
+ public override uint Signature => 0x30484E55; // 'UNH0'
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x18);
+ if (header.ToInt32 (4) != 1)
+ return null;
+ return new ImageMetaData {
+ Width = header.ToUInt32 (0x10),
+ Height = header.ToUInt32 (0x14),
+ BPP = 16,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ file.Position = 0x44;
+ var pixels = new ushort[info.iWidth * info.iHeight];
+ var frame = new ushort[0x1000];
+ int frame_pos = 0;
+ int dst = 0;
+ byte mask = 0;
+ int ctl = 0;
+ while (dst < pixels.Length)
+ {
+ mask <<= 1;
+ if (0 == mask)
+ {
+ ctl = file.ReadByte();
+ if (-1 == ctl)
+ break;
+ mask = 1;
+ }
+ ushort word = file.ReadUInt16();
+ if ((ctl & mask) == 0)
+ {
+ pixels[dst++] = frame[frame_pos++ & 0xFFF] = word;
+ }
+ else
+ {
+ int offset = word >> 4;
+ int count = (word & 0xF) + 2;
+ while (count --> 0)
+ {
+ ushort u = frame[offset++ & 0xFFF];
+ pixels[dst++] = frame[frame_pos++ & 0xFFF] = u;
+ }
+ }
+ }
+ return ImageData.Create (info, PixelFormats.Bgr565, null, pixels);
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("UnhFormat.Write not implemented");
+ }
+ }
+}
diff --git a/Legacy/Izumi/ImageMAI2.cs b/Legacy/Izumi/ImageMAI2.cs
new file mode 100644
index 00000000..23234971
--- /dev/null
+++ b/Legacy/Izumi/ImageMAI2.cs
@@ -0,0 +1,436 @@
+//! \file ImageMAI2.cs
+//! \date 2023 Oct 24
+//! \brief Izumi engine image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace GameRes.Formats.Izumi
+{
+ internal class Mai2MetaData : ImageMetaData
+ {
+ public byte Flags;
+ public ushort Plane0Size;
+ public ushort Plane1Size;
+ public ushort Plane2Size;
+ public ushort Plane3Size;
+
+ public bool HasPalette => (Flags & 0x80) != 0;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class Mai2Format : ImageFormat
+ {
+ public override string Tag => "MAI/IZUMI";
+ public override string Description => "Izumi engine image format";
+ public override uint Signature => 0x3249414D; // 'MAI2'
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x14);
+ ushort xy = header.ToUInt16 (4);
+ return new Mai2MetaData {
+ Width = (uint)(header.ToUInt16 (6) << 3),
+ Height = header.ToUInt16 (8),
+ OffsetX = xy % 0x50,
+ OffsetY = xy / 0x50,
+ BPP = 4,
+ Flags = header[0xA],
+ Plane0Size = header.ToUInt16 (0x0C),
+ Plane1Size = header.ToUInt16 (0x0E),
+ Plane2Size = header.ToUInt16 (0x10),
+ Plane3Size = header.ToUInt16 (0x12),
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new Mai2Reader (file, (Mai2MetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("Mai2Format.Write not implemented");
+ }
+ }
+
+ internal class Mai2Reader
+ {
+ IBinaryStream m_input;
+ Mai2MetaData m_info;
+
+ public Mai2Reader (IBinaryStream input, Mai2MetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ byte[][] m_planes;
+ int m_stride;
+ int m_height;
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 0x14;
+ BitmapPalette palette = null;
+ if (m_info.HasPalette)
+ palette = ReadPalette();
+ else
+ palette = BitmapPalettes.Gray16;
+
+ m_height = m_info.iHeight;
+ m_stride = m_info.iWidth >> 3;
+ int plane_size = m_stride * m_info.iHeight;
+ m_planes = new byte[][] {
+ new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size],
+ };
+
+ long next_pos = m_input.Position + m_info.Plane0Size;
+ if ((m_info.Flags & 1) != 0)
+ UnpackPlane (m_planes[0]);
+
+ m_input.Position = next_pos;
+ next_pos += m_info.Plane1Size;
+ if ((m_info.Flags & 2) != 0)
+ UnpackPlane (m_planes[1]);
+
+ m_input.Position = next_pos;
+ next_pos += m_info.Plane2Size;
+ if ((m_info.Flags & 4) != 0)
+ UnpackPlane (m_planes[2]);
+
+ m_input.Position = next_pos;
+ if ((m_info.Flags & 8) != 0)
+ UnpackPlane (m_planes[3]);
+
+ int output_stride = m_info.iWidth >> 1;
+ var output = new byte[output_stride * m_info.iHeight];
+ FlattenPlanes (output, output_stride);
+
+ return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, output_stride);
+ }
+
+ void UnpackPlane (byte[] plane)
+ {
+ int dst_row = 0;
+ for (int x = 0; x < m_stride; ++x)
+ {
+ int dst = dst_row;
+ int remaining = m_height;
+ while (remaining > 0)
+ {
+ int count = 1;
+ int ctl = m_input.ReadUInt8();
+ if (ctl < 0x90)
+ {
+ count = ctl & 0x1F;
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ switch (ctl >> 5)
+ {
+ case 0:
+ for (int i = 0; i < count; ++i)
+ plane[dst++] = 0;
+ break;
+ case 1:
+ for (int i = 0; i < count; ++i)
+ plane[dst++] = 0xFF;
+ break;
+ case 2:
+ Buffer.BlockCopy (m_planes[0], dst, plane, dst, count);
+ dst += count;
+ break;
+ case 3:
+ Buffer.BlockCopy (m_planes[1], dst, plane, dst, count);
+ dst += count;
+ break;
+ case 4:
+ Buffer.BlockCopy (m_planes[2], dst, plane, dst, count);
+ dst += count;
+ break;
+ }
+ }
+ else if (ctl < 0xF0)
+ {
+ count = ctl & 0xF;
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ int off = 0;
+ switch (ctl >> 4)
+ {
+ case 0x9: off = 0x10; break;
+ case 0xA: off = 8; break;
+ case 0xB: off = 4; break;
+ case 0xC: off = 2; break;
+ case 0xD: off = m_height << 1; break;
+ case 0xE: off = m_height; break;
+ }
+ Binary.CopyOverlapped (plane, dst - off, dst, count);
+ dst += count;
+ }
+ else if (ctl < 0xF9)
+ {
+ count = ctl & 0xF;
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ m_input.Read (plane, dst, count);
+ dst += count;
+ }
+ else
+ {
+ count = m_input.ReadUInt8();
+ switch (ctl)
+ {
+ case 0xF9:
+ dst += count;
+ break;
+ case 0xFA:
+ {
+ byte b = m_input.ReadUInt8();
+ for (int i = 0; i < count; ++i)
+ plane[dst++] = b;
+ break;
+ }
+ case 0xFB:
+ {
+ int src = 0;
+ if ((count & 0x80) != 0)
+ {
+ count &= 0x7F;
+ src = 1;
+ }
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ for (int i = 0; i < count; ++i)
+ {
+ plane[dst] = (byte)~m_planes[src][dst];
+ dst++;
+ }
+ break;
+ }
+ case 0xFC:
+ if ((count & 0x80) != 0)
+ {
+ count &= 0x7F;
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ byte al, ah;
+ al = m_input.ReadUInt8();
+ ah = (byte)(al << 4 | al & 0x0F);
+ al = (byte)(al >> 4 | al & 0xF0);
+ for (int i = 0; i < count; ++i)
+ {
+ plane[dst++] = al;
+ plane[dst++] = ah;
+ }
+ count <<= 1;
+ }
+ else
+ {
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ for (int i = 0; i < count; ++i)
+ {
+ plane[dst] = (byte)~m_planes[2][dst];
+ dst++;
+ }
+ }
+ break;
+ case 0xFD:
+ if ((count & 0x80) != 0)
+ {
+ ctl = count;
+ count = ctl & 0x3F;
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ byte al, ah, bl, bh;
+ al = m_input.ReadUInt8();
+ bl = (byte)(al & 0xF0 | al >> 4);
+ bh = (byte)(al & 0x0F | al << 4);
+ if (ctl < 0xC0)
+ {
+ al = Binary.RotByteR (bl, 2);
+ ah = Binary.RotByteR (bh, 2);
+ }
+ else
+ {
+ ah = m_input.ReadUInt8();
+ al = (byte)(ah & 0xF0 | ah >> 4);
+ ah = (byte)(ah & 0x0F | ah << 4);
+ }
+ for (int i = 0; i < count; ++i)
+ {
+ plane[dst++] = bl;
+ plane[dst++] = bh;
+ plane[dst++] = al;
+ plane[dst++] = ah;
+ }
+ count <<= 2;
+ }
+ else
+ {
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ byte al = m_input.ReadUInt8();
+ byte ah = m_input.ReadUInt8();
+ for (int i = 0; i < count; ++i)
+ {
+ plane[dst++] = al;
+ plane[dst++] = ah;
+ }
+ count <<= 1;
+ }
+ break;
+ case 0xFE:
+ ctl = count;
+ count &= 0x3F;
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ if (ctl < 0x40)
+ {
+ m_input.Read (plane, dst, 4);
+ count <<= 2;
+ Binary.CopyOverlapped (plane, dst, dst + 4, count - 4);
+ dst += count;
+ }
+ else
+ {
+ int psrc, pmask;
+ if ((ctl & 0x80) == 0)
+ {
+ psrc = 0;
+ pmask = 1;
+ }
+ else if (ctl < 0xC0)
+ {
+ psrc = 0;
+ pmask = 2;
+ }
+ else
+ {
+ psrc = 1;
+ pmask = 2;
+ }
+ for (int i = 0; i < count; ++i)
+ {
+ byte b = m_planes[psrc][dst];
+ b &= m_planes[pmask][dst];
+ plane[dst++] = b;
+ }
+ }
+ break;
+ case 0xFF:
+ {
+ Func op;
+ if (count < 0x40)
+ {
+ op = src => (byte)(m_planes[0][src] | m_planes[1][src]);
+ }
+ else if (count < 0x80)
+ {
+ op = src => (byte)(m_planes[0][src] ^ m_planes[1][src]);
+ count &= 0x3F;
+ }
+ else
+ {
+ if (count < 0xA0)
+ op = src => (byte)(m_planes[0][src] | m_planes[2][src]);
+ else if (count < 0xC0)
+ op = src => (byte)(m_planes[1][src] | m_planes[2][src]);
+ else if (count < 0xE0)
+ op = src => (byte)(m_planes[0][src] ^ m_planes[2][src]);
+ else
+ op = src => (byte)(m_planes[1][src] ^ m_planes[2][src]);
+ count &= 0x1F;
+ }
+ if (0 == count)
+ count = m_input.ReadUInt8();
+ for (int i = 0; i < count; ++i)
+ {
+ plane[dst] = op (dst);
+ dst++;
+ }
+ break;
+ }
+ }
+ }
+ remaining -= count;
+ }
+ dst_row += m_height;
+ }
+ }
+
+ void FlattenPlanes (byte[] output, int output_stride)
+ {
+ int plane_size = m_planes[0].Length;
+ int src = 0;
+ for (int x = 0; x < output_stride; x += 4)
+ {
+ int dst = x;
+ for (int y = 0; y < m_info.iHeight; ++y)
+ {
+ byte b0 = m_planes[0][src];
+ byte b1 = m_planes[1][src];
+ byte b2 = m_planes[2][src];
+ byte b3 = m_planes[3][src];
+ ++src;
+ for (int j = 0; j < 8; j += 2)
+ {
+ byte px = (byte)((((b0 << j) & 0x80) >> 3)
+ | (((b1 << j) & 0x80) >> 2)
+ | (((b2 << j) & 0x80) >> 1)
+ | (((b3 << j) & 0x80) >> 0));
+ px |= (byte)((((b0 << j) & 0x40) >> 6)
+ | (((b1 << j) & 0x40) >> 5)
+ | (((b2 << j) & 0x40) >> 4)
+ | (((b3 << j) & 0x40) >> 3));
+ output[dst+j/2] = px;
+ }
+ dst += output_stride;
+ }
+ }
+ }
+
+ BitmapPalette ReadPalette ()
+ {
+ using (var bits = new MsbBitStream (m_input.AsStream, true))
+ {
+ var colors = new Color[16];
+ for (int i = 0; i < 16; ++i)
+ {
+ int r = bits.GetBits (4) * 0x11;
+ int g = bits.GetBits (4) * 0x11;
+ int b = bits.GetBits (4) * 0x11;
+ colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+ }
+}
diff --git a/Legacy/Izumi/ImageMAI3.cs b/Legacy/Izumi/ImageMAI3.cs
new file mode 100644
index 00000000..0823372c
--- /dev/null
+++ b/Legacy/Izumi/ImageMAI3.cs
@@ -0,0 +1,332 @@
+//! \file ImageMAI3.cs
+//! \date 2023 Oct 23
+//! \brief Izumi engine image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace GameRes.Formats.Izumi
+{
+ internal class Mai3MetaData : ImageMetaData
+ {
+ public int DataOffset;
+ public bool HasPalette;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class Mai3Format : ImageFormat
+ {
+ public override string Tag => "MI3";
+ public override string Description => "Izumi engine image format";
+ public override uint Signature => 0x3049414D; // 'MAI03'
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (14);
+ if (!header.AsciiEqual ("MAI03\x1A"))
+ return null;
+ return new Mai3MetaData {
+ Width = (uint)(header.ToUInt16 (8) << 3),
+ Height = header.ToUInt16 (0xA),
+ BPP = 4,
+ DataOffset = header.ToUInt16 (0xC) & 0x7FFF,
+ HasPalette = (header[0xD] & 0x80) != 0,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new Mai3Reader (file, (Mai3MetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("Mai3Format.Write not implemented");
+ }
+ }
+
+ internal class Mai3Reader
+ {
+ IBinaryStream m_input;
+ Mai3MetaData m_info;
+
+ public Mai3Reader (IBinaryStream input, Mai3MetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ ushort[] m_buffer;
+ int m_output_stride;
+ byte[] m_output;
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = m_info.DataOffset;
+ BitmapPalette palette = null;
+ if (m_info.HasPalette)
+ palette = ReadPalette();
+ else
+ palette = BitmapPalettes.Gray16;
+ m_buffer = new ushort[0x6D0];
+ m_output_stride = m_info.iWidth >> 1;
+ m_output = new byte[m_output_stride * m_info.iHeight];
+ InitPixels();
+ InitBitReader();
+ int output_dst = 0;
+ int stride = m_info.iWidth >> 3;
+ int x = stride >> 1;
+ while (x --> 0)
+ {
+ MoveBuffer();
+ UnpackLine (0x1C0);
+ UnpackLine (0x10);
+ MoveBuffer();
+ UnpackLine (0x1C0);
+ UnpackLine (0x10);
+ CopyOutput (output_dst);
+ output_dst += 8;
+ }
+ if ((stride & 1) != 0)
+ {
+ MoveBuffer();
+ UnpackLine (0x1C0);
+ UnpackLine (0x10);
+ CopyOutput (output_dst, 1);
+ }
+ return ImageData.Create (m_info, PixelFormats.Indexed4, palette, m_output, m_output_stride);
+ }
+
+ void UnpackLine (int dst)
+ {
+ int height = m_info.iHeight;
+ while (height > 0)
+ {
+ if (GetNextBit() == 0)
+ {
+ int offset;
+ if (GetNextBit() != 0)
+ offset = 0;
+ else if (GetNextBit() != 0)
+ offset = 0x1B0;
+ else
+ offset = 0x360;
+ if (0 == offset || GetNextBit() != 0)
+ {
+ if (GetNextBit() != 0)
+ offset = -1;
+ else if (GetNextBit() != 0)
+ offset -= 2;
+ else if (GetNextBit() != 0)
+ offset -= 4;
+ else if (GetNextBit() != 0)
+ offset -= 8;
+ else
+ offset -= 0x10;
+ }
+ else if (GetNextBit() == 0)
+ {
+ if (GetNextBit() != 0)
+ offset += 2;
+ else if (GetNextBit() != 0)
+ offset += 4;
+ else if (GetNextBit() != 0)
+ offset += 8;
+ else
+ offset += 0x10;
+ }
+ int length = GetCount (8);
+ int count = 1;
+ for (int j = 0; j < length; ++j)
+ count = count << 1 | GetNextBit();
+ count += 1;
+ int src = dst + offset;
+ height -= count;
+ while (count --> 0)
+ m_buffer[dst++] = m_buffer[src++];
+ }
+ else
+ {
+ ushort px = m_buffer[dst + 0x1B0];
+ int prev = (px >> 8) & 1;
+ prev <<= 1;
+ prev |= (px >> 12) & 1;
+ prev <<= 1;
+ prev |= px & 1;
+ prev <<= 1;
+ prev |= (px >> 4) & 1;
+
+ byte n0 = GetPixel ((byte)prev);
+ byte n1 = GetPixel (n0);
+ byte n2 = GetPixel (n1);
+ byte n3 = GetPixel (n2);
+
+ px = m_patterns[0,n3];
+ px |= m_patterns[1,n2];
+ px |= m_patterns[2,n1];
+ px |= m_patterns[3,n0];
+
+ m_buffer[dst++] = px;
+ --height;
+ }
+ }
+ }
+
+ static readonly ushort[,] m_patterns = {
+ { 0, 0x10, 1, 0x11, 0x1000, 0x1010, 0x1001, 0x1011, 0x100, 0x110, 0x101, 0x111, 0x1100, 0x1110, 0x1101, 0x1111 },
+ { 0, 0x20, 2, 0x22, 0x2000, 0x2020, 0x2002, 0x2022, 0x200, 0x220, 0x202, 0x222, 0x2200, 0x2220, 0x2202, 0x2222 },
+ { 0, 0x40, 4, 0x44, 0x4000, 0x4040, 0x4004, 0x4044, 0x400, 0x440, 0x404, 0x444, 0x4400, 0x4440, 0x4404, 0x4444 },
+ { 0, 0x80, 8, 0x88, 0x8000, 0x8080, 0x8008, 0x8088, 0x800, 0x880, 0x808, 0x888, 0x8800, 0x8880, 0x8808, 0x8888 },
+ };
+
+ byte GetPixel (byte prev)
+ {
+ int count = GetCount (15);
+ prev <<= 4;
+ prev += 0xF;
+ int src = prev - count;
+ int dst = src;
+ byte al = m_pixels[src++];
+ if (count > 0)
+ {
+ while (count --> 0)
+ m_pixels[dst++] = m_pixels[src++];
+ m_pixels[dst] = al;
+ }
+ return al;
+ }
+
+ int GetCount (int limit)
+ {
+ int count = 0;
+ while (count < limit && GetNextBit() == 0)
+ ++count;
+ return count;
+ }
+
+ void MoveBuffer ()
+ {
+ Buffer.BlockCopy (m_buffer, 0x20, m_buffer, 0x6E0, 0x360 << 1);
+ }
+
+ void CopyOutput (int dst_line, int rows = 2)
+ {
+ int src = 0x10;
+ int height = m_info.iHeight;
+ for (int y = 0; y < height; ++y)
+ {
+ ushort cx = m_buffer[src + 0x510];
+ ushort dx = m_buffer[src + 0x360];
+ ushort bx = m_buffer[src + 0x1B0];
+ ushort ax = m_buffer[src++];
+
+ int b0 = bx << 8 & 0xF000 | ax << 4 & 0x0F00 | cx & 0x00F0 | dx >> 4 & 0xF;
+ int b1 = bx << 12 & 0xF000 | ax << 8 & 0x0F00 | cx << 4 & 0x00F0 | dx & 0xF;
+ int b2 = bx & 0xF000 | ax >> 4 & 0x0F00 | cx >> 8 & 0x00F0 | dx >> 12;
+ int b3 = bx << 4 & 0xF000 | ax & 0x0F00 | cx >> 4 & 0x00F0 | dx >> 8 & 0xF;
+
+ int dst = dst_line;
+ for (int i = 0; i < rows; ++i)
+ {
+ for (int j = 0; j < 8; j += 2)
+ {
+ byte px = (byte)((((b0 << j) & 0x80) >> 3)
+ | (((b1 << j) & 0x80) >> 2)
+ | (((b2 << j) & 0x80) >> 1)
+ | (((b3 << j) & 0x80) ));
+ px |= (byte)((((b0 << j) & 0x40) >> 6)
+ | (((b1 << j) & 0x40) >> 5)
+ | (((b2 << j) & 0x40) >> 4)
+ | (((b3 << j) & 0x40) >> 3));
+ m_output[dst++] = px;
+ }
+ b0 >>= 8;
+ b1 >>= 8;
+ b2 >>= 8;
+ b3 >>= 8;
+ }
+ dst_line += m_output_stride;
+ }
+ }
+
+ byte[] m_pixels = new byte[0x100];
+
+ void InitPixels ()
+ {
+ int dst = m_pixels.Length - 1;
+ for (int i = 0x0F; i >= 0; --i)
+ {
+ byte n = (byte)i;
+ for (int j = 0; j < 0x10; ++j)
+ {
+ m_pixels[dst--] = (byte)(n-- & 0xF);
+ }
+ }
+ }
+
+ int m_bits;
+ int m_bit_count;
+
+ void InitBitReader ()
+ {
+ m_bits = m_input.ReadUInt16();
+ m_bit_count = 16;
+ }
+
+ byte GetNextBit ()
+ {
+ int bit = m_bits & 1;
+ m_bits >>= 1;
+ if (--m_bit_count <= 0)
+ {
+ if (m_input.PeekByte() != -1)
+ m_bits = m_input.ReadUInt16();
+ else
+ m_bits = 0;
+ m_bit_count = 16;
+ }
+ return (byte)bit;
+ }
+
+ BitmapPalette ReadPalette ()
+ {
+ using (var bits = new MsbBitStream (m_input.AsStream, true))
+ {
+ var colors = new Color[16];
+ for (int i = 0; i < 16; ++i)
+ {
+ int r = bits.GetBits (4) * 0x11;
+ int g = bits.GetBits (4) * 0x11;
+ int b = bits.GetBits (4) * 0x11;
+ colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+ }
+}
diff --git a/Legacy/Jam/ImageHTF.cs b/Legacy/Jam/ImageHTF.cs
new file mode 100644
index 00000000..3355d9fa
--- /dev/null
+++ b/Legacy/Jam/ImageHTF.cs
@@ -0,0 +1,69 @@
+//! \file ImageHTF.cs
+//! \date 2023 Oct 07
+//! \brief Huffman-compressed bitmap.
+//
+// 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.Imaging;
+
+namespace GameRes.Formats.Jam
+{
+ [Export(typeof(ImageFormat))]
+ public class HtfFormat : ImageFormat
+ {
+ public override string Tag => "HTF";
+ public override string Description => "Huffman-compressed bitmap";
+ public override uint Signature => 0;
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ if (!file.Name.HasExtension (".HTF"))
+ return null;
+ int unpacked_size = file.ReadInt32();
+ if (unpacked_size <= 0 || unpacked_size > 0x1000000)
+ return null;
+ using (var huff = new HuffmanStream (file.AsStream, true))
+ using (var input = new BinaryStream (huff, file.Name))
+ {
+ return Bmp.ReadMetaData (input);
+ }
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ file.Position = 4;
+ using (var input = new HuffmanStream (file.AsStream, true))
+ {
+ var decoder = new BmpBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
+ return new ImageData (decoder.Frames[0], info);
+ }
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("HtfFormat.Write not implemented");
+ }
+ }
+}
diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj
index 7459d23e..0bf4c50e 100644
--- a/Legacy/Legacy.csproj
+++ b/Legacy/Legacy.csproj
@@ -78,6 +78,9 @@
+
+
+
@@ -88,14 +91,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -116,6 +138,7 @@
+
@@ -133,17 +156,28 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -156,6 +190,9 @@
+
+
+
@@ -202,18 +239,26 @@
+
+
+
+
+
+
+
+
@@ -244,7 +289,7 @@
-
+
diff --git a/Legacy/Mapl/ImageMI2.cs b/Legacy/Mapl/ImageMI2.cs
new file mode 100644
index 00000000..38db3d88
--- /dev/null
+++ b/Legacy/Mapl/ImageMI2.cs
@@ -0,0 +1,438 @@
+//! \file ImageMI2.cs
+//! \date 2023 Oct 19
+//! \brief Mapl engine 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.Utility;
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+
+// [980130][Pias] Rashin
+
+namespace GameRes.Formats.Mapl
+{
+ internal class Mi2MetaData : ImageMetaData
+ {
+ public int Colors;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class Mi2Format : ImageFormat
+ {
+ public override string Tag => "MI2";
+ public override string Description => "Mapl engine image format";
+ public override uint Signature => 0x28;
+
+ public Mi2Format ()
+ {
+ Extensions = new[] { "mi2", "fcg" };
+ }
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x28); // BITMAPINFOHEADER
+ int bpp = header.ToUInt16 (0xE);
+ if (bpp != 8 && bpp != 24)
+ return null;
+ uint width = header.ToUInt32 (4);
+ uint height = header.ToUInt32 (8);
+ int colors = header.ToInt32 (0x20);
+ if (8 == bpp)
+ {
+ if (colors < 0 || colors > 0x100)
+ return null;
+ if (0 == colors)
+ colors = 0x100;
+ }
+ return new Mi2MetaData {
+ Width = width,
+ Height = height,
+ BPP = bpp,
+ Colors = colors,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new Mi2Reader (file, (Mi2MetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("Mi2Format.Write not implemented");
+ }
+ }
+
+ internal class Mi2Reader
+ {
+ IBinaryStream m_input;
+ Mi2MetaData m_info;
+
+ public Mi2Reader (IBinaryStream file, Mi2MetaData info)
+ {
+ m_input = file;
+ m_info = info;
+ }
+
+ byte[] m_block = new byte[64];
+ byte[] m_buffer = new byte[32];
+
+ int stride;
+ int block_stride;
+ int blocksW;
+ int blocksH;
+
+ public ImageData Unpack ()
+ {
+ stride = (m_info.iWidth + 7) & ~7;
+ block_stride = stride * 8;
+ blocksW = m_info.iWidth >> 3;
+ if ((m_info.iWidth & 7) != 0)
+ blocksW++;
+ blocksH = m_info.iHeight >> 3;
+ if ((m_info.iHeight & 7) != 0)
+ blocksH++;
+ var channel = new byte[stride * m_info.iHeight];
+ m_input.Position = 0x28;
+ if (8 == m_info.BPP)
+ {
+ var palette = ImageFormat.ReadPalette (m_input.AsStream, m_info.Colors);
+ UnpackChannel (channel);
+ return ImageData.Create (m_info, PixelFormats.Indexed8, palette, channel, stride);
+ }
+ else
+ {
+ int bgr_stride = m_info.iWidth * 3;
+ var bgr = new byte[bgr_stride * m_info.iHeight];
+ for (int c = 0; c < 3; ++c)
+ {
+ UnpackChannel (channel);
+ int src = 0;
+ int dst = c;
+ for (int y = 0; y < m_info.iHeight; ++y)
+ {
+ for (int x = 0; x < m_info.iWidth; ++x)
+ {
+ bgr[dst] = channel[src+x];
+ dst += 3;
+ }
+ src += stride;
+ }
+ }
+ return ImageData.Create (m_info, PixelFormats.Bgr24, null, bgr, bgr_stride);
+ }
+ }
+
+ void UnpackChannel (byte[] output)
+ {
+ int dst_row = output.Length - stride;
+ for (int y = 0; y < blocksH; ++y)
+ {
+ int dst_pos = dst_row;
+ for (int x = 0; x < blocksW; ++x)
+ {
+ byte ctl = m_input.ReadUInt8();
+ switch (ctl)
+ {
+ case 0:
+ m_input.Read (m_block, 0, m_block.Length);
+ break;
+ case 1:
+ {
+ byte b = m_input.ReadUInt8();
+ for (int i = 0; i < m_block.Length; ++i)
+ m_block[i] = b;
+ break;
+ }
+ case 2:
+ Op2();
+ break;
+ case 3:
+ Op3();
+ break;
+ case 4:
+ Op4();
+ break;
+ case 5:
+ Op5();
+ break;
+ case 6:
+ Op6();
+ break;
+ case 7:
+ Op7();
+ break;
+ case 8:
+ Op8();
+ AdjustBlock();
+ break;
+ case 9:
+ Op9();
+ AdjustBlock();
+ break;
+ case 10:
+ Op4();
+ AdjustBlock();
+ break;
+ case 11:
+ Op5();
+ AdjustBlock();
+ break;
+ case 12:
+ Op6();
+ AdjustBlock();
+ break;
+ case 13:
+ Op7();
+ AdjustBlock();
+ break;
+ default:
+ throw new InvalidFormatException();
+ }
+ int dst = dst_pos;
+ int src = 0;
+ while (src < m_block.Length && dst >= 0)
+ {
+ Buffer.BlockCopy (m_block, src, output, dst, 8);
+ src += 8;
+ dst -= stride;
+ }
+ dst_pos += 8;
+ }
+ dst_row -= block_stride;
+ }
+ }
+
+ void Op2 ()
+ {
+ byte b = m_input.ReadUInt8();
+ m_input.Read (m_buffer, 0, 8);
+ int pos = 0;
+ for (int i = 0; i < 8; ++i)
+ {
+ byte mask = 0x80;
+ for (int j = 0; j < 8; ++j)
+ {
+ if ((mask & m_buffer[i]) != 0)
+ m_block[pos++] = b;
+ else
+ m_block[pos++] = m_input.ReadUInt8();
+ mask >>= 1;
+ }
+ }
+ }
+
+ void Op3 ()
+ {
+ int pos = 0;
+ byte b1 = m_input.ReadUInt8();
+ byte b2 = m_input.ReadUInt8();
+ for (int i = 0; i < 8; ++i)
+ {
+ byte mask = 0x80;
+ byte b = m_input.ReadUInt8();
+ for (int j = 0; j < 8; ++j)
+ {
+ if ((mask & b) != 0)
+ m_block[pos++] = b2;
+ else
+ m_block[pos++] = b1;
+ mask >>= 1;
+ }
+ }
+ }
+
+ void Op4()
+ {
+ byte b1 = m_input.ReadUInt8();
+ byte b2 = m_input.ReadUInt8();
+ byte b3 = m_input.ReadUInt8();
+ int dst = 0;
+ m_input.Read (m_buffer, 0, 16);
+ for (int i = 0; i < 16; ++i)
+ {
+ byte bits = m_buffer[i];
+ for (int j = 0; j < 4; ++j)
+ {
+ switch (bits >> 6)
+ {
+ case 0: m_block[dst] = m_input.ReadUInt8(); break;
+ case 1: m_block[dst] = b1; break;
+ case 2: m_block[dst] = b2; break;
+ case 3: m_block[dst] = b3; break;
+ }
+ dst++;
+ bits <<= 2;
+ }
+ }
+ }
+
+ void Op5()
+ {
+ byte b0 = m_input.ReadUInt8();
+ byte b1 = m_input.ReadUInt8();
+ byte b2 = m_input.ReadUInt8();
+ byte b3 = m_input.ReadUInt8();
+ m_input.Read (m_buffer, 0, 16);
+ int dst = 0;
+ for (int i = 0; i < 16; ++i)
+ {
+ byte bits = m_buffer[i];
+ for (int j = 0; j < 4; ++j)
+ {
+ switch (bits >> 6)
+ {
+ case 0: m_block[dst] = b0; break;
+ case 1: m_block[dst] = b1; break;
+ case 2: m_block[dst] = b2; break;
+ case 3: m_block[dst] = b3; break;
+ }
+ dst++;
+ bits <<= 2;
+ }
+ }
+ }
+
+ byte[] m_buffer2 = new byte[0x100];
+ byte[] m_buffer6 = new byte[0x40];
+
+ void Op6()
+ {
+ int count = m_input.ReadUInt8();
+ m_input.Read (m_buffer2, 0, count);
+ m_input.Read (m_buffer, 0, 24);
+ for (int i = 0; i < m_buffer6.Length; ++i)
+ m_buffer6[i] = 0;
+ int buf_pos = 0;
+ int bits = 0;
+ int bit_count = 0;
+ int bit_src = 0;
+ byte mask = 0x80;
+ for (int i = 0; i < 64; ++i)
+ {
+ byte n0 = 0;
+ byte n1 = 4;
+ for (int j = 0; j < 3; ++j)
+ {
+ if (0 == bit_count)
+ {
+ bits = m_buffer[bit_src++];
+ bit_count = 8;
+ }
+ if ((bits & mask) != 0)
+ n0 |= n1;
+ --bit_count;
+ n1 >>= 1;
+ mask = Binary.RotByteR (mask, 1);
+ }
+ m_buffer6[buf_pos++] = n0;
+ }
+ for (int i = 0; i < 64; ++i)
+ {
+ byte b = m_buffer6[i];
+ if (b > 0)
+ m_block[i] = m_buffer2[b-1];
+ else
+ m_block[i] = m_input.ReadUInt8();
+ }
+ }
+
+ void Op7()
+ {
+ int count = m_input.ReadUInt8();
+ m_input.Read (m_buffer2, 0, count);
+ m_input.Read (m_buffer, 0, 32);
+ int dst = 0;
+ for (int i = 0; i < 32; ++i)
+ {
+ byte b = m_buffer[i];
+ for (int j = 0; j < 2; ++j)
+ {
+ int bits = b >> 4;
+ b <<= 4;
+ if (bits != 0)
+ m_block[dst++] = m_buffer2[bits-1];
+ else
+ m_block[dst++] = m_input.ReadUInt8();
+ }
+ }
+ }
+
+ void Op8()
+ {
+ byte b0 = m_input.ReadUInt8();
+ m_input.Read (m_buffer, 0, 8);
+ int src = 0;
+ int dst = 0;
+ for (int i = 0; i < 8; ++i)
+ {
+ byte mask = 0x80;
+ byte b = m_buffer[src++];
+ for (int j = 0; j < 8; ++j)
+ {
+ if ((mask & b) != 0)
+ m_block[dst++] = b0;
+ else
+ m_block[dst++] = m_input.ReadUInt8();
+ mask >>= 1;
+ }
+ }
+ }
+
+ void Op9()
+ {
+ byte b0 = m_input.ReadUInt8();
+ byte b1 = m_input.ReadUInt8();
+ int dst = 0;
+ for (int i = 0; i < 8; ++i)
+ {
+ byte mask = 0x80;
+ byte b = m_input.ReadUInt8();
+ for (int j = 0; j < 8; ++j)
+ {
+ if ((mask & b) != 0)
+ m_block[dst++] = b1;
+ else
+ m_block[dst++] = b0;
+ mask >>= 1;
+ }
+ }
+ }
+
+ void AdjustBlock()
+ {
+ int dst = 0;
+ for (int i = 0; i < 8; ++i)
+ {
+ m_input.ReadByte();
+ for (int j = 0; j < 8; ++j)
+ {
+ m_block[dst++] <<= 1;
+ }
+ }
+ }
+ }
+}
diff --git a/Legacy/Miami/ImageMIA.cs b/Legacy/Miami/ImageMIA.cs
new file mode 100644
index 00000000..93a65c7f
--- /dev/null
+++ b/Legacy/Miami/ImageMIA.cs
@@ -0,0 +1,318 @@
+//! \file ImageMIA.cs
+//! \date 2023 Oct 21
+//! \brief Miamisoft image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [950630][Miamisoft] Kotohigaoka Monogatari
+
+namespace GameRes.Formats.Miami
+{
+ [Export(typeof(ImageFormat))]
+ public class MiaFormat : ImageFormat
+ {
+ public override string Tag => "MIA";
+ public override string Description => "Miamisoft image format";
+ public override uint Signature => 0;
+
+ public MiaFormat ()
+ {
+ Signatures = new[] { 0x40u, 0u };
+ }
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x10);
+ if (!header.AsciiEqual (0xA, "CoB42"))
+ return null;
+ return new ImageMetaData {
+ Width = header.ToUInt16 (6),
+ Height = header.ToUInt16 (8),
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new MiaReader (file, info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("MiaFormat.Write not implemented");
+ }
+ }
+
+ internal class MiaReader
+ {
+ IBinaryStream m_input;
+ ImageMetaData m_info;
+
+ public MiaReader (IBinaryStream input, ImageMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ byte[] m_buffer;
+ int m_buf_dst;
+ byte[] m_order;
+
+ byte[] m_output;
+ int m_output_dst;
+ int m_output_stride;
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 0x10;
+ var palette = ReadPalette (m_input);
+ try
+ {
+ UnpackInternal();
+ }
+ catch (EndOfStreamException)
+ {
+ FlushBuffer();
+ }
+ return ImageData.Create (m_info, PixelFormats.Indexed4, palette, m_output, m_output_stride);
+ }
+
+ void UnpackInternal () // 1374:7A54
+ {
+ m_output_stride = m_info.iWidth >> 1;
+ m_output = new byte[m_output_stride * m_info.iHeight];
+ int buffer_size = m_info.iHeight * 0x10;
+ m_buffer = new byte[buffer_size];
+ SetupPattern();
+ m_order = m_input.ReadBytes (6);
+ byte prev_pixel = 0x10;
+ m_buf_dst = 0;
+ m_output_dst = 0;
+ while (m_output_dst < m_output_stride)
+ {
+ int ctl = GetInt() - 1;
+ if (ctl < 0) // @1@
+ {
+ m_buffer[m_buf_dst ] = 0;
+ m_buffer[m_buf_dst+1] = 0;
+ m_buffer[m_buf_dst+2] = 0;
+ m_buffer[m_buf_dst+3] = 0;
+ for (int i = 0; i < 4; ++i)
+ {
+ int count = GetInt();
+ int dst = count + (prev_pixel << 4);
+ byte al = m_pattern[dst];
+ int src = dst - 1;
+ while (count --> 0)
+ m_pattern[dst--] = m_pattern[src--];
+ m_pattern[dst] = al;
+ prev_pixel = al;
+ for (int j = 0; j < 4; ++j)
+ {
+ m_buffer[m_buf_dst+j] <<= 1;
+ m_buffer[m_buf_dst+j] |= (byte)(al & 1);
+ al >>= 1;
+ }
+ }
+ ushort ax = LittleEndian.ToUInt16 (m_buffer, m_buf_dst);
+ ax <<= 4;
+ LittleEndian.Pack (ax, m_buffer, m_buf_dst+4);
+ ax = LittleEndian.ToUInt16 (m_buffer, m_buf_dst+2);
+ ax <<= 4;
+ LittleEndian.Pack (ax, m_buffer, m_buf_dst+6);
+ m_buf_dst += 8;
+ }
+ else if (ctl < 5) // @2@
+ {
+ int count = 1 + GetInt();
+ switch (m_order[ctl])
+ {
+ case 1: CopyOp01 (count, 8); break;
+ case 2: CopyOp01 (count, 0x10); break;
+ case 3: CopyOp01 (count, 0x20); break;
+ case 4: CopyOp01 (count, m_info.iHeight << 3); break;
+ case 5: CopyOp05 (count); break;
+ default: throw new InvalidFormatException();
+ }
+ }
+ else // ctl >= 5
+ {
+ throw new InvalidFormatException();
+ }
+ if (buffer_size == m_buf_dst)
+ FlushBuffer();
+ }
+ }
+
+ int GetInt ()
+ {
+ int count = 0;
+ while (GetNextBit() == 0)
+ ++count;
+ return count;
+ }
+
+ void CopyOp01 (int count, int offset)
+ {
+ int bx = count;
+ while (count > 0)
+ {
+ int dst = m_buf_dst;
+ if (dst < offset)
+ {
+ int src = dst;
+ dst = -(dst - offset) >> 3;
+ if (count > dst)
+ count = dst;
+ src += m_info.iHeight << 4;
+ src -= offset;
+ bx -= count;
+ count <<= 3;
+ Binary.CopyOverlapped (m_buffer, src, m_buf_dst, count);
+ m_buf_dst += count;
+ count = bx;
+ if (0 == count)
+ break;
+ }
+ int remaining = m_buffer.Length - m_buf_dst;
+ remaining >>= 3;
+ if (count > remaining)
+ count = remaining;
+ bx -= count;
+ count <<= 3;
+ Binary.CopyOverlapped (m_buffer, m_buf_dst - offset, m_buf_dst, count);
+ m_buf_dst += count;
+ count = bx;
+ if (m_buffer.Length == m_buf_dst)
+ FlushBuffer();
+ }
+ }
+
+ void CopyOp05 (int count)
+ {
+ int src = m_buf_dst - 8;
+ if (src < 0)
+ src = m_buffer.Length - 8;
+
+ ushort ax = LittleEndian.ToUInt16 (m_buffer, src);
+ ax = (ushort)((ax << 1) & 0x0A0A | (ax >> 1) & 0x0505);
+ LittleEndian.Pack (ax, m_buffer, m_buf_dst);
+ ax <<= 4;
+ LittleEndian.Pack (ax, m_buffer, m_buf_dst+4);
+
+ ax = LittleEndian.ToUInt16 (m_buffer, src+2);
+ ax = (ushort)((ax << 1) & 0x0A0A | (ax >> 1) & 0x0505);
+ LittleEndian.Pack (ax, m_buffer, m_buf_dst+2);
+ ax <<= 4;
+ LittleEndian.Pack (ax, m_buffer, m_buf_dst+6);
+
+ m_buf_dst += 8;
+ if (m_buf_dst == m_buffer.Length)
+ FlushBuffer();
+ if (--count != 0)
+ CopyOp01 (count, 0x10);
+ }
+
+ void FlushBuffer ()
+ {
+ int height = m_info.iHeight;
+ int hi = height << 3;
+ int src = 0;
+ int dst = m_output_dst;
+ for (int y = 0; y < height; ++y)
+ {
+ int b0 = m_buffer[src+4] | m_buffer[src+hi ];
+ int b1 = m_buffer[src+5] | m_buffer[src+hi+1];
+ int b2 = m_buffer[src+6] | m_buffer[src+hi+2];
+ int b3 = m_buffer[src+7] | m_buffer[src+hi+3];
+ for (int j = 0; j < 8; j += 2)
+ {
+ byte px = (byte)((((b0 << j) & 0x80) >> 3)
+ | (((b1 << j) & 0x80) >> 2)
+ | (((b2 << j) & 0x80) >> 1)
+ | (((b3 << j) & 0x80) ));
+ px |= (byte)((((b0 << j) & 0x40) >> 6)
+ | (((b1 << j) & 0x40) >> 5)
+ | (((b2 << j) & 0x40) >> 4)
+ | (((b3 << j) & 0x40) >> 3));
+ m_output[dst+(j>>1)] = px;
+ }
+ src += 8;
+ dst += m_output_stride;
+ }
+ m_output_dst += 4;
+ m_buf_dst = 0;
+ }
+
+ byte[] m_pattern = new byte[0x110];
+
+ void SetupPattern ()
+ {
+ int dst = 0;
+ byte h = 0;
+ for (int i = 0; i < 0x11; ++i)
+ {
+ byte l = h;
+ for (int j = 0; j < 0x10; ++j)
+ m_pattern[dst++] = (byte)(l++ & 0xF);
+ h++;
+ }
+ }
+
+ int m_bit_count = 0;
+ int m_bits;
+
+ byte GetNextBit ()
+ {
+ if (--m_bit_count <= 0)
+ {
+ m_bits = m_input.ReadUInt8();
+ m_bit_count = 8;
+ }
+ int bit = m_bits & 1;
+ m_bits >>= 1;
+ return (byte)bit;
+ }
+
+ BitmapPalette ReadPalette (IBinaryStream input)
+ {
+ const int count = 16;
+ var colors = new Color[count];
+ for (int i = 0; i < count; ++i)
+ {
+ byte g = m_input.ReadUInt8();
+ byte r = m_input.ReadUInt8();
+ byte b = m_input.ReadUInt8();
+ colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+}
diff --git a/Legacy/Mina/ArcPAK.cs b/Legacy/Mina/ArcPAK.cs
new file mode 100644
index 00000000..bc7a74cd
--- /dev/null
+++ b/Legacy/Mina/ArcPAK.cs
@@ -0,0 +1,299 @@
+//! \file ArcPAK.cs
+//! \date 2023 Oct 09
+//! \brief Mina 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;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Text;
+using System.Windows.Media;
+
+// [010223][Mina] Storia ~Ouma no Mori no Himegimi-tachi~
+
+namespace GameRes.Formats.Mina
+{
+ [Export(typeof(ArchiveFormat))]
+ public class BmpPakOpener : ArchiveFormat
+ {
+ public override string Tag => "PAK/MINA/BMP";
+ public override string Description => "Mina bitmap archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!file.Name.HasExtension (".PAK"))
+ return null;
+ int pos;
+ for (pos = 0; pos < 0x10; ++pos)
+ {
+ if (0 == file.View.ReadByte (pos))
+ break;
+ }
+ if (pos >= 0x10 || pos <= 4 || !file.View.AsciiEqual (pos-4, ".BMP"))
+ return null;
+ using (var input = file.CreateStream())
+ {
+ var dir = new List();
+ while (input.PeekByte() != -1)
+ {
+ var name = input.ReadCString();
+ if (name.Length > 0x10)
+ return null;
+ var entry = Create (name);
+ entry.Offset = input.Position;
+ input.Seek (5, SeekOrigin.Current);
+ uint size = input.ReadUInt32();
+ entry.Size = size + 9;
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ dir.Add (entry);
+ input.Seek (size, SeekOrigin.Current);
+ }
+ return new ArcFile (file, this, dir);
+ }
+ }
+
+ public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
+ {
+ var input = arc.File.CreateStream (entry.Offset, entry.Size);
+ return new BitmapDecoder (input);
+ }
+ }
+
+ [Export(typeof(ArchiveFormat))]
+ public class WavPakOpener : ArchiveFormat
+ {
+ public override string Tag => "PAK/MINA/WAV";
+ public override string Description => "Mina audio archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!file.Name.HasExtension (".PAK"))
+ return null;
+ int pos;
+ for (pos = 4; pos < 0x14; ++pos)
+ {
+ if (0 == file.View.ReadByte (pos))
+ break;
+ }
+ if (pos >= 0x14 || pos <= 8 || !file.View.AsciiEqual (pos-4, ".WAV"))
+ return null;
+ using (var input = file.CreateStream())
+ {
+ var dir = new List();
+ while (input.PeekByte() != -1)
+ {
+ uint data_size = input.ReadUInt32();
+ var name = input.ReadCString();
+ if (name.Length > 0x10)
+ return null;
+ var entry = Create (name);
+ entry.Offset = input.Position;
+ uint fmt_size = input.ReadUInt32();
+ if (fmt_size < 0x10)
+ return null;
+ entry.Size = data_size + fmt_size + 4;
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ dir.Add (entry);
+ input.Seek (data_size + fmt_size, SeekOrigin.Current);
+ }
+ return new ArcFile (file, this, dir);
+ }
+ }
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ uint fmt_size = arc.File.View.ReadUInt32 (entry.Offset);
+ uint pcm_size = entry.Size - 4 - fmt_size;
+ using (var mem = new MemoryStream ((int)fmt_size))
+ {
+ using (var buffer = new BinaryWriter (mem, Encoding.ASCII, true))
+ {
+ buffer.Write (AudioFormat.Wav.Signature);
+ buffer.Write (entry.Size+0x10);
+ buffer.Write (0x45564157); // 'WAVE'
+ buffer.Write (0x20746d66); // 'fmt '
+ buffer.Write (fmt_size);
+ var fmt = arc.File.View.ReadBytes (entry.Offset+4, fmt_size);
+ buffer.Write (fmt, 0, fmt.Length);
+ buffer.Write (0x61746164); // 'data'
+ buffer.Write (pcm_size);
+ }
+ var header = mem.ToArray();
+ var data = arc.File.CreateStream (entry.Offset+4+fmt_size, pcm_size);
+ return new PrefixStream (header, data);
+ }
+ }
+ }
+
+ [Export(typeof(ArchiveFormat))]
+ public class ScriptPakOpener : ArchiveFormat
+ {
+ public override string Tag => "PAK/MINA/SPT";
+ public override string Description => "Mina scripts archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public ScriptPakOpener ()
+ {
+ ContainedFormats = new[] { "SCR" };
+ }
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!VFS.IsPathEqualsToFileName (file.Name, "SCRIPT.PAK"))
+ return null;
+ using (var input = file.CreateStream())
+ {
+ var dir = new List();
+ while (input.PeekByte() != -1)
+ {
+ var name = input.ReadCString();
+ if (name.Length > 0x10)
+ return null;
+ var entry = Create (name);
+ entry.Size = input.ReadUInt32();
+ entry.Offset = input.Position;
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ dir.Add (entry);
+ input.Seek (entry.Size, SeekOrigin.Current);
+ }
+ return new ArcFile (file, this, dir);
+ }
+ }
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
+ var mem = new MemoryStream (data.Length);
+ int pos = 0;
+ while (pos < data.Length)
+ {
+ int len = data[pos]+1;
+ int num = data.ToUInt16 (1);
+ pos += 3;
+ for (int j = 0; j < len; ++j)
+ data[pos+j] = Binary.RotByteR (data[pos+j], 4);
+ mem.Write (data, pos, len);
+ mem.WriteByte (0xD);
+ mem.WriteByte (0xA);
+ pos += len;
+ }
+ mem.Position = 0;
+ return mem;
+ }
+ }
+
+ internal class BmpMetaData : ImageMetaData
+ {
+ public byte Flags;
+ public bool IsCompressed => (Flags & 1) != 0;
+ }
+
+ internal class BitmapDecoder : IImageDecoder
+ {
+ IBinaryStream m_input;
+ BmpMetaData m_info;
+ ImageData m_image;
+
+ public Stream Source => m_input.AsStream;
+ public ImageFormat SourceFormat => null;
+ public ImageMetaData Info => m_info;
+ public ImageData Image => m_image ?? (m_image = Unpack());
+
+ public BitmapDecoder (IBinaryStream input)
+ {
+ m_input = input;
+ m_info = new BmpMetaData {
+ Width = input.ReadUInt16(),
+ Height = input.ReadUInt16(),
+ Flags = input.ReadUInt8(),
+ };
+ m_info.BPP = m_info.IsCompressed ? 32 : 24;
+ }
+
+ ImageData Unpack ()
+ {
+ m_input.Position = 9;
+ if (m_info.IsCompressed)
+ {
+ return RleUnpack();
+ }
+ else
+ {
+ int bitmap_size = m_info.iWidth * 3 * m_info.iHeight;
+ var pixels = m_input.ReadBytes (bitmap_size);
+ return ImageData.Create (m_info, PixelFormats.Rgb24, null, pixels);
+ }
+ }
+
+ ImageData RleUnpack ()
+ {
+ int stride = m_info.iWidth * 4;
+ var output = new byte[stride * m_info.iHeight];
+ byte alpha = 0;
+ int count = 0;
+ int dst = 0;
+ while (dst < output.Length)
+ {
+ if (--count <= 0)
+ {
+ alpha = m_input.ReadUInt8();
+ count = m_input.ReadUInt8();
+ }
+ if (alpha != 0)
+ {
+ output[dst+2] = m_input.ReadUInt8();
+ output[dst+1] = m_input.ReadUInt8();
+ output[dst ] = m_input.ReadUInt8();
+ output[dst+3] = alpha;
+ }
+ dst += 4;
+ }
+ return ImageData.Create (m_info, PixelFormats.Bgra32, null, output, stride);
+ }
+
+ #region IDisposable members
+ bool m_disposed = false;
+ public void Dispose ()
+ {
+ if (!m_disposed)
+ {
+ m_input.Dispose();
+ m_disposed = true;
+ }
+ }
+ #endregion
+ }
+}
diff --git a/Legacy/Nekotaro/ArcNSC.cs b/Legacy/Nekotaro/ArcNSC.cs
index 1eebc879..deeb89ef 100644
--- a/Legacy/Nekotaro/ArcNSC.cs
+++ b/Legacy/Nekotaro/ArcNSC.cs
@@ -27,6 +27,7 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
+// [991231][Jam] Kakuyuugou Shoujo Ripple-chan
// [000331][Jam] Zetsumetsu King
// [000630][STONE HEADS] Sei Cosplay Gakuen ~Game Bunkou~
diff --git a/Legacy/Nekotaro/ImageGCmp.cs b/Legacy/Nekotaro/ImageGCmp.cs
index f825a7c7..bf4b7f93 100644
--- a/Legacy/Nekotaro/ImageGCmp.cs
+++ b/Legacy/Nekotaro/ImageGCmp.cs
@@ -24,8 +24,10 @@
//
using System;
+using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
+using System.Text.RegularExpressions;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GameRes.Utility;
@@ -44,6 +46,11 @@ namespace GameRes.Formats.Nekotaro
public override string Description { get { return "Nekotaro Game System image format"; } }
public override uint Signature { get { return 0x706D4347; } } // 'GCmp'
+ public GCmpFormat ()
+ {
+ Extensions = new[] { "GCMP", "AIG" };
+ }
+
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
@@ -98,6 +105,8 @@ namespace GameRes.Formats.Nekotaro
Stride = (info.iWidth + 7) / 8;
}
+ static BitmapPalette LastUsedPalette = null;
+
public ImageData Unpack ()
{
m_input.Position = 0x10;
@@ -106,6 +115,8 @@ namespace GameRes.Formats.Nekotaro
pixels = Unpack24bpp();
else
pixels = Unpack8bpp();
+ if (8 == Info.BPP)
+ Palette = LastUsedPalette ?? (LastUsedPalette = RetrievePalette() ?? DefaultPalette);
return ImageData.CreateFlipped (Info, Format, Palette, pixels, Stride);
}
@@ -190,14 +201,11 @@ namespace GameRes.Formats.Nekotaro
byte[] Unpack8bpp ()
{
if (8 == Info.BPP)
- {
Format = PixelFormats.Indexed8;
- Palette = DefaultPalette;
- }
else
Format = PixelFormats.BlackWhite;
int pixel_count = Info.iHeight * Stride;
- if (m_info.IsCompressed)
+ if (!m_info.IsCompressed)
return m_input.ReadBytes (pixel_count);
var output = new byte[pixel_count];
@@ -272,268 +280,125 @@ namespace GameRes.Formats.Nekotaro
return output;
}
- static readonly BitmapPalette DefaultPalette = new BitmapPalette (
- /*
- new Color[] {
- Color.FromRgb (0x00, 0x00, 0x00),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0x22, 0x22, 0x22),
- Color.FromRgb (0x44, 0x44, 0x44),
- Color.FromRgb (0x55, 0x55, 0x55),
- Color.FromRgb (0x66, 0x66, 0x66),
- Color.FromRgb (0x77, 0x77, 0x77),
- Color.FromRgb (0x88, 0x88, 0x88),
- Color.FromRgb (0x99, 0x99, 0x99),
- Color.FromRgb (0xAA, 0xAA, 0xAA),
- Color.FromRgb (0xBB, 0xBB, 0xBB),
- Color.FromRgb (0xCC, 0xCC, 0xCC),
- Color.FromRgb (0xDD, 0xDD, 0xDD),
- Color.FromRgb (0xEE, 0xEE, 0xEE),
- Color.FromRgb (0x00, 0xFF, 0x00),
- Color.FromRgb (0x1C, 0x09, 0x05),
- Color.FromRgb (0x2F, 0x0A, 0x05),
- Color.FromRgb (0x4E, 0x04, 0x02),
- Color.FromRgb (0x41, 0x0C, 0x05),
- Color.FromRgb (0x29, 0x15, 0x36),
- Color.FromRgb (0x24, 0x22, 0x21),
- Color.FromRgb (0x6C, 0x07, 0x0D),
- Color.FromRgb (0x1F, 0x2D, 0x36),
- Color.FromRgb (0x4B, 0x21, 0x18),
- Color.FromRgb (0x5D, 0x1B, 0x0D),
- Color.FromRgb (0x8B, 0x00, 0x36),
- Color.FromRgb (0x8E, 0x06, 0x16),
- Color.FromRgb (0x7E, 0x11, 0x0F),
- Color.FromRgb (0x09, 0x44, 0x64),
- Color.FromRgb (0x48, 0x2C, 0x4B),
- Color.FromRgb (0x38, 0x37, 0x3A),
- Color.FromRgb (0x3A, 0x24, 0x88),
- Color.FromRgb (0x74, 0x23, 0x12),
- Color.FromRgb (0x0D, 0x53, 0x29),
- Color.FromRgb (0x22, 0x34, 0x86),
- Color.FromRgb (0xB1, 0x03, 0x2A),
- Color.FromRgb (0x4B, 0x37, 0x28),
- Color.FromRgb (0x64, 0x30, 0x28),
- Color.FromRgb (0x32, 0x4A, 0x2D),
- Color.FromRgb (0x9B, 0x17, 0x20),
- Color.FromRgb (0xB0, 0x10, 0x10),
- Color.FromRgb (0x3D, 0x19, 0xCC),
- Color.FromRgb (0x1B, 0x38, 0xB2),
- Color.FromRgb (0x97, 0x25, 0x13),
- Color.FromRgb (0x30, 0x4C, 0x5E),
- Color.FromRgb (0x77, 0x38, 0x22),
- Color.FromRgb (0xD3, 0x0B, 0x1F),
- Color.FromRgb (0x01, 0x69, 0x65),
- Color.FromRgb (0x5F, 0x46, 0x33),
- Color.FromRgb (0x4B, 0x4D, 0x4F),
- Color.FromRgb (0xB6, 0x1B, 0x34),
- Color.FromRgb (0x0A, 0x74, 0x34),
- Color.FromRgb (0xBB, 0x26, 0x11),
- Color.FromRgb (0xED, 0x0B, 0x26),
- Color.FromRgb (0x2F, 0x52, 0x97),
- Color.FromRgb (0x49, 0x20, 0xFB),
- Color.FromRgb (0x89, 0x44, 0x15),
- Color.FromRgb (0x67, 0x46, 0x65),
- Color.FromRgb (0x06, 0x76, 0x72),
- Color.FromRgb (0x93, 0x3F, 0x2D),
- Color.FromRgb (0x3F, 0x65, 0x49),
- Color.FromRgb (0x6D, 0x52, 0x3B),
- Color.FromRgb (0x88, 0x4C, 0x38),
- Color.FromRgb (0xE5, 0x26, 0x17),
- Color.FromRgb (0xA6, 0x47, 0x1D),
- Color.FromRgb (0x43, 0x68, 0x7D),
- Color.FromRgb (0x23, 0x50, 0xE8),
- Color.FromRgb (0xE3, 0x24, 0x43),
- Color.FromRgb (0x94, 0x56, 0x1C),
- Color.FromRgb (0x60, 0x63, 0x64),
- Color.FromRgb (0xBC, 0x3E, 0x49),
- Color.FromRgb (0x06, 0x9C, 0x45),
- Color.FromRgb (0xC4, 0x44, 0x24),
- Color.FromRgb (0xB1, 0x55, 0x2B),
- Color.FromRgb (0x8D, 0x60, 0x53),
- Color.FromRgb (0x63, 0x46, 0xFB),
- Color.FromRgb (0x7B, 0x6C, 0x61),
- Color.FromRgb (0x91, 0x57, 0x97),
- Color.FromRgb (0xAA, 0x5A, 0x4C),
- Color.FromRgb (0x49, 0x7E, 0xA0),
- Color.FromRgb (0xF8, 0x3C, 0x29),
- Color.FromRgb (0xA9, 0x67, 0x20),
- Color.FromRgb (0xC9, 0x56, 0x36),
- Color.FromRgb (0xA2, 0x6A, 0x3E),
- Color.FromRgb (0xBF, 0x56, 0x6C),
- Color.FromRgb (0x77, 0x7A, 0x7B),
- Color.FromRgb (0x5D, 0x79, 0xD2),
- Color.FromRgb (0xCC, 0x62, 0x44),
- Color.FromRgb (0xA3, 0x75, 0x63),
- Color.FromRgb (0xDE, 0x60, 0x31),
- Color.FromRgb (0xB5, 0x79, 0x23),
- Color.FromRgb (0x45, 0x80, 0xF5),
- Color.FromRgb (0xFD, 0x56, 0x29),
- Color.FromRgb (0xEE, 0x52, 0x64),
- Color.FromRgb (0x8C, 0x83, 0x6E),
- Color.FromRgb (0xCD, 0x70, 0x2A),
- Color.FromRgb (0xC4, 0x6E, 0x53),
- Color.FromRgb (0x86, 0x87, 0x87),
- Color.FromRgb (0x5E, 0x95, 0xAC),
- Color.FromRgb (0x7D, 0x6C, 0xFD),
- Color.FromRgb (0x36, 0xC5, 0x22),
- Color.FromRgb (0xAC, 0x6F, 0xB2),
- Color.FromRgb (0xD9, 0x6E, 0x4E),
- Color.FromRgb (0xC1, 0x84, 0x2D),
- Color.FromRgb (0xDB, 0x6C, 0x72),
- Color.FromRgb (0xEB, 0x6F, 0x42),
- Color.FromRgb (0x9F, 0x8B, 0x81),
- Color.FromRgb (0x92, 0x94, 0x93),
- Color.FromRgb (0x76, 0x90, 0xDB),
- Color.FromRgb (0x85, 0x9A, 0x99),
- Color.FromRgb (0xE0, 0x79, 0x58),
- Color.FromRgb (0xBE, 0x87, 0x6D),
- Color.FromRgb (0xD5, 0x7E, 0x62),
- Color.FromRgb (0x5B, 0xA7, 0xDF),
- Color.FromRgb (0xCC, 0x91, 0x2A),
- Color.FromRgb (0xF5, 0x6F, 0x76),
- Color.FromRgb (0x7B, 0xA7, 0xA7),
- Color.FromRgb (0xF1, 0x7C, 0x54),
- Color.FromRgb (0xA1, 0x9C, 0x87),
- Color.FromRgb (0xE5, 0x81, 0x61),
- Color.FromRgb (0xF2, 0x8A, 0x47),
- Color.FromRgb (0xEE, 0x88, 0x67),
- Color.FromRgb (0xA1, 0xA3, 0xA3),
- Color.FromRgb (0x8A, 0xA0, 0xE5),
- Color.FromRgb (0xC4, 0x9A, 0x7F),
- Color.FromRgb (0xD9, 0x9F, 0x36),
- Color.FromRgb (0x95, 0xAC, 0xAA),
- Color.FromRgb (0xEC, 0x88, 0x8B),
- Color.FromRgb (0xAE, 0xA7, 0x92),
- Color.FromRgb (0xE8, 0x90, 0x70),
- Color.FromRgb (0xF5, 0x8F, 0x6F),
- Color.FromRgb (0xD5, 0x8B, 0xDC),
- Color.FromRgb (0x6A, 0xC2, 0xF7),
- Color.FromRgb (0xEE, 0x9A, 0x7A),
- Color.FromRgb (0xF7, 0x98, 0x74),
- Color.FromRgb (0x8D, 0xBA, 0xDB),
- Color.FromRgb (0xBA, 0xB1, 0x9C),
- Color.FromRgb (0xB2, 0xB3, 0xB1),
- Color.FromRgb (0xD2, 0xA8, 0x9B),
- Color.FromRgb (0xA6, 0xBA, 0xBD),
- Color.FromRgb (0xEC, 0xB4, 0x3A),
- Color.FromRgb (0xFC, 0x98, 0x9F),
- Color.FromRgb (0xF7, 0xA1, 0x80),
- Color.FromRgb (0xED, 0xA7, 0x85),
- Color.FromRgb (0xFA, 0xA9, 0x83),
- Color.FromRgb (0xDD, 0xB3, 0xAF),
- Color.FromRgb (0xFA, 0xA6, 0xA7),
- Color.FromRgb (0xC8, 0xC0, 0xAD),
- Color.FromRgb (0xFA, 0xB0, 0x8F),
- Color.FromRgb (0x89, 0xD9, 0xFC),
- Color.FromRgb (0xA9, 0xCF, 0xE8),
- Color.FromRgb (0xBB, 0xCC, 0xCB),
- Color.FromRgb (0xFB, 0xB2, 0xB2),
- Color.FromRgb (0xFB, 0xB9, 0x97),
- Color.FromRgb (0xE2, 0xC2, 0xAF),
- Color.FromRgb (0xFC, 0xCA, 0x40),
- Color.FromRgb (0xFA, 0xBF, 0x82),
- Color.FromRgb (0xC9, 0xCA, 0xC9),
- Color.FromRgb (0xF8, 0xAC, 0xF8),
- Color.FromRgb (0xD4, 0xCD, 0xC2),
- Color.FromRgb (0xFC, 0xC2, 0x9D),
- Color.FromRgb (0xFC, 0xBE, 0xBA),
- Color.FromRgb (0xD2, 0xD3, 0xD0),
- Color.FromRgb (0xEC, 0xC9, 0xC6),
- Color.FromRgb (0xCA, 0xD9, 0xD7),
- Color.FromRgb (0xFD, 0xCA, 0xA5),
- Color.FromRgb (0xFE, 0xDB, 0x5B),
- Color.FromRgb (0xD8, 0xD8, 0xD4),
- Color.FromRgb (0xFD, 0xCA, 0xC9),
- Color.FromRgb (0xC3, 0xDF, 0xF1),
- Color.FromRgb (0xFE, 0xD2, 0xB1),
- Color.FromRgb (0xFD, 0xD6, 0xA1),
- Color.FromRgb (0xEE, 0xD7, 0xCA),
- Color.FromRgb (0xFB, 0xCB, 0xF7),
- Color.FromRgb (0xFE, 0xDB, 0xB6),
- Color.FromRgb (0xFE, 0xF5, 0x2C),
- Color.FromRgb (0xFD, 0xD6, 0xD4),
- Color.FromRgb (0xE2, 0xE2, 0xDC),
- Color.FromRgb (0xFE, 0xEC, 0x74),
- Color.FromRgb (0xFE, 0xE1, 0xBE),
- Color.FromRgb (0xED, 0xE5, 0xDC),
- Color.FromRgb (0xD9, 0xEC, 0xF8),
- Color.FromRgb (0xFB, 0xE3, 0xD4),
- Color.FromRgb (0xFD, 0xDD, 0xFA),
- Color.FromRgb (0xFE, 0xE7, 0xC6),
- Color.FromRgb (0xFE, 0xFA, 0x91),
- Color.FromRgb (0xFE, 0xEF, 0xCD),
- Color.FromRgb (0xFC, 0xEB, 0xEA),
- Color.FromRgb (0xFE, 0xF6, 0xDC),
- Color.FromRgb (0xFE, 0xFD, 0xE4),
- Color.FromRgb (0x35, 0x29, 0x24),
- Color.FromRgb (0x1A, 0x43, 0x25),
- Color.FromRgb (0x01, 0x49, 0x96),
- Color.FromRgb (0x86, 0x27, 0x16),
- Color.FromRgb (0x4D, 0x52, 0x3F),
- Color.FromRgb (0xEB, 0x0E, 0x0A),
- Color.FromRgb (0x00, 0x6A, 0xCC),
- Color.FromRgb (0x80, 0x34, 0xC1),
- Color.FromRgb (0xFD, 0x00, 0xFF),
- Color.FromRgb (0x08, 0x87, 0xEF),
- Color.FromRgb (0x76, 0x70, 0x56),
- Color.FromRgb (0xB8, 0x55, 0x3F),
- Color.FromRgb (0x35, 0x9F, 0xE1),
- Color.FromRgb (0xAA, 0x7E, 0x60),
- Color.FromRgb (0x01, 0xFD, 0x00),
- Color.FromRgb (0xAB, 0x93, 0x8A),
- Color.FromRgb (0xD3, 0x8C, 0x56),
- Color.FromRgb (0x77, 0xC0, 0xAC),
- Color.FromRgb (0xB9, 0xA6, 0x9E),
- Color.FromRgb (0xE6, 0xAB, 0x63),
- Color.FromRgb (0x9D, 0xCC, 0xA5),
- Color.FromRgb (0xD1, 0xB6, 0x91),
- Color.FromRgb (0xA6, 0xD9, 0xCF),
- Color.FromRgb (0xEA, 0xC9, 0x9D),
- Color.FromRgb (0xDF, 0xE2, 0xBC),
- Color.FromRgb (0xFC, 0xE8, 0xA2),
- Color.FromRgb (0xF9, 0xF2, 0xDE),
- Color.FromRgb (0x23, 0x0D, 0x1A),
- Color.FromRgb (0x02, 0x58, 0x1A),
- Color.FromRgb (0x66, 0x39, 0x13),
- Color.FromRgb (0x36, 0x6D, 0x66),
- Color.FromRgb (0x90, 0x5F, 0x2A),
- Color.FromRgb (0x51, 0x9E, 0x7E),
- Color.FromRgb (0xC0, 0x91, 0x52),
- Color.FromRgb (0x7F, 0xC3, 0xAE),
- Color.FromRgb (0xE0, 0xBF, 0x78),
- Color.FromRgb (0xDC, 0xE8, 0xD4),
- Color.FromRgb (0x65, 0x39, 0x12),
- Color.FromRgb (0x22, 0x69, 0x49),
- Color.FromRgb (0x90, 0x5E, 0x2B),
- Color.FromRgb (0x36, 0x87, 0x5F),
- Color.FromRgb (0x53, 0x9F, 0x81),
- Color.FromRgb (0xBB, 0x85, 0x4C),
- Color.FromRgb (0xD9, 0xB9, 0x6F),
- Color.FromRgb (0x9A, 0xCE, 0xC2),
- Color.FromRgb (0xDF, 0xEF, 0xDE),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
- Color.FromRgb (0xFF, 0xFF, 0xFF),
+ static void LzssUnpack (IBinaryStream input, byte[] output)
+ {
+ int dst = 0;
+ int mask = 0;
+ int ctl = 0;
+ while (dst < output.Length)
+ {
+ mask >>= 1;
+ if (0 == mask)
+ {
+ ctl = input.ReadUInt8();
+ mask = 0x80;
+ }
+ if ((ctl & mask) != 0)
+ {
+ int off = input.ReadUInt16();
+ int count = (off & 0xF) + 3;
+ off >>= 4;
+ int src = dst - off - 1;
+ Binary.CopyOverlapped (output, src, dst, count);
+ dst += count;
+ }
+ else
+ {
+ output[dst++] = input.ReadUInt8();
+ }
}
- */
+ }
+
+ BitmapPalette RetrievePalette ()
+ {
+ // find SYSTEM.LZS file, decompress and read it as text file
+ // find 'P' line that denotes archive name and entry number
+ // if entry number is zero, then it's just a file (possibly compressed)
+ // open referenced file and retrieve palette
+ try
+ {
+ string system_name = "SYSTEM.LZS";
+ if (!File.Exists (system_name))
+ {
+ system_name = @"..\SYSTEM.LZS";
+ if (!File.Exists (system_name))
+ return null;
+ }
+ byte[] system_bin;
+ using (var input = BinaryStream.FromFile (system_name))
+ {
+ int unpacked_size = input.ReadUInt16();
+ input.ReadUInt16();
+ system_bin = new byte[unpacked_size];
+ LzssUnpack (input, system_bin);
+ }
+ string line;
+ using (var mem = new MemoryStream (system_bin))
+ using (var text = new StreamReader (mem, Encodings.cp932))
+ {
+ while ((line = text.ReadLine()) != null)
+ {
+ if (line.Length > 3 && line.StartsWith ("P:"))
+ break;
+ }
+ if (null == line)
+ return null;
+ }
+ var match = PLineRe.Match (line);
+ if (!match.Success)
+ return null;
+ int id;
+ if (!Int32.TryParse (match.Groups[2].Value, out id))
+ return null;
+ var arc_name = Path.Combine (Path.GetDirectoryName (system_name), match.Groups[1].Value);
+ if (0 == id)
+ {
+ using (var file = BinaryStream.FromFile (arc_name))
+ {
+ Stream pal_stream;
+ int unpacked_size = file.ReadUInt16();
+ int packed_size = file.ReadUInt16();
+ if (packed_size + 4 == file.Length)
+ {
+ var pal_data = new byte[unpacked_size];
+ LzssUnpack (file, pal_data);
+ pal_stream = new MemoryStream (pal_data);
+ }
+ else
+ {
+ file.Position = 0;
+ pal_stream = file.AsStream;
+ }
+ int colors = (int)pal_stream.Length / 3;
+ using (pal_stream)
+ return ImageFormat.ReadPalette (pal_stream, colors, PaletteFormat.Rgb);
+ }
+ }
+ else
+ {
+ using (var file = new ArcView (arc_name))
+ {
+ var arc = Nsc.Value.TryOpen (file);
+ if (null == arc)
+ return null;
+ var entry = ((List)arc.Dir)[id-1];
+ using (var input = arc.OpenEntry (entry))
+ return ImageFormat.ReadPalette (input, 0x100, PaletteFormat.Rgb);
+ }
+ }
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ static readonly Regex PLineRe = new Regex (@"^P:([^,]+),(\d+),(\d+)", RegexOptions.Compiled);
+ static readonly ResourceInstance Nsc = new ResourceInstance ("NSC");
+
+ static readonly BitmapPalette DefaultPalette = new BitmapPalette (
// [000317][PIL] Seek -remasters-
+#region colors
new Color[] {
Color.FromRgb (0x00, 0x00, 0x00),
Color.FromRgb (0xFF, 0xFF, 0xFF),
@@ -792,6 +657,7 @@ namespace GameRes.Formats.Nekotaro
Color.FromRgb (0x00, 0x00, 0x00),
Color.FromRgb (0x00, 0x00, 0x00),
}
+#endregion
);
bool m_disposed = false;
diff --git a/Legacy/Nekotaro/ImageNCG.cs b/Legacy/Nekotaro/ImageNCG.cs
new file mode 100644
index 00000000..7b0f5281
--- /dev/null
+++ b/Legacy/Nekotaro/ImageNCG.cs
@@ -0,0 +1,293 @@
+//! \file ImageNCG.cs
+//! \date 2023 Oct 10
+//! \brief Nekotaro Game System image format (PC-98).
+//
+// 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;
+
+namespace GameRes.Formats.Nekotaro
+{
+ [Export(typeof(ImageFormat))]
+ public class NcgFormat : ImageFormat
+ {
+ public override string Tag => "NCG";
+ public override string Description => "Nekotaro Game System image format";
+ public override uint Signature => 0;
+
+ public NcgFormat ()
+ {
+ Signatures = new[] { 0xC8500000u, 0u };
+ }
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (4);
+ int left = header[0] << 3;
+ int top = header[1] << 1;
+ int width = header[2] << 3;
+ int height = header[3] << 1;
+ int right = left + width;
+ int bottom = top + height;
+ if (right > 640 || bottom > 400 || 0 == width || 0 == height)
+ return null;
+ return new ImageMetaData {
+ Width = (uint)width,
+ Height = (uint)height,
+ OffsetX = left,
+ OffsetY = top,
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new NcgReader (file, info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("NcgFormat.Write not implemented");
+ }
+ }
+
+ internal class NcgReader
+ {
+ IBinaryStream m_input;
+ ImageMetaData m_info;
+
+ public NcgReader (IBinaryStream input, ImageMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ }
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 4;
+ var palette = ReadPalette();
+ int width = m_info.iWidth;
+ int height = m_info.iHeight;
+ int output_stride = width;
+ var pixels = new byte[output_stride * height];
+ int quart_width = width / 4;
+ int half_height = height / 2;
+ var blockmap = new bool[quart_width * half_height];
+ var bits1 = new byte[8];
+ var bits2 = new byte[8];
+ byte ctl;
+ int dst, pblk;
+ do
+ {
+ for (int i = 0; i < 8; ++i)
+ bits1[i] = bits2[i] = 0;
+ for (int shift = 0; shift < 4; ++shift)
+ {
+ byte bit = (byte)(1 << shift);
+ FillBits (bits1, bit);
+ FillBits (bits2, bit);
+ }
+ for (;;)
+ {
+ ctl = m_input.ReadUInt8();
+ if (0xFF == ctl || 0x7F == ctl)
+ break;
+ int pos = (ctl & 0x3F) << 8 | m_input.ReadUInt8();
+ int x = (pos % 80) << 3;
+ int y = (pos / 80) << 1;
+ dst = width * y + x;
+ pblk = x / 4 + quart_width * (y / 2);
+ switch (ctl >> 6)
+ {
+ case 0:
+ {
+ int w_count = m_input.ReadUInt8();
+ int h_count = m_input.ReadUInt8();
+ int gap = quart_width - 2 * w_count;
+ while (h_count --> 0)
+ {
+ for (int i = 0; i < w_count; ++i)
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ pixels[dst+width] = bits2[j];
+ pixels[dst++] = bits1[j];
+ }
+ blockmap[pblk++] = true;
+ blockmap[pblk++] = true;
+ }
+ pblk += gap;
+ dst += 2 * width - 8 * w_count;
+ }
+ break;
+ }
+ case 1:
+ {
+ int count = m_input.ReadUInt8();
+ while (count --> 0)
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ pixels[dst+width] = bits2[j];
+ pixels[dst++] = bits1[j];
+ }
+ blockmap[pblk++] = true;
+ blockmap[pblk++] = true;
+ }
+ break;
+ }
+ case 2:
+ {
+ int count = m_input.ReadUInt8();
+ while (count --> 0)
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ pixels[dst+width] = bits2[j];
+ pixels[dst++] = bits1[j];
+ }
+ blockmap[pblk ] = true;
+ blockmap[pblk+1] = true;
+ dst += 2 * width - 8;
+ pblk += quart_width;
+ }
+ break;
+ }
+ case 3:
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ pixels[dst+width] = bits2[j];
+ pixels[dst++] = bits1[j];
+ }
+ blockmap[pblk ] = true;
+ blockmap[pblk+1] = true;
+ break;
+ }
+ }
+ }
+ }
+ while (ctl != 0xFF);
+ do
+ {
+ for (int i = 0; i < 8; ++i)
+ bits1[i] = 0;
+ for (int shift = 0; shift < 4; ++shift)
+ FillBits (bits1, (byte)(1 << shift));
+ for (;;)
+ {
+ ctl = m_input.ReadUInt8();
+ if (0xFF == ctl || 0xFE == ctl)
+ break;
+ int pos = (ctl & 0x7F) << 8 | m_input.ReadUInt8();
+ dst = 4 * (pos % 160) + width * 2 * (pos / 160);
+ pblk = (pos % 160) + quart_width * (pos / 160);
+ if ((ctl & 0x80) == 0)
+ {
+ int count = m_input.ReadUInt8();
+ while (count --> 0)
+ {
+ for (int j = 0; j < 4; ++j)
+ {
+ pixels[dst+width] = bits1[j+4];
+ pixels[dst++] = bits1[j];
+ }
+ blockmap[pblk] = true;
+ pblk += quart_width;
+ dst += 2 * width - 4;
+ }
+ }
+ else
+ {
+ for (int j = 0; j < 4; ++j)
+ {
+ pixels[dst+width] = bits1[j+4];
+ pixels[dst++] = bits1[j];
+ }
+ blockmap[pblk] = true;
+ }
+ }
+ }
+ while (ctl != 0xFF);
+ dst = 0;
+ pblk = 0;
+ for (int y = 0; y < half_height; ++y)
+ {
+ for (int x = 0; x < quart_width; ++x)
+ {
+ if (blockmap[pblk++])
+ {
+ dst += 4;
+ }
+ else
+ {
+ for (int i = 0; i < 8; ++i)
+ bits1[i] = 0;
+ for (int shift = 0; shift < 4; ++shift)
+ FillBits (bits1, (byte)(1 << shift));
+ for (int j = 0; j < 4; ++j)
+ {
+ pixels[dst+width] = bits1[j+4];
+ pixels[dst++] = bits1[j];
+ }
+ }
+ }
+ dst += width;
+ }
+ return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, output_stride);
+ }
+
+ void FillBits (byte[] bits, byte bit)
+ {
+ sbyte s = m_input.ReadInt8();
+ for (int i = 0; i < 8; ++i)
+ {
+ if (s < 0)
+ bits[i] |= bit;
+ s <<= 1;
+ }
+ }
+
+ static readonly string PaletteKey = "NEKOTARO";
+
+ BitmapPalette ReadPalette ()
+ {
+ int k = 0;
+ var colors = new Color[16];
+ for (int c = 0; c < 16; ++c)
+ {
+ int g = m_input.ReadUInt8();
+ int r = m_input.ReadUInt8();
+ int b = m_input.ReadUInt8();
+ b = (~b - PaletteKey[k++ & 7]) & 0xFF;
+ r = (~r - PaletteKey[k++ & 7]) & 0xFF;
+ g = (~g - PaletteKey[k++ & 7]) & 0xFF;
+ colors[c] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+}
diff --git a/Legacy/Neon/ArcAR2.cs b/Legacy/Neon/ArcAR2.cs
new file mode 100644
index 00000000..4ce6b765
--- /dev/null
+++ b/Legacy/Neon/ArcAR2.cs
@@ -0,0 +1,90 @@
+//! \file ArcAR2.cs
+//! \date 2023 Oct 17
+//! \brief Neon 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;
+
+// [990527][Neon] Onegai! Maid☆Roid
+
+namespace GameRes.Formats.Neon
+{
+ [Export(typeof(ArchiveFormat))]
+ public class Ar2Opener : ArchiveFormat
+ {
+ public override string Tag => "AR2/NEON";
+ public override string Description => "Neon resource archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ const byte DefaultKey = 0x55;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ uint uKey = DefaultKey | DefaultKey << 16;
+ uKey |= uKey << 8;
+ if (file.MaxOffset <= 0x10
+ || file.View.ReadUInt32 (8) != uKey
+ || file.View.ReadUInt32 (0) != file.View.ReadUInt32 (4)
+ || (file.View.ReadUInt32 (0xC) ^ uKey) > 0x100)
+ return null;
+ using (var stream = file.CreateStream())
+ using (var input = new XoredStream (stream, DefaultKey))
+ {
+ var buffer = new byte[0x100];
+ var dir = new List();
+ while (0x10 == input.Read (buffer, 0, 0x10))
+ {
+ uint size = buffer.ToUInt32 (0);
+// uint orig_size = buffer.ToUInt32 (4); // original size?
+// uint extra = buffer.ToUInt32 (8); // header size/compression?
+ int name_length = buffer.ToInt32 (0xC);
+ if (0 == size && 0 == name_length)
+ continue;
+ if (name_length <= 0 || name_length > buffer.Length)
+ return null;
+ input.Read (buffer, 0, name_length);
+ var name = Encodings.cp932.GetString (buffer, 0, name_length);
+ var entry = Create (name);
+ entry.Offset = input.Position;
+ entry.Size = size;
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ dir.Add (entry);
+ input.Seek (size, SeekOrigin.Current);
+ }
+ return new ArcFile (file, this, dir);
+ }
+ }
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ var input = arc.File.CreateStream (entry.Offset, entry.Size);
+ return new XoredStream (input, DefaultKey);
+ }
+ }
+}
diff --git a/Legacy/Pearl/ArcARY.cs b/Legacy/Pearl/ArcARY.cs
new file mode 100644
index 00000000..7e7020c4
--- /dev/null
+++ b/Legacy/Pearl/ArcARY.cs
@@ -0,0 +1,83 @@
+//! \file ArcARY.cs
+//! \date 2023 Sep 23
+//! \brief Pearl 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.Pearl
+{
+ // implementation based on BMX/TRIANGLE
+ // exact same layout, but doesn't have compressed entries.
+
+ [Export(typeof(ArchiveFormat))]
+ public class AryOpener : ArchiveFormat
+ {
+ public override string Tag => "ARY";
+ public override string Description => "Pearl Soft resource archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ int count = file.View.ReadInt32 (0);
+ if (!IsSaneCount (count))
+ return null;
+ uint index_size = (uint)count * 4 + 8;
+ if (index_size > file.View.Reserve (0, index_size))
+ return null;
+ uint index_offset = 4;
+ uint offset = file.View.ReadUInt32 (index_offset);
+ if (offset != index_size)
+ return null;
+ uint last_offset = file.View.ReadUInt32 (index_size - 4);
+ if (last_offset != file.MaxOffset)
+ return null;
+ var base_name = Path.GetFileNameWithoutExtension (file.Name);
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ index_offset += 4;
+ var entry = new Entry {
+ Name = string.Format ("{0}#{1:D4}", base_name, i),
+ Offset = offset,
+ };
+ offset = file.View.ReadUInt32 (index_offset);
+ entry.Size = (uint)(offset - entry.Offset);
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ dir.Add (entry);
+ }
+ foreach (var entry in dir)
+ {
+ uint signature = file.View.ReadUInt32 (entry.Offset);
+ entry.ChangeType (AutoEntry.DetectFileType (signature));
+ }
+ return new ArcFile (file, this, dir);
+ }
+ }
+}
diff --git a/Legacy/Pearl/ImagePL4.cs b/Legacy/Pearl/ImagePL4.cs
new file mode 100644
index 00000000..2d6bd0af
--- /dev/null
+++ b/Legacy/Pearl/ImagePL4.cs
@@ -0,0 +1,382 @@
+//! \file ImagePL4.cs
+//! \date 2023 Sep 23
+//! \brief Pearl Soft 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.Utility;
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [980424][Pearl Soft] Watashi
+
+namespace GameRes.Formats.Pearl
+{
+ internal class Pl4MetaData : ImageMetaData
+ {
+ public ushort CompressionMethod;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class Pl4Format : ImageFormat
+ {
+ public override string Tag => "PL4";
+ public override string Description => "Pearl Soft image format";
+ public override uint Signature => 0x20344C50; // 'PL4 '
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x10);
+ int version = header.ToUInt16 (4);
+ if (version != 1)
+ return null;
+ var info = new Pl4MetaData {
+ Width = header.ToUInt16 (0xC) * 8u,
+ Height = header.ToUInt16 (0xE),
+ CompressionMethod = header.ToUInt16 (6),
+ BPP = 8,
+ };
+ if (info.CompressionMethod > 1)
+ return null;
+ return info;
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new Pl4Reader (file, (Pl4MetaData)info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("Pl4Format.Write not implemented");
+ }
+ }
+
+ internal class Pl4Reader
+ {
+ IBinaryStream m_input;
+ Pl4MetaData m_info;
+ int m_stride;
+
+ public Pl4Reader (IBinaryStream input, Pl4MetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ m_stride = m_info.iWidth;
+ }
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 0x10;
+ var palette = ReadPalette (16);
+ var pixels = new byte[m_stride * m_info.iHeight];
+ m_input.Position = 0x40;
+ if (m_info.CompressionMethod == 0)
+ UnpackV0 (pixels);
+ else if (m_info.CompressionMethod == 1)
+ UnpackV1 (pixels);
+ return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, m_stride);
+ }
+
+ BitmapPalette ReadPalette (int colors)
+ {
+ var color_data = m_input.ReadBytes (colors * 3);
+ var color_map = new Color[colors];
+ int src = 0;
+ for (int i = 0; i < colors; ++i)
+ {
+ color_map[i] = Color.FromRgb ((byte)(color_data[src ] * 0x11),
+ (byte)(color_data[src+1] * 0x11),
+ (byte)(color_data[src+2] * 0x11));
+ src += 3;
+ }
+ return new BitmapPalette (color_map);
+ }
+
+ void UnpackV0 (byte[] output)
+ {
+ int height = m_info.iHeight;
+ int x = m_info.iWidth / 4;
+ int output_size = height * m_stride;
+ int row = 0;
+ int dst = 0;
+ int ctl, word;
+ while ((ctl = m_input.ReadByte()) != -1)
+ {
+ byte next = m_input.ReadUInt8();
+ if (0x98 == ctl)
+ {
+ ctl = m_input.ReadUInt8();
+ if (0 == next)
+ {
+ next = m_input.ReadUInt8();
+ }
+ else
+ {
+ word = ctl << 8 | next;
+ int count = ((word >> 1) & 0x1F) + 2;
+ int src_y;
+ int src_x = Math.DivRem ((word >> 6) + 1, height, out src_y);
+ int src = dst - src_y * m_stride - 4 * src_x;
+ src_y = row - src_y;
+ if (src_y < 0)
+ {
+ src_y += height;
+ src += output_size - 4;
+ }
+ while (count --> 0)
+ {
+ output[dst ] = output[src ];
+ output[dst+1] = output[src+1];
+ output[dst+2] = output[src+2];
+ output[dst+3] = output[src+3];
+ dst += m_stride;
+ if (++row >= height)
+ {
+ row = 0;
+ dst -= output_size - 4;
+ if (--x <= 0)
+ return;
+ }
+ src += m_stride;
+ if (++src_y >= height)
+ {
+ src_y = 0;
+ src -= output_size - 4;
+ }
+ }
+ continue;
+ }
+ }
+ word = next << 8 | ctl;
+ int px = 0;
+ if ((word & 0x1000) != 0) px = 0x01000000;
+ if ((word & 0x2000) != 0) px |= 0x00010000;
+ if ((word & 0x4000) != 0) px |= 0x00000100;
+ if ((word & 0x8000) != 0) px |= 0x00000001;
+ if ((word & 0x0100) != 0) px |= 0x02000000;
+ if ((word & 0x0200) != 0) px |= 0x00020000;
+ if ((word & 0x0400) != 0) px |= 0x00000200;
+ if ((word & 0x0800) != 0) px |= 0x00000002;
+ if ((word & 0x0010) != 0) px |= 0x04000000;
+ if ((word & 0x0020) != 0) px |= 0x00040000;
+ if ((word & 0x0040) != 0) px |= 0x00000400;
+ if ((word & 0x0080) != 0) px |= 0x00000004;
+ if ((word & 0x0001) != 0) px |= 0x08000000;
+ if ((word & 0x0002) != 0) px |= 0x00080000;
+ if ((word & 0x0004) != 0) px |= 0x00000800;
+ if ((word & 0x0008) != 0) px |= 0x00000008;
+ LittleEndian.Pack (px, output, dst);
+ dst += m_stride;
+ if (++row >= height)
+ {
+ row = 0;
+ dst -= output_size - 4;
+ if (--x <= 0)
+ break;
+ }
+ }
+ }
+
+ byte[] m_pixelBuffer;
+ MsbBitStream m_bits;
+
+ void UnpackV1 (byte[] output)
+ {
+ m_pixelBuffer = InitLineBuffer();
+ int height = m_info.iHeight;
+ int dst = 0;
+ int output_size = m_stride * height;
+ int x = m_info.iWidth / 8;
+ int y = 0;
+ using (m_bits = new MsbBitStream (m_input.AsStream, true))
+ {
+ int p1 = 0, p2 = 0, p3 = 0, p4 = 0;
+ int ctl_bit;
+ while ((ctl_bit = m_bits.GetNextBit()) != -1)
+ {
+ if (ctl_bit != 0)
+ {
+ int src = dst;
+ int src_y = y;
+ switch (m_bits.GetBits (2))
+ {
+ case 0:
+ src_y = y - 2;
+ src = dst - 2 * m_stride;
+ break;
+ case 1:
+ src_y = y - 1;
+ src = dst - m_stride;
+ break;
+ case 2:
+ src_y = y - 4;
+ src = dst - 4 * m_stride;
+ break;
+ case 3:
+ src = dst - 8;
+ break;
+ }
+ if (src_y < 0)
+ {
+ src_y += height;
+ src += output_size - 8;
+ }
+ int count_length = 0;
+ while (m_bits.GetNextBit() == 0)
+ ++count_length;
+ int count = 1;
+ if (count_length != 0)
+ {
+ count = m_bits.GetBits (count_length) | 1 << count_length;
+ }
+ while (count --> 0)
+ {
+ Buffer.BlockCopy (output, src, output, dst, 8);
+ dst += m_stride;
+ if (++y >= height)
+ {
+ y = 0;
+ dst -= output_size - 8;
+ if (--x <= 0)
+ return;
+ }
+ src += m_stride;
+ if (++src_y >= height)
+ {
+ src_y = 0;
+ src -= output_size - 8;
+ }
+ }
+ }
+ else
+ {
+ int px1 = 0;
+ int px2 = 0;
+ p1 = UpdatePixel (p1);
+ p2 = UpdatePixel (p2);
+ p3 = UpdatePixel (p3);
+ p4 = UpdatePixel (p4);
+ if ((p1 & 0x80) != 0) px1 = 0x00000001;
+ if ((p1 & 0x40) != 0) px1 |= 0x00000100;
+ if ((p1 & 0x20) != 0) px1 |= 0x00010000;
+ if ((p1 & 0x10) != 0) px1 |= 0x01000000;
+ if ((p1 & 0x08) != 0) px2 = 0x00000001;
+ if ((p1 & 0x04) != 0) px2 |= 0x00000100;
+ if ((p1 & 0x02) != 0) px2 |= 0x00010000;
+ if ((p1 & 0x01) != 0) px2 |= 0x01000000;
+ if ((p2 & 0x80) != 0) px1 |= 0x00000002;
+ if ((p2 & 0x40) != 0) px1 |= 0x00000200;
+ if ((p2 & 0x20) != 0) px1 |= 0x00020000;
+ if ((p2 & 0x10) != 0) px1 |= 0x02000000;
+ if ((p2 & 0x08) != 0) px2 |= 0x00000002;
+ if ((p2 & 0x04) != 0) px2 |= 0x00000200;
+ if ((p2 & 0x02) != 0) px2 |= 0x00020000;
+ if ((p2 & 0x01) != 0) px2 |= 0x02000000;
+ if ((p3 & 0x80) != 0) px1 |= 0x00000004;
+ if ((p3 & 0x40) != 0) px1 |= 0x00000400;
+ if ((p3 & 0x20) != 0) px1 |= 0x00040000;
+ if ((p3 & 0x10) != 0) px1 |= 0x04000000;
+ if ((p3 & 0x08) != 0) px2 |= 0x00000004;
+ if ((p3 & 0x04) != 0) px2 |= 0x00000400;
+ if ((p3 & 0x02) != 0) px2 |= 0x00040000;
+ if ((p3 & 0x01) != 0) px2 |= 0x04000000;
+ if ((p4 & 0x80) != 0) px1 |= 0x00000008;
+ if ((p4 & 0x40) != 0) px1 |= 0x00000800;
+ if ((p4 & 0x20) != 0) px1 |= 0x00080000;
+ if ((p4 & 0x10) != 0) px1 |= 0x08000000;
+ if ((p4 & 0x08) != 0) px2 |= 0x00000008;
+ if ((p4 & 0x04) != 0) px2 |= 0x00000800;
+ if ((p4 & 0x02) != 0) px2 |= 0x00080000;
+ if ((p4 & 0x01) != 0) px2 |= 0x08000000;
+ LittleEndian.Pack (px1, output, dst);
+ LittleEndian.Pack (px2, output, dst+4);
+ dst += m_stride;
+ if (++y >= height)
+ {
+ y = 0;
+ dst -= output_size - 8;
+ if (--x <= 0)
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ int UpdatePixel (int pixel)
+ {
+ byte nibble = GetNextPixel (pixel);
+ return GetNextPixel (nibble) | nibble << 4;
+ }
+
+ byte GetNextPixel (int pixel)
+ {
+ int bits = GetPixelBits();
+ int prior = (pixel & 0xF) << 4;
+ byte next = m_pixelBuffer[prior+bits];
+ int pos = prior + bits;
+ if (bits == 0)
+ return next;
+ while (bits --> 0)
+ {
+ m_pixelBuffer[pos] = m_pixelBuffer[pos - 1];
+ --pos;
+ }
+ return m_pixelBuffer[prior] = next;
+ }
+
+ int GetPixelBits ()
+ {
+ if (m_bits.GetNextBit() != 0)
+ {
+ return m_bits.GetBits (1);
+ }
+ else if (m_bits.GetNextBit() != 0)
+ {
+ return m_bits.GetBits (1) + 2;
+ }
+ else if (m_bits.GetNextBit() != 0)
+ {
+ return m_bits.GetBits (2) + 4;
+ }
+ else
+ {
+ return m_bits.GetBits (3) + 8;
+ }
+ }
+
+ static byte[] InitLineBuffer ()
+ {
+ var buffer = new byte[256];
+ for (int i = 0; i < 256; ++i)
+ {
+ buffer[i] = (byte)((i + (i >> 4)) & 0xF);
+ }
+ return buffer;
+ }
+ }
+}
diff --git a/Legacy/Pias/ArcDAT.cs b/Legacy/Pias/ArcDAT.cs
index e5c59263..34c999fe 100644
--- a/Legacy/Pias/ArcDAT.cs
+++ b/Legacy/Pias/ArcDAT.cs
@@ -2,7 +2,7 @@
//! \date 2022 May 24
//! \brief Pias resource archive.
//
-// Copyright (C) 2022 by morkt
+// Copyright (C) 2022-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
@@ -36,77 +36,141 @@ using System.Windows.Media;
namespace GameRes.Formats.Pias
{
+ enum ResourceType
+ {
+ Undefined = -1,
+ Graphics = 1,
+ Sound = 2,
+ }
+
+ internal class IndexReader
+ {
+ internal const bool UseOffsetAsName = true;
+
+ protected ArcView m_arc;
+ protected ResourceType m_res;
+ protected List m_dir;
+
+ public bool IsEncrypted { get; protected set; }
+
+ public IndexReader (ArcView arc, ResourceType res)
+ {
+ m_arc = arc;
+ m_res = res;
+ m_dir = null;
+ }
+
+ public List GetIndex ()
+ {
+ if (m_res > 0)
+ {
+ var text_name = VFS.ChangeFileName (m_arc.Name, "text.dat");
+ if (!VFS.FileExists (text_name))
+ return null;
+ IBinaryStream input = VFS.OpenBinaryStream (text_name);
+ try
+ {
+ if (DatOpener.EncryptedSignatures.Contains (input.Signature))
+ return null;
+ var reader = new TextReader (input);
+ m_dir = reader.GetResourceList ((int)m_res);
+ }
+ finally
+ {
+ input.Dispose();
+ }
+ }
+ if (null == m_dir)
+ m_dir = new List();
+ if (!FillEntries())
+ return null;
+ return m_dir;
+ }
+
+ protected bool FillEntries ()
+ {
+ uint header_size = 4;
+ string entry_type = "audio";
+ if (ResourceType.Graphics == m_res)
+ {
+ header_size = 8;
+ entry_type = "image";
+ }
+ for (int i = m_dir.Count - 1; i >= 0; --i)
+ {
+ var entry = m_dir[i];
+ entry.Size = m_arc.View.ReadUInt32 (entry.Offset) + header_size;
+ entry.Name = i.ToString("D4");
+ entry.Type = entry_type;
+ }
+ var known_offsets = new HashSet (m_dir.Select (e => e.Offset));
+ long offset = 0;
+ while (offset < m_arc.MaxOffset)
+ {
+ uint entry_size = m_arc.View.ReadUInt32(offset);
+ if (uint.MaxValue == entry_size)
+ {
+ entry_size = 4;
+ }
+ else
+ {
+ entry_size += header_size;
+ if (!known_offsets.Contains (offset))
+ {
+ var entry = new Entry {
+ Name = GetName (offset, m_dir.Count),
+ Type = entry_type,
+ Offset = offset,
+ Size = entry_size,
+ };
+ if (!entry.CheckPlacement (m_arc.MaxOffset))
+ return false;
+ m_dir.Add (entry);
+ }
+ }
+ offset += entry_size;
+ }
+ return true;
+ }
+
+ internal string GetName (long offset, int num)
+ {
+ return UseOffsetAsName ? offset.ToString ("D8") : num.ToString("D4");
+ }
+ }
+
[Export(typeof(ArchiveFormat))]
public class DatOpener : ArchiveFormat
{
- public override string Tag { get { return "DAT/PIAS"; } }
- public override string Description { get { return "Pias 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 => "DAT/PIAS";
+ public override string Description => "Pias resource archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public DatOpener ()
+ {
+ Signatures = new[] { 0x0002C026u, 0u };
+ }
+
+ internal static readonly HashSet EncryptedSignatures = new HashSet { 0x03184767u };
public override ArcFile TryOpen (ArcView file)
{
var arc_name = Path.GetFileName (file.Name).ToLowerInvariant();
- string entry_type = null;
- if ("voice.dat" == arc_name || "music.dat" == arc_name || "sound.dat" == arc_name)
- entry_type = "audio";
+ ResourceType resource_type = ResourceType.Undefined;
+ if ("sound.dat" == arc_name)
+ resource_type = ResourceType.Sound;
else if ("graph.dat" == arc_name)
- entry_type = "image";
- else
+ resource_type = ResourceType.Graphics;
+ else if ("voice.dat" != arc_name && "music.dat" != arc_name)
return null;
- int resource_type = -1;
- if ("graph.dat" == arc_name)
- resource_type = 1;
- else if ("sound.dat" == arc_name)
- resource_type = 2;
-
- uint header_size = 1 == resource_type ? 8u : 4u;
- List dir = null;
- if (resource_type > 0)
- {
- var text_name = VFS.ChangeFileName (file.Name, "text.dat");
- if (!VFS.FileExists (text_name))
- return null;
- using (var text_dat = VFS.OpenBinaryStream (text_name))
- {
- var reader = new TextReader (text_dat);
- dir = reader.GetResourceList (resource_type);
- if (dir != null)
- {
- for (int i = dir.Count - 1; i >= 0; --i)
- {
- var entry = dir[i];
- entry.Size = file.View.ReadUInt32 (entry.Offset) + header_size;
- entry.Name = i.ToString("D4");
- entry.Type = entry_type;
- }
- }
- }
- }
+ var index = new IndexReader (file, resource_type);
+ var dir = index.GetIndex();
if (null == dir)
- dir = new List();
- var known_offsets = new HashSet (dir.Select (e => e.Offset));
- long offset = 0;
- while (offset < file.MaxOffset)
- {
- uint entry_size = file.View.ReadUInt32(offset) + header_size;
- if (!known_offsets.Contains (offset))
- {
- var entry = new Entry {
- Name = dir.Count.ToString("D4"),
- Type = entry_type,
- Offset = offset,
- Size = entry_size,
- };
- if (!entry.CheckPlacement (file.MaxOffset))
- return null;
- dir.Add (entry);
- }
- offset += entry_size;
- }
+ return null;
return new ArcFile (file, this, dir);
}
@@ -120,7 +184,6 @@ namespace GameRes.Formats.Pias
{
if (entry.Type != "audio")
return base.OpenEntry (arc, entry);
- uint size = arc.File.View.ReadUInt32 (entry.Offset);
var format = new WaveFormat
{
FormatTag = 1,
@@ -138,6 +201,12 @@ namespace GameRes.Formats.Pias
format.BlockAlign = 1;
}
format.SetBPS();
+ return OpenAudioEntry (arc, entry, format);
+ }
+
+ public Stream OpenAudioEntry (ArcFile arc, Entry entry, WaveFormat format)
+ {
+ uint size = arc.File.View.ReadUInt32 (entry.Offset);
byte[] header;
using (var buffer = new MemoryStream())
{
diff --git a/Legacy/Pias/EncryptedGraphDat.cs b/Legacy/Pias/EncryptedGraphDat.cs
new file mode 100644
index 00000000..b54a6578
--- /dev/null
+++ b/Legacy/Pias/EncryptedGraphDat.cs
@@ -0,0 +1,377 @@
+//! \file EncryptedGraphDat.cs
+//! \date 2023 Oct 20
+//! \brief Pias encrypted 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;
+using System.Linq;
+using System.Windows.Media;
+
+// [000526][Pias] Ningyou no Hako
+
+namespace GameRes.Formats.Pias
+{
+ internal class PiasEncryptedArchive : ArcFile
+ {
+ public PiasEncryptedArchive (ArcView arc, ArchiveFormat impl, ICollection dir)
+ : base (arc, impl, dir)
+ {
+ }
+ }
+
+ internal class EncryptedIndexReader : IndexReader
+ {
+ public EncryptedIndexReader (ArcView arc, ResourceType res) : base (arc, res)
+ {
+ }
+
+ new public List GetIndex ()
+ {
+ if (m_res > 0)
+ {
+ var text_name = VFS.ChangeFileName (m_arc.Name, "text.dat");
+ if (!VFS.FileExists (text_name))
+ return null;
+ IBinaryStream input = VFS.OpenBinaryStream (text_name);
+ try
+ {
+ if (!DatOpener.EncryptedSignatures.Contains (input.Signature))
+ return null;
+
+ input.Position = 4;
+ var rnd = new KeyGenerator (1);
+ rnd.Seed (input.Signature);
+ var crypto = new InputCryptoStream (input.AsStream, new PiasTransform (rnd));
+ input = new BinaryStream (crypto, text_name);
+
+ var reader = new TextReader (input);
+ m_dir = reader.GetResourceList ((int)m_res);
+ }
+ finally
+ {
+ input.Dispose();
+ }
+ }
+ IsEncrypted = ResourceType.Graphics == m_res;
+ if (null == m_dir)
+ {
+ m_dir = new List();
+ }
+ if (!IsEncrypted)
+ {
+ if (!FillEntries())
+ return null;
+ return m_dir;
+ }
+ var buffer = new byte[4];
+ var key = new KeyGenerator (0);
+ for (int i = m_dir.Count - 1; i >= 0; --i)
+ {
+ var entry = m_dir[i];
+ uint seed = m_arc.View.ReadUInt32 (entry.Offset);
+ m_arc.View.Read (entry.Offset+4, buffer, 0, 4);
+ key.Seed (seed);
+ Decrypt (buffer, 0, 4, key);
+ entry.Size = (buffer.ToUInt32 (0) & 0xFFFFFu) + 8u;
+ entry.Name = GetName (entry.Offset, i);
+ entry.Type = "image";
+ }
+ var known_offsets = new HashSet (m_dir.Select (e => e.Offset));
+ long offset = 0;
+ while (offset < m_arc.MaxOffset)
+ {
+ uint seed = m_arc.View.ReadUInt32 (offset);
+ m_arc.View.Read (offset+4, buffer, 0, 4);
+ key.Seed (seed);
+ Decrypt (buffer, 0, 4, key);
+ uint entry_size = (buffer.ToUInt32 (0) & 0xFFFFFu) + 8u;
+ if (!known_offsets.Contains (offset))
+ {
+ var entry = new Entry {
+ Name = GetName (offset, m_dir.Count) + "_",
+ Type = "image",
+ Offset = offset,
+ Size = entry_size,
+ };
+ if (!entry.CheckPlacement (m_arc.MaxOffset))
+ return null;
+ m_dir.Add (entry);
+ }
+ offset += entry_size + 4;
+ }
+ return m_dir;
+ }
+
+ internal static void Decrypt (byte[] data, int pos, int length, KeyGenerator key)
+ {
+ for (int i = 0; i < length; ++i)
+ {
+ data[pos+i] ^= (byte)key.Next();
+ }
+ }
+ }
+
+ [Export(typeof(ArchiveFormat))]
+ public class EncryptedDatOpener : DatOpener
+ {
+ public override string Tag => "DAT/PIAS/ENC";
+ public override string Description => "Pias encrypted resource archive";
+ public override uint Signature => 0;
+ public override bool CanWrite => false;
+
+ public EncryptedDatOpener ()
+ {
+ Signatures = new[] { 0x02F3A62Bu, 0u };
+ }
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ var arc_name = Path.GetFileName (file.Name).ToLowerInvariant();
+
+ ResourceType resource_type = ResourceType.Undefined;
+ if ("sound.dat" == arc_name)
+ resource_type = ResourceType.Sound;
+ else if ("graph.dat" == arc_name)
+ resource_type = ResourceType.Graphics;
+ else
+ return null;
+
+ var index = new EncryptedIndexReader (file, resource_type);
+ var dir = index.GetIndex();
+ if (null == dir)
+ return null;
+ if (index.IsEncrypted)
+ return new PiasEncryptedArchive (file, this, dir);
+ else
+ return new ArcFile (file, this, dir);
+ }
+
+ public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
+ {
+ var input = arc.OpenBinaryEntry (entry);
+ return new EncryptedGraphDecoder (input);
+ }
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ if (entry.Type != "audio")
+ return OpenEncrypted (arc, entry);
+ var format = new WaveFormat
+ {
+ FormatTag = 1,
+ Channels = 2,
+ SamplesPerSecond = 22050,
+ AverageBytesPerSecond = 88200,
+ BitsPerSample = 16,
+ BlockAlign = 4,
+ };
+ return OpenAudioEntry (arc, entry, format);
+ }
+
+ public Stream OpenEncrypted (ArcFile arc, Entry entry)
+ {
+ uint seed = arc.File.View.ReadUInt32 (entry.Offset);
+ var stream = arc.File.CreateStream (entry.Offset+4, entry.Size);
+ var key = new KeyGenerator (0);
+ key.Seed (seed);
+ return new InputCryptoStream (stream, new PiasTransform (key));
+ }
+ }
+
+ internal class KeyGenerator
+ {
+ int m_type;
+ uint m_seed;
+
+ // 0 -> graph.dat
+ // 1 -> text.dat
+ // 2 -> save.dat
+
+ public KeyGenerator (int type)
+ {
+ m_type = type;
+ m_seed = 0;
+ }
+
+ public void Seed (uint seed)
+ {
+ m_seed = seed;
+ }
+
+ public uint Next ()
+ {
+ uint y, x;
+ if (0 == m_type)
+ {
+ x = 0xD22;
+ y = 0x849;
+ }
+ else if (1 == m_type)
+ {
+ x = 0xF43;
+ y = 0x356B;
+ }
+ else if (2 == m_type)
+ {
+ x = 0x292;
+ y = 0x57A7;
+ }
+ else
+ {
+ x = 0;
+ y = 0;
+ }
+ uint a = x + m_seed * y;
+ uint b = 0;
+ if ((a & 0x400000) != 0)
+ b = 1;
+ if ((a & 0x400) != 0)
+ b ^= 1;
+ if ((a & 1) != 0)
+ b ^= 1;
+ m_seed = (a >> 1) | (b != 0 ? 0x80000000u : 0u);
+ return m_seed;
+ }
+ }
+
+ internal sealed class PiasTransform : ByteTransform
+ {
+ KeyGenerator m_key;
+
+ public PiasTransform (KeyGenerator key)
+ {
+ m_key = key;
+ }
+
+ public override int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount,
+ byte[] outputBuffer, int outputOffset)
+ {
+ for (int i = 0; i < inputCount; ++i)
+ {
+ outputBuffer[outputOffset++] = (byte)(m_key.Next() ^ inputBuffer[inputOffset+i]);
+ }
+ return inputCount;
+ }
+ }
+
+ internal class EncryptedGraphDecoder : BinaryImageDecoder
+ {
+ public EncryptedGraphDecoder (IBinaryStream input) : base (input, new ImageMetaData { BPP = 16 })
+ {
+ m_input.ReadInt32(); // skip size
+ Info.Width = m_input.ReadUInt16() & 0x3FFu;
+ Info.Height = m_input.ReadUInt16() & 0x3FFu;
+ }
+
+ protected override ImageData GetImageData ()
+ {
+ m_input.Position = 8;
+ int width = Info.iWidth;
+ int output_size = width * Info.iHeight;
+ var pixels = new ushort[output_size];
+ var prev = new int[8];
+ int dst = 0;
+ while (dst < output_size)
+ {
+ int count;
+ ushort w = m_input.ReadUInt16();
+ if ((w & 0x2000) != 0)
+ {
+ count = (((w >> 1) & 0x6000 | w & 0x1000) >> 12) + 1;
+ int idx = prev[count - 1]++ % 19;
+ int off = w & 0xFFF;
+ bool step_back = (off & StepBackMask[idx]) != 0;
+ bool step_vertical = (off & StepVerticalMask[idx]) != 0;
+ int m = (off & OffsetMask0[idx]) | (off >> 1) & (OffsetMask1[idx] >> 1) | (off >> 2) & (OffsetMask2[idx] >> 2);
+ int n = 16 - width * ((m + 16) / 32);
+ int p = m + 16;
+ int hidword = p >> 31;
+ p = (p & ~0xFF) | ((hidword & 0xFF) ^ (m + 16));
+ int src = dst + n - (hidword ^ ((p - hidword) & 0x1F) - hidword);
+ count = Math.Min (count, output_size - dst);
+ if (step_vertical)
+ {
+ if (step_back)
+ {
+ for (int i = 0; i < count; ++i)
+ {
+ pixels[dst+i] = pixels[src];
+ src -= width;
+ }
+ }
+ else
+ {
+ int step = width;
+ for (int i = 0; i < count; ++i)
+ {
+ pixels[dst+i] = pixels[src];
+ src += width;
+ }
+ }
+ }
+ else if (step_back)
+ {
+ for (int i = 0; i < count; ++i)
+ {
+ pixels[dst+i] = pixels[src--];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < count; ++i)
+ {
+ pixels[dst+i] = pixels[src++];
+ }
+ }
+ }
+ else
+ {
+ pixels[dst] = (ushort)((w >> 1) & 0x6000 | w & 0x1FFF);
+ count = 1;
+ }
+ dst += count;
+ }
+ int stride = width * 2;
+ return ImageData.Create (Info, PixelFormats.Bgr555, null, pixels, stride);
+ }
+
+ static readonly ushort[] OffsetMask2 = {
+ 0, 0x800, 0x0C00, 0x0E00, 0x800, 0x0FC0, 0, 0x0F00, 0x0FF0, 0x0FF0, 0x0C00, 0x0F00, 0x800, 0x0E00, 0x0F00, 0x0C00, 0x0C00, 0x0F80, 0,
+ };
+ static readonly ushort[] OffsetMask1 = {
+ 0, 0, 0, 0x0F0, 0x200, 0x18, 0x7C0, 0x7E, 0, 0, 0x1FE, 0x7E, 0x3E0, 0x0C0, 0x78, 0x1F0, 0, 0x30, 0x7E0,
+ };
+ static readonly ushort[] OffsetMask0 = {
+ 0x3FF, 0x1FF, 0x0FF, 7, 0x0FF, 3, 0x1F, 0, 3, 3, 0, 0, 0x0F, 0x1F, 3, 7, 0x0FF, 7, 0x0F,
+ };
+ static readonly ushort[] StepBackMask = {
+ 0x800, 0x400, 0x100, 8, 0x400, 0x20, 0x20, 0x80, 4, 8, 0x200, 1, 0x10, 0x100, 4, 0x200, 0x100, 0x40, 0x800,
+ };
+ static readonly ushort[] StepVerticalMask = {
+ 0x400, 0x200, 0x200, 0x100, 0x100, 4, 0x800, 1, 8, 4, 1, 0x80, 0x400, 0x20, 0x80, 8, 0x200, 8, 0x10,
+ };
+ }
+}
diff --git a/Legacy/PlanTech/ArcPAC.cs b/Legacy/PlanTech/ArcPAC.cs
index 21374276..f1c8dbc0 100644
--- a/Legacy/PlanTech/ArcPAC.cs
+++ b/Legacy/PlanTech/ArcPAC.cs
@@ -29,9 +29,7 @@ using System.IO;
namespace GameRes.Formats.PlanTech
{
-#if DEBUG
[Export(typeof(ArchiveFormat))]
-#endif
public class PacOpener : ArchiveFormat
{
public override string Tag { get { return "PAC/PLANTECH"; } }
diff --git a/Legacy/PlanTech/ImagePAC.cs b/Legacy/PlanTech/ImagePAC.cs
index 02ce04a3..b2efee40 100644
--- a/Legacy/PlanTech/ImagePAC.cs
+++ b/Legacy/PlanTech/ImagePAC.cs
@@ -29,9 +29,7 @@ using System.Windows.Media;
namespace GameRes.Formats.PlanTech
{
-#if DEBUG
[Export(typeof(ImageFormat))]
-#endif
public class PacFormat : ImageFormat
{
public override string Tag { get { return "PAC/PLANTECH"; } }
diff --git a/Legacy/Ponytail/ArcBND.cs b/Legacy/Ponytail/ArcBND.cs
new file mode 100644
index 00000000..d92ba2b3
--- /dev/null
+++ b/Legacy/Ponytail/ArcBND.cs
@@ -0,0 +1,125 @@
+//! \file ArcBND.cs
+//! \date 2023 Sep 28
+//! \brief Ponytail Adventure System 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;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Linq;
+
+// [951115][Ponytail Soft] Masuzume Yume
+
+namespace GameRes.Formats.Ponytail
+{
+ [Export(typeof(ArchiveFormat))]
+ public class BndOpener : ArchiveFormat
+ {
+ public override string Tag => "BND/NMI";
+ public override string Description => "Ponytail Soft resource archive";
+ public override uint Signature => 0x646E6942; // 'Bind'
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!file.View.AsciiEqual (4, " ver.0"))
+ return null;
+ int count = file.View.ReadInt16 (0xD);
+ if (!IsSaneCount (count))
+ return null;
+ uint index_offset = file.View.ReadUInt32 (0xF);
+ if (index_offset >= file.MaxOffset)
+ return null;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ var name = file.View.ReadString (index_offset, 8).Trim();
+ var ext = file.View.ReadString (index_offset+8, 3);
+ name = name + '.' + ext;
+ var entry = Create (name);
+ entry.Size = file.View.ReadUInt32 (index_offset+0x0C);
+ entry.Offset = file.View.ReadUInt32 (index_offset+0x14);
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ dir.Add (entry);
+ index_offset += 0x18;
+ }
+ foreach (PackedEntry entry in dir.Where (e => e.Name.EndsWith ("Z") && e.Type != "image"))
+ {
+ if (file.View.AsciiEqual (entry.Offset, "lz1_"))
+ {
+ entry.IsPacked = true;
+ char last_chr =(char)file.View.ReadByte (entry.Offset+4);
+ entry.UnpackedSize = file.View.ReadUInt32 (entry.Offset+5);
+ string name = entry.Name.Remove (entry.Name.Length-1);
+ entry.Name = name + char.ToUpperInvariant (last_chr);
+ }
+ }
+ return new ArcFile (file, this, dir);
+ }
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ var pent = (PackedEntry)entry;
+ if (!pent.IsPacked)
+ return base.OpenEntry (arc, entry);
+ var output = new byte[pent.UnpackedSize];
+ using (var input = arc.File.CreateStream (pent.Offset+9, pent.Size-9))
+ Lz1Unpack (input, output);
+ return new BinMemoryStream (output, pent.Name);
+ }
+
+ internal static void Lz1Unpack (IBinaryStream input, byte[] output)
+ {
+ byte mask = 0;
+ int ctl = 0;
+ int dst = 0;
+ while (dst < output.Length)
+ {
+ mask <<= 1;
+ if (0 == mask)
+ {
+ ctl = input.ReadUInt8();
+ if (ctl < 0)
+ break;
+ mask = 1;
+ }
+ if ((ctl & mask) != 0)
+ {
+ output[dst++] = input.ReadUInt8();
+ }
+ else
+ {
+ int code = input.ReadUInt16();
+ int offset = (code >> 5) + 1;
+ int count = Math.Min (3 + (code & 0x1F), output.Length - dst);
+ Binary.CopyOverlapped (output, dst - offset, dst, count);
+ dst += count;
+ }
+ }
+ }
+ }
+}
diff --git a/Legacy/Ponytail/ImageTCZ.cs b/Legacy/Ponytail/ImageTCZ.cs
new file mode 100644
index 00000000..fc79a3ff
--- /dev/null
+++ b/Legacy/Ponytail/ImageTCZ.cs
@@ -0,0 +1,212 @@
+//! \file ImageTCZ.cs
+//! \date 2023 Sep 28
+//! \brief Ponytail NMI 2.5 image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// Graphics driver version 0.3 95/10/30
+// by Y.Nakamura / NARIMI.A
+
+namespace GameRes.Formats.Ponytail
+{
+ [Export(typeof(ImageFormat))]
+ public class TczFormat : ImageFormat
+ {
+ public override string Tag => "TCZ";
+ public override string Description => "Ponytail Soft NMI image format";
+ public override uint Signature => 0x20494D4E; // 'NMI '
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x10);
+ if (!header.AsciiEqual (4, "2.5\0"))
+ return null;
+ var info = new ImageMetaData {
+ Width = header.ToUInt16 (0xC),
+ Height = header.ToUInt16 (0xE),
+ OffsetX = header.ToInt16 (0x8),
+ OffsetY = header.ToInt16 (0xA),
+ BPP = 4,
+ };
+ if (info.Height > TczReader.MaxHeight)
+ return null;
+ return info;
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new TczReader (file, info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("TczFormat.Write not implemented");
+ }
+ }
+
+ internal class TczReader : TszReader
+ {
+ internal const int MaxHeight = 0x190;
+ const int BufferSize = MaxHeight * 0x10;
+
+ public TczReader (IBinaryStream input, ImageMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ m_stride = m_info.iWidth >> 1;
+ }
+
+ byte[] m_buffer;
+
+ static short[] s_offTable0 = { -4, -3, -2, -1, 0, 1, 2, 3 };
+ static short[] s_offTable1 = { -16, -8, -6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 6, 8, 10, 16 };
+
+ public new ImageData Unpack ()
+ {
+ m_input.Position = 0x10;
+ var palette = ReadPalette();
+ var output = new byte[m_stride * m_info.iHeight];
+ FillBuffers();
+ ResetBitReader();
+ int dst = 2;
+ int output_pos = 0;
+ int x = 0;
+ while (x < m_stride)
+ {
+ int y = 0;
+ while (y < m_info.iHeight)
+ {
+ if (GetNextBit()) // @1@
+ {
+ int src = dst;
+ if (!GetNextBit()) // @2@
+ {
+ src += m_lineOffsets1[1];
+ int off = GetBits (4);
+ src += s_offTable1[off];
+ }
+ else if (!GetNextBit()) // @3@
+ {
+ src += GetBits (2) - 4;
+ }
+ else
+ {
+ if (!GetNextBit()) // @4@
+ {
+ src += m_lineOffsets1[2];
+ }
+ else if (!GetNextBit()) // @5@
+ {
+ src += m_lineOffsets1[4];
+ }
+ else
+ {
+ src += m_lineOffsets1[8];
+ }
+ int off = GetBits (3);
+ src += s_offTable0[off];
+ }
+ src &= 0xFFFF;
+ int count = GetBitLength() + 1;
+ Binary.CopyOverlapped (m_buffer, src, dst, count);
+ y += count;
+ dst += count;
+ }
+ else
+ {
+ int bx = m_buffer[dst-2];
+ bx = (bx << 8 | bx) & 0xF00F;
+ if (GetNextBit()) // @8@
+ {
+ int ax = GetBits (4);
+ bx = (bx & 0xFF) | ax << 12;
+ }
+ if (GetNextBit()) // @9@
+ {
+ int ax = GetBits (4);
+ bx = (bx & 0xFF00) | ax;
+ }
+ bx = (bx & 0xFF) | (bx >> 8);
+ m_buffer[dst++] = (byte)bx;
+ ++y;
+ }
+ }
+ ++x;
+ if ((x & 3) == 0)
+ {
+ CopyScanline (m_lineOffsets0[(x - 1) & 0xC], output, output_pos);
+ output_pos += 4;
+ }
+ int z = x & 0xF;
+ dst = m_lineOffsets0[z];
+ if (z != 0)
+ {
+ m_lineOffsets1[z] -= BufferSize;
+ }
+ else
+ {
+ for (int i = 1; i < 16; ++i)
+ {
+ m_lineOffsets1[i] += BufferSize;
+ }
+ }
+ }
+ return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, m_stride);
+ }
+
+ ushort[] m_lineOffsets0 = new ushort[16];
+ ushort[] m_lineOffsets1 = new ushort[16];
+
+ void FillBuffers ()
+ {
+ m_buffer = new byte[BufferSize + MaxHeight];
+ m_buffer[0] = m_buffer[1] = 3;
+ ushort p = 2;
+ for (int i = 0; i < 16; ++i)
+ {
+ m_lineOffsets1[i] = (ushort)(((16 - i) & 0xF) * MaxHeight);
+ m_lineOffsets0[i] = p;
+ p += MaxHeight;
+ }
+ }
+
+ void CopyScanline (int src, byte[] output, int dst)
+ {
+ for (int i = 0; i < m_info.iHeight; ++i)
+ {
+ output[dst ] = m_buffer[src + i ];
+ output[dst+1] = m_buffer[src + i + MaxHeight ];
+ output[dst+2] = m_buffer[src + i + MaxHeight*2];
+ output[dst+3] = m_buffer[src + i + MaxHeight*3];
+ dst += m_stride;
+ }
+ }
+ }
+}
diff --git a/Legacy/Ponytail/ImageTSZ.cs b/Legacy/Ponytail/ImageTSZ.cs
new file mode 100644
index 00000000..57cea061
--- /dev/null
+++ b/Legacy/Ponytail/ImageTSZ.cs
@@ -0,0 +1,330 @@
+//! \file ImageTSZ.cs
+//! \date 2023 Sep 27
+//! \brief Ponytail NMI 2.05 image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [930413][Ponytail Soft] Yougen Doumu
+
+namespace GameRes.Formats.Ponytail
+{
+ [Export(typeof(ImageFormat))]
+ public class TszFormat : ImageFormat
+ {
+ public override string Tag => "TSZ";
+ public override string Description => "Ponytail Soft NMI image format";
+ public override uint Signature => 0x20494D4E; // 'NMI '
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x10);
+ if (!header.AsciiEqual (4, "2.05"))
+ return null;
+ return new ImageMetaData {
+ Width = (uint)header.ToUInt16 (0xC) << 2,
+ Height = header.ToUInt16 (0xE),
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new TszReader (file, info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("TszFormat.Write not implemented");
+ }
+ }
+
+ internal class TszReader
+ {
+ protected IBinaryStream m_input;
+ protected ImageMetaData m_info;
+ protected int m_stride;
+
+ protected TszReader () { }
+
+ public TszReader (IBinaryStream input, ImageMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ m_stride = m_info.iWidth >> 1;
+ }
+
+ private int m_previous_row;
+ private ushort[] m_linebuffer;
+ private int m_dst;
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 0x10;
+ var palette = ReadPalette();
+ var output = new byte[m_stride * m_info.iHeight];
+ m_linebuffer = new ushort[m_info.iHeight * 2];
+ m_previous_row = m_info.iHeight;
+ int width = m_info.iWidth >> 2;
+ int dst_x = 0;
+ int x = 0;
+ ResetBitReader();
+ while (x < width)
+ {
+ int y = 0;
+ m_dst = 0;
+ if ((x & 1) != 0)
+ m_dst += m_info.iHeight;
+ while (y < m_info.iHeight)
+ {
+ int ctl = 0;
+ while (GetNextBit())
+ {
+ ++ctl;
+ }
+ int count; // bx
+ switch (ctl)
+ {
+ case 0: count = CopyMethod0(); break;
+ case 1: count = CopyMethod1(); break;
+ case 2:
+ m_linebuffer[m_dst] = m_input.ReadUInt16();
+ count = 1;
+ break;
+ case 3: count = CopyMethod3(); break;
+ case 4: count = CopyMethod4(); break;
+ default: throw new InvalidFormatException();
+ }
+ m_dst += count;
+ y += count;
+ }
+ if ((x & 1) != 0)
+ {
+ CopyScanline (output, dst_x);
+ dst_x += 4;
+ }
+ m_previous_row = -m_previous_row;
+ ++x;
+ }
+ return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, m_stride);
+ }
+
+ void CopyScanline (byte[] output, int dst)
+ {
+ for (int i = 0; i < m_info.iHeight; ++i)
+ {
+ ushort px1 = m_linebuffer[i];
+ ushort px2 = m_linebuffer[i + m_info.iHeight];
+ // these bytes contain 8 pixels that are being put into 4 planes
+ int b0 = (px1 << 4) & 0xF0 | (px2 ) & 0x0F;
+ int b1 = (px1 ) & 0xF0 | (px2 >> 4) & 0x0F;
+ int b2 = (px1 >> 4) & 0xF0 | (px2 >> 8) & 0x0F;
+ int b3 = (px1 >> 8) & 0xF0 | (px2 >> 12) & 0x0F;
+ // repack pixels into flat surface, 2 pixels per byte
+ for (int j = 0; j < 8; j += 2)
+ {
+ byte px = (byte)((((b0 << j) & 0x80) >> 3)
+ | (((b1 << j) & 0x80) >> 2)
+ | (((b2 << j) & 0x80) >> 1)
+ | (((b3 << j) & 0x80) ));
+ px |= (byte)((((b0 << j) & 0x40) >> 6)
+ | (((b1 << j) & 0x40) >> 5)
+ | (((b2 << j) & 0x40) >> 4)
+ | (((b3 << j) & 0x40) >> 3));
+ output[dst+j/2] = px;
+ }
+ dst += m_stride;
+ }
+ }
+
+ int CopyMethod0 ()
+ {
+ int count = GetBitLength();
+ int offset = GetBits (4);
+ offset += m_previous_row - 8;
+ CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count);
+ return count;
+ }
+
+ int CopyMethod1 ()
+ {
+ int count = GetBitLength();
+ int offset = m_input.ReadUInt8();
+ offset += m_previous_row - 0x80;
+ CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count);
+ return count;
+ }
+
+ int CopyMethod3 ()
+ {
+ int count = GetBitLength();
+ int offset = GetBits (4);
+ offset -= 0x10;
+ CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count);
+ return count;
+ }
+
+ int CopyMethod4 ()
+ {
+ byte al = m_input.ReadUInt8();
+ int nibble = al >> 4;
+ ushort mask1 = s_pattern1[al >> 4];
+ ushort mask2 = s_pattern2[al & 0xF];
+ ushort pixel = m_linebuffer[m_dst + m_previous_row];
+ pixel &= mask1;
+ for (int i = 0; i < 4; ++i)
+ {
+ short carry = (short)(nibble & 1);
+ nibble >>= 1;
+ pixel |= (ushort)(-carry & mask2);
+ pixel = RotU16R (pixel, 1);
+ }
+ pixel = RotU16L (pixel, 4);
+ m_linebuffer[m_dst] = pixel;
+ return 1;
+ }
+
+ static readonly ushort[] s_pattern1 = new ushort[] {
+ 0xFFFF, 0xEEEE, 0xDDDD, 0xCCCC, 0xBBBB, 0xAAAA, 0x9999, 0x8888,
+ 0x7777, 0x6666, 0x5555, 0x4444, 0x3333, 0x2222, 0x1111, 0x0000,
+ };
+ static readonly ushort[] s_pattern2 = new ushort[] {
+ 0x0000, 0x0001, 0x0010, 0x0011, 0x0100, 0x0101, 0x0110, 0x0111,
+ 0x1000, 0x1001, 0x1010, 0x1011, 0x1100, 0x1101, 0x1110, 0x1111,
+ };
+
+ ushort m_bits;
+ int m_bit_count;
+
+ protected void ResetBitReader ()
+ {
+ m_bits = 0;
+ m_bit_count = 0;
+ }
+
+ protected int GetBitLength ()
+ {
+ if (!GetNextBit())
+ return 1;
+ int count = 1;
+ while (GetNextBit())
+ {
+ ++count;
+ }
+ return GetBits (count) | 1 << count;
+ }
+
+ protected bool GetNextBit ()
+ {
+ if (--m_bit_count < 0)
+ {
+ m_bits = m_input.ReadUInt16();
+ m_bit_count = 15;
+ }
+ bool bit = (m_bits & 0x8000) != 0;
+ m_bits <<= 1;
+ return bit;
+ }
+
+ static readonly ushort[] s_bit_mask = new ushort[] {
+ 0, 1, 3, 7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,
+ };
+
+ protected int GetBits (int count)
+ {
+ m_bit_count -= count;
+ if (m_bit_count < 0)
+ {
+ m_bits = RotU16L (m_bits, count);
+ int cl = -m_bit_count;
+ ushort bits = m_input.ReadUInt16();
+ bits = RotU16L (bits, cl);
+ ushort mask = s_bit_mask[cl];
+ ushort new_bits = (ushort)(bits & ~mask);
+ bits &= mask;
+ bits |= m_bits;
+ m_bits = new_bits;
+ m_bit_count = 16 - cl;
+ return bits;
+ }
+ else
+ {
+ m_bits = RotU16L (m_bits, count);
+ ushort mask = s_bit_mask[count];
+ int bits = m_bits & mask;
+ m_bits &= (ushort)~mask;
+ return bits;
+ }
+ }
+
+ protected BitmapPalette ReadPalette ()
+ {
+ const int count = 16;
+ var colors = new Color[count];
+ for (int i = 0; i < count; ++i)
+ {
+ byte r = m_input.ReadUInt8();
+ byte g = m_input.ReadUInt8();
+ byte b = m_input.ReadUInt8();
+ colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
+ }
+ return new BitmapPalette (colors);
+ }
+
+ static internal ushort RotU16L (ushort val, int count)
+ {
+ return (ushort)(val << count | val >> (16 - count));
+ }
+
+ static internal ushort RotU16R (ushort val, int count)
+ {
+ return (ushort)(val >> count | val << (16 - count));
+ }
+
+ static internal void CopyOverlapped (ushort[] data, int src, int dst, int count)
+ {
+ src <<= 1;
+ dst <<= 1;
+ count <<= 1;
+ if (dst > src)
+ {
+ while (count > 0)
+ {
+ int preceding = Math.Min (dst - src, count);
+ Buffer.BlockCopy (data, src, data, dst, preceding);
+ dst += preceding;
+ count -= preceding;
+ }
+ }
+ else
+ {
+ Buffer.BlockCopy (data, src, data, dst, count);
+ }
+ }
+ }
+}
diff --git a/Legacy/ProjectMyu/ImageGAM.cs b/Legacy/ProjectMyu/ImageGAM.cs
index 736d546f..49ff4791 100644
--- a/Legacy/ProjectMyu/ImageGAM.cs
+++ b/Legacy/ProjectMyu/ImageGAM.cs
@@ -28,6 +28,7 @@ using System.ComponentModel.Composition;
using System.IO;
using GameRes.Compression;
+// [031219][Project-μ] Gin no Hebi Kuro no Tsuki
// [040528][Lakshmi] Mabuta Tojireba Soko ni...
namespace GameRes.Formats.ProjectMu
diff --git a/Legacy/Properties/AssemblyInfo.cs b/Legacy/Properties/AssemblyInfo.cs
index d63b355a..59d342b0 100644
--- a/Legacy/Properties/AssemblyInfo.cs
+++ b/Legacy/Properties/AssemblyInfo.cs
@@ -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.10.208")]
-[assembly: AssemblyFileVersion ("1.0.10.208")]
+[assembly: AssemblyVersion ("1.0.10.212")]
+[assembly: AssemblyFileVersion ("1.0.10.212")]
diff --git a/Legacy/RedZone/ArcPAK.cs b/Legacy/RedZone/ArcPAK.cs
new file mode 100644
index 00000000..69a82a42
--- /dev/null
+++ b/Legacy/RedZone/ArcPAK.cs
@@ -0,0 +1,68 @@
+//! \file ArcPAK.cs
+//! \date 2023 Sep 18
+//! \brief RED-ZONE 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.Collections.Generic;
+using System.ComponentModel.Composition;
+
+// [010706][RED-ZONE] Kenkyuu Nisshi
+
+namespace GameRes.Formats.RedZone
+{
+ [Export(typeof(ArchiveFormat))]
+ public class PakOpener : ArchiveFormat
+ {
+ public override string Tag => "PAK/REDZONE";
+ public override string Description => "RED-ZONE resource archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ int count = file.View.ReadInt32 (0);
+ if (!IsSaneCount (count))
+ return null;
+
+ uint index_offset = 4;
+ const uint index_entry_size = 0x54;
+ long min_offset = index_offset + count * index_entry_size;
+ if (min_offset >= file.MaxOffset)
+ return null;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ var name = file.View.ReadString (index_offset, 0x44);
+ var entry = Create (name);
+ entry.Offset = file.View.ReadUInt32 (index_offset+0x44);
+ entry.Size = file.View.ReadUInt32 (index_offset+0x48);
+ if (entry.Offset < min_offset || !entry.CheckPlacement (file.MaxOffset))
+ return null;
+ dir.Add (entry);
+ index_offset += index_entry_size;
+ }
+ return new ArcFile (file, this, dir);
+ }
+ }
+}
diff --git a/Legacy/RedZone/ScriptQDO.cs b/Legacy/RedZone/ScriptQDO.cs
new file mode 100644
index 00000000..9ad1ce32
--- /dev/null
+++ b/Legacy/RedZone/ScriptQDO.cs
@@ -0,0 +1,74 @@
+//! \file ScriptQDO.cs
+//! \date 2023 Sep 21
+//! \brief RED-ZONE binary script.
+//
+// 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.RedZone
+{
+ [Export(typeof(ScriptFormat))]
+ public class QdoOpener : GenericScriptFormat
+ {
+ public override string Tag => "QDO";
+ public override string Description => "Red-Zone script file";
+ public override uint Signature => 0x5F4F4451; // 'QDO_SHO'
+
+ public override bool IsScript (IBinaryStream file)
+ {
+ var header = file.ReadHeader (8);
+ return header.AsciiEqual ("QDO_SHO");
+ }
+
+ const int ScriptDataPos = 0x0E;
+
+ public override Stream ConvertFrom (IBinaryStream file)
+ {
+ var data = file.ReadBytes ((int)file.Length);
+ if (data[0xC] != 0)
+ {
+ for (int i = ScriptDataPos; i < data.Length; ++i)
+ {
+ data[i] = (byte)~(data[i] - 13);
+ }
+ data[0xC] = 0;
+ }
+ return new BinMemoryStream (data, file.Name);
+ }
+
+ public override Stream ConvertBack (IBinaryStream file)
+ {
+ var data = file.ReadBytes ((int)file.Length);
+ if (data[0xC] == 0)
+ {
+ for (int i = ScriptDataPos; i < data.Length; ++i)
+ {
+ data[i] = (byte)(~data[i] + 13);
+ }
+ data[0xC] = 1;
+ }
+ return new BinMemoryStream (data, file.Name);
+ }
+ }
+}
diff --git a/Legacy/Sophia/ArcNOR.cs b/Legacy/Sophia/ArcNOR.cs
new file mode 100644
index 00000000..003243c3
--- /dev/null
+++ b/Legacy/Sophia/ArcNOR.cs
@@ -0,0 +1,146 @@
+//! \file ArcNOR.cs
+//! \date 2023 Sep 26
+//! \brief Sophia 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.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+
+// [991210][Sophia] Film Noir
+
+namespace GameRes.Formats.Sophia
+{
+ internal class NorEntry : PackedEntry
+ {
+ public int Method;
+ }
+
+ [Export(typeof(ArchiveFormat))]
+ public class NorOpener : ArchiveFormat
+ {
+ public override string Tag => "NOR";
+ public override string Description => "Sophia resource archive";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ int count = file.View.ReadInt32 (0);
+ if (!file.View.AsciiEqual (4, "NRCOMB01\0") || !IsSaneCount (count))
+ return null;
+ var dir = new List (count);
+ using (var index = file.CreateStream())
+ {
+ index.Position = 0x10;
+ for (int i = 0; i < count; ++i)
+ {
+ uint offset = index.ReadUInt32();
+ uint size = index.ReadUInt32();
+ string name = index.ReadCString();
+ var entry = Create (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)
+ {
+ var nent = (NorEntry)entry;
+ if (!nent.IsPacked)
+ {
+ if (nent.Method == 0x1F4 || nent.Method == 0x67
+ || !arc.File.View.AsciiEqual (nent.Offset, "NCMB01"))
+ return base.OpenEntry (arc, nent);
+ nent.Method = arc.File.View.ReadInt32 (nent.Offset+0x28);
+ if (nent.Method == 0x1F4 || nent.Method == 0x67)
+ {
+ nent.Size = arc.File.View.ReadUInt32 (nent.Offset+0x24);
+ nent.Offset += 0x2C;
+ return base.OpenEntry (arc, nent);
+ }
+ nent.IsPacked = true;
+ nent.UnpackedSize = arc.File.View.ReadUInt32 (nent.Offset+0x24);
+ nent.Size = arc.File.View.ReadUInt32 (nent.Offset+0x10);
+ nent.Offset += 0x2C;
+ }
+ using (var input = arc.File.CreateStream (nent.Offset, nent.Size))
+ {
+ var output = new byte[nent.UnpackedSize];
+ NcmbDecompress (input, output);
+ return new BinMemoryStream (output, nent.Name);
+ }
+ }
+
+ internal static void NcmbDecompress (IBinaryStream input, byte[] output)
+ {
+ var dict = new int[0xC00];
+ int root = input.ReadInt32();
+ int tree_size = input.ReadInt32();
+ int unpacked_size = input.ReadInt32();
+ int count = root + tree_size - 0xFF;
+ while (count --> 0)
+ {
+ int token = 6 * input.ReadInt32();
+ dict[token ] = input.ReadInt32();
+ dict[token + 1] = input.ReadInt32();
+ }
+ if (unpacked_size > 0)
+ {
+ int cur_byte = 0;
+ int mask = 0;
+ for (int dst = 0; dst < unpacked_size; ++dst)
+ {
+ int token = root;
+ do
+ {
+ if (0 == mask)
+ {
+ cur_byte = input.ReadUInt8();
+ mask = 0x80;
+ }
+ if ((cur_byte & mask) != 0)
+ token = dict[6 * token + 1];
+ else
+ token = dict[6 * token];
+ mask >>= 1;
+ }
+ while (dict[6 * token] != -1);
+ output[dst] = (byte)token;
+ }
+ }
+ }
+ }
+
+ [Export(typeof(ResourceAlias))]
+ [ExportMetadata("Extension", "M")]
+ [ExportMetadata("Target", "MP3")]
+ [ExportMetadata("Type", "audio")]
+ public class MFormat : ResourceAlias { }
+}
diff --git a/Legacy/SquadraD/ArcPLA.cs b/Legacy/SquadraD/ArcPLA.cs
new file mode 100644
index 00000000..c9ac5e84
--- /dev/null
+++ b/Legacy/SquadraD/ArcPLA.cs
@@ -0,0 +1,114 @@
+//! \file ArcPLA.cs
+//! \date 2023 Sep 26
+//! \brief Squadra D audio 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.SquadraD
+{
+ internal class PlaEntry : PackedEntry
+ {
+ public int Id;
+ public int n1;
+ public uint SampleRate;
+ public int Channels;
+ public byte n2;
+ public byte n3;
+ public int[] Data;
+ }
+
+ [Export(typeof(ArchiveFormat))]
+ public class PlaOpener : ArchiveFormat
+ {
+ public override string Tag => "PLA";
+ public override string Description => "Squadra D audio archive";
+ public override uint Signature => 0x2E616C50; // 'Pla.'
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ uint arc_size = file.View.ReadUInt32 (4);
+ if (arc_size != file.MaxOffset || file.View.ReadUInt32 (0x10) != 2)
+ return null;
+ uint check = (arc_size & 0xD5555555u) << 1 | arc_size & 0xAAAAAAAAu;
+ if (check != file.View.ReadUInt32 (8))
+ return null;
+ int count = file.View.ReadUInt16 (0xE);
+ if (!IsSaneCount (count))
+ return null;
+
+ var dir = new List (count);
+ using (var index = file.CreateStream())
+ {
+ index.Position = 0x14;
+ for (int i = 0; i < count; ++i)
+ {
+ var entry = new PlaEntry {
+ Id = index.ReadInt32()
+ };
+ entry.Name = entry.Id.ToString ("D5");
+ dir.Add (entry);
+ }
+ foreach (PlaEntry entry in dir)
+ {
+ entry.n1 = index.ReadInt32();
+ entry.SampleRate = index.ReadUInt32();
+ entry.Channels = index.ReadInt32();
+ entry.n2 = index.ReadUInt8();
+ entry.n3 = index.ReadUInt8();
+ index.ReadInt16();
+ }
+ foreach (PlaEntry entry in dir)
+ {
+ entry.Offset = index.ReadUInt32();
+ }
+ foreach (PlaEntry entry in dir)
+ {
+ int n = entry.Channels * 2;
+ entry.Data = new int[n];
+ for (int j = 0; j < n; ++j)
+ entry.Data[j] = index.ReadInt32();
+ }
+ }
+ long last_offset = file.MaxOffset;
+ for (int i = dir.Count - 1; i >= 0; --i)
+ {
+ dir[i].Size = (uint)(last_offset - dir[i].Offset);
+ last_offset = dir[i].Offset;
+ }
+ return new ArcFile (file, this, dir);
+ }
+
+ /*
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ return base.OpenEntry (arc, entry);
+ }
+ */
+ }
+}
diff --git a/Legacy/SquadraD/ArcSDA.cs b/Legacy/SquadraD/ArcSDA.cs
new file mode 100644
index 00000000..dabf18bc
--- /dev/null
+++ b/Legacy/SquadraD/ArcSDA.cs
@@ -0,0 +1,121 @@
+//! \file ArcSDA.cs
+//! \date 2023 Sep 26
+//! \brief Squadra D resource archive 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 System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+
+namespace GameRes.Formats.SquadraD
+{
+ [Export(typeof(ArchiveFormat))]
+ public class SdaOpener : ArchiveFormat
+ {
+ public override string Tag => "SDA/SD";
+ public override string Description => "Squadra D resource archive";
+ public override uint Signature => 0x4153; // 'SA'
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public SdaOpener ()
+ {
+ Signatures = new[] { 0x4153u, 0xCC004153u, 0u };
+ }
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!file.View.AsciiEqual (0, "SA\0"))
+ return null;
+ int data_offset = file.View.ReadInt32 (4);
+ if (data_offset <= 8 || data_offset >= file.MaxOffset)
+ return null;
+ int count = (data_offset - 8) / 0x14;
+ if (!IsSaneCount (count))
+ return null;
+ string arc_name = Path.GetFileNameWithoutExtension (file.Name);
+ bool is_cg = arc_name == "g";
+ uint index = 8;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ var name = file.View.ReadString (index, 0x10).Trim();;
+ var entry = Create (name);
+ entry.Offset = file.View.ReadUInt32 (index+0xC) + data_offset;
+ entry.Size = file.View.ReadUInt32 (index+0x10);
+ if (is_cg)
+ entry.Type = "image";
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ dir.Add (entry);
+ index += 0x14;
+ }
+ return new ArcFile (file, this, dir);
+ }
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ using (var input = arc.File.CreateStream (entry.Offset, entry.Size))
+ {
+ var data = LzssDecompress (input);
+ return new BinMemoryStream (data, entry.Name);
+ }
+ }
+
+ internal static byte[] LzssDecompress (IBinaryStream input)
+ {
+ int unpacked_size = input.ReadInt32();
+ var output = new byte[unpacked_size];
+ using (var bits = new LsbBitStream (input.AsStream, true))
+ {
+ var frame = new byte[0x1000];
+ int frame_pos = 0xFC0;
+ int dst = 0;
+ while (dst < unpacked_size)
+ {
+ if (bits.GetNextBit() == 0)
+ {
+ byte b = (byte)bits.GetBits (8);
+ output[dst++] = frame[frame_pos++ & 0xFFF] = b;
+ }
+ else
+ {
+ int count_len = 4;
+ if (bits.GetNextBit() != 0)
+ count_len = 6;
+ int offset = bits.GetBits (12);
+ int count = bits.GetBits (count_len);
+ count = Math.Min (count + 3, unpacked_size - dst);
+ while (count --> 0)
+ {
+ byte b = frame[offset++ & 0xFFF];
+ output[dst++] = frame[frame_pos++ & 0xFFF] = b;
+ }
+ }
+ }
+ return output;
+ }
+ }
+ }
+}
diff --git a/Legacy/System98/ArcLIB.cs b/Legacy/System98/ArcLIB.cs
new file mode 100644
index 00000000..e2717ab0
--- /dev/null
+++ b/Legacy/System98/ArcLIB.cs
@@ -0,0 +1,141 @@
+//! \file ArcLIB.cs
+//! \date 2023 Oct 15
+//! \brief System-98 resource archive (PC-98).
+//
+// 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;
+
+namespace GameRes.Formats.System98
+{
+ [Export(typeof(ArchiveFormat))]
+ public class LibOpener : ArchiveFormat
+ {
+ public override string Tag => "LIB/SYSTEM98";
+ public override string Description => "System-98 engine resource archive";
+ public override uint Signature => 0x3062694C; // 'Lib0'
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ var cat_name = Path.ChangeExtension (file.Name, ".CAT");
+ if (!VFS.FileExists (cat_name))
+ return null;
+ int count;
+ byte[] index;
+ using (var cat = VFS.OpenView (cat_name))
+ {
+ count = cat.View.ReadInt16 (4);
+ if (!IsSaneCount (count))
+ return null;
+ int index_size = count * 0x16;
+ if (cat.View.AsciiEqual (0, "Cat0"))
+ {
+ index = file.View.ReadBytes (6, (uint)index_size);
+ }
+ else if (cat.View.AsciiEqual (0, "Cat1"))
+ {
+ index = new byte[index_size];
+ using (var input = cat.CreateStream (6))
+ LzssUnpack (input, index);
+ }
+ else
+ return null;
+ }
+ int pos = 0;
+ var dir = new List (count);
+ for (int i = 0; i < count; ++i)
+ {
+ var name = Binary.GetCString (index, pos, 0xC).TrimEnd();
+ var entry = Create (name);
+ entry.Size = index.ToUInt32 (pos+0xE);
+ entry.Offset = index.ToUInt32 (pos+0x12);
+ if (!entry.CheckPlacement (file.MaxOffset))
+ return null;
+ entry.IsPacked = index[pos+0xC] != 0;
+ dir.Add (entry);
+ pos += 0x16;
+ }
+ return new ArcFile (file, this, dir);
+ }
+
+ public override Stream OpenEntry (ArcFile arc, Entry entry)
+ {
+ var pent = entry as PackedEntry;
+ if (null == pent || !pent.IsPacked)
+ return base.OpenEntry (arc, entry);
+ if (pent.UnpackedSize == 0)
+ pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset+6);
+ var data = new byte[pent.UnpackedSize];
+ using (var input = arc.File.CreateStream (entry.Offset+10, entry.Size-10))
+ {
+ int length = LzssUnpack (input, data);
+ return new BinMemoryStream (data, 0, length, entry.Name);
+ }
+ }
+
+ internal static int LzssUnpack (IBinaryStream input, byte[] output)
+ {
+ var frame = new byte[0x1000];
+ int frame_pos = 1;
+ int ctl = 0;
+ byte mask = 0;
+ int dst = 0;
+ while (dst < output.Length)
+ {
+ mask <<= 1;
+ if (0 == mask)
+ {
+ ctl = input.ReadByte();
+ if (-1 == ctl)
+ break;
+ mask = 1;
+ }
+ if (input.PeekByte() == -1)
+ break;
+ if ((ctl & mask) != 0)
+ {
+ output[dst++] = frame[frame_pos++ & 0xFFF] = input.ReadUInt8();
+ }
+ else
+ {
+ int lo = input.ReadByte();
+ int hi = input.ReadByte();
+ if (-1 == hi)
+ break;
+ int count = (lo & 0xF) + 3;
+ int off = hi << 4 | lo >> 4;
+ while (count --> 0)
+ {
+ byte b = frame[off++ & 0xFFF];
+ output[dst++] = frame[frame_pos++ & 0xFFF] = b;
+ }
+ }
+ }
+ return dst;
+ }
+ }
+}
diff --git a/Legacy/System98/ImageG.cs b/Legacy/System98/ImageG.cs
new file mode 100644
index 00000000..bd051827
--- /dev/null
+++ b/Legacy/System98/ImageG.cs
@@ -0,0 +1,337 @@
+//! \file ImageG.cs
+//! \date 2023 Oct 15
+//! \brief System-98 engine image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+
+// [951216][Four-Nine] Lilith
+
+namespace GameRes.Formats.System98
+{
+ [Export(typeof(ImageFormat))]
+ public class GFormat : ImageFormat
+ {
+ public override string Tag => "G/SYSTEM98";
+ public override string Description => "System-98 engine image format";
+ public override uint Signature => 0;
+
+ public GFormat ()
+ {
+ Extensions = new[] { "g", "" };
+ }
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ if (file.Length < 61)
+ return null;
+ var header = file.ReadHeader (0xA);
+ ushort width = Binary.BigEndian (header.ToUInt16 (6));
+ ushort height = Binary.BigEndian (header.ToUInt16 (8));
+ if (0 == width || 0 == height || (width & 7) != 0 || width > 640 || height > 400)
+ return null;
+ return new ImageMetaData {
+ Width = width,
+ Height = height,
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ file.Position = 0xA;
+ var palette = ReadPalette (file.AsStream, 16, PaletteFormat.Rgb);
+ var reader = new GraBaseReader (file, info);
+ reader.UnpackBits();
+ return ImageData.Create (info, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride);
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("GFormat.Write not implemented");
+ }
+ }
+
+ ///
+ /// This compression format is used in several PC-98 game engines.
+ ///
+ internal class GraBaseReader
+ {
+ protected IBinaryStream m_input;
+ protected ImageMetaData m_info;
+ protected int m_output_stride;
+ protected byte[] m_pixels;
+ protected int m_dst;
+
+ public byte[] Pixels => m_pixels;
+ public int Stride => m_output_stride;
+
+ public GraBaseReader (IBinaryStream file, ImageMetaData info)
+ {
+ m_input = file;
+ m_info = info;
+ m_output_stride = m_info.iWidth >> 1;
+ m_pixels = new byte[m_output_stride * m_info.iHeight];
+ }
+
+ protected ushort[] m_buffer;
+
+ public void UnpackBits ()
+ {
+ try
+ {
+ UnpackBitsInternal();
+ }
+ catch (EndOfStreamException)
+ {
+ FlushBuffer();
+ }
+ }
+
+ void UnpackBitsInternal ()
+ {
+ int width = m_info.iWidth;
+ int wTimes2 = width << 1;
+ int wTimes4 = width << 2;
+ int buffer_size = wTimes4 + wTimes2;
+ m_buffer = new ushort[buffer_size >> 1];
+ m_dst = 0;
+ InitFrame();
+ InitBitReader();
+ ushort p = ReadPair (0);
+ for (int i = 0; i < width; ++i)
+ m_buffer[i] = p;
+ int dst = wTimes2;
+ int prev_src = 0;
+ while (m_dst < m_pixels.Length)
+ {
+ bool same_line = false;
+ int src = -width;
+ if (GetNextBit() != 0)
+ {
+ if (GetNextBit() == 0)
+ src <<= 1;
+ else if (GetNextBit() == 0)
+ src += 1;
+ else
+ src -= 1;
+ }
+ else if (GetNextBit() == 0)
+ {
+ src = -4;
+ p = m_buffer[dst/2-1];
+ if ((p & 0xFF) == (p >> 8))
+ same_line = src != prev_src;
+ }
+ if (src != prev_src)
+ {
+ prev_src = src;
+ if (!same_line)
+ src += dst;
+ else
+ src = dst - 2;
+ if (GetNextBit() != 0)
+ {
+ int bitlength = 0;
+ do
+ {
+ ++bitlength;
+ }
+ while (GetNextBit() != 0);
+ int count = 1;
+ while (bitlength --> 0)
+ count = count << 1 | GetNextBit();
+ int remaining = (buffer_size - dst) >> 1;
+ while (count > remaining)
+ {
+ count -= remaining;
+ MovePixels (m_buffer, src, dst, remaining);
+ src += remaining << 1;
+ if (FlushBuffer())
+ return;
+ dst = wTimes2;
+ src -= wTimes4;
+ remaining = wTimes4 >> 1;
+ }
+ MovePixels (m_buffer, src, dst, count);
+ dst += count << 1;
+ if (dst == buffer_size)
+ {
+ if (FlushBuffer())
+ return;
+ dst = wTimes2;
+ }
+ }
+ else
+ {
+ MovePixels (m_buffer, src, dst, 1);
+ dst += 2;
+ if (dst == buffer_size)
+ {
+ if (FlushBuffer())
+ return;
+ dst = wTimes2;
+ }
+ }
+ }
+ else
+ {
+ p = m_buffer[dst/2-1];
+ do
+ {
+ byte prev = (byte)(p >> 8);
+ p = ReadPair (prev);
+ m_buffer[dst >> 1] = p;
+ dst += 2;
+ if (dst == buffer_size)
+ {
+ if (FlushBuffer())
+ return;
+ dst = wTimes2;
+ }
+ }
+ while (GetNextBit() != 0);
+ prev_src = 0;
+ }
+ }
+ }
+
+ bool FlushBuffer ()
+ {
+ MovePixels (m_buffer, m_info.iWidth * 4, 0, m_info.iWidth);
+ int src = m_info.iWidth;
+ int count = Math.Min (m_info.iWidth << 1, m_pixels.Length - m_dst);
+ while (count --> 0)
+ {
+ ushort p = m_buffer[src++];
+ m_pixels[m_dst++] = (byte)((p & 0xF0) | p >> 12);
+ }
+ return m_dst == m_pixels.Length;
+ }
+
+ protected ushort ReadPair (int pos)
+ {
+ byte al = ReadPixel (pos);
+ byte ah = ReadPixel (al);
+ return (ushort)(al | ah << 8);
+ }
+
+ protected byte ReadPixel (int pos)
+ {
+ byte px = 0;
+ if (GetNextBit() == 0)
+ {
+ int count = 1;
+ if (GetNextBit() != 0)
+ {
+ if (GetNextBit() != 0)
+ {
+ count = count << 1 | GetNextBit();
+ }
+ count = count << 1 | GetNextBit();
+ }
+ count = count << 1 | GetNextBit();
+ pos += count;
+ px = m_frame[pos--];
+ while (count --> 0)
+ {
+ m_frame[pos+1] = m_frame[pos];
+ --pos;
+ }
+ m_frame[pos+1] = px;
+ }
+ else if (GetNextBit() == 0)
+ {
+ px = m_frame[pos];
+ }
+ else
+ {
+ px = m_frame[pos+1];
+ m_frame[pos+1] = m_frame[pos];
+ m_frame[pos] = px;
+ }
+ return px;
+ }
+
+ byte[] m_frame;
+
+ protected void InitFrame ()
+ {
+ m_frame = new byte[0x100];
+ int p = 0;
+ byte a = 0;
+ for (int j = 0; j < 0x10; ++j)
+ {
+ for (int i = 0; i < 0x10; ++i)
+ {
+ m_frame[p++] = a;
+ a -= 0x10;
+ }
+ a += 0x10;
+ }
+ }
+
+ protected void MovePixels (ushort[] pixels, int src, int dst, int count)
+ {
+ count <<= 1;
+ if (dst > src)
+ {
+ while (count > 0)
+ {
+ int preceding = Math.Min (dst - src, count);
+ Buffer.BlockCopy (pixels, src, pixels, dst, preceding);
+ dst += preceding;
+ count -= preceding;
+ }
+ }
+ else
+ {
+ Buffer.BlockCopy (pixels, src, pixels, dst, count);
+ }
+ }
+
+ int m_bits;
+ int m_bit_count;
+
+ protected void InitBitReader ()
+ {
+ m_bit_count = 1;
+ }
+
+ protected byte GetNextBit ()
+ {
+ if (--m_bit_count <= 0)
+ {
+ m_bits = m_input.ReadUInt8();
+ m_bit_count = 8;
+ }
+ int bit = (m_bits >> 7) & 1;
+ m_bits <<= 1;
+ return (byte)bit;
+ }
+ }
+}
diff --git a/Legacy/Tiare/ImageGRA.cs b/Legacy/Tiare/ImageGRA.cs
new file mode 100644
index 00000000..ad4aa379
--- /dev/null
+++ b/Legacy/Tiare/ImageGRA.cs
@@ -0,0 +1,122 @@
+//! \file ImageGRA.cs
+//! \date 2023 Oct 11
+//! \brief Tiare image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [950616][JAST] Tenshi-tachi no Gogo ~Tenkousei~
+// [950922][Tiare] Vanishing Point -Tenshi no Kieta Machi-
+
+namespace GameRes.Formats.Tiare
+{
+ internal class GraMetaData : ImageMetaData
+ {
+ public byte Flags;
+ public long DataOffset;
+
+ public bool HasPalette => (Flags & 0x80) == 0;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class GraFormat : ImageFormat
+ {
+ public override string Tag => "GRA/TIARE";
+ public override string Description => "Tiare image format";
+ public override uint Signature => 0;
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x30);
+ int pos = header.IndexOf (0x1A);
+ if (-1 == pos)
+ return null;
+ ++pos;
+ while (pos < header.Length && header[pos++] != 0)
+ ;
+ if (pos + 3 >= header.Length || header[pos+3] != 4)
+ return null;
+ byte flags = header[pos];
+ file.Position = pos + 8;
+ int skip = Binary.BigEndian (file.ReadUInt16());
+ if (skip != 0)
+ file.Seek (skip, SeekOrigin.Current);
+ uint width = Binary.BigEndian (file.ReadUInt16());
+ uint height = Binary.BigEndian (file.ReadUInt16());
+ if (width == 0 || height == 0)
+ return null;
+ return new GraMetaData
+ {
+ Width = width,
+ Height = height,
+ BPP = 4,
+ Flags = flags,
+ DataOffset = file.Position,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var gra = (GraMetaData)info;
+ file.Position = gra.DataOffset;
+ BitmapPalette palette;
+ if (gra.HasPalette)
+ palette = ReadPalette (file.AsStream, 16, PaletteFormat.Rgb);
+ else
+ palette = DefaultPalette;
+ var reader = new System98.GraBaseReader (file, info);
+ reader.UnpackBits();
+ return ImageData.Create (gra, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride);
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("GraFormat.Write not implemented");
+ }
+
+ static readonly BitmapPalette DefaultPalette = new BitmapPalette (new Color[] {
+ #region Default palette
+ Color.FromRgb (0x00, 0x00, 0x00),
+ Color.FromRgb (0x00, 0x00, 0x77),
+ Color.FromRgb (0x77, 0x00, 0x00),
+ Color.FromRgb (0x77, 0x00, 0x77),
+ Color.FromRgb (0x00, 0x77, 0x00),
+ Color.FromRgb (0x00, 0x77, 0x77),
+ Color.FromRgb (0x77, 0x77, 0x00),
+ Color.FromRgb (0x77, 0x77, 0x77),
+ Color.FromRgb (0x00, 0x00, 0x00),
+ Color.FromRgb (0x00, 0x00, 0xFF),
+ Color.FromRgb (0xFF, 0x00, 0x00),
+ Color.FromRgb (0xFF, 0x00, 0xFF),
+ Color.FromRgb (0x00, 0xFF, 0x00),
+ Color.FromRgb (0x00, 0xFF, 0xFF),
+ Color.FromRgb (0xFF, 0xFF, 0x00),
+ Color.FromRgb (0xFF, 0xFF, 0xFF),
+ #endregion
+ });
+ }
+}
diff --git a/Legacy/Tobe/ImageWBI.cs b/Legacy/Tobe/ImageWBI.cs
new file mode 100644
index 00000000..f37d5da8
--- /dev/null
+++ b/Legacy/Tobe/ImageWBI.cs
@@ -0,0 +1,107 @@
+//! \file ImageWBI.cs
+//! \date 2023 Oct 18
+//! \brief TOBE 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 System.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+
+// [000915][TOBE] One's Own [or very]
+
+namespace GameRes.Formats.Tobe
+{
+ internal class WbiMetaData : ImageMetaData
+ {
+ public byte RleCode;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class WbiFormat : ImageFormat
+ {
+ public override string Tag => "WBI";
+ public override string Description => "TOBE image format";
+ public override uint Signature => 0x2D494257; // 'WBI-'
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ var header = file.ReadHeader (0x20);
+ if (!header.AsciiEqual (4, "V1.00\0"))
+ return null;
+ return new WbiMetaData
+ {
+ Width = header.ToUInt16 (0xE),
+ Height = header.ToUInt16 (0x10),
+ BPP = 24,
+ RleCode = header[0x1C],
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var wbi = (WbiMetaData)info;
+ file.Position = 0x20;
+ int stride = wbi.iWidth * 3;
+ var pixels = new byte[stride * wbi.iHeight];
+ int dst = 0;
+ bool skip = false;
+ byte r = 0, g = 0, b = 0;
+ int count = 0;
+ while (dst < pixels.Length)
+ {
+ if (count <= 0)
+ {
+ b = file.ReadUInt8();
+ if (skip)
+ {
+ file.ReadByte();
+ skip = false;
+ }
+ g = file.ReadUInt8();
+ r = file.ReadUInt8();
+ count = 1;
+ if (wbi.RleCode == file.PeekByte())
+ {
+ count = file.ReadUInt16() >> 8;
+ if (count <= 0)
+ {
+ count = 1;
+ file.Seek (-2, SeekOrigin.Current);
+ skip = true;
+ }
+ }
+ }
+ --count;
+ pixels[dst++] = b;
+ pixels[dst++] = g;
+ pixels[dst++] = r;
+ }
+ return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, pixels, stride);
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("WbiFormat.Write not implemented");
+ }
+ }
+}
diff --git a/Legacy/Ucom/ImageUG.cs b/Legacy/Ucom/ImageUG.cs
new file mode 100644
index 00000000..ce8a27b8
--- /dev/null
+++ b/Legacy/Ucom/ImageUG.cs
@@ -0,0 +1,234 @@
+//! \file ImageUG.cs
+//! \date 2023 Oct 16
+//! \brief Ucom image format (PC-98).
+//
+// 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.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+// [961220][Ucom] Bunkasai
+
+namespace GameRes.Formats.Ucom
+{
+ [Export(typeof(ImageFormat))]
+ public class UgFormat : ImageFormat
+ {
+ public override string Tag => "UG";
+ public override string Description => "Ucom image format";
+ public override uint Signature => 0;
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ if (!file.Name.HasExtension (".UG"))
+ return null;
+ int left = file.ReadUInt16();
+ int top = file.ReadUInt16();
+ int right = file.ReadUInt16();
+ int bottom = file.ReadUInt16();
+ int width = (right - left + 1) << 3;
+ int height = bottom - top + 1;
+ if (width <= 0 || height <= 0 || width > 640 || height > 512)
+ return null;
+ return new ImageMetaData
+ {
+ Width = (uint)width,
+ Height = (uint)height,
+ OffsetX = left,
+ OffsetY = top,
+ BPP = 4,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new UgReader (file, info);
+ return reader.Unpack();
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("UgFormat.Write not implemented");
+ }
+ }
+
+ ///
+ /// Same compression algorithm as the base, but scanlines are vertical
+ ///
+ internal class UgReader : System98.GraBaseReader
+ {
+ public UgReader (IBinaryStream input, ImageMetaData info) : base (input, info)
+ {
+ }
+
+ public ImageData Unpack ()
+ {
+ m_input.Position = 8;
+ var palette = ReadPalette();
+ m_input.Position = 0x28;
+ try
+ {
+ UnpackBitsInternal();
+ }
+ catch (EndOfStreamException)
+ {
+ FlushBuffer();
+ }
+ return ImageData.Create (m_info, PixelFormats.Indexed4, palette, Pixels, Stride);
+ }
+
+ void UnpackBitsInternal ()
+ {
+ int height = m_info.iHeight;
+ int hTimes2 = height << 1;
+ int hTimes4 = height << 2;
+ int buffer_size = hTimes4 * 3;
+ m_buffer = new ushort[buffer_size >> 1];
+ m_dst = 0;
+ InitFrame();
+ InitBitReader();
+ ushort p = ReadPair (0);
+ for (int i = 0; i < hTimes2+1; ++i)
+ m_buffer[i] = p;
+ int dst = hTimes4;
+ int prev_src = 0;
+ while (m_dst < m_pixels.Length)
+ {
+ bool same_line = false;
+ int src = -hTimes2;
+ if (GetNextBit() != 0) // @1@
+ {
+ if (GetNextBit() == 0) // @4@
+ src += 4;
+ else if (GetNextBit() == 0) // @5@
+ src -= 4;
+ else
+ src <<= 1;
+ }
+ else if (GetNextBit() == 0) // @2@
+ {
+ src = -4;
+ p = m_buffer[dst/2-1];
+ if ((p & 0xFF) == (p >> 8))
+ same_line = src != prev_src;
+ }
+ if (src != prev_src) // @6@
+ {
+ prev_src = src;
+ if (!same_line)
+ src += dst;
+ else
+ src = dst - 2;
+ if (GetNextBit() != 0) // @3@
+ {
+ int bitlength = 0;
+ do
+ {
+ ++bitlength;
+ }
+ while (GetNextBit() != 0);
+ int count = 1;
+ while (bitlength --> 0)
+ count = count << 1 | GetNextBit();
+ MovePixels (m_buffer, src, dst, count);
+ dst += count << 1;
+ if (dst == buffer_size)
+ {
+ if (FlushBuffer())
+ return;
+ dst = hTimes4;
+ }
+ }
+ else
+ {
+ MovePixels (m_buffer, src, dst, 1);
+ dst += 2;
+ if (dst == buffer_size)
+ {
+ if (FlushBuffer())
+ return;
+ dst = hTimes4;
+ }
+ }
+ }
+ else
+ {
+ p = m_buffer[dst/2-1];
+ do
+ {
+ byte prev = (byte)(p >> 8);
+ p = ReadPair (prev);
+ m_buffer[dst >> 1] = p;
+ dst += 2;
+ if (dst == buffer_size)
+ {
+ if (FlushBuffer())
+ return;
+ dst = hTimes4;
+ }
+ }
+ while (GetNextBit() != 0);
+ prev_src = 0;
+ }
+ }
+ }
+
+ bool FlushBuffer ()
+ {
+ int height = m_info.iHeight;
+ int src_line = height << 1;
+ int dst = m_dst;
+ for (int i = 0; i < height; ++i)
+ {
+ int src = src_line;
+ for (int j = 0; j < 4; ++j)
+ {
+ ushort p = m_buffer[src];
+ m_pixels[dst+j] = (byte)((p & 0xF0) | p >> 12);
+ src += height;
+ }
+ src_line++;
+ dst += m_output_stride;
+ }
+ m_dst += 4;
+ MovePixels (m_buffer, height << 3, 0, height << 1);
+ return m_dst >= m_output_stride;
+ }
+
+ BitmapPalette ReadPalette ()
+ {
+ var colors = new Color[16];
+ for (int i = 0; i < 16; ++i)
+ {
+ ushort rgb = m_input.ReadUInt16();
+ int b = (rgb & 0xF) * 0x11;
+ int r = ((rgb >> 4) & 0xF) * 0x11;
+ int g = ((rgb >> 8) & 0xF) * 0x11;
+ colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
+ }
+ return new BitmapPalette (colors);
+ }
+ }
+}
diff --git a/Legacy/WestGate/ArcUCA.cs b/Legacy/WestGate/ArcUCA.cs
index 7b9f66cd..821870a2 100644
--- a/Legacy/WestGate/ArcUCA.cs
+++ b/Legacy/WestGate/ArcUCA.cs
@@ -74,13 +74,15 @@ namespace GameRes.Formats.WestGate
uint next_offset = file.View.ReadUInt32 (index_offset+0xC);
if (next_offset < data_offset)
return null;
+ string last_name = null;
var invalid_chars = Path.GetInvalidFileNameChars();
var dir = new List (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index_offset, 0xC);
- if (string.IsNullOrWhiteSpace (name) || name.IndexOfAny (invalid_chars) != -1)
+ if (last_name == name || string.IsNullOrWhiteSpace (name) || name.IndexOfAny (invalid_chars) != -1)
return null;
+ last_name = name;
index_offset += 0x10;
var entry = new Entry { Name = name, Type = entry_type };
entry.Offset = next_offset;