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