diff --git a/ArcFormats/ImageEDT.cs b/ArcFormats/ImageEDT.cs new file mode 100644 index 00000000..0ad9f82c --- /dev/null +++ b/ArcFormats/ImageEDT.cs @@ -0,0 +1,403 @@ +//! \file ImageEDT.cs +//! \date Sun Feb 15 10:13:07 2015 +//! \brief Active Soft image format implementation. +// +// 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.Text; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.AdPack +{ + internal class EdtMetaData : ImageMetaData + { + public uint CompSize; + public uint ExtraSize; + } + + internal class Ed8MetaData : ImageMetaData + { + public uint PaletteSize; + public uint CompSize; + } + + internal class BitReader + { + int m_bits = 1; + + protected void ResetBits () + { + m_bits = 1; + } + + protected int NextBit (Stream input) + { + if (1 == m_bits) + { + m_bits = input.ReadByte(); + if (-1 == m_bits) + throw new InvalidFormatException ("Unexpected end of input"); + m_bits |= 0x100; + } + int bit = m_bits & 1; + m_bits >>= 1; + return bit; + } + + protected int ReadBits (int data, int count, Stream input) + { + while (count > 0) + { + data = (data << 1) | NextBit (input); + --count; + } + return data; + } + + protected int CountBits (Stream input) + { + int bit = 1, count = 0; + while (count < 0x20 && 1 == bit) + { + ++count; + bit = NextBit (input); + } + if (--count != 0) + return ReadBits (1, count, input); + else + return 1; + } + } + + [Export(typeof(ImageFormat))] + public class EdtFormat : ImageFormat + { + public override string Tag { get { return "EDT"; } } + public override string Description { get { return "Active Soft RGB image format"; } } + public override uint Signature { get { return 0x5552542eu; } } // '.TRU' + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("EdtFormat.Write not implemented"); + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x22]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + if (!Binary.AsciiEqual (header, ".TRUE\x8d\x5d\x8c\xcb\x00")) + return null; + uint width = LittleEndian.ToUInt16 (header, 0x0e); + uint height = LittleEndian.ToUInt16 (header, 0x10); + uint comp_size = LittleEndian.ToUInt32 (header, 0x1a); + uint extra_size = LittleEndian.ToUInt32 (header, 0x1e); + if (extra_size % 3 != 0 || 0 == extra_size) + return null; + + return new EdtMetaData + { + Width = width, + Height = height, + BPP = 24, + CompSize = comp_size, + ExtraSize = extra_size, + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as EdtMetaData; + if (null == meta) + throw new ArgumentException ("EdtFormat.Read should be supplied with EdtMetaData", "info"); + + stream.Position = 0x22; + using (var reader = new Reader (stream, meta)) + { + reader.Unpack(); + byte[] pixels = reader.Data; + var bitmap = BitmapSource.Create ((int)meta.Width, (int)meta.Height, 96, 96, + PixelFormats.Bgr24, null, pixels, (int)meta.Width*3); + bitmap.Freeze(); + return new ImageData (bitmap, meta); + } + } + + internal class Reader : BitReader, IDisposable + { + MemoryStream m_packed; + MemoryStream m_extra; + byte[] m_output; + int[] ShiftTable = new int[32]; + + public byte[] Data { get { return m_output; } } + + public Reader (Stream file, EdtMetaData info) + { + byte[] packed = new byte[info.CompSize]; + file.Read (packed, 0, packed.Length); + m_packed = new MemoryStream (packed, false); + + byte[] extra = new byte[info.ExtraSize]; + file.Read (extra, 0, extra.Length); + m_extra = new MemoryStream (extra, false); + m_output = new byte[info.Width*info.Height*3]; + + int stride = (int)info.Width * 3; + int offset = stride * -4; + int i = 0; + while (offset != 0) + { + int shift = offset - 9; + for (int j = 0; j < 7; ++j) + { + ShiftTable[i++] = shift; + shift += 3; + } + offset += stride; + } + offset = -12; + while (offset != 0) + { + ShiftTable[i++] = offset; + offset += 3; + } + } + + public void Unpack () + { + int dst = 0; + if (3 != m_extra.Read (m_output, dst, 3)) + throw new InvalidFormatException ("Unexpected end of input"); + dst += 3; + while (dst < m_output.Length) + { + if (1 == NextBit (m_packed)) + { + if (0 == NextBit (m_packed)) + { + int offset = ShiftTable[ReadBits (0, 5, m_packed)]; + if (dst < -offset) + return; + int count = CountBits (m_packed) * 3; + Binary.CopyOverlapped (m_output, dst + offset, dst, count); + dst += count; + } + else + { + int offset = -3; + if (1 == NextBit (m_packed)) + { + offset = ShiftTable[(0x11191718 >> ((ReadBits (0, 2, m_packed) << 3)) & 0xFF)]; + if (dst < -offset) + return; + } + for (int i = 0; i < 3; ++i) + { + int b = m_output[dst+offset]; + if (b < 2) + b = 2; + else if (b > 0xfd) + b = 0xfd; + if (1 == NextBit (m_packed)) + { + int b2 = 1 + NextBit (m_packed); + if (0 == NextBit (m_packed)) + b -= b2; + else + b += b2; + } + m_output[dst++] = (byte)b; + } + } + } + else + { + if (3 != m_extra.Read (m_output, dst, 3)) + throw new InvalidFormatException ("Unexpected end of input"); + dst += 3; + } + } + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + { + m_packed.Dispose(); + m_extra.Dispose(); + } + disposed = true; + } + } + #endregion + } + } + + [Export(typeof(ImageFormat))] + public class Ed8Format : ImageFormat + { + public override string Tag { get { return "ED8"; } } + public override string Description { get { return "Active Soft indexed image format"; } } + public override uint Signature { get { return 0x6942382eu; } } // '.8Bi' + + public Ed8Format () + { + Extensions = new string[] { "ed8", "sal" }; + } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("Ed8Format.Write not implemented"); + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x1a]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + if (!Binary.AsciiEqual (header, ".8Bit\x8d\x5d\x8c\xcb\x00")) + return null; + uint width = LittleEndian.ToUInt16 (header, 0x0e); + uint height = LittleEndian.ToUInt16 (header, 0x10); + uint palette_size = LittleEndian.ToUInt32 (header, 0x12); + uint comp_size = LittleEndian.ToUInt32 (header, 0x16); + if (palette_size > 0x100) + return null; + + return new Ed8MetaData + { + Width = width, + Height = height, + BPP = 8, + PaletteSize = palette_size, + CompSize = comp_size, + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as Ed8MetaData; + if (null == meta) + throw new ArgumentException ("Ed8Format.Read should be supplied with Ed8MetaData", "info"); + + stream.Position = 0x1a; + var reader = new Reader (stream, meta); + reader.Unpack(); + byte[] pixels = reader.Data; + var palette = new BitmapPalette (reader.Palette); + var bitmap = BitmapSource.Create ((int)info.Width, (int)info.Height, 96, 96, + PixelFormats.Indexed8, palette, pixels, (int)info.Width); + bitmap.Freeze(); + return new ImageData (bitmap, info); + } + + internal class Reader : BitReader + { + Stream m_input; + byte[] m_data; + Color[] m_palette; + int m_width; + + public Color[] Palette { get { return m_palette; } } + public byte[] Data { get { return m_data; } } + + private static readonly sbyte[] ShiftTable = new sbyte[] { + // (-1,0) (0,1) (-2,0) (-1,1) (1,1) (0,2) (-2,1) (2,1) + -0x10, 0x01, -0x20, -0x0F, 0x11, 0x02, -0x1F, 0x21, + // (-2,2) (-1,2) (1,2) (2,2) (0,3) (-1,3) + -0x1E, -0x0E, 0x12, 0x22, 0x03, -0x0D + }; + + public Reader (Stream file, Ed8MetaData info) + { + m_width = (int)info.Width; + int palette_size = (int)info.PaletteSize*3; + var palette_data = new byte[Math.Max (0x300, palette_size)]; + if (palette_size != file.Read (palette_data, 0, palette_size)) + throw new InvalidFormatException(); + m_palette = new Color[0x100]; + for (int i = 0; i < m_palette.Length; ++i) + { + m_palette[i] = Color.FromRgb (palette_data[i*3+2], palette_data[i*3+1], palette_data[i*3]); + } + m_input = file; + m_data = new byte[info.Width * info.Height]; + } + + public void Unpack () + { + int data_pos = 0; + while (data_pos < m_data.Length) + { + m_data[data_pos++] = (byte)ReadBits (0, 8, m_input); + if (m_data.Length == data_pos) + break; + if (1 == NextBit (m_input)) + continue; + int prev_code = -1; + while (data_pos < m_data.Length) + { + int code = 0; + if (1 == NextBit (m_input)) + { + if (1 == NextBit (m_input)) + code = NextBit (m_input) + 1; + code = (code << 1) + NextBit (m_input) + 1; + } + code = (code << 1) + NextBit (m_input); + if (code == prev_code) + break; + prev_code = code; + int count = CountBits (m_input); + if (prev_code >= 2) + ++count; + if (data_pos + count > m_data.Length) + throw new InvalidFormatException(); + int shift = ShiftTable[prev_code]; + int offset = (shift >> 4) - (shift & 0xf) * m_width; + Binary.CopyOverlapped (m_data, data_pos+offset, data_pos, count); + data_pos += count; + } + } + } + } + } +} diff --git a/ArcFormats/ImageGCP.cs b/ArcFormats/ImageGCP.cs new file mode 100644 index 00000000..ec2b87f3 --- /dev/null +++ b/ArcFormats/ImageGCP.cs @@ -0,0 +1,209 @@ +//! \file ImageGCP.cs +//! \date Fri Feb 20 14:26:51 2015 +//! \brief GCP image format implementation. +// +// 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.Text; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.Riddle +{ + internal class GcpMetaData : ImageMetaData + { + public int DataSize; + public int PackedSize; + } + + [Export(typeof(ImageFormat))] + public class GcpFormat : ImageFormat + { + public override string Tag { get { return "GCP"; } } + public override string Description { get { return "Riddle Soft RGB image format"; } } + public override uint Signature { get { return 0x31504d43u; } } // 'CMP1' + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("GcpFormat.Write not implemented"); + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[12]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + int data_size = LittleEndian.ToInt32 (header, 4); + int pack_size = LittleEndian.ToInt32 (header, 8); + if (data_size < 54) + return null; + using (var reader = new Reader (stream, pack_size, 0x22)) // BMP header + { + reader.Unpack(); + var bmp = reader.Data; + if (bmp[0] != 'B' || bmp[1] != 'M') + return null; + int width = LittleEndian.ToInt32 (bmp, 0x12); + int height = LittleEndian.ToInt32 (bmp, 0x16); + int bpp = LittleEndian.ToInt16 (bmp, 0x1c); + return new GcpMetaData + { + Width = (uint)width, + Height = (uint)height, + BPP = bpp, + DataSize = data_size, + PackedSize = pack_size, + }; + } + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as GcpMetaData; + if (null == meta) + throw new ArgumentException ("GcpFormat.Read should be supplied with GcpMetaData", "info"); + + stream.Position = 12; + using (var reader = new Reader (stream, meta.PackedSize, meta.DataSize)) + { + reader.Unpack(); + using (var bmp = new MemoryStream (reader.Data, false)) + { + var decoder = new BmpBitmapDecoder (bmp, + BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + BitmapSource frame = decoder.Frames[0]; + frame.Freeze(); + return new ImageData (frame, info); + } + } + } + } + + internal class Reader : IDisposable + { + Stream m_input; + byte[] m_output; + int m_src_count = 0; + int m_src_total; + + public byte[] Data { get { return m_output; } } + + public Reader (Stream file, int src_size, int dst_size) + { + m_input = file; + m_output = new byte[dst_size]; + m_src_total = src_size; + } + + public void Unpack () + { + int dst = 0; + var shift = new byte[0x800]; + int edi = 0x7ef; + for (int i = 0; i < edi; ++i) + shift[i] = 0x20; + while (dst < m_output.Length) + { + int bit = GetNextBit(); + if (-1 == bit) + break; + if (1 == bit) + { + int data = GetBits (8); + if (-1 == data) + break; + m_output[dst++] = (byte)data; + shift[edi++] = (byte)data; + edi &= 0x7ff; + } + else + { + int offset = GetBits (11); // [esp+10] + if (-1 == offset) + break; + int count = GetBits (4); + if (-1 == count) + break; + count += 2; + for (int i = 0; i < count; ++i) + { + byte data = shift[(offset + i) & 0x7ff]; + m_output[dst++] = data; + shift[edi++] = data; + edi &= 0x7ff; + if (m_output.Length == dst) + return; + } + } + } + } + + int m_bits = 0; + + int GetNextBit () + { + bool bit = 0 != (m_bits & 0x80); + m_bits <<= 1; + if (0 == (m_bits & 0xff)) + { + if (m_src_count++ >= m_src_total) + return -1; + m_bits = m_input.ReadByte(); + if (-1 == m_bits) + throw new EndOfStreamException ("Invalid compressed stream"); + bit = 0 != (m_bits & 0x80); + m_bits = (m_bits << 1) | 1; + } + return bit ? 1 : 0; + } + + int GetBits (int count, int data = 0) + { + while (count > 0) + { + int bit = GetNextBit(); + if (-1 == bit) + return -1; + data = (data << 1) | bit; + --count; + } + return data; + } + + #region IDisposable Members + public void Dispose () + { + if (null != m_input) + { + m_input = null; + } + GC.SuppressFinalize (this); + } + #endregion + } +} diff --git a/ArcFormats/ImageTGF.cs b/ArcFormats/ImageTGF.cs new file mode 100644 index 00000000..a3281d1f --- /dev/null +++ b/ArcFormats/ImageTGF.cs @@ -0,0 +1,183 @@ +//! \file ImageTGF.cs +//! \date Sun Feb 15 21:46:24 2015 +//! \brief Tactics TGF image format implementation. +// +// 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.Text; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.Tactics +{ + internal class TgfMetaData : ImageMetaData + { + public uint BitmapSize; + public int ChunkSize; + } + + [Export(typeof(ImageFormat))] + public class TgfFormat : ImageFormat + { + public override string Tag { get { return "TGF"; } } + public override string Description { get { return "Tactics graphics file"; } } + public override uint Signature { get { return 0; } } + + public override void Write (Stream file, ImageData image) + { + throw new NotImplementedException ("TgfFormat.Write not implemented"); + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[8]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + uint length = LittleEndian.ToUInt32 (header, 0); + int chunk_size = LittleEndian.ToInt32 (header, 4); + if (length > 0xffffff) + return null; + using (var reader = new Reader (stream, (uint)Math.Max (0x20, chunk_size+2), chunk_size)) + { + reader.Unpack(); + var bmp = reader.Data; + return new TgfMetaData + { + Width = LittleEndian.ToUInt32 (bmp, 0x12), + Height = LittleEndian.ToUInt32 (bmp, 0x16), + BPP = LittleEndian.ToInt16 (bmp, 0x1c), + BitmapSize = length, + ChunkSize = chunk_size, + }; + } + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as TgfMetaData; + if (null == meta) + throw new ArgumentException ("TgfFormat.Read should be supplied with TgfMetaData", "info"); + + stream.Position = 8; + using (var reader = new Reader (stream, meta.BitmapSize, meta.ChunkSize)) + { + reader.Unpack(); + using (var bmp = new MemoryStream (reader.Data)) + { + var decoder = new BmpBitmapDecoder (bmp, + BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + BitmapSource frame = decoder.Frames[0]; + frame.Freeze(); + return new ImageData (frame, info); + } + } + } + + internal class Reader : IDisposable + { + BinaryReader m_input; + byte[] m_output; + int m_chunk_size; + + public byte[] Data { get { return m_output; } } + + public Reader (Stream file, uint bmp_size, int chunk_size) + { + m_chunk_size = chunk_size; + m_output = new byte[bmp_size]; + m_input = new BinaryReader (file, Encoding.ASCII, true); + } + + public void Unpack () + { + int dst = 0; + while (dst < m_output.Length) + { + int code = m_input.ReadByte(); + switch (code) + { + case 0: + { + int count = m_input.ReadByte(); + if (dst + count > m_output.Length) + count = m_output.Length - dst; + m_input.Read (m_output, dst, count); + dst += count; + break; + } + case 1: + { + int count = m_input.ReadByte() * m_chunk_size; + if (dst + count > m_output.Length) + count = m_output.Length - dst; + m_input.Read (m_output, dst, count); + dst += count; + break; + } + default: + { + if (dst + m_chunk_size > m_output.Length) + return; + m_input.Read (m_output, dst, m_chunk_size); + int src = dst; + dst += m_chunk_size; + for (int i = 1; i < code; ++i) + { + if (dst + m_chunk_size > m_output.Length) + return; + System.Buffer.BlockCopy (m_output, src, m_output, dst, m_chunk_size); + dst += m_chunk_size; + } + break; + } + } + } + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + m_input.Dispose(); + disposed = true; + } + } + #endregion + } + } +}