From f20a22f56eb56999bc0d3f3d39c2cec8901ad03d Mon Sep 17 00:00:00 2001 From: morkt Date: Sun, 12 Jul 2015 15:37:50 +0400 Subject: [PATCH] implemented Wild Bug's WBM image format. --- ArcFormats/ArcFormats.csproj | 2 +- ArcFormats/ImageWBM.cs | 747 +++++++++++++++++++++++++++++++++++ 2 files changed, 748 insertions(+), 1 deletion(-) create mode 100644 ArcFormats/ImageWBM.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index a637dd19..e65df7d3 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -215,7 +215,7 @@ - + diff --git a/ArcFormats/ImageWBM.cs b/ArcFormats/ImageWBM.cs new file mode 100644 index 00000000..b870a3d5 --- /dev/null +++ b/ArcFormats/ImageWBM.cs @@ -0,0 +1,747 @@ +//! \file ImageWBM.cs +//! \date Thu Jul 09 20:59:09 2015 +//! \brief Wild Bug's image format. +// +// 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.WildBug +{ + internal class WbmMetaData : ImageMetaData + { + public int EntryCount; + public int EntrySize; + public byte[] Header; + } + + [Export(typeof(ImageFormat))] + public class WbmFormat : ImageFormat + { + public override string Tag { get { return "WBM"; } } + public override string Description { get { return "Wild Bug's image format"; } } + public override uint Signature { get { return 0x1A585057; } } // 'WPX' + + public override ImageMetaData ReadMetaData (Stream stream) + { + byte[] header = new byte[0x10]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + int count = header[0xE]; + int dir_size = header[0xF]; + if (1 != header[0xC] || 0 == count || 0 == dir_size) + return null; + header = new byte[count * dir_size]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + int? ptr = GetDataBlock (header, 0x10, count, dir_size); + if (null == ptr) + return null; + int data_offset = LittleEndian.ToInt32 (header, ptr.Value+4); + int size = LittleEndian.ToInt32 (header, ptr.Value+8); + if (size < 0x10) + return null; + + stream.Seek (data_offset, SeekOrigin.Begin); + byte[] data = new byte[size]; + if (size != stream.Read (data, 0, size)) + return null; + + return new WbmMetaData + { + Width = LittleEndian.ToUInt16 (data, 4), + Height = LittleEndian.ToUInt16 (data, 6), + BPP = data[0xC], + EntryCount = count, + EntrySize = dir_size, + Header = header, + }; + } + + private int? GetDataBlock (byte[] header, byte id, int count, int dir_size) + { + int ptr = 0; + int n = 0; + while (header[ptr] != id) + { + ptr += dir_size; + if (ptr >= header.Length) + return null; + if (++n >= count) + return null; + } + return ptr; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as WbmMetaData; + if (null == meta) + throw new ArgumentException ("WbmFormat.Read should be supplied with WbmMetaData", "info"); + + int? ptr = GetDataBlock (meta.Header, 0x11, meta.EntryCount, meta.EntrySize); + if (null == ptr) + throw new InvalidFormatException(); + int data_format = meta.Header[ptr.Value+1]; + int data_offset = LittleEndian.ToInt32 (meta.Header, ptr.Value+4); + int unpacked_size = LittleEndian.ToInt32 (meta.Header, ptr.Value+8); + int packed_size = LittleEndian.ToInt32 (meta.Header, ptr.Value+12); + + PixelFormat format; + int pixel_size; + switch (meta.BPP) + { + case 24: + format = PixelFormats.Bgr24; + pixel_size = 3; + break; + case 32: + format = PixelFormats.Bgr32; + pixel_size = 4; + break; + case 16: + format = PixelFormats.Bgr555; + pixel_size = 2; + break; + default: + throw new NotSupportedException ("Not supported WBM bitdepth"); + } + int stride = ((int)meta.Width * pixel_size + 3) & -4; + var reader = new WbmReader (stream, data_offset, unpacked_size, packed_size); + var pixels = reader.Unpack (stride, pixel_size, meta.Header[ptr.Value+1]); + if (null == pixels) + throw new InvalidFormatException(); + + return ImageData.Create (info, format, null, pixels, stride); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("WbmFormat.Write not implemented"); + } + } + + internal class WbmReader + { + Stream m_input; + byte[] m_output; + int m_packed_size; + + public byte[] Data { get { return m_output; } } + + public WbmReader (Stream input, int offset, int unpacked_size, int packed_size) + { + m_input = input; + m_input.Position = offset; + m_output = new byte[unpacked_size]; + m_packed_size = packed_size; + } + + public byte[] Unpack (int stride, int pixel_size, int flags) // sub_40919C + { + int[] offset_table = new int[8]; + offset_table[4] = pixel_size; + offset_table[2] = 2 * pixel_size; + offset_table[5] = 3 * pixel_size; + if (5 * pixel_size < stride) + { + offset_table[6] = stride - pixel_size; + offset_table[0] = stride; + offset_table[7] = pixel_size + stride; + offset_table[3] = 2 * pixel_size + stride; + offset_table[1] = 2 * stride; + } + else + { + offset_table[6] = 4 * pixel_size; + offset_table[0] = 5 * pixel_size; + offset_table[7] = 6 * pixel_size; + offset_table[3] = 7 * pixel_size; + offset_table[1] = 8 * pixel_size; + } + if (0 == (flags & 0x80) && 0 != m_packed_size) + { + byte[] ref_table = new byte[0x10000]; + if (0 != (flags & 1)) + { + if (0 != (flags & 4)) + return UnpackV5 (ref_table, offset_table, pixel_size); + else if (0 != (flags & 2)) + return UnpackV3 (ref_table, offset_table, pixel_size); + else + return UnpackV1 (offset_table, pixel_size); + } + else if (0 != (flags & 4)) + return UnpackV4 (ref_table, offset_table, pixel_size); + else if (0 != (flags & 2)) + return UnpackV2 (ref_table, offset_table, pixel_size); + else + return UnpackV0 (offset_table, pixel_size); + } + else if (m_output.Length == m_input.Read (m_output, 0, m_output.Length)) + return m_output; + + return null; + } + + byte[] UnpackV5 (byte[] a4, int[] offset_table, int pixel_size) // 0x07 format +// int sub_409AF4 (void *a1, FILE *stream, void *ptr, void *a4, unsigned int m_packed_size, int *offset_table, int unpacked_size, unsigned int pixel_size) + { + byte[] v46 = BuildTable(); + int min_count = 1 == pixel_size ? 2 : 1; + if (0 == m_packed_size) + return null; + m_available = FillBuffer(); + if (0 == m_available) + return null; + + int step = (pixel_size + 3) & -4; + if (m_available < step + 0x80) + return null; + + int v10 = -pixel_size & 3; + Buffer.BlockCopy (m_buffer, 0, m_output, 0, pixel_size); + int dst = pixel_size; + int remaining = m_output.Length - pixel_size; + m_current = pixel_size + v10 + 128; + + if (!FillRefTable (a4, pixel_size + v10)) + return null; + + int v43 = 16384; + while (remaining > 0) + { + while (0 == GetNextBit()) + { + int v25 = 0; + int v26 = 0; + v43 &= ~0xff00; + v43 |= m_output[dst-pixel_size] << 8; + int v27 = 16384; + for (;;) + { + v25 = (v25 + 1) & 0xff; + if (GetNextBit() != 0) + v26 |= v27; + if (a4[2 * v26] == v25) + break; + v27 >>= 1; + if (0 == v27) + return null; + } + v25 = a4[2 * v26 + 1]; + byte v29 = v46[v43 + v25]; + if (0 != v25) + { + Buffer.BlockCopy (v46, v43, v46, v43+1, v25); + v46[v43] = v29; + } + m_output[dst++] = v29; + --remaining; + if (0 == remaining) + return m_output; + } + int src_offset; + int count; + if (GetNextBit() != 0) + { + count = min_count; + int v32 = GetNextBit() << 1; + v32 |= GetNextBit(); + v32 <<= 1; + v32 |= GetNextBit(); + src_offset = dst - offset_table[v32]; + } + else + { + byte v35 = ReadNext(); + count = 2; + src_offset = dst - 1 - v35; + } + if (0 == GetNextBit()) + { + count += ReadCount(); + } + if (remaining < count) + return null; + Binary.CopyOverlapped (m_output, src_offset, dst, count); + dst += count; + remaining -= count; + } + return m_output; + } + + byte[] UnpackV4 (byte[] a4, int[] offset_table, int pixel_size) // 0x06 format +// signed int __cdecl sub_40B044(void *a1, FILE *stream, void *ptr, void *a4, unsigned int packed_size, int *offset_table, int unpacked_size, unsigned int pixel_size) + { + byte[] v48 = BuildTable(); + int min_count = 1 == pixel_size ? 2 : 1; + if (0 == m_packed_size) + return null; + m_available = FillBuffer(); + if (0 == m_available) + return null; + + int step = (pixel_size + 4) & -4; + if (m_available < step + 0x80) + return null; + + int v10 = -pixel_size & 3; + Buffer.BlockCopy (m_buffer, 0, m_output, 0, pixel_size); + int dst = pixel_size; + int remaining = m_output.Length - pixel_size; + m_current = pixel_size + v10 + 128; + + if (!FillRefTable (a4, pixel_size + v10)) + return null; + + int v46 = 16384; + while (remaining > 0) + { + int v28; + while (0 == GetNextBit()) + { + int v27 = 0; + v28 = 0; + v46 &= ~0xff00; + v46 |= m_output[dst - pixel_size] << 8; + int v29 = 16384; + for (;;) + { + v27 = (v27 + 1) & 0xff; + if (GetNextBit() != 0) + v28 |= v29; + if (a4[2 * v28] == v27) + break; + v29 >>= 1; + if (0 == v29) + return null; + } + v27 = a4[2 * v28 + 1]; + byte v31 = v48[v46 + v27]; + if (0 != v27) + { + Buffer.BlockCopy (v48, v46, v48, v46+1, v27); + v48[v46] = v31; + } + m_output[dst++] = v31; + --remaining; + if (0 == remaining) + return m_output; + } + v28 = GetNextBit() << 1; + v28 |= GetNextBit(); + v28 <<= 1; + v28 |= GetNextBit(); + int src_offset = dst - offset_table[v28]; + int count; + if (GetNextBit() != 0) + { + count = min_count; + } + else + { + count = min_count + ReadCount(); + } + if (remaining < count) + return null; + Binary.CopyOverlapped (m_output, src_offset, dst, count); + dst += count; + remaining -= count; + } + return m_output; + } + + byte[] UnpackV3 (byte[] a4, int[] offset_table, int pixel_size) // 0x03 format +// int sub_409F70(void *a1, FILE *stream, void *ptr, void *a4, unsigned int packed_size, int *offset_table, int unpacked_size, unsigned int pixel_size) + { + int min_count = 1 == pixel_size ? 2 : 1; + if (0 == m_packed_size) + return null; + + m_available = FillBuffer(); + if (0 == m_available) + return null; + + int step = (pixel_size + 3) & -4; + if (m_available < step + 0x80) + return null; + + int v9 = -pixel_size & 3; + Buffer.BlockCopy (m_buffer, 0, m_output, 0, pixel_size); + int dst = pixel_size; + int remaining = m_output.Length - pixel_size; + m_current = pixel_size + v9 + 128; + + if (!FillRefTable (a4, pixel_size + v9)) + return null; + + while (remaining > 0) + { + while (GetNextBit() == 0) + { + int v24 = 0; + int v25 = 0; + int v26 = 16384; + for (;;) + { + ++v24; + if (GetNextBit() != 0) + v25 |= v26; + if (a4[2 * v25] == v24) + break; + v26 >>= 1; + if (0 == v26) + return null; + } + m_output[dst++] = a4[2 * v25 + 1]; + --remaining; + if (0 == remaining) + return m_output; + } + int count, src_offset; + if (GetNextBit() != 0) + { + count = min_count; + int v28 = GetNextBit() << 1; + v28 |= GetNextBit(); + v28 <<= 1; + v28 |= GetNextBit(); + src_offset = dst - offset_table[v28]; + } + else + { + count = 2; + src_offset = dst - 1 - ReadNext(); + } + if (GetNextBit() == 0) + { + count += ReadCount(); + } + if (remaining < count) + return null; + Binary.CopyOverlapped (m_output, src_offset, dst, count); + dst += count; + remaining -= count; + } + return m_output; + } + + byte[] UnpackV2 (byte[] a4, int[] offset_table, int pixel_size) // 0x02 format +// int sub_40B458(void *a1, FILE *stream, unsigned __int8 *ptr, void *a4, unsigned int packed_size, int *offset_table, int unpacked_size, unsigned int a8) + { + byte[] v48 = BuildTable(); + int min_count = 1 == pixel_size ? 2 : 1; + if (0 == m_packed_size) + return null; + m_available = FillBuffer(); + if (0 == m_available) + return null; + + int step = (pixel_size + 3) & -4; + if (m_available < step + 0x80) + return null; + + int v9 = -pixel_size & 3; + Buffer.BlockCopy (m_buffer, 0, m_output, 0, pixel_size); + int dst = pixel_size; + int remaining = m_output.Length - pixel_size; + m_current = pixel_size + v9 + 128; // within m_buffer + + if (!FillRefTable (a4, pixel_size + v9)) + return null; + + while (remaining > 0) + { + while (0 == GetNextBit()) + { + int v20 = 0; + int v21 = 0; + v9 = 16384; + for (;;) + { + ++v20; + if (0 != GetNextBit()) + v21 |= v9; + if (a4[2 * v21] == v20) + break; + v9 >>= 1; + if (0 == v9) + return null; + } + m_output[dst++] = a4[2 * v21 + 1]; + --remaining; + if (0 == remaining) + return m_output; + } + int v22 = GetNextBit() << 1; + v22 |= GetNextBit(); + v22 <<= 1; + v22 |= GetNextBit(); + int src_offset = dst - offset_table[v22]; + int count; + if (0 != GetNextBit()) + { + count = min_count; + } + else + { + count = min_count + ReadCount(); + } + if (remaining < count) + return null; + Binary.CopyOverlapped (m_output, src_offset, dst, count); + dst += count; + remaining -= count; + } + return m_output; + } + + byte[] UnpackV1 (int[] offset_table, int pixel_size) // 0x01 format + //int sub_40A3C4(void *a1, FILE *stream, const void *ptr, unsigned int packed_size, int *offset_table, int unpacked_size, unsigned int pixel_size) + { + int min_count = 1 == pixel_size ? 2 : 1; + if (0 == m_packed_size) + return null; + m_available = FillBuffer(); + if (0 == m_available) + return null; + + int step = (pixel_size + 3) & -4; + if (m_available < step) + return null; + + Buffer.BlockCopy (m_buffer, 0, m_output, 0, pixel_size); + int dst = pixel_size; + int remaining = m_output.Length - pixel_size; + m_current = pixel_size + (-pixel_size & 3); + + m_bits = m_buffer[m_current++]; + m_bit_count = 8; + while (remaining > 0) + { + while (0 == GetNextBit()) + { + m_output[dst++] = ReadNext(); + --remaining; + if (0 == remaining) + return m_output; + } + int count, src_offset; + if (GetNextBit() != 0) + { + count = min_count; + int v14 = GetNextBit() << 1; + v14 |= GetNextBit(); + v14 <<= 1; + v14 |= GetNextBit(); + src_offset = dst - offset_table[v14]; + } + else + { + count = 2; + src_offset = dst - 1 - ReadNext(); + } + if (GetNextBit() == 0) + { + count += ReadCount(); + } + if (remaining < count) + return null; + Binary.CopyOverlapped (m_output, src_offset, dst, count); + dst += count; + remaining -= count; + } + return m_output; + } + + byte[] UnpackV0 (int[] offset_table, int pixel_size) // 0x00 format +// int sub_40B83C(void *a1, FILE *stream, const void *ptr, unsigned int packed_size, int *a5, int unpacked_size, unsigned int pixel_size) + { + int min_count = 1 == pixel_size ? 2 : 1; + if (0 == m_packed_size) + return null; + m_available = FillBuffer(); + if (0 == m_available) + return null; + + int step = (pixel_size + 3) & -4; + if (m_available < step) + return null; + + Buffer.BlockCopy (m_buffer, 0, m_output, 0, pixel_size); + int dst = pixel_size; + int remaining = m_output.Length - pixel_size; + m_current = pixel_size + (-pixel_size & 3); + + m_bits = m_buffer[m_current++]; + m_bit_count = 8; + while (remaining > 0) + { + while (0 == GetNextBit()) + { + m_output[dst++] = ReadNext(); + --remaining; + if (0 == remaining) + return m_output; + } + int v14 = GetNextBit() << 1; + v14 |= GetNextBit(); + v14 <<= 1; + v14 |= GetNextBit(); + int src_offset = dst - offset_table[v14]; + int count; + if (GetNextBit() != 0) + { + count = min_count; + } + else + { + count = min_count + ReadCount(); + } + if (remaining < count) + return null; + Binary.CopyOverlapped (m_output, src_offset, dst, count); + dst += count; + remaining -= count; + } + return m_output; + } + + int ReadCount () + { + int n = 1; + while (0 == GetNextBit()) + { + ++n; + } + int count = 1; + for (int i = 0; i < n; ++i) + { + count += count + GetNextBit(); + } + return count - 1; + } + + static byte[] BuildTable () // sub_4090E0 + { + var table = new byte[0x100*0x100]; + for (int i = 0; i < 0x100; ++i) + { + byte v2 = (byte)(-1 - i); + for (int j = 0; j < 0x100; ++j) + { + table[0x100*i + j] = v2--; // *(&byte_41B0C0[256 * i] + j) = v2--; + } + } + return table; + } + + byte[] m_buffer = new byte[0x8000]; + int m_current = 0; + int m_available = 0; + + byte ReadNext () + { + if (m_current >= m_available) + { + m_available = FillBuffer(); + if (0 == m_available) + throw new InvalidFormatException ("Unexpected end of file"); + m_current = 0; + } + return m_buffer[m_current++]; + } + + int FillBuffer () // sub_409B02 + { + int read = 0; + if (m_packed_size > 0) + { + int size = Math.Min (m_packed_size, 0x8000); + m_packed_size -= size; + read = m_input.Read (m_buffer, 0, size); + } + return read; + } + + byte m_bits; + int m_bit_count = 0; + + int GetNextBit () + { + if (0 == m_bit_count) + { + m_bits = ReadNext(); + m_bit_count = 8; + } + int bit = m_bits >> 7; + m_bits <<= 1; + --m_bit_count; + return bit; + } + + bool FillRefTable (byte[] table, int src) + { + m_bits = m_buffer[m_current++]; + m_bit_count = 8; + for (int n = 0; n < 0x100; ) + { + byte v16 = m_buffer[src++]; + for (int half = 0; half < 2; ++half) + { + byte v17 = (byte)(v16 & 0xF); + if (0 != v17) + { + int v18 = 0; + for (int i = v17; i != 0; --i) + { + if (0 == m_bit_count) + { + if (m_current >= m_available) + return false; + m_bits = m_buffer[m_current++]; + m_bit_count = 8; + } + int bit = m_bits >> 7; + m_bits <<= 1; + --m_bit_count; + v18 += v18 + bit; + } + if (15 != v17) + v18 <<= 15 - v17; + table[2 * v18] = v17; + table[2 * v18 + 1] = (byte)n; + } + ++n; + v16 >>= 4; + } + } + return true; + } + } +}