From 83ff904cae821343590ec8d5ab6df916053d2a06 Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 7 Feb 2018 03:53:41 +0400 Subject: [PATCH] (Legacy): implemented Lune and Discovery archives. --- Legacy/Discovery/ArcDAT.cs | 320 ++++++++++++++++++++++++++++++++++++ Legacy/Legacy.csproj | 7 + Legacy/Lune/ArcPACK.cs | 329 +++++++++++++++++++++++++++++++++++++ 3 files changed, 656 insertions(+) create mode 100644 Legacy/Discovery/ArcDAT.cs create mode 100644 Legacy/Lune/ArcPACK.cs diff --git a/Legacy/Discovery/ArcDAT.cs b/Legacy/Discovery/ArcDAT.cs new file mode 100644 index 00000000..a2331742 --- /dev/null +++ b/Legacy/Discovery/ArcDAT.cs @@ -0,0 +1,320 @@ +//! \file ArcDAT.cs +//! \date 2018 Feb 06 +//! \brief Discovery resource archive. +// +// Copyright (C) 2018 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; +using System.Windows.Media.Imaging; +using GameRes.Compression; + +// [000225][Discovery] Tsukiyo no Hitomi wa Kurenai ni + +namespace GameRes.Formats.Discovery +{ + internal class BDataEntry : PackedEntry + { + public uint Width; + public uint Height; + public int BPP; + public int Colors; + public int Extra; + } + + internal class EDataEntry : PackedEntry + { + public uint HeaderSize; + public uint HeaderUnpacked; + public uint BodyOffset; + public uint BodySize; + public uint BodyUnpacked; + } + + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat + { + public override string Tag { get { return "DAT/DISCOVERY"; } } + public override string Description { get { return "Discovery 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 (".dat")) + return null; + int count = file.View.ReadInt32 (file.MaxOffset-4); + if (!IsSaneCount (count)) + return null; + var arc_name = Path.GetFileName (file.Name); + List dir = null; + if (arc_name.StartsWith ("BData", StringComparison.OrdinalIgnoreCase)) + dir = ReadBDataIndex (file, count); + else if (arc_name.StartsWith ("EData", StringComparison.OrdinalIgnoreCase)) + dir = ReadEDataIndex (file, count); + else if (arc_name.StartsWith ("VData", StringComparison.OrdinalIgnoreCase)) + dir = ReadVDataIndex (file, count); + if (null == dir) + return null; + return new ArcFile (file, this, dir); + } + + List ReadBDataIndex (ArcView file, int count) + { + int entry_size = 0x3C; + int index_size = count * entry_size; + if (index_size+4 >= file.MaxOffset) + return null; + var index = file.View.ReadBytes (file.MaxOffset - 4 - index_size, (uint)index_size); + int index_offset = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + Decrypt (index, index_offset, entry_size); + int name_length = index[index_offset]; + if (0 == name_length || name_length > entry_size - 0x18) + return null; + var name = Encodings.cp932.GetString (index, index_offset+0x18, name_length); + var entry = FormatCatalog.Instance.Create (name); + entry.Size = index.ToUInt32 (index_offset+4); + entry.UnpackedSize = index.ToUInt32 (index_offset+8); + entry.Offset = index.ToUInt32 (index_offset+0xC); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + entry.Width = index.ToUInt32 (index_offset+0x10); + entry.Height = index.ToUInt32 (index_offset+0x14); + entry.BPP = index[index_offset+1]; + entry.Extra = index.ToUInt16 (index_offset+0x28); + entry.Colors = index.ToUInt16 (index_offset+0x2A); + dir.Add (entry); + index_offset += entry_size; + } + return dir; + } + + List ReadEDataIndex (ArcView file, int count) + { + int entry_size = 0x2C; + int index_size = count * entry_size; + if (index_size+4 >= file.MaxOffset) + return null; + var index = file.View.ReadBytes (file.MaxOffset - 4 - index_size, (uint)index_size); + int index_offset = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + Decrypt (index, index_offset, entry_size); + int name_length = index[index_offset]; + if (0 == name_length || name_length > entry_size - 0x1C) + return null; + var name = Encodings.cp932.GetString (index, index_offset+0x1C, name_length); + var entry = FormatCatalog.Instance.Create (name); + entry.BodySize = index.ToUInt32 (index_offset+4); + entry.BodyUnpacked = index.ToUInt32 (index_offset+8); + entry.BodyOffset = index.ToUInt32 (index_offset+0xC); + entry.HeaderUnpacked = index.ToUInt32 (index_offset+0x10); + entry.HeaderSize = index.ToUInt32 (index_offset+0x14); + entry.Offset = index.ToUInt32 (index_offset+0x18); + entry.Size = entry.HeaderSize + entry.BodySize; + entry.UnpackedSize = entry.HeaderUnpacked + entry.BodyUnpacked; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + entry.IsPacked = true; + dir.Add (entry); + index_offset += entry_size; + } + return dir; + } + + List ReadVDataIndex (ArcView file, int count) + { + int entry_size = 0x20; + int index_size = count * entry_size; + if (index_size+4 >= file.MaxOffset) + return null; + var index = file.View.ReadBytes (file.MaxOffset - 4 - index_size, (uint)index_size); + for (int i = 0; i < index.Length; ++i) + index[i] ^= 0xDE; + int index_offset = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + int name_length = index[index_offset]; + if (0 == name_length || name_length > entry_size - 0x10) + return null; + var name = Encodings.cp932.GetString (index, index_offset+0x10, name_length); + var entry = FormatCatalog.Instance.Create (name); + entry.Size = index.ToUInt32 (index_offset+8); + entry.Offset = index.ToUInt32 (index_offset+0xC); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += entry_size; + } + return dir; + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var eent = entry as EDataEntry; + if (null == eent || !eent.IsPacked) + return base.OpenEntry (arc, entry); + var header = new byte[eent.HeaderUnpacked]; + using (var input = arc.File.CreateStream (eent.Offset, eent.HeaderSize)) + using (var lzss = new LzssStream (input)) + lzss.Read (header, 0, header.Length); + Stream body = arc.File.CreateStream (eent.BodyOffset, eent.BodySize); + body = new LzssStream (body); + return new PrefixStream (header, body); + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var bent = entry as BDataEntry; + if (null == bent) + return base.OpenImage (arc, entry); + return new BDataDecoder (arc, bent); + } + + void Decrypt (byte[] data, int pos, int length) + { + Descramble32 (data, pos, length, 13); + Descramble8 (data, pos, length, 7); + for (int i = 0; i < length; ++i) + data[pos+i] ^= 0xD6; + } + + unsafe void Descramble32 (byte[] data, int pos, int length, int seed) + { + length /= 4; + fixed (byte* data8 = &data[pos]) + { + uint* data32 = (uint*)data8; + for (int i = 0; i < length; ++i) + { + int s = 0; + uint x = ~(data32[i] & 1) & data32[i]; + int shift = 0; + for (int j = 0; j < 31; ++j) + { + shift = s - seed; + if (shift < 0) + shift += ((31 - shift) >> 5) << 5; + uint bit = x & (1u << shift); + uint a = ~bit & x; + uint b; + if (shift <= s) + b = bit << (s - shift); + else + b = bit >> (shift - s); + x = b | a; + s = shift; + } + data32[i] = x | ((data32[i] & 1) << shift); + } + } + } + + void Descramble8 (byte[] data, int pos, int length, int seed) + { + byte first = data[pos]; + int x = 0; + int i = 0; + for (int count = length - 1; count > 0; --count) + { + i = x - seed; + while (i < 0) + i += length; + data[pos+x] = data[pos+i]; + x = i; + } + data[pos+i] = first; + } + } + + internal sealed class BDataDecoder : IImageDecoder + { + IBinaryStream m_input; + byte[] m_output; + ImageData m_image; + int m_colors; + int m_extra; + + public Stream Source { get { m_input.Position = 0; return m_input.AsStream; } } + public ImageFormat SourceFormat { get { return null; } } + public ImageMetaData Info { get; private set; } + public PixelFormat Format { get; private set; } + public int Stride { get; private set; } + public BitmapPalette Palette { get; private set; } + + public ImageData Image { + get { + if (null == m_image) + { + Unpack(); + m_image = ImageData.CreateFlipped (Info, Format, Palette, m_output, Stride); + } + return m_image; + } + } + + public BDataDecoder (ArcFile arc, BDataEntry entry) + { + Info = new ImageMetaData { Width = entry.Width, Height = entry.Height, BPP = entry.BPP }; + uint total_size = entry.Size; + total_size += (uint)entry.Colors * 4; + if (entry.Extra > 0) + total_size += 10 * (uint)entry.Extra + 2; + Stride = ((int)entry.Width * entry.BPP / 8 + 3) & ~3; + Format = PixelFormats.Bgr24; + m_output = new byte[Stride * (int)entry.Height]; + m_colors = entry.Colors; + m_extra = entry.Extra; + m_input = arc.File.CreateStream (entry.Offset, total_size); + } + + private void Unpack () + { + m_input.Position = 0; + if (m_colors > 0) + Palette = ImageFormat.ReadPalette (m_input.AsStream, m_colors); + if (m_extra > 0) + m_input.Seek (10 * m_extra + 2, SeekOrigin.Current); + using (var lzss = new LzssStream (m_input.AsStream, LzssMode.Decompress, true)) + lzss.Read (m_output, 0, m_output.Length); + } + + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + m_input.Dispose(); + m_disposed = true; + } + } + } +} diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index 08fd9e5d..c1f0d020 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -57,6 +57,9 @@ + + + @@ -76,8 +79,12 @@ + + + + diff --git a/Legacy/Lune/ArcPACK.cs b/Legacy/Lune/ArcPACK.cs new file mode 100644 index 00000000..d555d9d9 --- /dev/null +++ b/Legacy/Lune/ArcPACK.cs @@ -0,0 +1,329 @@ +//! \file ArcPACK.cs +//! \date 2018 Feb 03 +//! \brief Lune Adv Game resource archive. +// +// Copyright (C) 2018 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.Windows.Media; +using GameRes.Utility; + +// [001110][Lune] Wasurekaketa Kokoro no Kakera + +namespace GameRes.Formats.Lune +{ +#if DEBUG + [Export(typeof(ArchiveFormat))] +#endif + public class PackOpener : ArchiveFormat + { + public override string Tag { get { return "PACK/LUNE"; } } + public override string Description { get { return "Lune Adv Game 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 PackOpener () + { + Extensions = new string[] { "dat", "wda" }; + } + + public override ArcFile TryOpen (ArcView file) + { + uint first_offset = file.View.ReadUInt32 (0); + if (first_offset <= 8 || first_offset >= file.MaxOffset || 0 != (first_offset & 7)) + return null; + int count = (int)(first_offset / 8); + if (!IsSaneCount (count)) + return null; + var base_name = Path.GetFileNameWithoutExtension (file.Name); + string type = file.Name.HasExtension (".wda") ? "audio" + : file.Name.HasExtension (".scr") ? "script" + : "image"; + uint index_offset = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = new Entry { + Name = string.Format ("{0}#{1:D5}", base_name, i), + Type = type, + Offset = file.View.ReadUInt32 (index_offset), + Size = file.View.ReadUInt32 (index_offset+4), + }; + if (entry.Offset < first_offset || !entry.CheckPlacement (file.MaxOffset)) + return null; + if (entry.Size > 0) + dir.Add (entry); + index_offset += 8; + } + if (dir.Count == 0 || dir[dir.Count-1].Offset + dir[dir.Count-1].Size != file.MaxOffset) + return null; + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (!arc.File.Name.HasExtension (".wda")) + return base.OpenEntry (arc, entry); + var format = new WaveFormat { + FormatTag = 1, + Channels = 1, + SamplesPerSecond = 22050, + AverageBytesPerSecond = 44100, + BlockAlign = 2, + BitsPerSample = 16, + }; + byte[] wav_header; + using (var output = new MemoryStream (0x2C)) + { + WaveAudio.WriteRiffHeader (output, format, entry.Size); + wav_header = output.ToArray(); + } + var pcm_data = arc.File.CreateStream (entry.Offset, entry.Size); + return new PrefixStream (wav_header, pcm_data); + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + if (arc.File.Name.HasExtension (".msk")) + return new PackMaskDecoder (input); + else + return new PackImageDecoder (input); + } + } + + internal class PackImageDecoder : IImageDecoder + { + protected IBinaryStream m_input; + protected byte[] m_output; + private ImageData m_image; + + public Stream Source { get { m_input.Position = 0; return m_input.AsStream; } } + public ImageFormat SourceFormat { get { return null; } } + public PixelFormat Format { get; private set; } + public ImageMetaData Info { get; private set; } + + public ImageData Image { + get { + if (null == m_image) + { + Unpack(); + m_image = ImageData.Create (Info, Format, null, m_output); + } + return m_image; + } + } + + public PackImageDecoder (IBinaryStream input) : this (input, PixelFormats.Bgr24) { } + + protected PackImageDecoder (IBinaryStream input, PixelFormat format) + { + m_input = input; + uint width = input.ReadUInt16(); + uint height = input.ReadUInt16(); + Format = format; + Info = new ImageMetaData { Width = width, Height = height, BPP = format.BitsPerPixel }; + m_output = new byte[(int)width * (int)height * Info.BPP / 8]; + } + + protected virtual void Unpack () + { + m_input.Position = 4; + var data = ReadDataBytes(); + using (var bits = new MsbBitStream (m_input.AsStream, true)) + { + int src = 0; + m_output[0] = data[src++]; + for (int dst = 1; dst < m_output.Length; ++dst) + { + int ctl = bits.GetBits (2); + byte v; + if (0 == ctl) + { + v = data[src++]; + } + else + { + v = m_output[dst-3]; + if (ctl == 2) + { + if (bits.GetNextBit() != 0) + v -= 1; + else + v += 1; + } + else if (ctl == 3) + { + ctl = bits.GetBits (2); + if (ctl == 2) + { + if (bits.GetNextBit() != 0) + v -= 3; + else + v += 3; + } + else if (ctl == 3) + { + ctl = bits.GetBits (2); + if (ctl == 2) + { + if (bits.GetNextBit() != 0) + v -= 5; + else + v += 5; + } + else if (ctl == 3) + { + switch (bits.GetBits (2)) + { + case 3: v -= 7; break; + case 2: v += 7; break; + case 1: v -= 6; break; + default: v += 6; break; + } + } + else if (ctl == 1) + v -= 4; + else + v += 4; + } + else if (ctl == 1) + v -= 2; + else + v += 2; + } + } + m_output[dst] = v; + } + } + } + + protected byte[] ReadDataBytes () + { + uint data_pos = m_input.ReadUInt32(); + long ctl_pos = m_input.Position; + m_input.Seek (data_pos, SeekOrigin.Current); + var data = m_input.ReadBytes ((int)(m_input.Length - data_pos)); + if (0 == data.Length) + throw new InvalidFormatException(); + m_input.Position = ctl_pos; + return data; + } + + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + m_input.Dispose(); + m_disposed = true; + } + } + } + + internal sealed class PackMaskDecoder : PackImageDecoder + { + public PackMaskDecoder (IBinaryStream input) : base (input, PixelFormats.Gray8) { } + + protected override void Unpack () + { + m_input.Position = 4; + var data = ReadDataBytes(); + using (var bits = new MsbBitStream (m_input.AsStream, true)) + { + int stride = (int)Info.Width; + int src = 0; + int dst = 0; + byte init_value = data[src++]; + m_output[dst++] = init_value; + int bit_count = 0; + int y = 0; + int x = 1; + while (dst < m_output.Length) + { + int ctl = bits.GetBits (2); + if (0 == ctl) + { + int count; + if (bit_count > 0) + count = bits.GetBits (14 - bit_count); + else + count = bits.GetBits (6); + while (count --> 0 && dst < m_output.Length) + { + if (y == 0 || x + 1 == stride) + m_output[dst] = init_value; + else + m_output[dst] = m_output[dst - stride + 1]; + ++dst; + if (++x == stride) + { + x = 0; + ++y; + } + } + bit_count = 0; + continue; + } + else if (1 == ctl) + { + bit_count += 2; + if (0 == x) + m_output[dst] = init_value; + else + m_output[dst] = m_output[dst - stride - 1]; + } + else if (2 == ctl) + { + bit_count += 2; + m_output[dst] = data[src++]; + } + else + { + bit_count += 3; + if (bits.GetNextBit() != 0) + m_output[dst] = m_output[dst - stride]; + else + m_output[dst] = m_output[dst-1]; + } + ++dst; + if (++x == stride) + { + x = 0; + ++y; + } + if (bit_count >= 8) + bit_count -= 8; + } + } + const byte max = 0x20; // System.Linq.Enumerable.Max (m_output); + if (max != 0 && max != 0xFF) + { + for (int i = 0; i < m_output.Length; ++i) + m_output[i] = (byte)(m_output[i] * 0xFF / max); + } + } + } +}