diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index e7ec450d..438b98b7 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -75,6 +75,7 @@ + WidgetAGS.xaml @@ -113,8 +114,12 @@ + + + + diff --git a/ArcFormats/Ivory/ArcPK.cs b/ArcFormats/Ivory/ArcPK.cs new file mode 100644 index 00000000..2f2d6fdb --- /dev/null +++ b/ArcFormats/Ivory/ArcPK.cs @@ -0,0 +1,175 @@ +//! \file ArcPK.cs +//! \date Mon Sep 12 01:39:49 2016 +//! \brief 'fPK' resource archive. +// +// Copyright (C) 2016 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 GameRes.Utility; + +namespace GameRes.Formats.Ivory +{ + internal class PkEntry : Entry + { + public int NameOffset; + } + + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag { get { return "PK/IVORY"; } } + public override string Description { get { return "Ivory resource archive"; } } + public override uint Signature { get { return 0x204B5066; } } // 'fPK ' + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + if (file.MaxOffset != file.View.ReadUInt32 (4)) + return null; + + long base_offset = 0; + List dir = null; + bool got_names = false; + long offset = 8; + while (offset < file.MaxOffset) + { + var id = new AsciiString (file.View.ReadBytes (offset, 4)); + uint section_size = file.View.ReadUInt32 (offset+4); + if (0 == section_size) + return null; + uint header_size = file.View.ReadUInt32 (offset+8); + if ("cLST" == id) + { + int count = file.View.ReadInt32 (offset+0x10); + if (!IsSaneCount (count)) + return null; + uint key = file.View.ReadUInt32 (offset+0x14); + var clst = file.View.ReadBytes (offset+header_size, section_size - header_size); + Decrypt (clst, key); + dir = new List (count); + int index_offset = 0; + for (int i = 0; i < count; ++i) + { + var entry = new PkEntry { + NameOffset = LittleEndian.ToInt32 (clst, index_offset), + Offset = LittleEndian.ToUInt32 (clst, index_offset+4), + Size = LittleEndian.ToUInt32 (clst, index_offset+8), + }; + dir.Add (entry); + index_offset += 12; + } + } + else if ("cNAM" == id) + { + if (null == dir) + return null; + uint key = file.View.ReadUInt32 (offset+0x10); + var cnam = file.View.ReadBytes (offset+header_size, section_size - header_size); + Decrypt (cnam, key); + foreach (PkEntry entry in dir) + { + var name = Binary.GetCString (cnam, entry.NameOffset); + entry.Name = name; + if (name.EndsWith (".px", StringComparison.InvariantCultureIgnoreCase)) + entry.Type = "audio"; + else + entry.Type = FormatCatalog.Instance.GetTypeFromName (name); + } + got_names = true; + } + else if ("cDAT" == id) + { + base_offset = offset + header_size; + } + offset += section_size; + } + if (null == dir || !got_names || 0 == base_offset) + return null; + + foreach (var entry in dir) + { + entry.Offset += base_offset; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + } + return new ArcFile (file, this, dir); + } + + internal static void Decrypt (byte[] data, uint seed) + { + int length = data.Length / 4; + if (0 == length) + return; + var ctl = new ushort[32]; + var key = new uint[32]; + + for (int i = 0; i < 32; ++i) + { + uint code = 0; + uint k = seed; + for (int j = 0; j < 16; ++j) + { + code = (k ^ (k >> 1)) << 15 | (code & 0xFFFF) >> 1; + k >>= 2; + } + key[i] = seed; + ctl[i] = (ushort)code; + seed = Binary.RotL (seed, 1); + } + unsafe + { + fixed (byte* data8 = data) + { + uint* data32 = (uint*)data8; + for (int i = 0; i < length; ++i) + { + uint s = *data32; + ushort code = ctl[i & 0x1F]; + uint d = 0; + uint v3 = 3; + uint v2 = 2; + uint v1 = 1; + for (int j = 0; j < 16; ++j) + { + if (0 != (code & 1)) + { + d |= (s & v1) << 1 | (s >> 1) & (v2 >> 1); + } + else + { + d |= s & v3; + } + code >>= 1; + v3 <<= 2; + v2 <<= 2; + v1 <<= 2; + } + *data32++ = d ^ key[i & 0x1F]; + } + } + } + } + } +} diff --git a/ArcFormats/Ivory/ArcPX.cs b/ArcFormats/Ivory/ArcPX.cs new file mode 100644 index 00000000..9a683e65 --- /dev/null +++ b/ArcFormats/Ivory/ArcPX.cs @@ -0,0 +1,74 @@ +//! \file ArcPX.cs +//! \date Mon Sep 12 13:12:04 2016 +//! \brief 'fPX' audio archive. +// +// Copyright (C) 2016 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.Ivory +{ + [Export(typeof(ArchiveFormat))] + public class PxOpener : ArchiveFormat + { + public override string Tag { get { return "PX/IVORY"; } } + public override string Description { get { return "Ivory audio archive"; } } + public override uint Signature { get { return 0x20585066; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + if (file.MaxOffset != file.View.ReadUInt32 (4)) + return null; + + var base_name = Path.GetFileNameWithoutExtension (file.Name); + long offset = 8; + var dir = new List(); + while (offset < file.MaxOffset) + { + if (!file.View.AsciiEqual (offset, "cTRK")) + break; + uint size = file.View.ReadUInt32 (offset+4); + if (0 == size) + return null; + int num = file.View.ReadInt32 (offset+8); + var entry = new Entry { + Name = string.Format ("{0}#{1:D4}.trk", base_name, num), + Type = "audio", + Offset = offset, + Size = size + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + offset += size; + } + if (0 == dir.Count) + return null; + return new ArcFile (file, this, dir); + } + } +} diff --git a/ArcFormats/Ivory/AudioCTRK.cs b/ArcFormats/Ivory/AudioCTRK.cs new file mode 100644 index 00000000..2e8849b7 --- /dev/null +++ b/ArcFormats/Ivory/AudioCTRK.cs @@ -0,0 +1,200 @@ +//! \file AudioCTRK.cs +//! \date Mon Sep 12 13:17:22 2016 +//! \brief cTRK audio format. +// +// Copyright (C) 2016 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 GameRes.Utility; + +namespace GameRes.Formats.Ivory +{ + [Export(typeof(AudioFormat))] + public class PxAudio : AudioFormat + { + public override string Tag { get { return "PX/cTRK"; } } + public override string Description { get { return "Ivory audio format"; } } + public override uint Signature { get { return 0x20585066; } } // 'fPX ' + + public PxAudio () + { + Extensions = new string[] { "px", "trk" }; + Signatures = new uint[] { 0x20585066, 0x4B525463 }; + } + + public override SoundInput TryOpen (Stream file) + { + var header = new byte[0x24]; + if (8 != file.Read (header, 0, 8)) + return null; + int start_offset = 0; + if (Binary.AsciiEqual (header, "fPX ")) + { + start_offset = 8; + file.Read (header, 0, 0x24); + } + else + { + file.Read (header, 8, 0x1C); + } + if (!Binary.AsciiEqual (header, "cTRK")) + return null; + int header_length = LittleEndian.ToInt32 (header, 0xC); + if (header_length < 0x20) + return null; + int data_length = LittleEndian.ToInt32 (header, 4) - header_length; + start_offset += header_length; + int type = LittleEndian.ToUInt16 (header, 0x1E); + if (0 == type) + { + var format = new WaveFormat + { + FormatTag = 1, + Channels = LittleEndian.ToUInt16 (header, 0x1A), + SamplesPerSecond = LittleEndian.ToUInt32 (header, 0x14), + BitsPerSample = LittleEndian.ToUInt16 (header, 0x1C), + }; + format.BlockAlign = (ushort)(format.BitsPerSample * format.Channels / 8); + format.AverageBytesPerSecond = format.SamplesPerSecond * format.BlockAlign; + var input = new StreamRegion (file, start_offset, data_length); + return new RawPcmInput (input, format); + } + else if (2 == type) + { + using (var decoder = new TrkDecoder (file, header, start_offset, data_length)) + { + var pcm = decoder.Decode(); + return new RawPcmInput (new MemoryStream (pcm), decoder.Format); + } + } + else if (3 == type) + { + var input = new StreamRegion (file, start_offset, data_length); + return new OggInput (input); + } + else + throw new InvalidFormatException (string.Format ("Unknown cTRK format ({0})", type)); + } + } + + internal sealed class TrkDecoder : IDisposable + { + MsbBitStream m_input; + WaveFormat m_format; + byte[] m_output; + int m_start; + int m_end; + int m_sample_count; + + public WaveFormat Format { get { return m_format; } } + public byte[] Data { get { return m_output; } } + + public TrkDecoder (Stream input, byte[] header, int start_offset, int data_length) + { + m_format.FormatTag = 1; + m_format.Channels = LittleEndian.ToUInt16 (header, 0x1A); + m_format.SamplesPerSecond = LittleEndian.ToUInt32 (header, 0x14); + m_format.BitsPerSample = 16; + m_format.BlockAlign = (ushort)(2 * m_format.Channels); + m_format.AverageBytesPerSecond = m_format.SamplesPerSecond * m_format.BlockAlign; + m_sample_count = LittleEndian.ToInt32 (header, 0x10); + m_input = new MsbBitStream (input, true); + m_start = start_offset; + m_end = start_offset + data_length; + m_output = new byte[m_sample_count * m_format.BlockAlign]; + ref_sample = new int[m_format.Channels]; + } + + int[] ref_sample; + + public byte[] Decode () // decode_audio_420170 + { + int dst_block = 0; + int step = m_format.BlockAlign; + m_input.Input.Position = m_start; + int remaining = m_sample_count; + while (remaining > 0) + { + int block_count = Math.Min (remaining, 28); + remaining -= block_count; + for (int c = 0; c < m_format.Channels; ++c) + { + int sample = ref_sample[c]; + int first = m_input.GetBits (8); + int shift = m_input.GetBits (4); + int first_shift = m_input.GetBits (4); + int diff = first << ((first_shift & 7) + 1); + if (0 != (first_shift & 8)) + { + sample -= diff; + if (sample < -32768) + sample = -32768; + } + else + { + sample += diff; + if (sample > 0x7FFF) + sample = 0x7FFF; + } + int dst = dst_block + 2 * c; + for (int i = 0; i < block_count; ++i) + { + int bits = m_input.GetBits (4); + if (-1 == bits) + throw new EndOfStreamException(); + if (0 != (bits & 8)) + { + sample -= (((bits & 7 ^ 7) + 1) & 0xFF) << shift; + if (sample < -32768) + sample = -32768; + } + else + { + sample += (bits & 7) << shift; + if (sample > 0x7FFF) + sample = 0x7FFF; + } + LittleEndian.Pack ((ushort)sample, m_output, dst); + dst += step; + } + ref_sample[c] = sample; + } + dst_block += block_count * step; + } + return m_output; + } + + #region IDisposable Members + bool _disposed = false; + public void Dispose () + { + if (!_disposed) + { + m_input.Dispose(); + _disposed = true; + } + } + #endregion + } +} diff --git a/ArcFormats/Ivory/ImageSG.cs b/ArcFormats/Ivory/ImageSG.cs new file mode 100644 index 00000000..42573347 --- /dev/null +++ b/ArcFormats/Ivory/ImageSG.cs @@ -0,0 +1,372 @@ +//! \file ImageSG.cs +//! \date Mon Sep 12 14:37:19 2016 +//! \brief 'fSG' image format. +// +// Copyright (C) 2016 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; +using GameRes.Utility; + +namespace GameRes.Formats.Ivory +{ + internal class SgMetaData : ImageMetaData + { + public SgType Type; + public int DataOffset; + public int DataSize; + public int RgbMode; + public uint JpegKey; + } + + internal enum SgType + { + cRGB, cJPG + } + + [Export(typeof(ImageFormat))] + public class SgFormat : ImageFormat + { + public override string Tag { get { return "SG"; } } + public override string Description { get { return "Ivory image format"; } } + public override uint Signature { get { return 0x20475366; } } // 'fSG ' + + public override ImageMetaData ReadMetaData (Stream stream) + { + stream.Position = 8; + var header = new byte[0x24]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + int header_size = LittleEndian.ToInt32 (header, 8); + if (Binary.AsciiEqual (header, "cRGB")) + return new SgMetaData + { + Type = SgType.cRGB, + Width = LittleEndian.ToUInt16 (header, 0x1C), + Height = LittleEndian.ToUInt16 (header, 0x1E), + BPP = LittleEndian.ToUInt16 (header, 0x22), + OffsetX = LittleEndian.ToInt16 (header, 0x18), + OffsetY = LittleEndian.ToInt16 (header, 0x1A), + RgbMode = LittleEndian.ToUInt16 (header, 0x10), + DataOffset = 8 + header_size, + DataSize = LittleEndian.ToInt32 (header, 0xC), + }; + else if (Binary.AsciiEqual (header, "cJPG")) + return new SgMetaData + { + Type = SgType.cJPG, + Width = LittleEndian.ToUInt16 (header, 0x18), + Height = LittleEndian.ToUInt16 (header, 0x1A), + BPP = 24, + OffsetX = LittleEndian.ToInt16 (header, 0x14), + OffsetY = LittleEndian.ToInt16 (header, 0x16), + DataOffset = 8 + header_size, + DataSize = LittleEndian.ToInt32 (header, 0xC), + JpegKey = LittleEndian.ToUInt32 (header, 0x20), + }; + else + return null; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = (SgMetaData)info; + if (SgType.cRGB == meta.Type) + { + using (var reader = new SgRgbReader (stream, meta)) + { + reader.Unpack(); + return ImageData.Create (info, reader.Format, reader.Palette, reader.Data, reader.Stride); + } + } + else + { + return ReadJpeg (stream, meta); + } + } + + ImageData ReadJpeg (Stream stream, SgMetaData info) + { + stream.Position = info.DataOffset; + var input = new byte[info.DataSize]; + if (input.Length != stream.Read (input, 0, input.Length)) + throw new EndOfStreamException(); + PakOpener.Decrypt (input, info.JpegKey); + using (var img = new MemoryStream (input)) + { + var decoder = new JpegBitmapDecoder (img, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + var frame = decoder.Frames[0]; + frame.Freeze(); + return new ImageData (frame, info); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("SgFormat.Write not implemented"); + } + } + + internal sealed class SgRgbReader : IDisposable + { + BinaryReader m_input; + SgMetaData m_info; + int m_width; + int m_height; + int m_stride; + int m_channels; + byte[] m_output; + + public PixelFormat Format { get; private set; } + public BitmapPalette Palette { get; private set; } + public byte[] Data { get { return m_output; } } + public int Stride { get { return m_stride; } } + + public SgRgbReader (Stream input, SgMetaData info) + { + if (info.Type != SgType.cRGB || !(0x18 == info.BPP || 0x20 == info.BPP)) + throw new InvalidFormatException(); + m_input = new ArcView.Reader (input); + m_info = info; + m_width = (int)info.Width; + m_height = (int)info.Height; + m_stride = m_width * 4; + m_channels = m_info.BPP / 8; + } + + public void Unpack () + { + m_input.BaseStream.Position = m_info.DataOffset; + switch (m_info.RgbMode) + { + case 0: UnpackV0(); break; + case 1: UnpackV1(); break; + case 2: + if (4 == m_channels) + UnpackV2Alpha(); + else + UnpackV2(); + break; + default: + throw new NotImplementedException (string.Format ("sRGB image type {0} not implemented", m_info.RgbMode)); + } + } + + void UnpackV1 () + { + Format = 3 == m_channels ? PixelFormats.Bgr32 : PixelFormats.Bgra32; + m_output = new byte[m_stride * m_height]; + var line = new byte[m_stride]; + + int dst = 0; + int alpha_pos = m_width * 3; + for (int y = 0; y < m_height; ++y) + { + int line_pos = 0; + for (int c = 0; c < m_channels; ++c) + { + for (int x = 0; x < m_width; ) + { + byte ctl = m_input.ReadByte(); + int count = ctl & 0x3F; + if (0 != (ctl & 0x40)) + { + count <<= 8; + count |= m_input.ReadByte(); + } + if (0 != (ctl & 0x80)) + { + byte v = m_input.ReadByte(); + for (int i = 0; i < count; ++i) + line[line_pos++] = v; + } + else + { + m_input.Read (line, line_pos, count); + line_pos += count; + } + x += count; + } + } + line_pos = 0; + for (int x = 0; x < m_width; ++x) + { + m_output[dst ] = line[line_pos]; + m_output[dst+1] = line[line_pos+m_width]; + m_output[dst+2] = line[line_pos+m_width*2]; + if (4 == m_channels) + { + m_output[dst+3] = line[line_pos+alpha_pos]; + } + dst += 4; + ++line_pos; + } + } + } + + void UnpackV0 () + { + Format = 3 == m_channels ? PixelFormats.Bgr24 : PixelFormats.Bgra32; + m_stride = m_width * m_channels; + m_output = m_input.ReadBytes (m_stride * m_height); + } + + void UnpackV2 () + { + m_stride = m_width; + m_output = new byte[m_width * m_height]; + Format = PixelFormats.Indexed8; + + Palette = new BitmapPalette (ReadPalette()); + var index = new int[m_height]; + for (int i = 0; i < m_height; ++i) + index[i] = m_input.ReadInt32(); + var data_pos = m_input.BaseStream.Position; + int dst = 0; + for (int y = 0; y < m_height; ++y) + { + m_input.BaseStream.Position = data_pos + index[y]; + for (int x = 0; x < m_width; ) + { + int ctl = m_input.ReadByte(); + int count = ctl >> 2; + if (0 != (ctl & 2)) + { + count |= m_input.ReadByte() << 6; + } + count = Math.Min (count, m_width - x); + x += count; + if (0 != (ctl & 1)) + { + byte c = m_input.ReadByte(); + for (int i = 0; i < count; ++i) + m_output[dst++] = c; + } + else + { + m_input.Read (m_output, dst, count); + dst += count; + } + } + } + } + + void UnpackV2Alpha () + { + m_output = new byte[m_stride * m_height]; + Format = PixelFormats.Bgra32; + + var palette = ReadPalette(); + var index = new int[m_height]; + for (int i = 0; i < m_height; ++i) + index[i] = m_input.ReadInt32(); + var data_pos = m_input.BaseStream.Position; + int dst = 0; + using (var bits = new LsbBitStream (m_input.BaseStream, true)) + { + for (int y = 0; y < m_height; ++y) + { + bits.Input.Position = data_pos + index[y]; + bits.Reset(); + for (int x = 0; x < m_width; ) + { + int ctl = bits.GetBits (2); + int count; + if (0 != (ctl & 2)) + { + count = bits.GetBits (10); + } + else + { + count = bits.GetBits (2); + } + count = Math.Min (count, m_width - x); + x += count; + if (0 != (ctl & 1)) + { + byte a = (byte)bits.GetBits (4); + if (a != 0) + a = (byte)((a << 4) | 0xF); + int c = bits.GetBits (8); + if (-1 == c) + throw new EndOfStreamException(); + var color = palette[c]; + for (int i = 0; i < count; ++i) + { + m_output[dst++] = color.B; + m_output[dst++] = color.G; + m_output[dst++] = color.R; + m_output[dst++] = a; + } + } + else + { + for (int i = 0; i < count; ++i) + { + int a = bits.GetBits (4); + if (a != 0) + a = (a << 4) | 0xF; + int c = bits.GetBits (8); + if (-1 == c) + throw new EndOfStreamException(); + var color = palette[c]; + m_output[dst++] = color.B; + m_output[dst++] = color.G; + m_output[dst++] = color.R; + m_output[dst++] = (byte)a; + } + } + } + } + } + } + + Color[] ReadPalette () + { + var palette_data = m_input.ReadBytes (0x400); + if (palette_data.Length != 0x400) + throw new EndOfStreamException(); + var palette = new Color[0x100]; + for (int i = 0; i < 0x100; ++i) + { + int c = i * 4; + palette[i] = Color.FromRgb (palette_data[c+2], palette_data[c+1], palette_data[c]); + } + return palette; + } + + #region IDisposable Members + bool _disposed = false; + public void Dispose () + { + if (!_disposed) + { + m_input.Dispose(); + _disposed = true; + } + } + #endregion + } +} diff --git a/supported.html b/supported.html index 7e994839..0a01b9d4 100644 --- a/supported.html +++ b/supported.html @@ -339,6 +339,7 @@ Ren no Koi *gramasdifNo *.ald-NoAlice Soft +Boku dake no Hokenshitsu Daiteikoku Pastel Chime 3 Bind Seeker Shaman's Sanctuary -Miko no Seiiki- @@ -1111,12 +1112,17 @@ Inumimi Berserk *.kgpGRPHNo *.kslKSLMNo *.moe-MMD\x1ANoIvory -Triangle Heart +Triangle Heart 1998 release *.pakpackNoScrPlayer Stray Sheep ~Chijoku no Zangeshitsu~ *.iIMG2No +*.pkfPKNoIvory +Triangle Heart 1-2-3 + +*.sgfSGNo +*.pxfPXNo 1 Non-encrypted only
1 Non-encrypted only