diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index db64baa0..222931c4 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -106,6 +106,7 @@ + @@ -153,6 +154,8 @@ + + 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/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/Macintosh/ImagePICT.cs b/ArcFormats/Macintosh/ImagePICT.cs index 98d999d9..95c9c55c 100644 --- a/ArcFormats/Macintosh/ImagePICT.cs +++ b/ArcFormats/Macintosh/ImagePICT.cs @@ -294,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]; @@ -326,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/ArcDXR.cs b/ArcFormats/Macromedia/ArcDXR.cs index 31a19e29..1fa780da 100644 --- a/ArcFormats/Macromedia/ArcDXR.cs +++ b/ArcFormats/Macromedia/ArcDXR.cs @@ -49,7 +49,7 @@ namespace GameRes.Formats.Macromedia public DxrOpener () { - Extensions = new[] { "dxr", "cxt", "cct", "dcr", "exe" }; + Extensions = new[] { "dxr", "cxt", "cct", "dcr", "dir", "exe" }; Signatures = new[] { SignatureXFIR, SignatureRIFX, 0x00905A4Du, 0u }; } @@ -411,7 +411,7 @@ namespace GameRes.Formats.Macromedia pos = exe.Overlay.Offset; if (pos >= file.MaxOffset) return 0; - if (file.View.AsciiEqual (pos, "10JP")) + if (file.View.AsciiEqual (pos, "10JP") || file.View.AsciiEqual (pos, "59JP")) { pos = file.View.ReadUInt32 (pos+4); } @@ -436,7 +436,8 @@ namespace GameRes.Formats.Macromedia // 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")) + if (file.View.AsciiEqual (entry.Offset-8, "XFIR") + && !file.View.AsciiEqual (entry.Offset, "artX")) return entry.Offset-8; } } @@ -469,12 +470,15 @@ namespace GameRes.Formats.Macromedia Left = reader.ReadI16(); Bottom = reader.ReadI16(); Right = reader.ReadI16(); - reader.Skip (0x0C); - BitDepth = reader.ReadU16() & 0xFF; // ??? - if (data.Length >= 0x1C) + if (data.Length > 0x16) { - reader.Skip (2); - Palette = reader.ReadI16(); + reader.Skip (0x0C); + BitDepth = reader.ReadU16() & 0xFF; // ??? + if (data.Length >= 0x1C) + { + reader.Skip (2); + Palette = reader.ReadI16(); + } } } } 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/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 }