278 lines
7.5 KiB
C#
278 lines
7.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace UniversalEditor.Compression.LZSS
|
|
{
|
|
public class LZSS2
|
|
{
|
|
class LzWindowDictionary
|
|
{
|
|
int WindowSize = 0x1000;
|
|
int WindowStart = 0;
|
|
int WindowLength = 0;
|
|
int MinMatchAmount = 3;
|
|
int MaxMatchAmount = 18;
|
|
int BlockSize = 0;
|
|
List<int>[] OffsetList;
|
|
|
|
public LzWindowDictionary()
|
|
{
|
|
// Build the offset list, so Lz compression will become significantly faster
|
|
OffsetList = new List<int>[0x100];
|
|
for (int i = 0; i < OffsetList.Length; i++)
|
|
OffsetList[i] = new List<int>();
|
|
}
|
|
|
|
public int[] Search(byte[] DecompressedData, uint offset, uint length)
|
|
{
|
|
RemoveOldEntries(DecompressedData[offset]); // Remove old entries for this index
|
|
|
|
if (offset < MinMatchAmount || length - offset < MinMatchAmount) // Can't find matches if there isn't enough data
|
|
return new int[] { 0, 0 };
|
|
|
|
// Start finding matches
|
|
int[] Match = new int[] { 0, 0 };
|
|
int MatchStart;
|
|
int MatchSize;
|
|
|
|
for (int i = OffsetList[DecompressedData[offset]].Count - 1; i >= 0; i--)
|
|
{
|
|
MatchStart = OffsetList[DecompressedData[offset]][i];
|
|
MatchSize = 1;
|
|
|
|
while (MatchSize < MaxMatchAmount && MatchSize < WindowLength && MatchStart + MatchSize < offset && offset + MatchSize < length && DecompressedData[offset + MatchSize] == DecompressedData[MatchStart + MatchSize])
|
|
MatchSize++;
|
|
|
|
if (MatchSize >= MinMatchAmount && MatchSize > Match[1]) // This is a good match
|
|
{
|
|
Match = new int[] { (int)(offset - MatchStart), MatchSize };
|
|
|
|
if (MatchSize == MaxMatchAmount) // Don't look for more matches
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Return the match.
|
|
// If no match was made, the distance & length pair will be zero
|
|
return Match;
|
|
}
|
|
|
|
// Slide the window
|
|
public void SlideWindow(int Amount)
|
|
{
|
|
if (WindowLength == WindowSize)
|
|
WindowStart += Amount;
|
|
else
|
|
{
|
|
if (WindowLength + Amount <= WindowSize)
|
|
WindowLength += Amount;
|
|
else
|
|
{
|
|
Amount -= (WindowSize - WindowLength);
|
|
WindowLength = WindowSize;
|
|
WindowStart += Amount;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Slide the window to the next block
|
|
public void SlideBlock()
|
|
{
|
|
WindowStart += BlockSize;
|
|
}
|
|
|
|
// Remove old entries
|
|
private void RemoveOldEntries(byte index)
|
|
{
|
|
for (int i = 0; i < OffsetList[index].Count;) // Don't increment i
|
|
{
|
|
if (OffsetList[index][i] >= WindowStart)
|
|
break;
|
|
else
|
|
OffsetList[index].RemoveAt(0);
|
|
}
|
|
}
|
|
|
|
// Set variables
|
|
public void SetWindowSize(int size)
|
|
{
|
|
WindowSize = size;
|
|
}
|
|
public void SetMinMatchAmount(int amount)
|
|
{
|
|
MinMatchAmount = amount;
|
|
}
|
|
public void SetMaxMatchAmount(int amount)
|
|
{
|
|
MaxMatchAmount = amount;
|
|
}
|
|
public void SetBlockSize(int size)
|
|
{
|
|
BlockSize = size;
|
|
WindowLength = size; // The window will work in blocks now
|
|
}
|
|
|
|
// Add entries
|
|
public void AddEntry(byte[] DecompressedData, int offset)
|
|
{
|
|
OffsetList[DecompressedData[offset]].Add(offset);
|
|
}
|
|
public void AddEntryRange(byte[] DecompressedData, int offset, int length)
|
|
{
|
|
for (int i = 0; i < length; i++)
|
|
AddEntry(DecompressedData, offset + i);
|
|
}
|
|
}
|
|
|
|
public class LZSS
|
|
{
|
|
|
|
/* Decompress */
|
|
public static byte[] Decompress(byte[] data)
|
|
{
|
|
// Compressed & Decompressed Data Information
|
|
uint CompressedSize = (uint)data.Length;
|
|
uint DecompressedSize = (uint)(data[0] + data[1] * 256);
|
|
|
|
uint SourcePointer = 0x4;
|
|
uint DestPointer = 0x0;
|
|
|
|
byte[] CompressedData = data;
|
|
byte[] DecompressedData = new byte[DecompressedSize];
|
|
|
|
// Start Decompression
|
|
while (SourcePointer < CompressedSize && DestPointer < DecompressedSize)
|
|
{
|
|
byte Flag = CompressedData[SourcePointer]; // Compression Flag
|
|
SourcePointer++;
|
|
|
|
for (int i = 7; i >= 0; i--)
|
|
{
|
|
if ((Flag & (1 << i)) == 0) // Data is not compressed
|
|
{
|
|
DecompressedData[DestPointer] = CompressedData[SourcePointer];
|
|
SourcePointer++;
|
|
DestPointer++;
|
|
}
|
|
else // Data is compressed
|
|
{
|
|
int Distance = (((CompressedData[SourcePointer] & 0xF) << 8) | CompressedData[SourcePointer + 1]) + 1;
|
|
int Amount = (CompressedData[SourcePointer] >> 4) + 3;
|
|
SourcePointer += 2;
|
|
|
|
// Copy the data
|
|
for (int j = 0; j < Amount; j++)
|
|
{
|
|
DecompressedData[DestPointer + j] = DecompressedData[DestPointer - Distance + j];
|
|
}
|
|
DestPointer += (uint)Amount;
|
|
}
|
|
|
|
// Check for out of range
|
|
if (SourcePointer >= CompressedSize || DestPointer >= DecompressedSize) break;
|
|
}
|
|
}
|
|
|
|
return DecompressedData;
|
|
}
|
|
|
|
/* Compress */
|
|
public static byte[] Compress(byte[] DecompressedData, string filename)
|
|
{
|
|
try
|
|
{
|
|
uint DecompressedSize = (uint)DecompressedData.Length;
|
|
|
|
System.IO.MemoryStream CompressedData = new System.IO.MemoryStream();
|
|
|
|
uint SourcePointer = 0x0;
|
|
uint DestPointer = 0x4;
|
|
|
|
// Test if the file is too large to be compressed
|
|
if (DecompressedData.Length > 0xFFFFFF)
|
|
throw new Exception("Input file is too large to compress.");
|
|
|
|
// Set up the Lz Compression Dictionary
|
|
LzWindowDictionary LzDictionary = new LzWindowDictionary();
|
|
LzDictionary.SetWindowSize(0x1000);
|
|
LzDictionary.SetMaxMatchAmount(0xF + 3);
|
|
|
|
// Start compression
|
|
CompressedData.Write((uint)('\x10' | (DecompressedSize << 8)));
|
|
while (SourcePointer < DecompressedSize)
|
|
{
|
|
byte Flag = 0x0;
|
|
uint FlagPosition = DestPointer;
|
|
CompressedData.WriteByte(Flag); // It will be filled in later
|
|
DestPointer++;
|
|
|
|
for (int i = 7; i >= 0; i--)
|
|
{
|
|
int[] LzSearchMatch = LzDictionary.Search(DecompressedData, SourcePointer, DecompressedSize);
|
|
if (LzSearchMatch[1] > 0) // There is a compression match
|
|
{
|
|
Flag |= (byte)(1 << i);
|
|
|
|
CompressedData.WriteByte((byte)((((LzSearchMatch[1] - 3) & 0xF) << 4) | (((LzSearchMatch[0] - 1) & 0xFFF) >> 8)));
|
|
CompressedData.WriteByte((byte)((LzSearchMatch[0] - 1) & 0xFF));
|
|
|
|
LzDictionary.AddEntryRange(DecompressedData, (int)SourcePointer, LzSearchMatch[1]);
|
|
LzDictionary.SlideWindow(LzSearchMatch[1]);
|
|
|
|
SourcePointer += (uint)LzSearchMatch[1];
|
|
DestPointer += 2;
|
|
}
|
|
else // There wasn't a match
|
|
{
|
|
Flag |= (byte)(0 << i);
|
|
|
|
CompressedData.WriteByte(DecompressedData[SourcePointer]);
|
|
|
|
LzDictionary.AddEntry(DecompressedData, (int)SourcePointer);
|
|
LzDictionary.SlideWindow(1);
|
|
|
|
SourcePointer++;
|
|
DestPointer++;
|
|
}
|
|
|
|
// Check for out of bounds
|
|
if (SourcePointer >= DecompressedSize)
|
|
break;
|
|
}
|
|
|
|
// Write the flag.
|
|
// Note that the original position gets reset after writing.
|
|
CompressedData.Seek(FlagPosition, System.IO.SeekOrigin.Begin);
|
|
CompressedData.WriteByte(Flag);
|
|
CompressedData.Seek(DestPointer, System.IO.SeekOrigin.Begin);
|
|
}
|
|
|
|
return CompressedData.ToArray();
|
|
}
|
|
catch
|
|
{
|
|
return null; // An error occured while compressing
|
|
}
|
|
}
|
|
|
|
// Check
|
|
public static bool Check(byte[] data, string filename)
|
|
{
|
|
try
|
|
{
|
|
// Because this can conflict with other compression formats we are going to add a check them too
|
|
return (data.Length > 1 && data[0] == 0x10); //&&
|
|
//!Compression.Dictionary[CompressionFormat.PRS].Check(ref data, filename) &&
|
|
//!Images.Dictionary[GraphicFormat.PVR].Check(ref data, filename));
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|