internal static async Task <long> Decompress(string infile, string outfile) { // make sure the output directory exists string outDirectory = Path.GetDirectoryName(outfile); if (!Directory.Exists(outDirectory)) { Directory.CreateDirectory(outDirectory); } // open the two given files, and delegate to the format-specific code. using (FileStream inStream = new FileStream(infile, FileMode.Open), outStream = new FileStream(outfile, FileMode.Create)) { return(await Lzss.Decompress(inStream, inStream.Length, outStream)); } }
internal static async Task <int> Compress(string infile, string outfile) { // make sure the output directory exists string outDirectory = Path.GetDirectoryName(outfile); if (!Directory.Exists(outDirectory)) { Directory.CreateDirectory(outDirectory); } // open the proper Streams, and delegate to the format-specific code. using (FileStream inStream = File.Open(infile, FileMode.Open), outStream = File.Create(outfile)) { if (inStream.Length == 0) // empty file 'compression' to lzss container { byte[] blank = { 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; outStream.Write(blank, 0, blank.Length); return(blank.Length); } return(await Lzss.Compress(inStream, inStream.Length, outStream, true)); } }
/// <summary> /// Variation of the original compression method, making use of Dynamic Programming to 'look ahead' /// and determine the optimal 'length' values for the compressed blocks. Is not 100% optimal, /// as the flag-bytes are not taken into account. /// </summary> internal static async Task <int> CompressWithLA(Stream instream, long inLength, Stream outstream) { // 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 = await instream.ReadAsync(indata, 0, (int)inLength); if (numReadBytes != inLength) { throw new StreamTooShortException(); } // write the compression header first byte[] header = { 0x11, (byte)(inLength & 0xFF), (byte)((inLength >> 8) & 0xFF), (byte)((inLength >> 16) & 0xFF) }; await outstream.WriteAsync(header, 0, header.Length); int compressedLength = 4; byte[] outbuffer = new byte[8 * 4 + 1]; outbuffer[0] = 0; int bufferlength = 1, bufferedBlocks = 0; int readBytes = 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. // blocks are at most 4 bytes long. Lzss.GetOptimalCompressionLengths(indata, indata.Length, out int[] lengths, out int[] disps); 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; } if (lengths[readBytes] == 1) { outbuffer[bufferlength++] = indata[readBytes++]; } else { // mark the next block as compressed outbuffer[0] |= (byte)(1 << (7 - bufferedBlocks)); if (lengths[readBytes] > 0x110) { // case 1: 1(B CD E)(F GH) + (0x111)(0x1) = (LEN)(DISP) outbuffer[bufferlength] = 0x10; outbuffer[bufferlength] |= (byte)(((lengths[readBytes] - 0x111) >> 12) & 0x0F); bufferlength++; outbuffer[bufferlength] = (byte)(((lengths[readBytes] - 0x111) >> 4) & 0xFF); bufferlength++; outbuffer[bufferlength] = (byte)(((lengths[readBytes] - 0x111) << 4) & 0xF0); } else if (lengths[readBytes] > 0x10) { // case 0; 0(B C)(D EF) + (0x11)(0x1) = (LEN)(DISP) outbuffer[bufferlength] = 0x00; outbuffer[bufferlength] |= (byte)(((lengths[readBytes] - 0x111) >> 4) & 0x0F); bufferlength++; outbuffer[bufferlength] = (byte)(((lengths[readBytes] - 0x111) << 4) & 0xF0); } else { // case > 1: (A)(B CD) + (0x1)(0x1) = (LEN)(DISP) outbuffer[bufferlength] = (byte)(((lengths[readBytes] - 1) << 4) & 0xF0); } // the last 1.5 bytes are always the disp outbuffer[bufferlength] |= (byte)(((disps[readBytes] - 1) >> 8) & 0x0F); bufferlength++; outbuffer[bufferlength] = (byte)((disps[readBytes] - 1) & 0xFF); bufferlength++; readBytes += lengths[readBytes]; } bufferedBlocks++; } // copy the remaining blocks to the output if (bufferedBlocks > 0) { await outstream.WriteAsync(outbuffer, 0, bufferlength); compressedLength += bufferlength; /*/ make the compressed file 4-byte aligned. * while ((compressedLength % 4) != 0) * { * outstream.WriteByte(0); * compressedLength++; * }/**/ } return(compressedLength); }
/// <summary> /// Compresses the input using the 'original', unoptimized compression algorithm. /// This algorithm should yield files that are the same as those found in the games. /// (delegates to the optimized method if LookAhead is set) /// </summary> internal static async Task <int> Compress(Stream instream, long inLength, Stream outstream, bool original) { // 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(); } // use the other method if lookahead is enabled if (!original) { return(await Lzss.CompressWithLA(instream, inLength, outstream)); } // 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 = await instream.ReadAsync(indata, 0, (int)inLength); if (numReadBytes != inLength) { throw new StreamTooShortException(); } // write the compression header first byte[] header = { 0x11, (byte)(inLength & 0xFF), (byte)((inLength >> 8) & 0xFF), (byte)((inLength >> 16) & 0xFF) }; await outstream.WriteAsync(header, 0, header.Length); int compressedLength = 4; byte[] outbuffer = new byte[8 * 4 + 1]; outbuffer[0] = 0; int bufferlength = 1, bufferedBlocks = 0; int readBytes = 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. // (a block is at most 4 bytes long) while (readBytes < inLength) { #region If 8 blocks are bufferd, write them and reset the buffer // 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; } #endregion // 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 oldLength = Math.Min(readBytes, 0x1000); int length = LZUtil.GetOccurrenceLength(ref indata, readBytes, (int)Math.Min(inLength - readBytes, 0x10110), ref indata, readBytes - oldLength, oldLength, out int disp); // length not 3 or more? next byte is raw data if (length < 3) { outbuffer[bufferlength++] = indata[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)); if (length > 0x110) { // case 1: 1(B CD E)(F GH) + (0x111)(0x1) = (LEN)(DISP) outbuffer[bufferlength] = 0x10; outbuffer[bufferlength] |= (byte)(((length - 0x111) >> 12) & 0x0F); bufferlength++; outbuffer[bufferlength] = (byte)(((length - 0x111) >> 4) & 0xFF); bufferlength++; outbuffer[bufferlength] = (byte)(((length - 0x111) << 4) & 0xF0); } else if (length > 0x10) { // case 0; 0(B C)(D EF) + (0x11)(0x1) = (LEN)(DISP) outbuffer[bufferlength] = 0x00; outbuffer[bufferlength] |= (byte)(((length - 0x111) >> 4) & 0x0F); bufferlength++; outbuffer[bufferlength] = (byte)(((length - 0x111) << 4) & 0xF0); } else { // case > 1: (A)(B CD) + (0x1)(0x1) = (LEN)(DISP) outbuffer[bufferlength] = (byte)(((length - 1) << 4) & 0xF0); } // the last 1.5 bytes are always the disp 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) { await outstream.WriteAsync(outbuffer, 0, bufferlength); compressedLength += bufferlength; /*/ make the compressed file 4-byte aligned. * while ((compressedLength % 4) != 0) * { * outstream.WriteByte(0); * compressedLength++; * }/**/ } return(compressedLength); }