mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-07 10:47:19 -05:00
229 lines
8.0 KiB
C#
229 lines
8.0 KiB
C#
|
//
|
|||
|
// MpqLibDecompress.cs
|
|||
|
//
|
|||
|
// Authors:
|
|||
|
// Foole (fooleau@gmail.com)
|
|||
|
//
|
|||
|
// (C) 2006 Foole (fooleau@gmail.com)
|
|||
|
// Based on code from StormLib by Ladislav Zezula
|
|||
|
//
|
|||
|
// 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.IO;
|
|||
|
|
|||
|
namespace OpenDiablo2.Common.Models
|
|||
|
{
|
|||
|
enum CompressionType
|
|||
|
{
|
|||
|
Binary = 0,
|
|||
|
Ascii = 1
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// A decompressor for PKLib implode/explode
|
|||
|
/// </summary>
|
|||
|
public class PKLibDecompress
|
|||
|
{
|
|||
|
private BitStream _bitstream;
|
|||
|
private CompressionType _compressionType;
|
|||
|
private int _dictSizeBits; // Dictionary size in bits
|
|||
|
|
|||
|
private static byte[] sPosition1;
|
|||
|
private static byte[] sPosition2;
|
|||
|
|
|||
|
private static readonly byte[] sLenBits =
|
|||
|
{
|
|||
|
3, 2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7
|
|||
|
};
|
|||
|
|
|||
|
private static readonly byte[] sLenCode =
|
|||
|
{
|
|||
|
5, 3, 1, 6, 10, 2, 12, 20, 4, 24, 8, 48, 16, 32, 64, 0
|
|||
|
};
|
|||
|
|
|||
|
private static readonly byte[] sExLenBits =
|
|||
|
{
|
|||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8
|
|||
|
};
|
|||
|
|
|||
|
private static readonly UInt16[] sLenBase =
|
|||
|
{
|
|||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
|
|||
|
0x0008, 0x000A, 0x000E, 0x0016, 0x0026, 0x0046, 0x0086, 0x0106
|
|||
|
};
|
|||
|
|
|||
|
private static readonly byte[] sDistBits =
|
|||
|
{
|
|||
|
2, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
|||
|
6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|||
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|||
|
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
|
|||
|
};
|
|||
|
|
|||
|
private static readonly byte[] sDistCode =
|
|||
|
{
|
|||
|
0x03, 0x0D, 0x05, 0x19, 0x09, 0x11, 0x01, 0x3E, 0x1E, 0x2E, 0x0E, 0x36, 0x16, 0x26, 0x06, 0x3A,
|
|||
|
0x1A, 0x2A, 0x0A, 0x32, 0x12, 0x22, 0x42, 0x02, 0x7C, 0x3C, 0x5C, 0x1C, 0x6C, 0x2C, 0x4C, 0x0C,
|
|||
|
0x74, 0x34, 0x54, 0x14, 0x64, 0x24, 0x44, 0x04, 0x78, 0x38, 0x58, 0x18, 0x68, 0x28, 0x48, 0x08,
|
|||
|
0xF0, 0x70, 0xB0, 0x30, 0xD0, 0x50, 0x90, 0x10, 0xE0, 0x60, 0xA0, 0x20, 0xC0, 0x40, 0x80, 0x00
|
|||
|
};
|
|||
|
|
|||
|
static PKLibDecompress()
|
|||
|
{
|
|||
|
sPosition1 = GenerateDecodeTable(sDistBits, sDistCode);
|
|||
|
sPosition2 = GenerateDecodeTable(sLenBits, sLenCode);
|
|||
|
}
|
|||
|
|
|||
|
public PKLibDecompress(Stream input)
|
|||
|
{
|
|||
|
_bitstream = new BitStream(input);
|
|||
|
|
|||
|
_compressionType = (CompressionType)input.ReadByte();
|
|||
|
if (_compressionType != CompressionType.Binary && _compressionType != CompressionType.Ascii)
|
|||
|
throw new InvalidDataException("Invalid compression type: " + _compressionType);
|
|||
|
|
|||
|
_dictSizeBits = input.ReadByte();
|
|||
|
// This is 6 in test cases
|
|||
|
if (4 > _dictSizeBits || _dictSizeBits > 6)
|
|||
|
throw new InvalidDataException("Invalid dictionary size: " + _dictSizeBits);
|
|||
|
}
|
|||
|
|
|||
|
public byte[] Explode(int expectedSize)
|
|||
|
{
|
|||
|
byte[] outputbuffer = new byte[expectedSize];
|
|||
|
Stream outputstream = new MemoryStream(outputbuffer);
|
|||
|
|
|||
|
int instruction;
|
|||
|
while ((instruction = DecodeLit()) != -1)
|
|||
|
{
|
|||
|
if (instruction < 0x100)
|
|||
|
{
|
|||
|
outputstream.WriteByte((byte)instruction);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If instruction is greater than 0x100, it means "Repeat n - 0xFE bytes"
|
|||
|
int copylength = instruction - 0xFE;
|
|||
|
int moveback = DecodeDist(copylength);
|
|||
|
if (moveback == 0) break;
|
|||
|
|
|||
|
int source = (int)outputstream.Position - moveback;
|
|||
|
// We can't just outputstream.Write the section of the array
|
|||
|
// because it might overlap with what is currently being written
|
|||
|
while (copylength-- > 0)
|
|||
|
outputstream.WriteByte(outputbuffer[source++]);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (outputstream.Position == expectedSize)
|
|||
|
{
|
|||
|
return outputbuffer;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Resize the array
|
|||
|
byte[] result = new byte[outputstream.Position];
|
|||
|
Array.Copy(outputbuffer, 0, result, 0, result.Length);
|
|||
|
return result;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Return values:
|
|||
|
// 0x000 - 0x0FF : One byte from compressed file.
|
|||
|
// 0x100 - 0x305 : Copy previous block (0x100 = 1 byte)
|
|||
|
// -1 : EOF
|
|||
|
private int DecodeLit()
|
|||
|
{
|
|||
|
switch (_bitstream.ReadBits(1))
|
|||
|
{
|
|||
|
case -1:
|
|||
|
return -1;
|
|||
|
|
|||
|
case 1:
|
|||
|
// The next bits are position in buffers
|
|||
|
int pos = sPosition2[_bitstream.PeekByte()];
|
|||
|
|
|||
|
// Skip the bits we just used
|
|||
|
if (_bitstream.ReadBits(sLenBits[pos]) == -1) return -1;
|
|||
|
|
|||
|
int nbits = sExLenBits[pos];
|
|||
|
if (nbits != 0)
|
|||
|
{
|
|||
|
// TODO: Verify this conversion
|
|||
|
int val2 = _bitstream.ReadBits(nbits);
|
|||
|
if (val2 == -1 && (pos + val2 != 0x10e)) return -1;
|
|||
|
|
|||
|
pos = sLenBase[pos] + val2;
|
|||
|
}
|
|||
|
return pos + 0x100; // Return number of bytes to repeat
|
|||
|
|
|||
|
case 0:
|
|||
|
if (_compressionType == CompressionType.Binary)
|
|||
|
return _bitstream.ReadBits(8);
|
|||
|
|
|||
|
// TODO: Text mode
|
|||
|
throw new NotImplementedException("Text mode is not yet implemented");
|
|||
|
default:
|
|||
|
return 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private int DecodeDist(int length)
|
|||
|
{
|
|||
|
if (_bitstream.EnsureBits(8) == false) return 0;
|
|||
|
int pos = sPosition1[_bitstream.PeekByte()];
|
|||
|
byte skip = sDistBits[pos]; // Number of bits to skip
|
|||
|
|
|||
|
// Skip the appropriate number of bits
|
|||
|
if (_bitstream.ReadBits(skip) == -1) return 0;
|
|||
|
|
|||
|
if (length == 2)
|
|||
|
{
|
|||
|
if (_bitstream.EnsureBits(2) == false) return 0;
|
|||
|
pos = (pos << 2) | _bitstream.ReadBits(2);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (_bitstream.EnsureBits(_dictSizeBits) == false) return 0;
|
|||
|
pos = ((pos << _dictSizeBits)) | _bitstream.ReadBits(_dictSizeBits);
|
|||
|
}
|
|||
|
|
|||
|
return pos + 1;
|
|||
|
}
|
|||
|
|
|||
|
private static byte[] GenerateDecodeTable(byte[] bits, byte[] codes)
|
|||
|
{
|
|||
|
byte[] result = new byte[256];
|
|||
|
|
|||
|
for (int i = bits.Length - 1; i >= 0; i--)
|
|||
|
{
|
|||
|
UInt32 idx1 = codes[i];
|
|||
|
UInt32 idx2 = (UInt32)1 << bits[i];
|
|||
|
|
|||
|
do
|
|||
|
{
|
|||
|
result[idx1] = (byte)i;
|
|||
|
idx1 += idx2;
|
|||
|
} while (idx1 < 0x100);
|
|||
|
}
|
|||
|
return result;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|