mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-03-15 23:01:21 +00:00
419 lines
16 KiB
C#
419 lines
16 KiB
C#
|
using System;
|
|||
|
using System.Collections;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.IO;
|
|||
|
|
|||
|
namespace FSO.Files.FAR3
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// 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/
|
|||
|
/// </summary>
|
|||
|
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; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Copies data from source to destination array.<br>
|
|||
|
/// The copy is byte by byte from srcPos to destPos and given length.
|
|||
|
/// </summary>
|
|||
|
/// <param name="Src">The source array.</param>
|
|||
|
/// <param name="SrcPos">The source Position.</param>
|
|||
|
/// <param name="Dest">The destination array.</param>
|
|||
|
/// <param name="DestPos">The destination Position.</param>
|
|||
|
/// <param name="Length">The length.</param>
|
|||
|
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];
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Copies data from array at destPos-srcPos to array at destPos.
|
|||
|
/// </summary>
|
|||
|
/// <param name="array">The array.</param>
|
|||
|
/// <param name="srcPos">The Position to copy from (reverse from end of array!)</param>
|
|||
|
/// <param name="destPos">The Position to copy to.</param>
|
|||
|
/// <param name="length">The length of data to copy.</param>
|
|||
|
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];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Compresses data and returns it as an array of bytes.
|
|||
|
/// Assumes that the array of bytes passed contains
|
|||
|
/// uncompressed data.
|
|||
|
/// </summary>
|
|||
|
/// <param name="Data">The data to be compressed.</param>
|
|||
|
/// <returns>An array of bytes with compressed data.</returns>
|
|||
|
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<int, ArrayList> cmpmap2 = new Dictionary<int, ArrayList>();
|
|||
|
|
|||
|
// 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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Decompresses data and returns it as an
|
|||
|
/// uncompressed array of bytes.
|
|||
|
/// </summary>
|
|||
|
/// <param name="Data">The data to decompress.</param>
|
|||
|
/// <returns>An uncompressed array of bytes.</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|