From 97c9bded678f8ede6a302b04faf92a8ea673dff4 Mon Sep 17 00:00:00 2001 From: morkt Date: Tue, 3 May 2022 13:46:36 +0400 Subject: [PATCH] (IPQ): TechnoBrain's animation resources as archives. --- ArcFormats/TechnoBrain/ArcIPQ.cs | 116 +++++++++++++++++++++++++++++ ArcFormats/TechnoBrain/ImageIPF.cs | 92 ++++++++++++++++++----- 2 files changed, 189 insertions(+), 19 deletions(-) create mode 100644 ArcFormats/TechnoBrain/ArcIPQ.cs diff --git a/ArcFormats/TechnoBrain/ArcIPQ.cs b/ArcFormats/TechnoBrain/ArcIPQ.cs new file mode 100644 index 00000000..8a4db8f9 --- /dev/null +++ b/ArcFormats/TechnoBrain/ArcIPQ.cs @@ -0,0 +1,116 @@ +//! \file ArcIPQ.cs +//! \date 2022 May 02 +//! \brief TechnoBrain's animation resource. +// +// Copyright (C) 2022 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.TechnoBrain +{ + internal class IpqArchive : ArcFile + { + public readonly IpfMetaData Info; + + public IpqArchive (ArcView arc, ArchiveFormat impl, ICollection dir, IpfMetaData info) + : base (arc, impl, dir) + { + Info = info; + } + } + + [Export(typeof(ArchiveFormat))] + public class IpqOpener : ArchiveFormat + { + public override string Tag { get { return "IPQ"; } } + public override string Description { get { return "TechnoBrain's animation resource"; } } + public override uint Signature { get { return 0; } } // 'RIFF' + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + static readonly ResourceInstance Ipf = new ResourceInstance("IPF"); + + public override ArcFile TryOpen (ArcView file) + { + if (file.View.ReadUInt32 (0) != 0x46464952 // 'RIFF' + || !file.View.AsciiEqual (8, "IPQ fmt ")) + return null; + IpfMetaData ipq_info; + using (var ipq = file.CreateStream()) + { + ipq_info = Ipf.Value.ReadIpfHeader (ipq); + if (null == ipq_info || ipq_info.FormatString != "IPQ fmt ") + return null; + ipq.Position = ipq_info.DataOffset; + if (ipq.ReadUInt32() != 0x6D696E61) // "anim" + return null; + uint index_size = ipq.ReadUInt32(); + int count = ipq.ReadInt32(); + if (!IsSaneCount (count)) + return null; + + var base_name = Path.GetFileNameWithoutExtension (file.Name); + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = new Entry { + Name = string.Format ("{0}#{1:D3}", base_name, i), + Type = "image", + Offset = ipq.ReadUInt32(), + }; + dir.Add (entry); + } + long last_offset = file.MaxOffset; + for (int i = count-1; i >= 0; --i) + { + dir[i].Size = (uint)(last_offset - dir[i].Offset); + last_offset = dir[i].Offset; + } + return new IpqArchive (file, this, dir, ipq_info); + } + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var ipq = arc as IpqArchive; + if (null == ipq) + return base.OpenImage (arc, entry); + var info = ipq.Info.Clone() as IpfMetaData; + var file = arc.File.CreateStream(); + try + { + file.Position = entry.Offset; + if (!Ipf.Value.ReadBmpInfo (file, info)) + throw new InvalidFormatException ("Invalid 'bmp' section."); + return new IpfReader (file, info, Ipf.Value); + } + catch + { + file.Dispose(); + throw; + } + } + } +} diff --git a/ArcFormats/TechnoBrain/ImageIPF.cs b/ArcFormats/TechnoBrain/ImageIPF.cs index 2ac78b20..d0cb670f 100644 --- a/ArcFormats/TechnoBrain/ImageIPF.cs +++ b/ArcFormats/TechnoBrain/ImageIPF.cs @@ -23,6 +23,7 @@ // IN THE SOFTWARE. // +using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; @@ -31,13 +32,21 @@ using GameRes.Utility; namespace GameRes.Formats.TechnoBrain { - internal class IpfMetaData : ImageMetaData + internal class IpfMetaData : ImageMetaData, ICloneable { public bool HasPalette; + public bool HasBitmap; public bool IsCompressed; public long PalOffset; public int PalSize; public long BmpOffset; + public long DataOffset; + public string FormatString; + + public object Clone () + { + return MemberwiseClone(); + } } [Export(typeof(ImageFormat))] @@ -47,24 +56,25 @@ namespace GameRes.Formats.TechnoBrain public override string Description { get { return "TechnoBrain's 'Inteligent Picture Format'"; } } public override uint Signature { get { return 0; } } // 'RIFF' - public override ImageMetaData ReadMetaData (IBinaryStream file) + internal IpfMetaData ReadIpfHeader (IBinaryStream file) { // 'RIFF' isn't included into signature to avoid auto-detection of the WAV files as IPF images. if (0x46464952 != file.Signature) // 'RIFF' return null; var header = file.ReadHeader (0x14); - if (!header.AsciiEqual (8, "IPF fmt ")) + if (!header.AsciiEqual (0xC, "fmt ")) return null; int fmt_size = header.ToInt32 (0x10); if (fmt_size < 0x24) return null; header = file.ReadHeader (0x14 + fmt_size); - bool has_palette = header.ToInt32 (0x18) != 0; - bool has_bitmap = header.ToInt32 (0x28) != 0; - if (!has_bitmap) - return null; - var info = new IpfMetaData { BPP = 8, HasPalette = has_palette }; - if (has_palette) + var info = new IpfMetaData { + BPP = 8, + HasPalette = header.ToInt32 (0x18) != 0, + HasBitmap = header.ToInt32 (0x28) != 0, + FormatString = header.GetCString (8, 8), + }; + if (info.HasPalette) { if (0x206C6170 != file.ReadInt32()) // 'pal ' return null; @@ -74,24 +84,43 @@ namespace GameRes.Formats.TechnoBrain info.PalOffset = file.Position; file.Position = info.PalOffset + info.PalSize; } + info.DataOffset = file.Position; + return info; + } + + internal bool ReadBmpInfo (IBinaryStream file, IpfMetaData info) + { if (0x20706D62 != file.ReadInt32()) // 'bmp ' - return null; + return false; int bmp_size = file.ReadInt32(); if (bmp_size <= 0x20) - return null; + return false; info.BmpOffset = file.Position + 0x18; info.Width = file.ReadUInt16(); info.Height = file.ReadUInt16(); - file.Seek (0xE, SeekOrigin.Current); + file.ReadUInt32(); + info.OffsetX = file.ReadInt16(); + info.OffsetY = file.ReadInt16(); + file.Seek (6, SeekOrigin.Current); info.IsCompressed = 0 != (file.ReadByte() & 1); + return true; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var info = ReadIpfHeader (file); + if (null == info || info.FormatString != "IPF fmt " || !info.HasBitmap) + return null; + file.Position = info.DataOffset; + if (!ReadBmpInfo (file, info)) + return null; return info; } public override ImageData Read (IBinaryStream file, ImageMetaData info) { - var reader = new IpfReader (file, (IpfMetaData)info); - var pixels = reader.Unpack(); - return ImageData.Create (info, reader.Format, reader.Palette, pixels); + var reader = new IpfReader (file, (IpfMetaData)info, this); + return reader.Image; } public override void Write (Stream file, ImageData image) @@ -100,22 +129,34 @@ namespace GameRes.Formats.TechnoBrain } } - internal sealed class IpfReader + internal sealed class IpfReader : IImageDecoder { IBinaryStream m_input; IpfMetaData m_info; byte[] m_output; + ImageData m_image; public BitmapPalette Palette { get; private set; } public PixelFormat Format { get; private set; } - public byte[] Data { get { return m_output; } } - public IpfReader (IBinaryStream input, IpfMetaData info) + public Stream Source { get { return m_input.AsStream; } } + public ImageMetaData Info { get { return m_info; } } + public ImageData Image { get { return m_image ?? (m_image = GetImageData()); } } + public ImageFormat SourceFormat { get; private set; } + + public IpfReader (IBinaryStream input, IpfMetaData info, ImageFormat impl) { m_input = input; m_info = info; m_output = new byte[m_info.Width*m_info.Height]; Format = m_info.HasPalette ? PixelFormats.Indexed8 : PixelFormats.Gray8; + SourceFormat = impl; + } + + private ImageData GetImageData () + { + var pixels = Unpack(); + return ImageData.Create (m_info, Format, Palette, pixels); } public byte[] Unpack () @@ -151,7 +192,7 @@ namespace GameRes.Formats.TechnoBrain for (int j = 0; j < 8; ++j) { int dst = (i << 3) + j; - if (dst >= 0x0A && 0 != (bits & 0x80)) + if (dst >= min_index && 0 != (bits & 0x80)) { if (dst >= min_index && dst <= max_index) { @@ -199,5 +240,18 @@ namespace GameRes.Formats.TechnoBrain } } } + + #region IDisposable members + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + m_input.Dispose(); + m_disposed = true; + } + GC.SuppressFinalize (this); + } + #endregion } }