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;
+ }
+ }
+}