diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index ca1c4a25..4b7dc197 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -140,6 +140,8 @@ + + diff --git a/ArcFormats/TopCat/ArcTCD3.cs b/ArcFormats/TopCat/ArcTCD3.cs new file mode 100644 index 00000000..98515be3 --- /dev/null +++ b/ArcFormats/TopCat/ArcTCD3.cs @@ -0,0 +1,295 @@ +//! \file ArcTCD3.cs +//! \date Thu Oct 08 13:14:57 2015 +//! \brief TopCat data archives (TCD) +// +// Copyright (C) 2015 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.Linq; +using GameRes.Utility; + +namespace GameRes.Formats.TopCat +{ + internal class TcdSection + { + public uint DataSize; + public uint IndexOffset; + public int DirCount; + public int DirNameLength; + public int FileCount; + public int FileNameLength; + } + + internal struct TcdDirEntry + { + public int FileCount; + public int NamesOffset; + public int FirstIndex; + } + + internal class TcdEntry : AutoEntry + { + public int Index; + + public TcdEntry (int index, string name, ArcView file, long offset) + : base (name, () => DetectFileType (file, offset)) + { + Index = index; + Offset = offset; + } + + private static IResource DetectFileType (ArcView file, long offset) + { + uint signature = file.View.ReadUInt32 (offset); + return FormatCatalog.Instance.LookupSignature (signature).FirstOrDefault(); + } + } + + internal class TcdArchive : ArcFile + { + public int? Key; + + public TcdArchive (ArcView arc, ArchiveFormat impl, ICollection dir) + : base (arc, impl, dir) + { + } + } + + [Serializable] + public class TcdScheme : ResourceScheme + { + public Dictionary KnownKeys; + } + + [Export(typeof(ArchiveFormat))] + public class TcdOpener : ArchiveFormat + { + public override string Tag { get { return "TCD3"; } } + public override string Description { get { return "TopCat data archive"; } } + public override uint Signature { get { return 0x33444354; } } // 'TCD3' + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return false; } } + + public TcdOpener () + { + Extensions = new string[] { "tcd" }; + } + + public static Dictionary KnownKeys = new Dictionary(); + + public override ResourceScheme Scheme + { + get { return new TcdScheme { KnownKeys = KnownKeys }; } + set { KnownKeys = ((TcdScheme)value).KnownKeys; } + } + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (4); + if (!IsSaneCount (count)) + return null; + + uint current_offset = 8; + var sections = new List (5); + for (int i = 0; i < 5; ++i, current_offset += 0x20) + { + uint index_offset = file.View.ReadUInt32 (current_offset+4); + if (0 == index_offset) + continue; + var section = new TcdSection + { + IndexOffset = index_offset, + DataSize = file.View.ReadUInt32 (current_offset), + DirCount = file.View.ReadInt32 (current_offset+8), + DirNameLength = file.View.ReadInt32 (current_offset+0x0C), + FileCount = file.View.ReadInt32 (current_offset+0x10), + FileNameLength = file.View.ReadInt32 (current_offset+0x14), + }; + sections.Add (section); + } + + var list = new List (count); + foreach (var section in sections) + { + current_offset = section.IndexOffset; + uint dir_size = (uint)(section.DirCount * section.DirNameLength); + var dir_names = new byte[dir_size]; + if (dir_size != file.View.Read (current_offset, dir_names, 0, dir_size)) + return null; + current_offset += dir_size; + DecryptNames (dir_names, section.DirNameLength); + + var dirs = new TcdDirEntry[section.DirCount]; + for (int i = 0; i < dirs.Length; ++i) + { + dirs[i].FileCount = file.View.ReadInt32 (current_offset); + dirs[i].NamesOffset = file.View.ReadInt32 (current_offset+4); + dirs[i].FirstIndex = file.View.ReadInt32 (current_offset+8); + current_offset += 0x10; + } + + uint entries_size = (uint)(section.FileCount * section.FileNameLength); + var file_names = new byte[entries_size]; + if (entries_size != file.View.Read (current_offset, file_names, 0, entries_size)) + return null; + current_offset += entries_size; + DecryptNames (file_names, section.FileNameLength); + + var offsets = new uint[section.FileCount + 1]; + for (int i = 0; i < offsets.Length; ++i) + { + offsets[i] = file.View.ReadUInt32 (current_offset); + current_offset += 4; + } + + int dir_name_offset = 0; + foreach (var dir in dirs) + { + string dir_name = Binary.GetCString (dir_names, dir_name_offset, section.DirNameLength); + dir_name_offset += section.DirNameLength; + int index = dir.FirstIndex; + int name_offset = dir.NamesOffset; + for (int i = 0; i < dir.FileCount; ++i) + { + string name = Binary.GetCString (file_names, name_offset, section.FileNameLength); + name_offset += section.FileNameLength; + name = dir_name + '\\' + name; + var entry = new TcdEntry (index, name, file, offsets[index]); + entry.Size = offsets[index+1] - offsets[index]; + ++index; + list.Add (entry); + } + } + } + return new TcdArchive (file, this, list); + } + + private void DecryptNames (byte[] buffer, int name_length) + { + byte key = buffer[name_length-1]; + for (int i = 0; i < buffer.Length; ++i) + buffer[i] -= key; + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var tcde = entry as TcdEntry; + var tcda = arc as TcdArchive; + if (null == tcde || null == tcda || entry.Size <= 0x14) + return arc.File.CreateStream (entry.Offset, entry.Size); + int signature = arc.File.View.ReadInt32 (entry.Offset); + if (0x43445053 == signature) // 'SPDC' + return arc.File.CreateStream (entry.Offset, entry.Size); + if (0x5367674F == signature) // 'OggS' + return DecryptOgg (arc, entry); + var header = new byte[0x14]; + arc.File.View.Read (entry.Offset, header, 0, 0x14); + unsafe + { + fixed (byte* raw = header) + { + int* dw = (int*)raw; + if (null == tcda.Key) + { + foreach (var key in KnownKeys.Values) + { + int first = signature + key * (tcde.Index + 3); + if (0x43445053 == first) // 'SPDC' + { + tcda.Key = key; + break; + } + } + } + if (null != tcda.Key && 0 != tcda.Key.Value) + { + for (int i = 0; i < 5; ++i) + dw[i] += tcda.Key.Value * (tcde.Index + 3 + i); + } + } + } + var rest = arc.File.CreateStream (entry.Offset+0x14, entry.Size-0x14); + return new PrefixStream (header, rest); + } + + static Lazy OggCrcTable = new Lazy (InitOggCrcTable); + + static uint[] InitOggCrcTable () + { + var table = new uint[0x100]; + for (uint i = 0; i < 0x100; ++i) + { + uint a = i << 24; + for (int j = 0; j < 8; ++j) + { + bool carry = 0 != (a & 0x80000000); + a <<= 1; + if (carry) + a ^= 0x04C11DB7; + } + table[i] = a; + } + return table; + } + + Stream DecryptOgg (ArcFile arc, Entry entry) + { + var data = new byte[entry.Size]; + arc.File.View.Read (entry.Offset, data, 0, entry.Size); + int remaining = data.Length; + int src = 0; + while (remaining > 0x1B && Binary.AsciiEqual (data, src, "OggS")) + { + int d = data[src+0x1A]; + data[src+0x16] = 0; + data[src+0x17] = 0; + data[src+0x18] = 0; + data[src+0x19] = 0; + int dst = src + 0x1B; + int count = d + 0x1B; + if (d != 0) + { + if (remaining < count) + break; + for (int i = 0; i < d; ++i) + count += data[dst++]; + } + remaining -= count; + if (remaining < 0) + break; + dst = src + 0x16; + uint crc = 0; + for (int i = 0; i < count; ++i) + { + uint x = (crc >> 24) ^ data[src++]; + crc <<= 8; + crc ^= OggCrcTable.Value[x]; + } + LittleEndian.Pack (crc, data, dst); + } + return new MemoryStream (data); + } + } +} diff --git a/ArcFormats/TopCat/ImageSPD.cs b/ArcFormats/TopCat/ImageSPD.cs new file mode 100644 index 00000000..5dc1b2a1 --- /dev/null +++ b/ArcFormats/TopCat/ImageSPD.cs @@ -0,0 +1,404 @@ +//! \file ImageSPD.cs +//! \date Thu Oct 08 16:25:55 2015 +//! \brief TopCat compressed image. +// +// Copyright (C) 2015 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.TopCat +{ + internal class SpdMetaData : ImageMetaData + { + public Compression Method; + public uint UnpackedSize; + } + + internal enum Compression + { + LzRle = 0, + Lz = 1, + LzRleAlpha = 2, + LzRle2 = 0x100, + Spdc = 0x101, + LzRleAlpha2 = 0x102, + Jpeg = 0x103, + } + + [Export(typeof(ImageFormat))] + public class SpdFormat : ImageFormat + { + public override string Tag { get { return "SPD"; } } + public override string Description { get { return "TopCat compressed image format"; } } + public override uint Signature { get { return 0x43445053; } } // 'SPDC' + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x14]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + unsafe + { + fixed (byte* raw = header) + { + uint* dw = (uint*)raw; + dw[3] -= (dw[4] >> 2) & 0xF731; + dw[2] -= (dw[4] << 2) & 0x137F; + dw[1] -= (dw[4] << 4) & 0xFFFF; + return new SpdMetaData + { + Width = dw[2], + Height = dw[3], + BPP = (int)(dw[1] >> 16), + Method = (Compression)(dw[1] & 0xFFFF), + UnpackedSize = dw[4], + }; + } + } + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as SpdMetaData; + if (null == meta) + throw new ArgumentException ("SpdFormat.Read should be supplied with SpdMetaData", "info"); + if (Compression.Jpeg == meta.Method) + return ReadJpeg (stream, meta); + + using (var reader = new SpdReader (stream, meta)) + { + reader.Unpack(); + return ImageData.Create (info, reader.Format, null, reader.Data); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("SpdFormat.Write not implemented"); + } + + private ImageData ReadJpeg (Stream file, SpdMetaData info) + { + file.Position = 0x18; + var header = new byte[0x3C]; + if (header.Length != file.Read (header, 0, header.Length)) + throw new EndOfStreamException(); + unsafe + { + fixed (byte* raw = header) + { + uint* dw = (uint*)raw; + for (int i = 0; i < 0xF; ++i) + dw[i] += 0xA8961EF1; + } + } + using (var rest = new StreamRegion (file, file.Position, file.Length-file.Position, true)) + using (var jpeg = new PrefixStream (header, rest)) + { + var decoder = new JpegBitmapDecoder (jpeg, + BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + var frame = decoder.Frames[0]; + frame.Freeze(); + return new ImageData (frame, info); + } + } + } + + internal sealed class SpdReader : IDisposable + { + Stream m_input; + byte[] m_output; + SpdMetaData m_info; + + public PixelFormat Format { get; private set; } + public byte[] Data { get { return m_output; } } + + public SpdReader (Stream input, SpdMetaData info) + { + m_input = input; + m_output = new byte[info.UnpackedSize]; + m_info = info; + if (24 == info.BPP) + Format = PixelFormats.Bgr24; + else if (32 == info.BPP) + Format = PixelFormats.Bgra32; + else + throw new NotSupportedException ("Not supported SPD image bitdepth"); + } + + public void Unpack () + { + m_input.Position = 0x14; + switch (m_info.Method) + { + case Compression.Spdc: + UnpackSpdc(); + break; + case Compression.LzRle: + case Compression.LzRle2: + case Compression.LzRleAlpha: + case Compression.LzRleAlpha2: + { + UnpackLz(); + var rgb = new byte[m_info.Height * m_info.Width * 4]; + if (Compression.LzRle == m_info.Method || Compression.LzRle2 == m_info.Method) + UnpackRle (rgb); + else + UnpackRleAlpha (rgb); + m_output = rgb; + Format = PixelFormats.Bgra32; + break; + } + default: + throw new NotImplementedException ("SPD compression method not implemented"); + } + } + + void UnpackLz () + { + int dst = 0; + while (dst < m_output.Length) + { + int ctl = m_input.ReadByte(); + if (-1 == ctl) + break; + for (int bit = 1; bit != 0x100 && dst < m_output.Length; bit <<= 1) + { + if (0 != (ctl & bit)) + { + int b = m_input.ReadByte(); + if (-1 == b) + return; + m_output[dst++] = (byte)b; + } + else + { + int lo = m_input.ReadByte(); + if (-1 == lo) + return; + int hi = m_input.ReadByte(); + if (-1 == hi) + return; + int src = lo >> 4 | hi << 4; + int count = Math.Min (3 + (lo & 0xF), m_output.Length - dst); + Binary.CopyOverlapped (m_output, dst-src, dst, count); + dst += count; + } + } + } + } + + void UnpackRle (byte[] rgb) + { + int rgb_src = LittleEndian.ToInt32 (m_output, 0); + bool skip = 0 == LittleEndian.ToInt32 (m_output, 8); + int ctl_src = 12; + int dst = 0; + while (dst < rgb.Length) + { + int n = LittleEndian.ToInt32 (m_output, ctl_src); + ctl_src += 4; + if (skip) + { + dst += n * 4; + } + else + { + for (; n != 0; --n) + { + rgb[dst++] = m_output[rgb_src++]; + rgb[dst++] = m_output[rgb_src++]; + rgb[dst++] = m_output[rgb_src++]; + rgb[dst++] = 0xFF; + } + } + skip = !skip; + } + } + + void UnpackRleAlpha (byte[] rgb) + { + int rgb_src = LittleEndian.ToInt32 (m_output, 0); + int ctl_src = 8; + int dst = 0; + while (dst < rgb.Length) + { + int count = LittleEndian.ToUInt16 (m_output, ctl_src); + ctl_src += 2; + + int control = count >> 14; + count &= 0x3FFF; + if (0 == control) + { + dst += 4 * count; + } + else + { + for (; count != 0; --count) + { + rgb[dst++] = m_output[rgb_src++]; + rgb[dst++] = m_output[rgb_src++]; + rgb[dst++] = m_output[rgb_src++]; + if (1 == control) + rgb[dst++] = 0xFF; + else + rgb[dst++] = m_output[ctl_src++]; + } + } + } + } + + void UnpackSpdc () + { + int pixel_size = m_info.BPP / 8; + int stride = pixel_size * (int)m_info.Width; + int[] offset_table = new int[28]; + int i = 0; + while (i < 16) + offset_table[i++] = -pixel_size; + while (i < 24) + offset_table[i++] = -stride; + while (i < 26) + offset_table[i++] = -stride - pixel_size; + while (i < 28) + offset_table[i++] = -stride + pixel_size; + + int dst = 0; + for (i = 0; i < pixel_size; ++i) + m_output[dst++] = (byte)GetBits (8); + + while (dst < m_output.Length) + { + int x = GetBits (5, true); + if (x > 0x1B) + { + GetBits (3); + m_output[dst+2] = (byte)GetBits (8); + m_output[dst+1] = (byte)GetBits (8); + m_output[dst] = (byte)GetBits (8); + } + else + { + int src = dst + offset_table[x]; + m_output[dst] = m_output[src]; + m_output[dst+1] = m_output[src+1]; + m_output[dst+2] = m_output[src+2]; + + GetBits (DiffPrefixTable[x] >> 1); + if (0 != (DiffPrefixTable[x] & 1)) + { + int i1 = GetBits (8, true); + int i2 = GetBits (8 + DiffLengthsTable[i1], true) & 0xFF; + int i3 = GetBits (8 + DiffLengthsTable[i1] + DiffLengthsTable[i2], true) & 0xFF; + + m_output[dst] += DiffTable[i1]; + m_output[dst+1] += (byte)(DiffTable[i1] + DiffTable[i2]); + m_output[dst+2] += (byte)(DiffTable[i1] + DiffTable[i3]); + + GetBits (DiffLengthsTable[i1] + DiffLengthsTable[i2] + DiffLengthsTable[i3]); + } + } + dst += pixel_size; + } + } + + int m_bits = 0; + int m_cached_bits = 0; + + // FIXME: add 'peek' feature to MsbBitStream class + int GetBits (int count, bool peek = false) + { + while (m_cached_bits < count) + { + int b = m_input.ReadByte(); + if (-1 == b) + return 0; + m_bits = (m_bits << 8) | b; + m_cached_bits += 8; + } + int mask = (1 << count) - 1; + int left_bits = m_cached_bits - count; + if (!peek) + m_cached_bits = left_bits; + return (m_bits >> left_bits) & mask; + } + + static readonly byte[] DiffPrefixTable = { + 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, + 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, + 0x6, 0x6, 0x6, 0x6, 0x7, 0x7, 0x7, 0x7, + 0xA, 0xB, 0xA, 0xB, + }; + static readonly byte[] DiffLengthsTable = { + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + }; + static readonly byte[] DiffTable = { + 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0, + 0x08, 0x08, 0x07, 0x07, 0x06, 0x06, 0x05, 0x05, 0xFB, 0xFB, 0xFA, 0xFA, 0xF9, 0xF9, 0xF8, 0xF8, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + }; + + #region IDisposable Members + bool _disposed = false; + public void Dispose () + { + if (!_disposed) + { + _disposed = true; + } + } + #endregion + } +} diff --git a/supported.html b/supported.html index a03dcd14..1db78a50 100644 --- a/supported.html +++ b/supported.html @@ -246,11 +246,12 @@ Rikorisu ~Lycoris Radiata~
*.mfg
*.mfm
*.mfsALPFNoSilky'sJokei Kazoku *MFG_
MFGA
MFGCNo *.pmp
*.pmw-YesScenePlayerNyuujoku Hitozuma Jogakuen -*.datGAMEDAT PACK
GAMEDAT PAC2No bootUP!
Pajamas Soft +*.datGAMEDAT PACK
GAMEDAT PAC2No bootUP!
Pajamas Soft
Aries Aneimo 2 ~Second Stage~
Momichupa Teacher!
Natsu no Owari no Nirvana
Prism Heart
+Trouble @ Spiral!
*.epaEPNo *.arcMAINoMatsuri KikakuChikan Sharyou Nigousha @@ -401,6 +402,12 @@ Soukai no Oujo-tachi
*.datDXNoFlat Cross Quartz
+*.tcdTCD3NoTopCat +Atori no Sora to Shinchuu no Tsuki
+Favorite Sweet!
+Nanapuri
+ +*.spdSPDCNo

1 Non-encrypted only