From 419a5f4e31b7ec99a1cb95a15060e873be8aa324 Mon Sep 17 00:00:00 2001 From: morkt Date: Thu, 7 Sep 2023 12:47:22 +0400 Subject: [PATCH] (Legacy): bunch of formats. (GSX): K5 archives + K4 images. (HyperWorks): G images. (IDA): better support packed entries. (Logg): ARF, MBM archives, FRM images. (Omi): DAT archives. (Rare): X archives. (RHSS): 'CRG' archives. (SplushWave): better SWG images support. --- Legacy/Gsx/ArcK3.cs | 2 +- Legacy/Gsx/ArcK5.cs | 70 ++++++ Legacy/Gsx/ImageK4.cs | 214 ++++++++++++++++- Legacy/HyperWorks/ImageG.cs | 421 ++++++++++++++++++++++++++++++++++ Legacy/HyperWorks/ImageI24.cs | 49 ++-- Legacy/Inspire/ArcIDA.cs | 29 ++- Legacy/Legacy.csproj | 8 + Legacy/Logg/ArcARF.cs | 141 ++++++++++++ Legacy/Logg/ArcMBM.cs | 28 ++- Legacy/Logg/ImageFRM.cs | 63 +++++ Legacy/Omi/ArcDAT.cs | 213 +++++++++++++++++ Legacy/Rare/ArcX.cs | 132 +++++++++++ Legacy/Rhss/ArcCRG.cs | 83 +++++++ Legacy/SplushWave/ArcDAT.cs | 90 ++++++++ Legacy/SplushWave/ImageSWG.cs | 128 +++++------ 15 files changed, 1564 insertions(+), 107 deletions(-) create mode 100644 Legacy/Gsx/ArcK5.cs create mode 100644 Legacy/HyperWorks/ImageG.cs create mode 100644 Legacy/Logg/ArcARF.cs create mode 100644 Legacy/Logg/ImageFRM.cs create mode 100644 Legacy/Omi/ArcDAT.cs create mode 100644 Legacy/Rare/ArcX.cs create mode 100644 Legacy/Rhss/ArcCRG.cs diff --git a/Legacy/Gsx/ArcK3.cs b/Legacy/Gsx/ArcK3.cs index 00f12ea0..e9972854 100644 --- a/Legacy/Gsx/ArcK3.cs +++ b/Legacy/Gsx/ArcK3.cs @@ -26,7 +26,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; -// [000225][Light Plan] My Fairink Yousei Byakuya Monogatari +// [000225][Light Plan] My Fair Link Yousei Byakuya Monogatari namespace GameRes.Formats.Gsx { diff --git a/Legacy/Gsx/ArcK5.cs b/Legacy/Gsx/ArcK5.cs new file mode 100644 index 00000000..82787c6a --- /dev/null +++ b/Legacy/Gsx/ArcK5.cs @@ -0,0 +1,70 @@ +//! \file ArcK5.cs +//! \date 2023 Aug 27 +//! \brief GSX 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; +using System.IO; +using System.Text; + +namespace GameRes.Formats.Gsx +{ + [Export(typeof(ArchiveFormat))] + public class K5Opener : ArchiveFormat + { + public override string Tag => "K5"; + public override string Description => "GSX engine resource archive"; + public override uint Signature => 0x01354B; // 'K5' + public override bool IsHierarchic => true; + public override bool CanWrite => false; + + public K5Opener () + { + ContainedFormats = new[] { "K4", "OGG" }; + } + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (4); + if (!IsSaneCount (count)) + return null; + uint index_offset = file.View.ReadUInt32 (8); + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var dir_name = file.View.ReadString (index_offset, 0x80, Encoding.Unicode); + var name = file.View.ReadString (index_offset+0x80, 0x40, Encoding.Unicode); + name = Path.Combine (dir_name, name); + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index_offset+0xC8); + entry.Size = file.View.ReadUInt32 (index_offset+0xCC); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 0x100; + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/Legacy/Gsx/ImageK4.cs b/Legacy/Gsx/ImageK4.cs index 93c3dca8..79866dc8 100644 --- a/Legacy/Gsx/ImageK4.cs +++ b/Legacy/Gsx/ImageK4.cs @@ -1,8 +1,8 @@ //! \file ImageK4.cs -//! \date 2019 Feb 07 -//! \brief Toyo GSX image format. +//! \date 2023 Aug 27 +//! \brief GSX engine image format. // -// Copyright (C) 2019 by morkt +// Copyright (C) 2023 by morkt // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -23,6 +23,8 @@ // IN THE SOFTWARE. // +using GameRes.Utility; +using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; @@ -33,16 +35,16 @@ namespace GameRes.Formats.Gsx { internal class K4MetaData : ImageMetaData { - public bool HasAlpha; + public byte AlphaMode; public int FrameCount; } [Export(typeof(ImageFormat))] public class K4Format : ImageFormat { - public override string Tag { get { return "K4"; } } - public override string Description { get { return "Toyo GSX image format"; } } - public override uint Signature { get { return 0x0201344B; } } // 'K4' + public override string Tag => "K4"; + public override string Description => "GSX engine image format"; + public override uint Signature => 0x0201344B; // 'K4' public override ImageMetaData ReadMetaData (IBinaryStream file) { @@ -51,20 +53,22 @@ namespace GameRes.Formats.Gsx return null; if (header[2] != 1 || header[3] != 2) return null; + int frame_count = header.ToInt16 (0xC); + if (frame_count <= 0) + return null; return new K4MetaData { Width = header.ToUInt16 (4), Height = header.ToUInt16 (6), BPP = header[0xF], - HasAlpha = header[0xB] != 0, - FrameCount = header.ToUInt16 (0xC), + AlphaMode = header[0xB], + FrameCount = frame_count, }; } public override ImageData Read (IBinaryStream file, ImageMetaData info) { - var meta = (K4MetaData)info; - - return ImageData.Create (info, format, palette, pixels); + var reader = new K4Reader (file, (K4MetaData)info); + return reader.Unpack(); } public override void Write (Stream file, ImageData image) @@ -72,4 +76,190 @@ namespace GameRes.Formats.Gsx throw new System.NotImplementedException ("K4Format.Write not implemented"); } } + + internal sealed class K4Reader + { + IBinaryStream m_input; + K4MetaData m_info; + + public K4Reader (IBinaryStream input, K4MetaData info) + { + m_input = input; + m_info = info; + } + + int m_stride; + int m_pixel_size; + + public ImageData Unpack () + { + uint base_offset = 0x30; + m_input.Position = base_offset; + m_info.Width = m_input.ReadUInt16(); + m_info.Height = m_input.ReadUInt16(); + m_input.Seek (8, SeekOrigin.Current); + int bpp = m_input.ReadUInt16(); + int flags = m_input.ReadUInt16(); + m_input.Seek (4, SeekOrigin.Current); + uint alpha_pos = m_input.ReadUInt32(); + m_input.Seek (12, SeekOrigin.Current); + int ctl_length = m_input.ReadInt32(); + + m_pixel_size = bpp / 8; + m_stride = (m_info.iWidth * m_pixel_size + 3) & ~3; + var pixels = new byte[m_info.iHeight * m_stride]; + int dst = 0; + bool do_delta = (flags & 1) != 0; + var control_bytes = m_input.ReadBytes (ctl_length - 0x10); + using (var mem = new MemoryStream (control_bytes)) + using (var ctl = new MsbBitStream (mem)) + using (var data = new MsbBitStream (m_input.AsStream, true)) + { + while (dst < pixels.Length) + { + int b = ctl.GetNextBit(); + if (-1 == b) + break; + if (b != 0) + { + if (!do_delta) + { + pixels[dst++] = (byte)data.GetBits (8); + } + else if (dst >= m_pixel_size) + { + pixels[dst] = (byte)(pixels[dst - m_pixel_size] + data.GetBits (9) + 1); + ++dst; + } + else + { + pixels[dst++] = (byte)data.GetBits (9); + } + } + else + { + int pos, count; + if (ctl.GetNextBit() != 0) + { + pos = data.GetBits (14); + count = data.GetBits (4) + 3; + } + else + { + pos = data.GetBits (9); + count = data.GetBits (3) + 2; + } + int src = dst - pos - 1; + count = Math.Min (count, pixels.Length - dst); + if (!do_delta || dst < m_pixel_size) + { + Binary.CopyOverlapped (pixels, src, dst, count); + dst += count; + } + else + { + while (count --> 0) + { + pixels[dst++] = (byte)(pixels[src] + pixels[src + pos - m_pixel_size + 1] - pixels[src - m_pixel_size]); + ++src; + } + } + } + } + } + if (0 == alpha_pos) + { + if (24 == bpp) + return ImageData.CreateFlipped (m_info, PixelFormats.Bgr24, null, pixels, m_stride); + else + return ImageData.CreateFlipped (m_info, PixelFormats.Bgr32, null, pixels, m_stride); + } + if (0xFF == m_info.AlphaMode) + pixels = UnpackAlphaFF (alpha_pos + base_offset, pixels); + else if (0xFE == m_info.AlphaMode) + pixels = UnpackAlphaFE (alpha_pos + base_offset, pixels); + else + throw new NotSupportedException (string.Format ("Not supported alpha channel mode 0x{0:X2}", m_info.AlphaMode)); + m_stride = m_info.iWidth * 4; + return ImageData.Create (m_info, PixelFormats.Bgra32, null, pixels, m_stride); + } + + byte[] UnpackAlphaFF (uint alpha_pos, byte[] pixels) + { + m_input.Position = alpha_pos; + var offsets = new int[m_info.iHeight]; + for (int i = 0; i < offsets.Length; ++i) + offsets[i] = m_input.ReadInt32(); + + var output = new byte[m_info.iWidth * m_info.iHeight * 4]; + int dst = 0; + for (int y = 0; y < m_info.iHeight; y++) + { + m_input.Position = alpha_pos + offsets[y]; + int src = (m_info.iHeight - y - 1) * m_stride; + int dst_a = dst + 3; + for (int x = 0; x < m_info.iWidth; ++x) + { + output[dst ] = pixels[src ]; + output[dst+1] = pixels[src+1]; + output[dst+2] = pixels[src+2]; + dst += 4; + src += m_pixel_size; + } + for (int x = 0; x < m_info.iWidth; ) + { + byte alpha = m_input.ReadUInt8(); + int count = m_input.ReadUInt8(); + count = Math.Min (count, m_info.iWidth - x); + x += count; + if (alpha > 0) + { + alpha = (byte)((alpha * 0xFF) >> 7); + while (count --> 0) + { + output[dst_a] = alpha; + dst_a += 4; + } + } + else + { + dst_a += 4 * count; + } + } + } + return output; + } + + byte[] UnpackAlphaFE (uint alpha_pos, byte[] pixels) + { + m_input.Position = alpha_pos; + var output = new byte[m_info.iWidth * m_info.iHeight * 4]; + int dst = 0; + for (int y = 0; y < m_info.iHeight; y++) + { + int src = (m_info.iHeight - y - 1) * m_stride; + int dst_a = dst + 3; + for (int x = 0; x < m_info.iWidth; ++x) + { + output[dst ] = pixels[src ]; + output[dst+1] = pixels[src+1]; + output[dst+2] = pixels[src+2]; + dst += 4; + src += m_pixel_size; + } + for (int x = 0; x < m_info.iWidth; x += 8) + { + byte alpha = m_input.ReadUInt8(); + int count = Math.Min (8, m_info.iWidth - x); + for (int i = 0; i < count; ++i) + { + output[dst_a] = (byte)-(alpha & 1); + dst_a += 4; + alpha >>= 1; + } + } + } + return output; + } + } } diff --git a/Legacy/HyperWorks/ImageG.cs b/Legacy/HyperWorks/ImageG.cs new file mode 100644 index 00000000..0e83d113 --- /dev/null +++ b/Legacy/HyperWorks/ImageG.cs @@ -0,0 +1,421 @@ +//! \file ImageG.cs +//! \date 2023 Aug 28 +//! \brief HyperWorks 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; + +// [951207][Love Gun] ACE OF SPADES +// I24 predecessor + +namespace GameRes.Formats.HyperWorks +{ + [Export(typeof(ImageFormat))] + public class GFormat : ImageFormat + { + public override string Tag => "G"; + public override string Description => "HyperWorks indexed image format"; + public override uint Signature => 0x1A477D00; + + public GFormat () + { + Signatures = new[] { 0x1A477D00u, 0u }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (12); + if (header.ToUInt16 (2) != 0x1A47) + return null; + // not sure if 0x7D00 is a required signature, so rely on filename + if (header.ToUInt16 (0) != 0x7D00 && !file.Name.HasExtension (".G")) + return null; + return new ImageMetaData { + Width = header.ToUInt16 (8), + Height = header.ToUInt16 (10), + OffsetX = header.ToInt16 (4), + OffsetY = header.ToInt16 (6), + BPP = 8, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new GReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("GFormat.Write not implemented"); + } + } + + internal sealed class GReader + { + IBinaryStream m_input; + ImageMetaData m_info; + int m_stride; + byte[] m_palette_data; + byte[] m_output; + ushort[] m_buffer; + int[] m_line_ptr; + + public GReader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + m_stride = (m_info.iWidth * m_info.BPP / 8 + 1) & -2; + int s = Math.Max (0x142, m_stride / 2 + 2); // line buffer size + m_buffer = new ushort[s * 3 + 1]; + m_line_ptr = new int[3] { 1, 1 + s, 1 + s*2 }; + } + + public ImageData Unpack () + { + m_input.Position = 0x0C; + m_palette_data = m_input.ReadBytes (0x30); + m_input.Position = 0x40; + int width = ((m_info.iWidth + 7) & -8); + int rows = ((m_info.iHeight + 1) & -2); + m_output = new byte[m_stride * rows]; + InitColorTable(); + int blockW = width >> 1; + int blockH = rows >> 1; + SetupBitReader(); + for (int y = 0; y < blockH; ++y) + { + var dst = m_line_ptr[2]; + m_line_ptr[2] = m_line_ptr[1]; + m_line_ptr[1] = m_line_ptr[0]; + m_line_ptr[0] = dst; + int x = 0; + while (x < blockW) + { + if (GetNextBit() != 0) + { + m_buffer[dst++] = GetColorFromTable (x); + ++x; + } + else + { + int count = ExtractBits (BitTable1); + if (count >= 0x40) + count += ExtractBits (BitTable1); + int idx = ExtractBits (BitTable2) * 2; + int src = m_line_ptr[OffTable[idx + 1]]; + src += OffTable[idx] + x; + x += count; + while (count --> 0) + { + m_buffer[dst++] = m_buffer[src++]; + } + } + } + UnpackRow (y, blockW, m_line_ptr[0]); + } + var palette = UnpackPalette(); + return ImageData.Create (m_info, PixelFormats.Indexed8, palette, m_output, m_stride); + } + + void UnpackRow (int y, int width, int buf_pos) + { + int row1 = m_stride * y * 2; + int row2 = row1 + m_stride; + for (int i = 0; i < width; ++i) + { + ushort v = m_buffer[buf_pos++]; + ushort v0 = (ushort)((v & 0xF00 | ((v & 0xF000) >> 12)) + 0xA0A); + LittleEndian.Pack (v0, m_output, row2); + row2 += 2; + ushort v1 = (ushort)((((v & 0xF) << 8) | ((v & 0xF0) >> 4)) + 0xA0A); + LittleEndian.Pack (v1, m_output, row1); + row1 += 2; + } + } + + byte[] g_palIndexes = { 0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 0xC, 0xD, 0xA, 0xB, 0xE, 0xF }; + + BitmapPalette UnpackPalette () + { + var colors = new Color[42]; + for (int i = 0; i < 16; ++i) + { + int R = m_palette_data[3 * g_palIndexes[i] ]; R |= R << 4; + int G = m_palette_data[3 * g_palIndexes[i] + 1]; G |= G << 4; + int B = m_palette_data[3 * g_palIndexes[i] + 2]; B |= B << 4; + colors[i+10] = Color.FromRgb ((byte)R, (byte)G, (byte)B); + + int b = R & 1; + int c = (sbyte)R >> 1; + if (c < 0) + c += b; + colors[i+26].R = (byte)c; + + b = G & 1; + c = (sbyte)G >> 1; + if (c < 0) + c += b; + colors[i+26].G = (byte)c; + + b = B & 1; + c = (sbyte)B >> 1; + if (c < 0) + c += b; + colors[i+26].B = (byte)c; + } + return new BitmapPalette (colors); + } + + byte[] g_colorTable = new byte[256]; + + void InitColorTable () + { + int dst = 0; + for (int i = 0; i < 16; ++i) + for (int j = 0; j < 16; ++j) + g_colorTable[dst++] = (byte)((j + i + 1) & 0xF); + } + + ushort GetColorFromTable (int x) + { + ushort b0 = m_buffer[m_line_ptr[1] + x]; + int n0 = b0 & 0xF; + int n1 = (b0 >> 4) & 0xF; + int n2 = (b0 >> 8) & 0xF; + int n3 = (b0 >> 12) & 0xF; + + ushort b1 = m_buffer[m_line_ptr[1] + x - 1]; + int m0 = b1 & 0xF; + int m1 = (b1 >> 4) & 0xF; + int m2 = (b1 >> 8) & 0xF; + int m3 = (b1 >> 12) & 0xF; + + ushort b2 = m_buffer[m_line_ptr[0] + x - 1]; + int p0 = b2 & 0xF; + int p1 = (b2 >> 4) & 0xF; + int p2 = (b2 >> 8) & 0xF; + int p3 = (b2 >> 12) & 0xF; + + int r1 = n1; + if (n1 != n3 && (n1 != m1 || n1 != p1)) + { + if (p0 == p1) + r1 = p0; + else + r1 = m2; + } + if (GetNextBit() != 0) + r1 = AdjustColorTable (r1); + + int r0 = n0; + if (n0 != n2 && (n0 != m0 || n0 != p0)) + { + if (r1 == p1) + r0 = p1; + else + r0 = n3; + } + if (GetNextBit() != 0) + r0 = AdjustColorTable (r0); + + int r3 = n3; + if (r1 != n3 && (n3 != m3 || n3 != p3)) + { + if (p2 == p3) + r3 = p2; + else + r3 = p0; + } + if (GetNextBit() != 0) + r3 = AdjustColorTable (r3); + + int r2 = n2; + if (n2 != r0 && (n2 != m2 || n2 != p2)) + { + if (p3 == r3) + r2 = p3; + else + r2 = r1; + } + if (GetNextBit() != 0) + r2 = AdjustColorTable (r2); + + return (ushort)((r3 << 12) | (r2 << 8) | (r1 << 4) | r0); + } + + byte AdjustColorTable (int idx) + { + int shift_count = ExtractBits (BitTable3); + int i = 16 * idx + shift_count; + byte c = g_colorTable[i]; + if (shift_count != 0) + { + while (shift_count --> 0) + { + g_colorTable[i] = g_colorTable[i-1]; + --i; + } + g_colorTable[i] = c; + } + return c; + } + + int ExtractBits (byte[] table) + { + int idx = ((bits >> 8) & 0xFF) << 1; + int n = table[idx]; + if (n != 0) + { + if (n >= bitCount) + { + n -= bitCount; + bits <<= bitCount; + int b = m_input.ReadByte(); + if (b != -1) // XXX ignore EOF + bits |= b; + bitCount = 8; + } + bits <<= n; + bitCount -= n; + return table[idx+1]; + } + else + { + bits <<= bitCount; + int b = m_input.ReadByte(); + if (b != -1) // XXX ignore EOF + bits |= b; + bits <<= 8 - bitCount; + int t = table[idx+1]; + do + { + int i = GetNextBit(); + t = OffTable[2 * t + 54 + i]; + } + while (OffTable[2 * t + 54] != 0); + return OffTable[2 * t + 55]; + } + } + + int bits; + int bitCount; + + private void SetupBitReader () + { + bits = m_input.ReadUInt8() << 8; + bits |= m_input.ReadUInt8(); + bitCount = 8; + } + + private int GetNextBit () + { + bits <<= 1; + if (0 == --bitCount) + { + int b = m_input.ReadByte(); + if (b != -1) // XXX ignore EOF + bits |= b; + bitCount = 8; + } + return (bits >> 16) & 1; + } + + static readonly byte[] BitTable1 = new byte[] { + 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, + 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 8, + 0xD, 0, 0, 0, 1, 8, 0xE, 6, 7, 6, 7, 6, 7, 6, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 7, 0xA, 7, 0xA, 0, 2, 0, 7, 7, 0xB, 7, 0xB, 8, 0xF, 0, 3, 6, 8, 6, 8, 6, 8, 6, 8, 8, 0x10, 0, 4, + 0, 5, 8, 0x11, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 7, 0xC, 7, 0xC, 0, 8, 0, 6, 6, 9, 6, + 9, 6, 9, 6, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + }; + static readonly byte[] BitTable2 = new byte[] { + 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 7, 0x17, 7, 0x17, 7, 0x16, 7, 0x16, 6, 0x0E, 6, + 0x0E, 6, 0x0E, 6, 0x0E, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, + 3, 4, 3, 4, 3, 4, 3, 6, 0x0D, 6, 0x0D, 6, 0x0D, 6, 0x0D, 6, 0x0C, 6, 0x0C, 6, 0x0C, 6, 0x0C, 5, 6, + 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 7, 0x14, 7, + 0x14, 7, 0x18, 7, 0x18, 6, 0x10, 6, 0x10, 6, 0x10, 6, 0x10, 6, 0x11, 6, 0x11, 6, 0x11, 6, 0x11, 6, + 0x0F, 6, 0x0F, 6, 0x0F, 6, 0x0F, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, + 0x0A, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, + 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, + 6, 0x13, 6, 0x13, 6, 0x13, 6, 0x13, 6, 0x12, 6, 0x12, 6, 0x12, 6, 0x12, 5, 0x0B, 5, 0x0B, 5, 0x0B, + 5, 0x0B, 5, 0x0B, 5, 0x0B, 5, 0x0B, 5, 0x0B, 7, 0x19, 7, 0x19, 7, 0x1A, 7, 0x1A, 6, 0x15, 6, 0x15, + 6, 0x15, 6, 0x15, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, + 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, + 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, + 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, + 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, + 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, + 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, + }; + static readonly byte[] BitTable3 = new byte[] { + 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, + 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 7, 8, 8, 0x0A, 8, 0x0B, + 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 6, 7, 6, 7, 6, 7, 7, 9, 7, 9, 0, 0x5F, 8, 0x0C, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + }; + static readonly short[] OffTable = new short[] { + 0, 1, 1, 1, -1, 0, -1, 1, -4, 0, -2, 0, 2, 1, -2, 1, 4, 1, 0, 2, 1, 2, -1, 2, -8, 0, -4, 1, 8, 1, + -2, 2, 2, 2, -4, 2, 4, 2, 8, 2, -8, 1, -0x10, 0, -8, 2, 0x10, 1, 0x10, 2, -0x10, 1, -0x10, 2, + 0x2C, 9, 0x2D, 0x0A, 0x2E, 0x0B, 0x2F, 0x0C, 0x30, 0x0D, 0x31, 0x32, 0x0E, 0x33, 0x0F, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x34, 0x15, 0x37, 0x35, 0x16, 0x38, 0x17, 0x39, 0x3D, 0x18, 0x19, 0x36, 0x1A, + 0x1B, 0x3A, 0x3C, 0x1C, 0x3B, 0x1D, 0x3E, 0x3F, 0x1E, 0x40, 0x1F, 0x45, 0x46, 0x43, 0x20, 0x21, + 0x48, 0x22, 0x41, 0x23, 0x42, 0x24, 0x25, 0x44, 0x47, 0x4F, 0x4A, 0x49, 0x53, 0x4B, 0x4D, 0x57, + 0x52, 0x59, 0x58, 0x4E, 0x50, 0x56, 0x26, 0x54, 0x4C, 0x51, 0x55, 0x27, 0x28, 0x5A, 0x2B, 0x29, + 0x5B, 0x2A, 0x5C, 0x5D, 0x5E, 0, 0, 0, 0x12, 0, 0x13, 0, 0x14, 0, 0x15, 0, 0x16, 0, 0x17, 0, 0x18, + 0, 0x19, 0, 0x1A, 0, 0x1B, 0, 0x1C, 0, 0x1D, 0, 0x1E, 0, 0x1F, 0, 0x20, 0, 0x21, 0, 0x22, 0, 0x23, + 0, 0x24, 0, 0x25, 0, 0x26, 0, 0x27, 0, 0x28, 0, 0x29, 0, 0x2A, 0, 0x2B, 0, 0x2C, 0, 0x2D, 0, 0x2E, + 0, 0x2F, 0, 0x30, 0, 0x31, 0, 0x32, 0, 0x33, 0, 0x34, 0, 0x35, 0, 0x36, 0, 0x37, 0, 0x38, 0, 0x39, + 0, 0x3A, 0, 0x3B, 0, 0x3C, 0, 0x3D, 0, 0x3E, 0, 0x3F, 0, 0x40, 0, 0x80, 0, 0x0C0, 0, 0x100, 0, + 0x140, 0x60, 0x61, 0, 0x0D, 0, 0x0E, + }; + } +} diff --git a/Legacy/HyperWorks/ImageI24.cs b/Legacy/HyperWorks/ImageI24.cs index ea822005..6b905437 100644 --- a/Legacy/HyperWorks/ImageI24.cs +++ b/Legacy/HyperWorks/ImageI24.cs @@ -23,22 +23,32 @@ // IN THE SOFTWARE. // -using GameRes.Utility; using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; +// [970905][Ucom] Winter Kiss // [980626][Love Gun] ACE OF SPADES 2 namespace GameRes.Formats.HyperWorks { + internal class I24MetaData : ImageMetaData + { + public byte Version; + } + [Export(typeof(ImageFormat))] public class I24Format : ImageFormat { - public override string Tag { get { return "I24"; } } - public override string Description { get { return "HyperWorks image format"; } } - public override uint Signature { get { return 0x41343249; } } // 'I24A' + public override string Tag => "I24"; + public override string Description => "HyperWorks RGB image format"; + public override uint Signature => 0x41343249; // 'I24A' + + public I24Format () + { + Signatures = new[] { 0x41343249u, 0x20343249u }; + } public override ImageMetaData ReadMetaData (IBinaryStream file) { @@ -46,16 +56,17 @@ namespace GameRes.Formats.HyperWorks int bpp = header.ToInt16 (0x10); if (bpp != 24) return null; - return new ImageMetaData { + return new I24MetaData { Width = header.ToUInt16 (0xC), Height = header.ToUInt16 (0xE), BPP = bpp, + Version = header[3], }; } public override ImageData Read (IBinaryStream file, ImageMetaData info) { - var reader = new I24Decoder (file, info); + var reader = new I24Decoder (file, (I24MetaData)info); return reader.Unpack(); } @@ -68,9 +79,9 @@ namespace GameRes.Formats.HyperWorks internal class I24Decoder { IBinaryStream m_input; - ImageMetaData m_info; + I24MetaData m_info; - public I24Decoder (IBinaryStream input, ImageMetaData info) + public I24Decoder (IBinaryStream input, I24MetaData info) { m_input = input; m_info = info; @@ -170,14 +181,24 @@ namespace GameRes.Formats.HyperWorks short s2 = shift_table[shift_idx + 1]; if (shift_token != 0) { - while (shift_token --> 0) + if (m_info.Version == 'A') { - shift_table[shift_idx] = shift_table[shift_idx - 2]; - shift_table[shift_idx+1] = shift_table[shift_idx - 1]; - shift_idx -= 2; + while (shift_idx > 0) + { + shift_table[shift_idx] = shift_table[shift_idx - 2]; + shift_table[shift_idx+1] = shift_table[shift_idx - 1]; + shift_idx -= 2; + } + shift_table[0] = s1; + shift_table[1] = s2; + } + else + { + shift_table[shift_idx ] = shift_table[shift_idx - 2]; + shift_table[shift_idx + 1] = shift_table[shift_idx - 1]; + shift_table[shift_idx - 2] = s1; + shift_table[shift_idx - 1] = s2; } - shift_table[0] = s1; - shift_table[1] = s2; } int src = 4 * (x + s1); if (color_token >= 216) diff --git a/Legacy/Inspire/ArcIDA.cs b/Legacy/Inspire/ArcIDA.cs index 21d92289..a5e767ee 100644 --- a/Legacy/Inspire/ArcIDA.cs +++ b/Legacy/Inspire/ArcIDA.cs @@ -30,19 +30,20 @@ using System.IO; using System.Text; using GameRes.Compression; +// [971205][Azlocks] Isle Mystique // [991001][Inspire] days innocent // [000707][inspire] ambience namespace GameRes.Formats.Inspire { - internal class IdaEntry : Entry + internal class IdaEntry : PackedEntry { public uint Flags; public uint Key; } [Export(typeof(ArchiveFormat))] - public class PakOpener : ArchiveFormat + public class IdaOpener : ArchiveFormat { public override string Tag { get { return "IDA"; } } public override string Description { get { return "Inspire resource archive"; } } @@ -50,6 +51,11 @@ namespace GameRes.Formats.Inspire public override bool IsHierarchic { get { return false; } } public override bool CanWrite { get { return false; } } + public IdaOpener () + { + Extensions = new[] { "ida", "mha" }; + } + public override ArcFile TryOpen (ArcView file) { int version = file.View.ReadInt32 (4); @@ -58,8 +64,9 @@ namespace GameRes.Formats.Inspire using (var index = file.CreateStream()) { var dir = new List(); + bool has_packed = false; long index_pos = 8; - for (;;) + do { index.Position = index_pos; uint entry_length = index.ReadUInt32(); @@ -76,15 +83,27 @@ namespace GameRes.Formats.Inspire var entry = FormatCatalog.Instance.Create (name); entry.Offset = offset; - entry.Size = size; - if (!entry.CheckPlacement (file.MaxOffset)) + entry.Size = entry.UnpackedSize = size; + if (offset > file.MaxOffset || offset < index_pos) return null; + entry.IsPacked = (flags & 0x14) != 0; entry.Flags = flags; entry.Key = key; + has_packed = has_packed || entry.IsPacked; dir.Add (entry); } + while (index_pos < dir[0].Offset); if (0 == dir.Count) return null; + if (has_packed) // set proper sizes + { + 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); } } diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index 86c05aad..ed3f5dc5 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -90,8 +90,11 @@ + + + @@ -125,6 +128,8 @@ + + @@ -132,6 +137,7 @@ + @@ -145,8 +151,10 @@ + + diff --git a/Legacy/Logg/ArcARF.cs b/Legacy/Logg/ArcARF.cs new file mode 100644 index 00000000..446ab626 --- /dev/null +++ b/Legacy/Logg/ArcARF.cs @@ -0,0 +1,141 @@ +//! \file ArcARF.cs +//! \date 2023 Sep 03 +//! \brief Logg resource archive. +// +// Copyright (C) 2023 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using GameRes.Utility; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +// [980417][Logg] Tenshi Kourin +// [980828][Logg] Kazeiro no Romance + +namespace GameRes.Formats.Logg +{ + [Export(typeof(ArchiveFormat))] + public class ArfOpener : ArchiveFormat + { + public override string Tag => "ARF"; + public override string Description => "Logg archive file"; + public override uint Signature => 0; + public override bool IsHierarchic => true; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (0); + if (!IsSaneCount (count)) + return null; + + uint index = 4; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + uint offset = file.View.ReadUInt32 (index); + if (offset <= index || offset > file.MaxOffset) + return null; + uint size = file.View.ReadUInt32 (index+4); + byte name_len = file.View.ReadByte (index+8); + var name = file.View.ReadString (index+9, name_len); + var entry = Create (name); + entry.Offset = offset; + entry.UnpackedSize = size; + dir.Add (entry); + index += name_len + 9u; + if (index > dir[0].Offset) + return null; + } + long last_offset = file.MaxOffset; + for (int i = count-1; i >= 0; --i) + { + var entry = dir[i] as PackedEntry; + entry.Size = (uint)(last_offset - entry.Offset); + last_offset = entry.Offset; + if (string.IsNullOrEmpty (entry.Name)) + dir.RemoveAt (i); + else + entry.IsPacked = entry.Size != entry.UnpackedSize; + } + 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 input = arc.File.CreateStream (pent.Offset, pent.Size); + var output = new byte[pent.UnpackedSize]; + Decompress (input, output); + return new BinMemoryStream (output, pent.Name); + } + + void Decompress (IBinaryStream input, byte[] output) + { + using (var bits = new LsbBitStream (input.AsStream, true)) + { + int dst = 0; + while (dst < output.Length) + { + if (bits.GetNextBit() == 0) + { + output[dst++] = (byte)bits.GetBits (8); + } + else + { + int count; + if (bits.GetNextBit() == 0) + count = 2; + else if (bits.GetNextBit() == 0) + count = 3; + else if (bits.GetNextBit() == 0) + count = 4; + else if (bits.GetNextBit() == 0) + count = 5; + else + { + switch (bits.GetBits (2)) + { + case 0: count = 6; break; + case 1: count = bits.GetBits (2) + 7; break; + case 2: count = bits.GetBits (4) + 11; break; + case 3: count = bits.GetBits (10) + 26; break; + default: throw new EndOfStreamException(); + } + } + int offset; + if (bits.GetNextBit() == 0) + offset = bits.GetBits (8); + else if (bits.GetNextBit() == 0) + offset = bits.GetBits (10) + 0x100; + else + offset = bits.GetBits (12) + 0x500; + Binary.CopyOverlapped (output, dst - offset - 1, dst, count); + dst += count; + } + } + } + } + } +} diff --git a/Legacy/Logg/ArcMBM.cs b/Legacy/Logg/ArcMBM.cs index dbb057e6..7d5870bf 100644 --- a/Legacy/Logg/ArcMBM.cs +++ b/Legacy/Logg/ArcMBM.cs @@ -36,11 +36,11 @@ namespace GameRes.Formats.Logg [Export(typeof(ArchiveFormat))] public class MbmOpener : ArchiveFormat { - public override string Tag { get { return "MBM"; } } - public override string Description { get { return "Logg Adv engine resource archive"; } } - public override uint Signature { get { return 0; } } - public override bool IsHierarchic { get { return false; } } - public override bool CanWrite { get { return false; } } + public override string Tag => "MBM"; + public override string Description => "Logg Adv engine resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; public override ArcFile TryOpen (ArcView file) { @@ -63,19 +63,27 @@ namespace GameRes.Formats.Logg IDictionary GetArchiveIndex (ArcView file) { - uint last_offset = FileListMap.Value.Keys.Last(); + string list_name; + if (!ArcSizeToFileListMap.TryGetValue ((uint)file.MaxOffset, out list_name)) + return null; + var file_map = ReadFileList (list_name); + uint last_offset = file_map.Keys.Last(); if (last_offset != file.MaxOffset) return null; - return FileListMap.Value; + return file_map; } - Lazy> FileListMap = new Lazy> (ReadFileList); + static readonly Dictionary ArcSizeToFileListMap = new Dictionary { + { 0x0AB0F5F4, "logg_pl.lst" }, + { 0x0BFFD3DA, "logg_ak.lst" }, + { 0x09809196, "logg_th.lst" }, + }; - static IDictionary ReadFileList () + static IDictionary ReadFileList (string list_name) { var file_map = new SortedDictionary(); var comma = new char[] {','}; - FormatCatalog.Instance.ReadFileList ("logg_pl.lst", line => { + FormatCatalog.Instance.ReadFileList (list_name, line => { var parts = line.Split (comma, 2); uint offset = uint.Parse (parts[0], NumberStyles.HexNumber); if (2 == parts.Length) diff --git a/Legacy/Logg/ImageFRM.cs b/Legacy/Logg/ImageFRM.cs new file mode 100644 index 00000000..145cedfc --- /dev/null +++ b/Legacy/Logg/ImageFRM.cs @@ -0,0 +1,63 @@ +//! \file ImageFRM.cs +//! \date 2023 Sep 03 +//! \brief Logg image file. +// +// 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.Logg +{ + [Export(typeof(ImageFormat))] + public class FrmFormat : ImageFormat + { + public override string Tag { get => "FRM"; } + public override string Description { get => "Logg image format"; } + public override uint Signature { get => 0x4D5246; } // 'FRM' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + return new ImageMetaData { + Width = header.ToUInt32 (4), + Height = header.ToUInt32 (8), + BPP = 8, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 0x0C; + int stride = file.ReadInt32(); + var palette = ReadPalette (file.AsStream); + var pixels = file.ReadBytes (stride * info.iHeight); + return ImageData.Create (info, PixelFormats.Indexed8, palette, pixels, stride); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("FrmFormat.Write not implemented"); + } + } +} diff --git a/Legacy/Omi/ArcDAT.cs b/Legacy/Omi/ArcDAT.cs new file mode 100644 index 00000000..c2f5a7fb --- /dev/null +++ b/Legacy/Omi/ArcDAT.cs @@ -0,0 +1,213 @@ +//! \file ArcDAT.cs +//! \date 2023 Sep 04 +//! \brief OMI Script 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 GameRes.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text; + +namespace GameRes.Formats.Omi +{ + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat + { + public override string Tag => "DAT/OMI"; + public override string Description => "OMI Script Engine resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + internal const uint DefaultKey = 7654321u; + + public DatOpener () + { + ContainedFormats = new[] { "BMP", "TGA", "WAV", "TXT" }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!VFS.IsPathEqualsToFileName (file.Name, "scrdat")) + return null; + using (var input = file.CreateStream()) + using (var index = new DecryptedStream (input, DefaultKey, 0)) + { + var line = index.ReadLine(); + int count = int.Parse (line); + if (!IsSaneCount (count)) + return null; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = index.ReadLine(); + line = index.ReadLine(); + uint size = uint.Parse (line); + var entry = Create (name); + entry.Size = size; + entry.IsPacked = entry.Type == "image"; + dir.Add (entry); + } + long data_pos = index.Position; + for (int i = 0; i < count; ++i) + { + dir[i].Offset = data_pos; + if (!dir[i].CheckPlacement (file.MaxOffset)) + return null; + data_pos += dir[i].Size; + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = (PackedEntry)entry; + Stream input = arc.File.CreateStream (entry.Offset, entry.Size); + input = new DecryptedStream (input, DefaultKey, (uint)entry.Offset); + if (!pent.IsPacked) + return input; + using (var packed = new BinaryStream (input, pent.Name)) + { + var unpacked = DecompressRle (packed); + if (pent.UnpackedSize == 0) + pent.UnpackedSize = (uint)unpacked.Length; + return new BinMemoryStream (unpacked, pent.Name); + } + } + + internal static byte[] DecompressRle (IBinaryStream input) + { + int size = input.ReadInt32(); + var output = new byte[size * 2]; + ushort rle_marker = input.ReadUInt16(); + int dst = 0; + while (dst < output.Length) + { + input.Read (output, dst, 2); + if (output.ToUInt16 (dst) == rle_marker) + { + input.Read (output, dst, 2); + dst += 2; + int count = input.ReadUInt16() - 1; + if (count > 0) + { + count *= 2; + Binary.CopyOverlapped (output, dst-2, dst, count); + dst += count; + } + } + else + { + dst += 2; + } + } + return output; + } + } + + internal class DecryptedStream : InputProxyStream + { + private uint m_key; + + static readonly Encoding Encoding = Encodings.cp932; + + public override bool CanSeek { get => false; } + public override long Position + { + get => BaseStream.Position; + set => throw new NotSupportedException ("Stream.Position property is not supported"); + } + + public DecryptedStream (Stream stream, uint key, uint start_offset) : base (stream) + { + if (start_offset > 0) + { + do + { + key = 5 * key - 3; + } + while (--start_offset > 0); + } + m_key = key; + } + + public override int Read (byte[] buffer, int offset, int count) + { + int read = BaseStream.Read (buffer, offset, count); + Decrypt (buffer, offset, read); + return read; + } + + byte[] m_byte_buffer = new byte[1]; + + public override int ReadByte () + { + int b = BaseStream.ReadByte(); + if (-1 != b) + { + m_byte_buffer[0] = (byte)b; + Decrypt (m_byte_buffer, 0, 1); + b = m_byte_buffer[0]; + } + return b; + } + + internal void Decrypt (byte[] data, int offset, int count) + { + for (int i = 0; i < count; ++i) + { + data[offset+i] = (byte)(Binary.RotByteR (data[offset+i], 1) - m_key); + m_key = 5 * m_key - 3; + } + } + + byte[] m_buffer; + + public string ReadLine () + { + if (null == m_buffer) + m_buffer = new byte[32]; + int size = 0; + for (;;) + { + int b = ReadByte(); + if (-1 == b || '\n' == b) + break; + if (m_buffer.Length == size) + { + Array.Resize (ref m_buffer, checked(size/2*3)); + } + m_buffer[size++] = (byte)b; + } + return Encoding.GetString (m_buffer, 0, size); + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + } +} diff --git a/Legacy/Rare/ArcX.cs b/Legacy/Rare/ArcX.cs new file mode 100644 index 00000000..4cca0ad0 --- /dev/null +++ b/Legacy/Rare/ArcX.cs @@ -0,0 +1,132 @@ +//! \file ArcX.cs +//! \date 2023 Sep 02 +//! \brief Rare 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; + +// [990528][Rare] Seisen Ren'ai Yuugi + +namespace GameRes.Formats.Rare +{ + [Export(typeof(ArchiveFormat))] + public class XOpener : ArchiveFormat + { + public override string Tag => "X/RARE"; + public override string Description => "Rare resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + static readonly Dictionary> KnownExeMap = new Dictionary> { + { "seisen.exe", Tuple.Create (0x3A9A0u, 715) }, + }; + + public override ArcFile TryOpen (ArcView file) + { + if (!VFS.IsPathEqualsToFileName (file.Name, "PP.X")) + return null; + string full_exe_name = null; + Tuple index_pos = null; + foreach (var exe_name in KnownExeMap.Keys) + { + full_exe_name = VFS.ChangeFileName (file.Name, exe_name); + if (VFS.FileExists (full_exe_name)) + { + index_pos = KnownExeMap[exe_name]; + break; + } + } + if (null == index_pos) + return null; + uint index_offset = index_pos.Item1; + int count = index_pos.Item2; + using (var index = VFS.OpenView (full_exe_name)) + { + index.View.Reserve (index_offset, (uint)count * 12u); + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = new PackedEntry { + Name = string.Format ("PP#{0:D5}.BMP", i), + Type = "image", + Offset = index.View.ReadUInt32 (index_offset), + Size = index.View.ReadUInt32 (index_offset+4), + UnpackedSize = index.View.ReadUInt32 (index_offset+8), + IsPacked = true, + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 12; + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = (PackedEntry)entry; + using (var input = arc.File.CreateStream (entry.Offset, entry.Size)) + { + var output = new byte[pent.UnpackedSize]; + Decompress (input, output); + return new BinMemoryStream (output, entry.Name); + } + } + + internal static void Decompress (IBinaryStream input, byte[] output) + { + var frame = new byte[0x400]; + int frame_pos = 1; + int dst = 0; + using (var bits = new MsbBitStream (input.AsStream, true)) + { + while (dst < output.Length) + { + int ctl = bits.GetNextBit(); + if (-1 == ctl) + break; + if (ctl != 0) + { + int v = bits.GetBits (8); + output[dst++] = frame[frame_pos++ & 0x3FF] = (byte)v; + } + else + { + int offset = bits.GetBits (10); + int count = bits.GetBits (5) + 2; + while (count --> 0) + { + byte v = frame[offset++ & 0x3FF]; + output[dst++] = frame[frame_pos++ & 0x3FF] = v; + } + } + } + } + } + } +} diff --git a/Legacy/Rhss/ArcCRG.cs b/Legacy/Rhss/ArcCRG.cs new file mode 100644 index 00000000..8a04632a --- /dev/null +++ b/Legacy/Rhss/ArcCRG.cs @@ -0,0 +1,83 @@ +//! \file ArcCRG.cs +//! \date 2023 Aug 28 +//! \brief Raishū Hyōjun Script 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.Compression; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.Rhss +{ + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag => "DAT/CRG"; + public override string Description => "RHSS engine resource archive"; + public override uint Signature => 0x00475243; // 'CRG' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (4); + if (!IsSaneCount (count)) + return null; + uint index_pos = 8; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index_pos+8, 0x30); + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index_pos); + entry.Size = file.View.ReadUInt32 (index_pos+4); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_pos += 60; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = entry as PackedEntry; + if (null != pent) + { + if (!pent.IsPacked && arc.File.View.AsciiEqual (entry.Offset, "CMP\0")) + { + pent.IsPacked = true; + pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset + 0x4C); + } + if (pent.IsPacked) + { + Stream input = arc.File.CreateStream (entry.Offset+0x50, entry.Size-0x50); + input = new ZLibStream (input, CompressionMode.Decompress); + return new XoredStream (input, 0xFF); + } + } + return base.OpenEntry (arc, entry); + } + } +} diff --git a/Legacy/SplushWave/ArcDAT.cs b/Legacy/SplushWave/ArcDAT.cs index 05b39396..dfd90fe9 100644 --- a/Legacy/SplushWave/ArcDAT.cs +++ b/Legacy/SplushWave/ArcDAT.cs @@ -27,6 +27,9 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Windows.Media; + +// [030817][Splush Wave] Knock Out -Taisengata Datsui Mahjong- namespace GameRes.Formats.SplushWave { @@ -129,5 +132,92 @@ namespace GameRes.Formats.SplushWave } return new BinMemoryStream (output, 0, dst); } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var fent = (FlkEntry)entry; + var input = BinaryStream.FromStream (OpenEntry (arc, fent), fent.Name); + if ((fent.Flags & 0x10) == 0) + return ImageFormatDecoder.Create (input); + try + { + var info = Swg.ReadMetaData (input) as SwgMetaData; + if (null == info) + { + input.Position = 0; + return new ImageFormatDecoder(input); + } + return new Swg1ImageDecoder (input, info); + } + catch + { + input.Dispose(); + throw; + } + } + + static readonly ResourceInstance s_swg = new ResourceInstance ("SWG"); + + internal static SwgFormat Swg { get => s_swg.Value; } + } + + internal sealed class Swg1ImageDecoder : BinaryImageDecoder + { + SwgMetaData m_info; + + public Swg1ImageDecoder (IBinaryStream input, SwgMetaData info) : base (input, info) + { + SourceFormat = DatOpener.Swg; + m_info = info; + } + + static readonly byte[] PlaneMap = { 3, 2, 1, 0 }; + + protected override ImageData GetImageData () + { + m_input.Position = m_info.DataOffset; + int stride = 4 * m_info.iWidth; + int plane_size = m_info.iWidth * m_info.iHeight; + var output = new byte[stride * m_info.iHeight]; + ushort[] ctl_buf = new ushort[m_info.iHeight]; + for (int c = 0; c < 4; ++c) + { + int compress_method = ReadU16BE(); + if (0 == compress_method) + { + int dst = PlaneMap[c] + stride * (m_info.iHeight - 1); + for (int y = 0; y < m_info.iHeight; ++y) + { + for (int x = 0; x < stride; x += 4) + { + output[dst+x] = m_input.ReadUInt8(); + } + dst -= stride; + } + continue; + } + if (compress_method != 1) + throw new InvalidFormatException(); + for (int y = 0; y < m_info.iHeight; ++y) + { + ctl_buf[y] = ReadU16BE(); + } + int row = PlaneMap[c]; + for (int y = 0; y < m_info.iHeight; ++y) + { + int dst = row; + int row_size = ctl_buf[y]; + SwgFormat.DecompressRow (m_input, row_size, output, dst, 4); + row += stride; + } + } + return ImageData.Create (m_info, PixelFormats.Bgra32, null, output, stride); + } + + ushort ReadU16BE () + { + ushort le = m_input.ReadUInt16(); + return (ushort)(le >> 8 | le << 8); + } } } diff --git a/Legacy/SplushWave/ImageSWG.cs b/Legacy/SplushWave/ImageSWG.cs index 41274bb9..490fe560 100644 --- a/Legacy/SplushWave/ImageSWG.cs +++ b/Legacy/SplushWave/ImageSWG.cs @@ -68,7 +68,7 @@ namespace GameRes.Formats.SplushWave { var meta = (SwgMetaData)info; PixelFormat format = meta.BPP == 8 ? PixelFormats.Indexed8 - : meta.BPP == 32 ? PixelFormats.Bgr32 : PixelFormats.Bgr24; + : meta.BPP == 32 ? PixelFormats.Bgra32 : PixelFormats.Bgr24; BitmapPalette palette = null; if (meta.BPP == 8) { @@ -77,36 +77,40 @@ namespace GameRes.Formats.SplushWave } int stride = meta.iWidth * meta.BPP / 8; file.Position = meta.DataOffset; -// var pixels = new byte[stride * meta.iHeight]; - var pixels = new byte[4 * meta.iWidth * meta.iHeight]; + var pixels = new byte[stride * meta.iHeight]; if (!meta.IsCompressed) { file.Read (pixels, 0, pixels.Length); return ImageData.CreateFlipped (meta, format, palette, pixels, stride); } - var input = file.ReadBytes ((int)(file.Length - file.Position)); - if (!Decompress (input, pixels, meta.Depth + 2, meta.iWidth, meta.iHeight)) + if (!Decompress (file, pixels, meta.Depth + 2, meta.iWidth, meta.iHeight)) throw new InvalidFormatException ("Invalid SWG file."); return ImageData.CreateFlipped (meta, format, palette, pixels, stride); } - bool Decompress (byte[] input, byte[] output, int channels, int width, int height) + static readonly byte[] PlaneMap = { 2, 1, 0, 3 }; + + bool Decompress (IBinaryStream input, byte[] output, int channels, int width, int height) { - int src = 0; - if (input[0] != 0 || input[1] != 1) + long start_pos = input.Position; + byte hi = input.ReadUInt8(); + byte lo = input.ReadUInt8(); + if (hi != 0 || lo != 1) { + input.Position = start_pos; int n = 0; for (int i = 0; i < channels; ++i) { - if (0 == input[i]) + if (0 == input.ReadByte()) ++n; } if (n != channels) return false; - src = 4; + input.Position = start_pos + 4; + hi = input.ReadUInt8(); + lo = input.ReadUInt8(); } - int compress_method = input[src+1] + (input[src] << 8); - src += 2; + int compress_method = lo | hi << 8; if (0 == compress_method) { for (int i = 0; i < channels; ++i) @@ -115,7 +119,7 @@ namespace GameRes.Formats.SplushWave int count = height * width; while (count --> 0) { - output[pos] = input[src++]; + output[pos] = input.ReadUInt8(); pos += channels; } } @@ -123,63 +127,57 @@ namespace GameRes.Formats.SplushWave } if (compress_method != 1) return false; - int dst = 0; - int v33 = src; - int v37 = height * channels; - src += 2 * v37; - for (int row = 0; row < v37; ++row) + int stride = width * channels; + var row_sizes = input.ReadBytes (2 * height * channels); + int ctl_pos = 0; + for (int c = 0; c < channels; ++c) + for (int y = height - 1; y >= 0; --y) { - int y = row % height; - dst = channels * (width * (height - y - 1) + 1) - row / height - 1; - if (dst > output.Length) - return true; - int v24 = 0; - int v36 = input[v33+1] + (input[v33] << 8); - v33 += 2; - do - { - byte lo = input[src]; - byte hi = input[src+1]; - if (lo != 0) - { - if (lo < 0x81) - { - ++src; - int count = lo + 1; - v24 += count + 1; - while (count --> 0) - { - output[dst] = input[src++]; - dst += channels; - } - } - else - { - src += 2; - v24 += 2; - int count = Math.Min (0x101 - lo, output.Length - dst); - while (count --> 0) - { - output[dst] = hi; - dst += channels; - } - } - } - else - { - src += 2; - v24 += 2; - output[dst] = hi; - dst += channels; - } - if (dst >= output.Length) - return true; - } - while (v24 < v36); + int dst = stride * y + PlaneMap[c]; + int row_size = row_sizes[ctl_pos+1] | row_sizes[ctl_pos] << 8; + ctl_pos += 2; + DecompressRow (input, row_size, output, dst, channels); } return true; } + internal static void DecompressRow (IBinaryStream input, int row_size, byte[] output, int dst, int step) + { + int x = 0; + while (x < row_size) + { + byte ctl = input.ReadUInt8(); + if (ctl == 0) + { + byte v = input.ReadUInt8(); + x += 2; + output[dst] = v; + dst += step; + } + else if (ctl < 0x81u) + { + int count = ctl + 1; + x += count + 1; + while (count --> 0) + { + output[dst] = input.ReadUInt8(); + dst += step; + } + } + else + { + byte v = input.ReadUInt8(); + x += 2; + int count = 0x101 - ctl; + while (count --> 0) + { + output[dst] = v; + dst += step; + } + } + } + } + public override void Write (Stream file, ImageData image) { throw new System.NotImplementedException ("SwgFormat.Write not implemented");