//! \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 // [001102][Discovery] Twins Rhapsody // [020414][Studio Air] Tamayura namespace GameRes.Formats.Discovery { internal class BDataEntry : PackedEntry { public uint Width; public uint Height; public int BPP; public int Colors; public int Extra; public bool IsMask; } 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.IsPacked = entry.Size != entry.UnpackedSize; 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.IsMask = index[index_offset+2] == 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.HeaderSize = index.ToUInt32 (index_offset+0x10); entry.HeaderUnpacked = 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; bool m_is_packed; bool m_is_mask; 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 { return m_image ?? (m_image = Unpack()); } } public BDataDecoder (ArcFile arc, BDataEntry entry) { Info = new ImageMetaData { Width = entry.Width, Height = entry.Height, BPP = entry.BPP }; m_is_packed = entry.IsPacked; m_is_mask = entry.IsMask; uint total_size = entry.Size; total_size += (uint)entry.Colors * 4; if (entry.Extra > 0) total_size += 10 * (uint)entry.Extra + 2; int bpp = m_is_mask ? 8 : entry.BPP; Stride = ((int)entry.Width * bpp / 8 + 3) & ~3; if (m_is_mask) Format = PixelFormats.Gray8; else if (8 == entry.BPP) Format = PixelFormats.Indexed8; else 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 ImageData 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); if (m_is_packed) { using (var lzss = new LzssStream (m_input.AsStream, LzssMode.Decompress, true)) lzss.Read (m_output, 0, m_output.Length); } else m_input.Read (m_output, 0, m_output.Length); return m_is_mask ? ImageData.Create (Info, Format, Palette, m_output, Stride) : ImageData.CreateFlipped (Info, Format, Palette, m_output, Stride); } bool m_disposed = false; public void Dispose () { if (!m_disposed) { m_input.Dispose(); m_disposed = true; } } } }