using System; using System.IO; using ICSharpCode.SharpZipLib.BZip2; namespace Wabbajack.Common { /* The original bsdiff.c source code (http://www.daemonology.net/bsdiff/) is distributed under the following license: Copyright 2003-2005 Colin Percival All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted providing that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Includes fixes from https://github.com/mendsley/bsdiff/pull/12/files * translated from C to C# to prevent stack overflows. */ public class BSDiff { private const long c_fileSignature = 0x3034464649445342L; private const int c_headerSize = 32; /// /// Creates a binary patch (in bsdiff format) that can be used /// (by ) to transform into . /// /// The original binary data. /// The new binary data. /// A to which the patch will be written. public static void Create(byte[] oldData, byte[] newData, Stream output) { // check arguments if (oldData == null) throw new ArgumentNullException("oldData"); if (newData == null) throw new ArgumentNullException("newData"); if (output == null) throw new ArgumentNullException("output"); if (!output.CanSeek) throw new ArgumentException("Output stream must be seekable.", "output"); if (!output.CanWrite) throw new ArgumentException("Output stream must be writable.", "output"); /* Header is 0 8 "BSDIFF40" 8 8 length of bzip2ed ctrl block 16 8 length of bzip2ed diff block 24 8 length of new file */ /* File is 0 32 Header 32 ?? Bzip2ed ctrl block ?? ?? Bzip2ed diff block ?? ?? Bzip2ed extra block */ var header = new byte[c_headerSize]; WriteInt64(c_fileSignature, header, 0); // "BSDIFF40" WriteInt64(0, header, 8); WriteInt64(0, header, 16); WriteInt64(newData.Length, header, 24); var startPosition = output.Position; output.Write(header, 0, header.Length); var I = SuffixSort(oldData); var db = new byte[newData.Length]; var eb = new byte[newData.Length]; var dblen = 0; var eblen = 0; using (var bz2Stream = new BZip2OutputStream(output) {IsStreamOwner = false}) { // compute the differences, writing ctrl as we go var scan = 0; var pos = 0; var len = 0; var lastscan = 0; var lastpos = 0; var lastoffset = 0; while (scan < newData.Length) { var oldscore = 0; for (var scsc = scan += len; scan < newData.Length; scan++) { len = Search(I, oldData, newData, scan, 0, oldData.Length, out pos); for (; scsc < scan + len; scsc++) if (scsc + lastoffset < oldData.Length && oldData[scsc + lastoffset] == newData[scsc]) oldscore++; if (len == oldscore && len != 0 || len > oldscore + 8) break; if (scan + lastoffset < oldData.Length && oldData[scan + lastoffset] == newData[scan]) oldscore--; } if (len != oldscore || scan == newData.Length) { var s = 0; var sf = 0; var lenf = 0; for (var i = 0; lastscan + i < scan && lastpos + i < oldData.Length;) { if (oldData[lastpos + i] == newData[lastscan + i]) s++; i++; if (s * 2 - i > sf * 2 - lenf) { sf = s; lenf = i; } } var lenb = 0; if (scan < newData.Length) { s = 0; var sb = 0; for (var i = 1; scan >= lastscan + i && pos >= i; i++) { if (oldData[pos - i] == newData[scan - i]) s++; if (s * 2 - i > sb * 2 - lenb) { sb = s; lenb = i; } } } if (lastscan + lenf > scan - lenb) { var overlap = lastscan + lenf - (scan - lenb); s = 0; var ss = 0; var lens = 0; for (var i = 0; i < overlap; i++) { if (newData[lastscan + lenf - overlap + i] == oldData[lastpos + lenf - overlap + i]) s++; if (newData[scan - lenb + i] == oldData[pos - lenb + i]) s--; if (s > ss) { ss = s; lens = i + 1; } } lenf += lens - overlap; lenb -= lens; } for (var i = 0; i < lenf; i++) db[dblen + i] = (byte) (newData[lastscan + i] - oldData[lastpos + i]); for (var i = 0; i < scan - lenb - (lastscan + lenf); i++) eb[eblen + i] = newData[lastscan + lenf + i]; dblen += lenf; eblen += scan - lenb - (lastscan + lenf); var buf = new byte[8]; WriteInt64(lenf, buf, 0); bz2Stream.Write(buf, 0, 8); WriteInt64(scan - lenb - (lastscan + lenf), buf, 0); bz2Stream.Write(buf, 0, 8); WriteInt64(pos - lenb - (lastpos + lenf), buf, 0); bz2Stream.Write(buf, 0, 8); lastscan = scan - lenb; lastpos = pos - lenb; lastoffset = pos - scan; } } } // compute size of compressed ctrl data var controlEndPosition = output.Position; WriteInt64(controlEndPosition - startPosition - c_headerSize, header, 8); // write compressed diff data using (var bz2Stream = new BZip2OutputStream(output) {IsStreamOwner = false}) { bz2Stream.Write(db, 0, dblen); } // compute size of compressed diff data var diffEndPosition = output.Position; WriteInt64(diffEndPosition - controlEndPosition, header, 16); // write compressed extra data using (var bz2Stream = new BZip2OutputStream(output) {IsStreamOwner = false}) { bz2Stream.Write(eb, 0, eblen); } // seek to the beginning, write the header, then seek back to end var endPosition = output.Position; output.Position = startPosition; output.Write(header, 0, header.Length); output.Position = endPosition; } /// /// Applies a binary patch (in bsdiff format) to the data in /// and writes the results of patching to . /// /// A containing the input data. /// /// A func that can open a positioned at the start of the patch data. /// This stream must support reading and seeking, and must allow multiple streams /// on /// the patch to be opened concurrently. /// /// A to which the patched data is written. public static void Apply(Stream input, Func openPatchStream, Stream output) { // check arguments if (input == null) throw new ArgumentNullException("input"); if (openPatchStream == null) throw new ArgumentNullException("openPatchStream"); if (output == null) throw new ArgumentNullException("output"); /* File format: 0 8 "BSDIFF40" 8 8 X 16 8 Y 24 8 sizeof(newfile) 32 X bzip2(control block) 32+X Y bzip2(diff block) 32+X+Y ??? bzip2(extra block) with control block a set of triples (x,y,z) meaning "add x bytes from oldfile to x bytes from the diff block; copy y bytes from the extra block; seek forwards in oldfile by z bytes". */ // read header long controlLength, diffLength, newSize; using (var patchStream = openPatchStream()) { // check patch stream capabilities if (!patchStream.CanRead) throw new ArgumentException("Patch stream must be readable.", "openPatchStream"); if (!patchStream.CanSeek) throw new ArgumentException("Patch stream must be seekable.", "openPatchStream"); var header = ReadExactly(patchStream, c_headerSize); // check for appropriate magic var signature = ReadInt64(header, 0); if (signature != c_fileSignature) throw new InvalidOperationException("Corrupt patch."); // read lengths from header controlLength = ReadInt64(header, 8); diffLength = ReadInt64(header, 16); newSize = ReadInt64(header, 24); if (controlLength < 0 || diffLength < 0 || newSize < 0) throw new InvalidOperationException("Corrupt patch."); } // preallocate buffers for reading and writing const int c_bufferSize = 1048576; var newData = new byte[c_bufferSize]; var oldData = new byte[c_bufferSize]; // prepare to read three parts of the patch in parallel using (var compressedControlStream = openPatchStream()) using (var compressedDiffStream = openPatchStream()) using (var compressedExtraStream = openPatchStream()) { // seek to the start of each part compressedControlStream.Seek(c_headerSize, SeekOrigin.Current); compressedDiffStream.Seek(c_headerSize + controlLength, SeekOrigin.Current); compressedExtraStream.Seek(c_headerSize + controlLength + diffLength, SeekOrigin.Current); // decompress each part (to read it) using (var controlStream = new BZip2InputStream(compressedControlStream)) using (var diffStream = new BZip2InputStream(compressedDiffStream)) using (var extraStream = new BZip2InputStream(compressedExtraStream)) { var control = new long[3]; var buffer = new byte[8]; var oldPosition = 0; var newPosition = 0; while (newPosition < newSize) { // read control data for (var i = 0; i < 3; i++) { ReadExactly(controlStream, buffer, 0, 8); control[i] = ReadInt64(buffer, 0); } // sanity-check if (newPosition + control[0] > newSize) throw new InvalidOperationException("Corrupt patch."); // seek old file to the position that the new data is diffed against input.Position = oldPosition; var bytesToCopy = (int) control[0]; while (bytesToCopy > 0) { var actualBytesToCopy = Math.Min(bytesToCopy, c_bufferSize); // read diff string ReadExactly(diffStream, newData, 0, actualBytesToCopy); // add old data to diff string var availableInputBytes = Math.Min(actualBytesToCopy, (int) (input.Length - input.Position)); ReadExactly(input, oldData, 0, availableInputBytes); for (var index = 0; index < availableInputBytes; index++) newData[index] += oldData[index]; output.Write(newData, 0, actualBytesToCopy); // adjust counters newPosition += actualBytesToCopy; oldPosition += actualBytesToCopy; bytesToCopy -= actualBytesToCopy; } // sanity-check if (newPosition + control[1] > newSize) throw new InvalidOperationException("Corrupt patch."); // read extra string bytesToCopy = (int) control[1]; while (bytesToCopy > 0) { var actualBytesToCopy = Math.Min(bytesToCopy, c_bufferSize); ReadExactly(extraStream, newData, 0, actualBytesToCopy); output.Write(newData, 0, actualBytesToCopy); newPosition += actualBytesToCopy; bytesToCopy -= actualBytesToCopy; } // adjust position oldPosition = (int) (oldPosition + control[2]); } } } } private static int CompareBytes(byte[] left, int leftOffset, byte[] right, int rightOffset) { for (var index = 0; index < left.Length - leftOffset && index < right.Length - rightOffset; index++) { var diff = left[index + leftOffset] - right[index + rightOffset]; if (diff != 0) return diff; } return 0; } private static int MatchLength(byte[] oldData, int oldOffset, byte[] newData, int newOffset) { int i; for (i = 0; i < oldData.Length - oldOffset && i < newData.Length - newOffset; i++) if (oldData[i + oldOffset] != newData[i + newOffset]) break; return i; } private static int Search(int[] I, byte[] oldData, byte[] newData, int newOffset, int start, int end, out int pos) { if (end - start < 2) { var startLength = MatchLength(oldData, I[start], newData, newOffset); var endLength = MatchLength(oldData, I[end], newData, newOffset); if (startLength > endLength) { pos = I[start]; return startLength; } pos = I[end]; return endLength; } var midPoint = start + (end - start) / 2; return CompareBytes(oldData, I[midPoint], newData, newOffset) < 0 ? Search(I, oldData, newData, newOffset, midPoint, end, out pos) : Search(I, oldData, newData, newOffset, start, midPoint, out pos); } private static void Split(int[] I, int[] v, int start, int len, int h) { if (len < 16) { int j; for (var k = start; k < start + len; k += j) { j = 1; var x = v[I[k] + h]; for (var i = 1; k + i < start + len; i++) { if (v[I[k + i] + h] < x) { x = v[I[k + i] + h]; j = 0; } if (v[I[k + i] + h] == x) { Swap(ref I[k + j], ref I[k + i]); j++; } } for (var i = 0; i < j; i++) v[I[k + i]] = k + j - 1; if (j == 1) I[k] = -1; } } else { int y, z, j, k, x, tmp; j = start + len / 2; k = start + len - 1; x = v[I[j] + h]; y = v[I[start] + h]; z = v[I[k] + h]; if (len > 40) { /* Big array: Pseudomedian of 9 */ tmp = len / 8; x = median3(x, v[I[j - tmp] + h], v[I[j + tmp] + h]); y = median3(y, v[I[start + tmp] + h], v[I[start + tmp + tmp] + h]); z = median3(z, v[I[k - tmp] + h], v[I[k - tmp - tmp] + h]); } ; /* Else medium array: Pseudomedian of 3 */ x = median3(x, y, z); //int x = v[I[start + len / 2] + h]; var jj = 0; var kk = 0; for (var i2 = start; i2 < start + len; i2++) { if (v[I[i2] + h] < x) jj++; if (v[I[i2] + h] == x) kk++; } jj += start; kk += jj; var i = start; j = 0; k = 0; while (i < jj) if (v[I[i] + h] < x) { i++; } else if (v[I[i] + h] == x) { Swap(ref I[i], ref I[jj + j]); j++; } else { Swap(ref I[i], ref I[kk + k]); k++; } while (jj + j < kk) if (v[I[jj + j] + h] == x) { j++; } else { Swap(ref I[jj + j], ref I[kk + k]); k++; } if (jj > start) Split(I, v, start, jj - start, h); for (i = 0; i < kk - jj; i++) v[I[jj + i]] = kk - 1; if (jj == kk - 1) I[jj] = -1; if (start + len > kk) Split(I, v, kk, start + len - kk, h); } } private static int[] SuffixSort(byte[] oldData) { var buckets = new int[256]; foreach (var oldByte in oldData) buckets[oldByte]++; for (var i = 1; i < 256; i++) buckets[i] += buckets[i - 1]; for (var i = 255; i > 0; i--) buckets[i] = buckets[i - 1]; buckets[0] = 0; var I = new int[oldData.Length + 1]; for (var i = 0; i < oldData.Length; i++) I[++buckets[oldData[i]]] = i; var v = new int[oldData.Length + 1]; for (var i = 0; i < oldData.Length; i++) v[i] = buckets[oldData[i]]; for (var i = 1; i < 256; i++) if (buckets[i] == buckets[i - 1] + 1) I[buckets[i]] = -1; I[0] = -1; for (var h = 1; I[0] != -(oldData.Length + 1); h += h) { var len = 0; var i = 0; while (i < oldData.Length + 1) if (I[i] < 0) { len -= I[i]; i -= I[i]; } else { if (len != 0) I[i - len] = -len; len = v[I[i]] + 1 - i; Split(I, v, i, len, h); i += len; len = 0; } if (len != 0) I[i - len] = -len; } for (var i = 0; i < oldData.Length + 1; i++) I[v[i]] = i; return I; } private static void Swap(ref int first, ref int second) { var temp = first; first = second; second = temp; } private static long ReadInt64(byte[] buf, int offset) { long value = buf[offset + 7] & 0x7F; for (var index = 6; index >= 0; index--) { value *= 256; value += buf[offset + index]; } if ((buf[offset + 7] & 0x80) != 0) value = -value; return value; } private static void WriteInt64(long value, byte[] buf, int offset) { var valueToWrite = value < 0 ? -value : value; for (var byteIndex = 0; byteIndex < 8; byteIndex++) { buf[offset + byteIndex] = unchecked((byte) valueToWrite); valueToWrite >>= 8; } if (value < 0) buf[offset + 7] |= 0x80; } /// /// Reads exactly bytes from . /// /// The stream to read from. /// The count of bytes to read. /// A new byte array containing the data read from the stream. private static byte[] ReadExactly(Stream stream, int count) { if (count < 0) throw new ArgumentOutOfRangeException("count"); var buffer = new byte[count]; ReadExactly(stream, buffer, 0, count); return buffer; } /// /// Reads exactly bytes from into /// , starting at the byte given by . /// /// The stream to read from. /// The buffer to read data into. /// The offset within the buffer at which data is first written. /// The count of bytes to read. private static void ReadExactly(Stream stream, byte[] buffer, int offset, int count) { // check arguments if (stream == null) throw new ArgumentNullException("stream"); if (buffer == null) throw new ArgumentNullException("buffer"); if (offset < 0 || offset > buffer.Length) throw new ArgumentOutOfRangeException("offset"); if (count < 0 || buffer.Length - offset < count) throw new ArgumentOutOfRangeException("count"); while (count > 0) { // read data var bytesRead = stream.Read(buffer, offset, count); // check for failure to read if (bytesRead == 0) throw new EndOfStreamException(); // move to next block offset += bytesRead; count -= bytesRead; } } private static int median3(int a, int b, int c) { return a < b ? b < c ? b : a < c ? c : a : b > c ? b : a > c ? c : a; } } }