From 1c2722315a70dc24dbd14b9a570e5607c243984f Mon Sep 17 00:00:00 2001 From: barubary Date: Fri, 13 May 2011 10:07:38 +0000 Subject: [PATCH] C#: added an initial compression algorithm for LZ-10. May not work yet, as it hasn't been tested. --- CSharp/DSDecmp/Formats/Nitro/LZ10.cs | 128 ++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 2 deletions(-) diff --git a/CSharp/DSDecmp/Formats/Nitro/LZ10.cs b/CSharp/DSDecmp/Formats/Nitro/LZ10.cs index 4989365..1bbd3d1 100644 --- a/CSharp/DSDecmp/Formats/Nitro/LZ10.cs +++ b/CSharp/DSDecmp/Formats/Nitro/LZ10.cs @@ -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; + } + + /// + /// Determine the maximum size of a LZ-compressed block starting at newPtr, using the already compressed data + /// starting at oldPtr. + /// + /// The start of the data that needs to be compressed. + /// The number of bytes that still need to be compressed. + /// The start of the raw file. + /// The number of bytes already compressed. + /// The offset of the start of the longest block to refer to. + /// The length of the longest sequence of bytes that can be copied from the already decompressed data. + 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; } } }