From baeab4bfca71be9a6dd516a7bfe311310c5ad031 Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 13 Mar 2019 12:53:29 +0400 Subject: [PATCH] (Legacy): FLK archives and BPA images. --- Legacy/Legacy.csproj | 7 + Legacy/Liddell/ArcFLK.cs | 74 ++++++++++ Legacy/Liddell/ImageBPA.cs | 279 +++++++++++++++++++++++++++++++++++++ 3 files changed, 360 insertions(+) create mode 100644 Legacy/Liddell/ArcFLK.cs create mode 100644 Legacy/Liddell/ImageBPA.cs diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index d41897d3..69a09190 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -90,6 +90,9 @@ + + + @@ -97,8 +100,11 @@ + + + @@ -106,6 +112,7 @@ + diff --git a/Legacy/Liddell/ArcFLK.cs b/Legacy/Liddell/ArcFLK.cs new file mode 100644 index 00000000..00cf1484 --- /dev/null +++ b/Legacy/Liddell/ArcFLK.cs @@ -0,0 +1,74 @@ +//! \file ArcFLK.cs +//! \date 2019 Mar 12 +//! \brief Liddell resource archive. +// +// Copyright (C) 2019 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 GameRes.Utility; + +// [020726][Liddell] Garasu no Yakata ~Kimi ga Inai Yoru~ + +namespace GameRes.Formats.Liddell +{ + [Export(typeof(ArchiveFormat))] + public class FlkOpener : ArchiveFormat + { + public override string Tag { get { return "FLK"; } } + public override string Description { get { return "Liddell 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 ArcFile TryOpen (ArcView file) + { + if (!file.Name.HasExtension (".flk")) + return null; + uint index_pos = 0; + var buffer = file.View.ReadBytes (index_pos, 0x10); + int base_offset = buffer[3] << 8; + int next_offset = ((buffer[1] << 8 | buffer[0]) << 4) + base_offset; + var dir = new List(); + while (buffer[4] != 0) + { + uint tail_size = buffer[2]; + var name = Binary.GetCString (buffer, 4, 12); + var entry = Create (name); + entry.Offset = next_offset; + index_pos += 0x10; + if (file.View.Read (index_pos, buffer, 0, 0x10) != 0x10) + return null; + next_offset = ((buffer[3] << 16 | buffer[1] << 8 | buffer[0]) << 4) + base_offset; + entry.Size = (uint)(next_offset - entry.Offset); + if (tail_size != 0) + entry.Size += tail_size - 0x10; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + if (0 == dir.Count) + return null; + return new ArcFile (file, this, dir); + } + } +} diff --git a/Legacy/Liddell/ImageBPA.cs b/Legacy/Liddell/ImageBPA.cs new file mode 100644 index 00000000..bfb39355 --- /dev/null +++ b/Legacy/Liddell/ImageBPA.cs @@ -0,0 +1,279 @@ +//! \file ImageBPA.cs +//! \date 2019 Mar 12 +//! \brief Liddell image format. +// +// Copyright (C) 2019 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Liddell +{ + internal class BpaMetaData : ImageMetaData + { + public int Colors; + public int PaletteOffset; + public int DataOffset; + } + + [Export(typeof(ImageFormat))] + public class BpaFormat : ImageFormat + { + public override string Tag { get { return "BPA"; } } + public override string Description { get { return "Liddell image format"; } } + public override uint Signature { get { return 0x4150422D; } } // '-BPA-' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x11); + if (header[4] != '-') + return null; + int palette_offset = header.ToUInt16 (0xC); + return new BpaMetaData { + Width = header.ToUInt16 (6), + Height = header.ToUInt16 (8), + BPP = 0 == palette_offset ? header[0x10] * 8 : 8, + Colors = header.ToUInt16 (0xA), + PaletteOffset = palette_offset, + DataOffset = header.ToUInt16 (0xE), + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new BpaDecoder (file, (BpaMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("BpaFormat.Write not implemented"); + } + } + + internal class BpaDecoder + { + IBinaryStream m_input; + BpaMetaData m_info; + int m_alignedWidth; + + public BpaDecoder (IBinaryStream input, BpaMetaData info) + { + m_input = input; + m_info = info; + m_alignedWidth = 4 * ((m_info.iWidth - 1) / 4 + 1); + } + + public ImageData Unpack () + { + BitmapPalette palette = null; + if (m_info.PaletteOffset != 0) + { + m_input.Position = m_info.PaletteOffset; + palette = ImageFormat.ReadPalette (m_input.AsStream, m_info.Colors, PaletteFormat.Rgb); + } + m_input.Position = m_info.DataOffset; + int stride = m_alignedWidth * m_info.BPP / 8; + var pixels = new byte[stride * m_info.iHeight]; + int channels = m_info.BPP / 8; + var channel = pixels; + if (channels > 1) + channel = new byte[m_alignedWidth * m_info.iHeight]; + for (int i = 0; i < channels; ++i) + { + Decompress (channel); + if (channels > 1) + { + int dst_row = i; + int src_row = channel.Length - m_alignedWidth; + for (int y = 0; y < m_info.iHeight; ++y) + { + int dst = dst_row; + int src = src_row; + for (int x = 0; x < m_alignedWidth; ++x) + { + pixels[dst] = channel[src++]; + dst += channels; + } + dst_row += stride; + src_row -= m_alignedWidth; + } + m_input.ReadByte(); + } + } + if (8 == m_info.BPP) + return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, m_alignedWidth); + PixelFormat format = 24 == m_info.BPP ? PixelFormats.Bgr24 : PixelFormats.Bgra32; + stride = ((m_info.iWidth * m_info.BPP / 8) + 3) & ~3; + return ImageData.CreateFlipped (m_info, format, palette, pixels, stride); + } + + void Decompress (byte[] output) + { + m_pixelStack[0] = 0; + m_pixelStack[4] = 0; + m_pixelStack[5] = 0; + int dst = 0; + for (int y = 0; y < m_info.iHeight; ++y) + { + byte ctlBits = 0; + int ctlCount = 0; + int w = m_alignedWidth; + while (w > 0) + { + if (0 == ctlCount) + { + ctlCount = 4; + ctlBits = m_input.ReadUInt8(); + } + int chunk_size = Math.Min (w, 16); + switch ((ctlBits >> 6) & 3) + { + case 0: + m_input.Read (output, dst, chunk_size); + dst += chunk_size; + break; + + case 1: + { + byte val = m_input.ReadUInt8(); + chunk_size = m_input.ReadUInt8(); + for (int i = 0; i < chunk_size; ++i) + { + output[dst++] = val; + } + StorePixel (val); + break; + } + case 2: + { + ushort ctl = m_input.ReadUInt16(); + byte val = m_input.ReadUInt8(); + for (int i = 0; i < chunk_size; ++i) + { + if ((ctl & 0x8000) != 0) + output[dst++] = val; + else + output[dst++] = m_input.ReadUInt8(); + ctl <<= 1; + } + StorePixel (val); + break; + } + case 3: + { + m_bitCount = 0; + for (int i = 0; i < chunk_size; ++i) + { + output[dst++] = RestorePixel(); + } + if (m_bitCount != 8) + m_input.ReadByte(); + break; + } + } + w -= chunk_size; + --ctlCount; + ctlBits <<= 2; + } + } + } + + byte[] m_pixelStack = new byte[6]; + + void StorePixel (byte val) + { + int i; + for (i = 0; i < 5; ++i) + { + if (m_pixelStack[i] == val) + break; + } + if (i == 0) + return; + do + { + m_pixelStack[i] = m_pixelStack[i-1]; + } + while (--i > 0); + m_pixelStack[0] = val; + } + + byte RestorePixel () + { + byte bits = GetNextBit (0); + if (0 == bits) + return m_pixelStack[0]; + + byte result = 0; + int count = 0; + bits = GetNextBit (bits); + if (2 == bits) + { + result = m_pixelStack[1]; + count = 1; + } + else if (GetNextBit (bits) == 6) + { + bits = GetNextBit (0); + switch (GetNextBit (bits)) + { + case 0: count = 2; break; + case 1: count = 3; break; + case 2: count = 4; break; + case 3: count = 5; break; + } + result = m_pixelStack[count]; + } + else + { + for (int i = 0; i < 8; ++i) + { + result = GetNextBit (result); + } + count = 5; + } + for (int i = count; i > 0; --i) + m_pixelStack[i] = m_pixelStack[i-1]; + m_pixelStack[0] = result; + return result; + } + + int m_bitCount; + byte m_curBits; + + byte GetNextBit (byte prev) + { + if (m_bitCount == 0) + m_curBits = m_input.ReadUInt8(); + + int result = prev << 1 | m_curBits >> 7; + m_curBits <<= 1; + if (++m_bitCount >= 8) + m_bitCount = 0; + return (byte)result; + } + } +}