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