mirror of
https://github.com/Barubary/dsdecmp.git
synced 2025-02-21 13:47:14 +01:00
C#: added an initial compression algorithm for LZ-10. May not work yet, as it hasn't been tested.
This commit is contained in:
parent
69cd6ee199
commit
1c2722315a
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user