using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace FSO.Files.FAR3
{
///
/// Represents a decompresser that can decompress files in a FAR3
/// archive. If you have some kind of need to understand this code, go to:
/// http://wiki.niotso.org/RefPack
/// The code in this class was ported from DBPF4J:
/// http://sc4dbpf4j.cvs.sourceforge.net/viewvc/sc4dbpf4j/DBPF4J/
///
public class Decompresser
{
private long m_CompressedSize = 0;
private long m_DecompressedSize = 0;
public long DecompressedSize
{
get { return m_DecompressedSize; }
set { m_DecompressedSize = value; }
}
public long CompressedSize
{
get { return m_CompressedSize; }
set { m_CompressedSize = value; }
}
///
/// Copies data from source to destination array.
/// The copy is byte by byte from srcPos to destPos and given length.
///
/// The source array.
/// The source Position.
/// The destination array.
/// The destination Position.
/// The length.
private void ArrayCopy2(byte[] Src, int SrcPos, ref byte[] Dest, int DestPos, long Length)
{
if (Dest.Length < DestPos + Length)
{
byte[] DestExt = new byte[(int)(DestPos + Length)];
Array.Copy(Dest, 0, DestExt, 0, Dest.Length);
Dest = DestExt;
}
for (int i = 0; i < Length/* - 1*/; i++)
Dest[DestPos + i] = Src[SrcPos + i];
}
///
/// Copies data from array at destPos-srcPos to array at destPos.
///
/// The array.
/// The Position to copy from (reverse from end of array!)
/// The Position to copy to.
/// The length of data to copy.
private void OffsetCopy(ref byte[] array, int srcPos, int destPos, long length)
{
srcPos = destPos - srcPos;
if (array.Length < destPos + length)
{
byte[] NewArray = new byte[(int)(destPos + length)];
Array.Copy(array, 0, NewArray, 0, array.Length);
array = NewArray;
}
for (int i = 0; i < length /*- 1*/; i++)
{
array[destPos + i] = array[srcPos + i];
}
}
///
/// Compresses data and returns it as an array of bytes.
/// Assumes that the array of bytes passed contains
/// uncompressed data.
///
/// The data to be compressed.
/// An array of bytes with compressed data.
public byte[] Compress(byte[] Data)
{
// if data is big enough for compress
if (Data.Length > 6)
{
// some Compression Data
const int MAX_OFFSET = 0x20000;
const int MAX_COPY_COUNT = 0x404;
// used to finetune the lookup (small values increase the
// compression for Big Files)
const int QFS_MAXITER = 0x80;
// contains the latest offset for a combination of two
// characters
Dictionary cmpmap2 = new Dictionary();
// will contain the compressed data (maximal size =
// uncompressedSize+MAX_COPY_COUNT)
byte[] cData = new byte[Data.Length + MAX_COPY_COUNT];
// init some vars
int writeIndex = 9; // leave 9 bytes for the header
int lastReadIndex = 0;
ArrayList indexList = null;
int copyOffset = 0;
int copyCount = 0;
int index = -1;
bool end = false;
// begin main compression loop
while (index < Data.Length - 3)
{
// get all Compression Candidates (list of offsets for all
// occurances of the current 3 bytes)
do
{
index++;
if (index >= Data.Length - 2)
{
end = true;
break;
}
int mapindex = Data[index] + (Data[index + 1] << 8)
+ (Data[index + 2] << 16);
indexList = cmpmap2[mapindex];
if (indexList == null)
{
indexList = new ArrayList();
cmpmap2.Add(mapindex, indexList);
}
indexList.Add(index);
} while (index < lastReadIndex);
if (end)
break;
// find the longest repeating byte sequence in the index
// List (for offset copy)
int offsetCopyCount = 0;
int loopcount = 1;
while ((loopcount < indexList.Count) && (loopcount < QFS_MAXITER))
{
int foundindex = (int) indexList[(indexList.Count - 1) - loopcount];
if ((index - foundindex) >= MAX_OFFSET)
{
break;
}
loopcount++;
copyCount = 3;
while ((Data.Length > index + copyCount)&& (Data[index + copyCount] == Data[foundindex + copyCount]) && (copyCount < MAX_COPY_COUNT))
{
copyCount++;
}
if (copyCount > offsetCopyCount)
{
offsetCopyCount = copyCount;
copyOffset = index - foundindex;
}
}
// check if we can compress this
// In FSH Tool stand additionally this:
if (offsetCopyCount > Data.Length - index)
{
offsetCopyCount = index - Data.Length;
}
if (offsetCopyCount <= 2)
{
offsetCopyCount = 0;
}
else if ((offsetCopyCount == 3) && (copyOffset > 0x400))
{ // 1024
offsetCopyCount = 0;
}
else if ((offsetCopyCount == 4) && (copyOffset > 0x4000))
{ // 16384
offsetCopyCount = 0;
}
// this is offset-compressable? so do the compression
if (offsetCopyCount > 0)
{
// plaincopy
// In FSH Tool stand this (A):
while (index - lastReadIndex >= 4)
{
copyCount = (index - lastReadIndex) / 4 - 1;
if (copyCount > 0x1B)
{
copyCount = 0x1B;
}
cData[writeIndex++] = (byte)(0xE0 + copyCount);
copyCount = 4 * copyCount + 4;
ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount);
lastReadIndex += copyCount;
writeIndex += copyCount;
}
// offsetcopy
copyCount = index - lastReadIndex;
copyOffset--;
if ((offsetCopyCount <= 0x0A) && (copyOffset < 0x400))
{
cData[writeIndex++] = (byte) (((copyOffset >> 8) << 5)
+ ((offsetCopyCount - 3) << 2) + copyCount);
cData[writeIndex++] = (byte)(copyOffset & 0xff);
}
else if ((offsetCopyCount <= 0x43) && (copyOffset < 0x4000))
{
cData[writeIndex++] = (byte)(0x80 + (offsetCopyCount - 4));
cData[writeIndex++] = (byte) ((copyCount << 6) + (copyOffset >> 8));
cData[writeIndex++] = (byte) (copyOffset & 0xff);
}
else if ((offsetCopyCount <= MAX_COPY_COUNT) && (copyOffset < MAX_OFFSET))
{
cData[writeIndex++] = (byte)(0xc0
+ ((copyOffset >> 16) << 4)
+ (((offsetCopyCount - 5) >> 8) << 2) + copyCount);
cData[writeIndex++] = (byte)((copyOffset >> 8) & 0xff);
cData[writeIndex++] = (byte)(copyOffset & 0xff);
cData[writeIndex++] = (byte)((offsetCopyCount - 5) & 0xff);
}
// do the offset copy
ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount);
writeIndex += copyCount;
lastReadIndex += copyCount;
lastReadIndex += offsetCopyCount;
}
}
// add the End Record
index = Data.Length;
// in FSH Tool stand the same as above (A)
while (index - lastReadIndex >= 4)
{
copyCount = (index - lastReadIndex) / 4 - 1;
if (copyCount > 0x1B)
copyCount = 0x1B;
cData[writeIndex++] = (byte)(0xE0 + copyCount);
copyCount = 4 * copyCount + 4;
ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount);
lastReadIndex += copyCount;
writeIndex += copyCount;
}
copyCount = index - lastReadIndex;
cData[writeIndex++] = (byte) (0xfc + copyCount);
ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount);
writeIndex += copyCount;
lastReadIndex += copyCount;
MemoryStream DataStream = new MemoryStream();
BinaryWriter Writer = new BinaryWriter(DataStream);
// write the header for the compressed data
// set the compressed size
Writer.Write((uint)writeIndex);
m_CompressedSize = writeIndex;
// set the MAGICNUMBER
Writer.Write((ushort)0xFB10);
// set the decompressed size
byte[] revData = BitConverter.GetBytes(Data.Length);
Writer.Write((revData[2] << 16) | (revData[1] << 8) | revData[0]);
Writer.Write(cData);
//Avoid nasty swearing here!
Writer.Flush();
m_DecompressedSize = Data.Length;
return DataStream.ToArray();
}
return Data;
}
///
/// Decompresses data and returns it as an
/// uncompressed array of bytes.
///
/// The data to decompress.
/// An uncompressed array of bytes.
public byte[] Decompress(byte[] Data)
{
MemoryStream MemData = new MemoryStream(Data);
BinaryReader Reader = new BinaryReader(MemData);
if (Data.Length > 6)
{
byte[] DecompressedData = new byte[(int)m_DecompressedSize];
int DataPos = 0;
int Pos = 0;
long Control1 = 0;
while (Control1 != 0xFC && Pos < Data.Length)
{
Control1 = Data[Pos];
Pos++;
if (Pos == Data.Length)
break;
if (Control1 >= 0 && Control1 <= 127)
{
// 0x00 - 0x7F
long control2 = Data[Pos];
Pos++;
long numberOfPlainText = (Control1 & 0x03);
ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText);
DataPos += (int)numberOfPlainText;
Pos += (int)numberOfPlainText;
if (DataPos == (DecompressedData.Length))
break;
int offset = (int)(((Control1 & 0x60) << 3) + (control2) + 1);
long numberToCopyFromOffset = ((Control1 & 0x1C) >> 2) + 3;
OffsetCopy(ref DecompressedData, offset, DataPos, numberToCopyFromOffset);
DataPos += (int)numberToCopyFromOffset;
if (DataPos == (DecompressedData.Length))
break;
}
else if ((Control1 >= 128 && Control1 <= 191))
{
// 0x80 - 0xBF
long control2 = Data[Pos];
Pos++;
long control3 = Data[Pos];
Pos++;
long numberOfPlainText = (control2 >> 6) & 0x03;
ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText);
DataPos += (int)numberOfPlainText;
Pos += (int)numberOfPlainText;
if (DataPos == (DecompressedData.Length))
break;
int offset = (int)(((control2 & 0x3F) << 8) + (control3) + 1);
long numberToCopyFromOffset = (Control1 & 0x3F) + 4;
OffsetCopy(ref DecompressedData, offset, DataPos, numberToCopyFromOffset);
DataPos += (int)numberToCopyFromOffset;
if (DataPos == (DecompressedData.Length))
break;
}
else if (Control1 >= 192 && Control1 <= 223)
{
// 0xC0 - 0xDF
long numberOfPlainText = (Control1 & 0x03);
long control2 = Data[Pos];
Pos++;
long control3 = Data[Pos];
Pos++;
long control4 = Data[Pos];
Pos++;
ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText);
DataPos += (int)numberOfPlainText;
Pos += (int)numberOfPlainText;
if (DataPos == (DecompressedData.Length))
break;
int offset = (int)(((Control1 & 0x10) << 12) + (control2 << 8) + (control3) + 1);
long numberToCopyFromOffset = ((Control1 & 0x0C) << 6) + (control4) + 5;
OffsetCopy(ref DecompressedData, offset, DataPos, numberToCopyFromOffset);
DataPos += (int)numberToCopyFromOffset;
if (DataPos == (DecompressedData.Length))
break;
}
else if (Control1 >= 224 && Control1 <= 251)
{
// 0xE0 - 0xFB
long numberOfPlainText = ((Control1 & 0x1F) << 2) + 4;
ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText);
DataPos += (int)numberOfPlainText;
Pos += (int)numberOfPlainText;
if (DataPos == (DecompressedData.Length))
break;
}
else
{
long numberOfPlainText = (Control1 & 0x03);
ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText);
DataPos += (int)numberOfPlainText;
Pos += (int)numberOfPlainText;
if (DataPos == (DecompressedData.Length))
break;
}
}
return DecompressedData;
}
//No data to decompress
return Data;
}
}
}