C#: added an initial compression algorithm for LZ-10. May not work yet, as it hasn't been tested.

This commit is contained in:
barubary 2011-05-13 10:07:38 +00:00
parent 69cd6ee199
commit 1c2722315a

View File

@ -145,9 +145,133 @@ namespace DSDecmp.Formats.Nitro
}
public override int Compress(Stream instream, long inLength, Stream outstream)
public unsafe override int Compress(Stream instream, long inLength, Stream outstream)
{
throw new NotImplementedException();
// make sure the decompressed size fits in 3 bytes.
// There should be room for four bytes, however I'm not 100% sure if that can be used
// in every game, as it may not be a built-in function.
if (inLength > 0xFFFFFF)
throw new InputTooLargeException();
// save the input data in an array to prevent having to go back and forth in a file
byte[] indata = new byte[inLength];
int numReadBytes = instream.Read(indata, 0, (int)inLength);
if (numReadBytes != inLength)
throw new StreamTooShortException();
// write the compression header first
outstream.WriteByte(0x10);
outstream.WriteByte((byte)(inLength & 0xFF));
outstream.WriteByte((byte)((inLength >> 8) & 0xFF));
outstream.WriteByte((byte)((inLength >> 16) & 0xFF));
int compressedLength = 4;
fixed (byte* instart = &indata[0])
{
// we do need to buffer the output, as the first byte indicates which blocks are compressed.
// this version does not use a look-ahead, so we do not need to buffer more than 8 blocks at a time.
byte[] outbuffer = new byte[8 * 2 + 1];
outbuffer[0] = 0;
int bufferlength = 1, bufferedBlocks = 0;
int readBytes = 0;
while (readBytes < inLength)
{
// we can only buffer 8 blocks at a time.
if (bufferedBlocks == 8)
{
outstream.Write(outbuffer, 0, bufferlength);
compressedLength += bufferlength;
// reset the buffer
outbuffer[0] = 0;
bufferlength = 1;
bufferedBlocks = 0;
}
// determine if we're dealing with a compressed or raw block.
// it is a compressed block when the next 3 or more bytes can be copied from
// somewhere in the set of already compressed bytes.
int disp;
int oldLength = Math.Min(readBytes, 0x1000);
int length = this.GetOccurrenceLength(instart + readBytes, (int)Math.Min(inLength - readBytes, 0x12),
instart + readBytes - oldLength, oldLength, out disp);
// length not 3 or more? next byte is raw data
if (length < 3)
{
outbuffer[bufferlength++] = *(instart + (readBytes++));
}
else
{
// 3 or more bytes can be copied? next (length) bytes will be compressed into 2 bytes
readBytes += length;
// mark the next block as compressed
outbuffer[0] |= (byte)(1 << (7 - bufferedBlocks));
outbuffer[bufferlength] = (byte)(((length - 3) << 4) & 0xF0);
outbuffer[bufferlength] |= (byte)(((disp - 1) >> 8) & 0x0F);
bufferlength++;
outbuffer[bufferlength] = (byte)((disp - 1) & 0xFF);
bufferlength++;
}
bufferedBlocks++;
}
// copy the remaining blocks to the output
if (bufferedBlocks > 0)
{
outstream.Write(outbuffer, 0, bufferlength);
compressedLength += bufferlength;
// make the compressed file 4-byte aligned.
while ((compressedLength % 4) != 0)
{
outstream.WriteByte(0);
compressedLength++;
}
}
}
return compressedLength;
}
/// <summary>
/// Determine the maximum size of a LZ-compressed block starting at newPtr, using the already compressed data
/// starting at oldPtr.
/// </summary>
/// <param name="newPtr">The start of the data that needs to be compressed.</param>
/// <param name="newLength">The number of bytes that still need to be compressed.</param>
/// <param name="oldPtr">The start of the raw file.</param>
/// <param name="oldLength">The number of bytes already compressed.</param>
/// <param name="disp">The offset of the start of the longest block to refer to.</param>
/// <returns>The length of the longest sequence of bytes that can be copied from the already decompressed data.</returns>
private unsafe int GetOccurrenceLength(byte* newPtr, int newLength, byte* oldPtr, int oldLength, out int disp)
{
disp = 0;
if (newLength == 0)
return 0;
int maxLength = 0;
for (int i = 1; i < oldLength; i++)
{
// work from the end of the old data to the start, to mimic the original implementation's behaviour
byte* currentOldStart = oldPtr + oldLength - i;
int currentLength = 0;
// determine the length we can copy if we go back i bytes
for (int j = 0; j < i && j < newLength; j++)
{
// stop when the bytes are no longer the same
if (*(currentOldStart + j) != *(newPtr + j))
break;
currentLength++;
}
if (currentLength > maxLength)
{
maxLength = currentLength;
disp = i;
}
}
return maxLength;
}
}
}