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/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/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/Legacy.csproj b/Legacy/Legacy.csproj
index 3f5b9ce2..ce3dac7f 100644
--- a/Legacy/Legacy.csproj
+++ b/Legacy/Legacy.csproj
@@ -89,8 +89,10 @@
+
+
@@ -144,6 +146,8 @@
+
+
@@ -156,6 +160,9 @@
+
+
+
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/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/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/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);
+ }
+ }
+}