mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Can create self-installing modlists
This commit is contained in:
parent
8b7b64ddd4
commit
e4ca3cd01e
656
Wabbajack.Common/BSDiff.cs
Normal file
656
Wabbajack.Common/BSDiff.cs
Normal file
@ -0,0 +1,656 @@
|
||||
namespace Wabbajack.Common
|
||||
{
|
||||
using ICSharpCode.SharpZipLib.BZip2;
|
||||
using System;
|
||||
using System.IO;
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
public class BSDiff
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a binary patch (in <a href="http://www.daemonology.net/bsdiff/">bsdiff</a> format) that can be used
|
||||
/// (by <see cref="Apply"/>) to transform <paramref name="oldData"/> into <paramref name="newData"/>.
|
||||
/// </summary>
|
||||
/// <param name="oldData">The original binary data.</param>
|
||||
/// <param name="newData">The new binary data.</param>
|
||||
/// <param name="output">A <see cref="Stream"/> to which the patch will be written.</param>
|
||||
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 */
|
||||
byte[] header = new byte[c_headerSize];
|
||||
WriteInt64(c_fileSignature, header, 0); // "BSDIFF40"
|
||||
WriteInt64(0, header, 8);
|
||||
WriteInt64(0, header, 16);
|
||||
WriteInt64(newData.Length, header, 24);
|
||||
|
||||
long startPosition = output.Position;
|
||||
output.Write(header, 0, header.Length);
|
||||
|
||||
int[] I = SuffixSort(oldData);
|
||||
|
||||
byte[] db = new byte[newData.Length];
|
||||
byte[] eb = new byte[newData.Length];
|
||||
|
||||
int dblen = 0;
|
||||
int eblen = 0;
|
||||
|
||||
using (BZip2OutputStream bz2Stream = new BZip2OutputStream(output) { IsStreamOwner = false })
|
||||
{
|
||||
// compute the differences, writing ctrl as we go
|
||||
int scan = 0;
|
||||
int pos = 0;
|
||||
int len = 0;
|
||||
int lastscan = 0;
|
||||
int lastpos = 0;
|
||||
int lastoffset = 0;
|
||||
while (scan < newData.Length)
|
||||
{
|
||||
int oldscore = 0;
|
||||
|
||||
for (int 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)
|
||||
{
|
||||
int s = 0;
|
||||
int sf = 0;
|
||||
int lenf = 0;
|
||||
for (int 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;
|
||||
}
|
||||
}
|
||||
|
||||
int lenb = 0;
|
||||
if (scan < newData.Length)
|
||||
{
|
||||
s = 0;
|
||||
int sb = 0;
|
||||
for (int 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)
|
||||
{
|
||||
int overlap = (lastscan + lenf) - (scan - lenb);
|
||||
s = 0;
|
||||
int ss = 0;
|
||||
int lens = 0;
|
||||
for (int 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 (int i = 0; i < lenf; i++)
|
||||
db[dblen + i] = (byte)(newData[lastscan + i] - oldData[lastpos + i]);
|
||||
for (int i = 0; i < (scan - lenb) - (lastscan + lenf); i++)
|
||||
eb[eblen + i] = newData[lastscan + lenf + i];
|
||||
|
||||
dblen += lenf;
|
||||
eblen += (scan - lenb) - (lastscan + lenf);
|
||||
|
||||
byte[] 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
|
||||
long controlEndPosition = output.Position;
|
||||
WriteInt64(controlEndPosition - startPosition - c_headerSize, header, 8);
|
||||
|
||||
// write compressed diff data
|
||||
using (BZip2OutputStream bz2Stream = new BZip2OutputStream(output) { IsStreamOwner = false })
|
||||
{
|
||||
bz2Stream.Write(db, 0, dblen);
|
||||
}
|
||||
|
||||
// compute size of compressed diff data
|
||||
long diffEndPosition = output.Position;
|
||||
WriteInt64(diffEndPosition - controlEndPosition, header, 16);
|
||||
|
||||
// write compressed extra data
|
||||
using (BZip2OutputStream bz2Stream = new BZip2OutputStream(output) { IsStreamOwner = false })
|
||||
{
|
||||
bz2Stream.Write(eb, 0, eblen);
|
||||
}
|
||||
|
||||
// seek to the beginning, write the header, then seek back to end
|
||||
long endPosition = output.Position;
|
||||
output.Position = startPosition;
|
||||
output.Write(header, 0, header.Length);
|
||||
output.Position = endPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a binary patch (in <a href="http://www.daemonology.net/bsdiff/">bsdiff</a> format) to the data in
|
||||
/// <paramref name="input"/> and writes the results of patching to <paramref name="output"/>.
|
||||
/// </summary>
|
||||
/// <param name="input">A <see cref="Stream"/> containing the input data.</param>
|
||||
/// <param name="openPatchStream">A func that can open a <see cref="Stream"/> positioned at the start of the patch data.
|
||||
/// This stream must support reading and seeking, and <paramref name="openPatchStream"/> must allow multiple streams on
|
||||
/// the patch to be opened concurrently.</param>
|
||||
/// <param name="output">A <see cref="Stream"/> to which the patched data is written.</param>
|
||||
public static void Apply(Stream input, Func<Stream> 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 (Stream 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");
|
||||
|
||||
byte[] header = ReadExactly(patchStream, c_headerSize);
|
||||
|
||||
// check for appropriate magic
|
||||
long 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;
|
||||
byte[] newData = new byte[c_bufferSize];
|
||||
byte[] oldData = new byte[c_bufferSize];
|
||||
|
||||
// prepare to read three parts of the patch in parallel
|
||||
using (Stream compressedControlStream = openPatchStream())
|
||||
using (Stream compressedDiffStream = openPatchStream())
|
||||
using (Stream 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 (BZip2InputStream controlStream = new BZip2InputStream(compressedControlStream))
|
||||
using (BZip2InputStream diffStream = new BZip2InputStream(compressedDiffStream))
|
||||
using (BZip2InputStream extraStream = new BZip2InputStream(compressedExtraStream))
|
||||
{
|
||||
long[] control = new long[3];
|
||||
byte[] buffer = new byte[8];
|
||||
|
||||
int oldPosition = 0;
|
||||
int newPosition = 0;
|
||||
while (newPosition < newSize)
|
||||
{
|
||||
// read control data
|
||||
for (int 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;
|
||||
|
||||
int bytesToCopy = (int)control[0];
|
||||
while (bytesToCopy > 0)
|
||||
{
|
||||
int actualBytesToCopy = Math.Min(bytesToCopy, c_bufferSize);
|
||||
|
||||
// read diff string
|
||||
ReadExactly(diffStream, newData, 0, actualBytesToCopy);
|
||||
|
||||
// add old data to diff string
|
||||
int availableInputBytes = Math.Min(actualBytesToCopy, (int)(input.Length - input.Position));
|
||||
ReadExactly(input, oldData, 0, availableInputBytes);
|
||||
|
||||
for (int 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)
|
||||
{
|
||||
int 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 (int index = 0; index < left.Length - leftOffset && index < right.Length - rightOffset; index++)
|
||||
{
|
||||
int 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)
|
||||
{
|
||||
int startLength = MatchLength(oldData, I[start], newData, newOffset);
|
||||
int endLength = MatchLength(oldData, I[end], newData, newOffset);
|
||||
|
||||
if (startLength > endLength)
|
||||
{
|
||||
pos = I[start];
|
||||
return startLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = I[end];
|
||||
return endLength;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int 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 (int k = start; k < start + len; k += j)
|
||||
{
|
||||
j = 1;
|
||||
int x = v[I[k] + h];
|
||||
for (int 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 (int i = 0; i < j; i++)
|
||||
v[I[k + i]] = k + j - 1;
|
||||
if (j == 1)
|
||||
I[k] = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int x = v[I[start + len / 2] + h];
|
||||
int jj = 0;
|
||||
int kk = 0;
|
||||
for (int i2 = start; i2 < start + len; i2++)
|
||||
{
|
||||
if (v[I[i2] + h] < x)
|
||||
jj++;
|
||||
if (v[I[i2] + h] == x)
|
||||
kk++;
|
||||
}
|
||||
jj += start;
|
||||
kk += jj;
|
||||
|
||||
int i = start;
|
||||
int j = 0;
|
||||
int 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)
|
||||
{
|
||||
int[] buckets = new int[256];
|
||||
|
||||
foreach (byte oldByte in oldData)
|
||||
buckets[oldByte]++;
|
||||
for (int i = 1; i < 256; i++)
|
||||
buckets[i] += buckets[i - 1];
|
||||
for (int i = 255; i > 0; i--)
|
||||
buckets[i] = buckets[i - 1];
|
||||
buckets[0] = 0;
|
||||
|
||||
int[] I = new int[oldData.Length + 1];
|
||||
for (int i = 0; i < oldData.Length; i++)
|
||||
I[++buckets[oldData[i]]] = i;
|
||||
|
||||
int[] v = new int[oldData.Length + 1];
|
||||
for (int i = 0; i < oldData.Length; i++)
|
||||
v[i] = buckets[oldData[i]];
|
||||
|
||||
for (int i = 1; i < 256; i++)
|
||||
{
|
||||
if (buckets[i] == buckets[i - 1] + 1)
|
||||
I[buckets[i]] = -1;
|
||||
}
|
||||
I[0] = -1;
|
||||
|
||||
for (int h = 1; I[0] != -(oldData.Length + 1); h += h)
|
||||
{
|
||||
int len = 0;
|
||||
int 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 (int i = 0; i < oldData.Length + 1; i++)
|
||||
I[v[i]] = i;
|
||||
|
||||
return I;
|
||||
}
|
||||
|
||||
private static void Swap(ref int first, ref int second)
|
||||
{
|
||||
int temp = first;
|
||||
first = second;
|
||||
second = temp;
|
||||
}
|
||||
|
||||
private static long ReadInt64(byte[] buf, int offset)
|
||||
{
|
||||
long value = buf[offset + 7] & 0x7F;
|
||||
|
||||
for (int 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)
|
||||
{
|
||||
long valueToWrite = value < 0 ? -value : value;
|
||||
|
||||
for (int byteIndex = 0; byteIndex < 8; byteIndex++)
|
||||
{
|
||||
buf[offset + byteIndex] = unchecked((byte)valueToWrite);
|
||||
valueToWrite >>= 8;
|
||||
}
|
||||
|
||||
if (value < 0)
|
||||
buf[offset + 7] |= 0x80;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads exactly <paramref name="count"/> bytes from <paramref name="stream"/>.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read from.</param>
|
||||
/// <param name="count">The count of bytes to read.</param>
|
||||
/// <returns>A new byte array containing the data read from the stream.</returns>
|
||||
private static byte[] ReadExactly(Stream stream, int count)
|
||||
{
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException("count");
|
||||
byte[] buffer = new byte[count];
|
||||
ReadExactly(stream, buffer, 0, count);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads exactly <paramref name="count"/> bytes from <paramref name="stream"/> into
|
||||
/// <paramref name="buffer"/>, starting at the byte given by <paramref name="offset"/>.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read from.</param>
|
||||
/// <param name="buffer">The buffer to read data into.</param>
|
||||
/// <param name="offset">The offset within the buffer at which data is first written.</param>
|
||||
/// <param name="count">The count of bytes to read.</param>
|
||||
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
|
||||
int 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;
|
||||
}
|
||||
}
|
||||
|
||||
const long c_fileSignature = 0x3034464649445342L;
|
||||
const int c_headerSize = 32;
|
||||
}
|
||||
}
|
@ -9,5 +9,6 @@ namespace Wabbajack.Common
|
||||
public static class Consts
|
||||
{
|
||||
public static string GameFolderFilesDir = "Game Folder Files";
|
||||
public static string ModPackMagic = "Celebration!, Cheese for Everyone!";
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ namespace Wabbajack.Common
|
||||
public string From;
|
||||
}
|
||||
|
||||
public class PatchedArchive : FromArchive
|
||||
public class PatchedFromArchive : FromArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// The file to apply to the source file to patch it
|
||||
@ -112,6 +112,18 @@ namespace Wabbajack.Common
|
||||
/// Human friendly name of this archive
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Meta INI for the downloaded archive
|
||||
/// </summary>
|
||||
public string Meta;
|
||||
}
|
||||
|
||||
public class NexusMod : Archive
|
||||
{
|
||||
public string GameName;
|
||||
public string ModID;
|
||||
public string FileID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -157,6 +169,7 @@ namespace Wabbajack.Common
|
||||
{
|
||||
public dynamic IniData;
|
||||
public string Name;
|
||||
public string Meta;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using IniParser;
|
||||
using ICSharpCode.SharpZipLib.BZip2;
|
||||
using IniParser;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -84,5 +85,40 @@ namespace Wabbajack.Common
|
||||
return file.Substring(folder.Length + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the string compressed via BZip2
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] BZip2String(this string data)
|
||||
{
|
||||
using (var os = new MemoryStream())
|
||||
{
|
||||
using (var bz = new BZip2OutputStream(os))
|
||||
{
|
||||
using (var bw = new BinaryWriter(bz))
|
||||
bw.Write(data);
|
||||
}
|
||||
return os.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the string compressed via BZip2
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static string BZip2String(this byte[] data)
|
||||
{
|
||||
using (var s = new MemoryStream(data))
|
||||
{
|
||||
using (var bz = new BZip2InputStream(s))
|
||||
{
|
||||
using (var bw = new BinaryReader(bz))
|
||||
return bw.ReadString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -32,9 +32,15 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="ICSharpCode.SharpZipLib, Version=1.1.0.145, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpZipLib.1.1.0\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="INIFileParser, Version=2.5.2.0, Culture=neutral, PublicKeyToken=79af7b307b65cf3c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MurmurHash, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\murmurhash.1.0.3\lib\net45\MurmurHash.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
@ -48,6 +54,7 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BSDiff.cs" />
|
||||
<Compile Include="Consts.cs" />
|
||||
<Compile Include="Data.cs" />
|
||||
<Compile Include="DynamicIniData.cs" />
|
||||
|
@ -3,8 +3,6 @@
|
||||
<package id="ini-parser" version="2.5.2" targetFramework="net472" />
|
||||
<package id="murmurhash" version="1.0.3" targetFramework="net472" />
|
||||
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" />
|
||||
<package id="System.Data.HashFunction.Core" version="2.0.0" targetFramework="net472" />
|
||||
<package id="System.Data.HashFunction.Interfaces" version="2.0.0" targetFramework="net472" />
|
||||
<package id="System.Data.HashFunction.MurmurHash" version="2.0.0" targetFramework="net472" />
|
||||
<package id="SharpZipLib" version="1.1.0" targetFramework="net472" />
|
||||
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net472" />
|
||||
</packages>
|
@ -1,9 +1,11 @@
|
||||
using Murmur;
|
||||
using Newtonsoft.Json;
|
||||
using SevenZipExtractor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@ -29,6 +31,25 @@ namespace Wabbajack
|
||||
}
|
||||
}
|
||||
|
||||
internal void PatchExecutable()
|
||||
{
|
||||
|
||||
var data = JsonConvert.SerializeObject(InstallDirectives).BZip2String();
|
||||
var executable = Assembly.GetExecutingAssembly().Location;
|
||||
var out_path = Path.Combine(Path.GetDirectoryName(executable), "out.exe");
|
||||
File.Copy(executable, out_path, true);
|
||||
using(var os = File.OpenWrite(out_path))
|
||||
using(var bw = new BinaryWriter(os))
|
||||
{
|
||||
long orig_pos = os.Length;
|
||||
os.Position = os.Length;
|
||||
bw.Write(data.LongLength);
|
||||
bw.Write(data);
|
||||
bw.Write(orig_pos);
|
||||
bw.Write(Encoding.ASCII.GetBytes(Consts.ModPackMagic));
|
||||
}
|
||||
}
|
||||
|
||||
public string MO2Profile;
|
||||
|
||||
public string MO2ProfileDir
|
||||
@ -41,6 +62,9 @@ namespace Wabbajack
|
||||
|
||||
public Action<string> Log_Fn { get; }
|
||||
public Action<string, long, long> Progress_Function { get; }
|
||||
public List<Directive> InstallDirectives { get; private set; }
|
||||
public List<Archive> SelectedArchives { get; private set; }
|
||||
|
||||
|
||||
public List<IndexedArchive> IndexedArchives;
|
||||
|
||||
@ -51,6 +75,14 @@ namespace Wabbajack
|
||||
Log_Fn(msg);
|
||||
}
|
||||
|
||||
private void Error(string msg, params object[] args)
|
||||
{
|
||||
if (args.Length > 0)
|
||||
msg = String.Format(msg, args);
|
||||
Log_Fn(msg);
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
public Compiler(string mo2_folder, Action<string> log_fn, Action<string, long, long> progress_function)
|
||||
{
|
||||
MO2Folder = mo2_folder;
|
||||
@ -82,7 +114,11 @@ namespace Wabbajack
|
||||
|
||||
var ini_name = file + ".meta";
|
||||
if (ini_name.FileExists())
|
||||
{
|
||||
info.IniData = ini_name.LoadIniFile();
|
||||
info.Meta = File.ReadAllText(ini_name);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -140,10 +176,69 @@ namespace Wabbajack
|
||||
foreach (var file in nomatch)
|
||||
Info(" {0}", file.To);
|
||||
|
||||
InstallDirectives = results.Where(i => !(i is IgnoredDirectly)).ToList();
|
||||
|
||||
GatherArchives();
|
||||
|
||||
results.ToJSON("out.json");
|
||||
|
||||
}
|
||||
|
||||
private void GatherArchives()
|
||||
{
|
||||
var archives = IndexedArchives.GroupBy(a => a.Hash).ToDictionary(k => k.Key, k => k.First());
|
||||
|
||||
var shas = InstallDirectives.OfType<FromArchive>()
|
||||
.Select(a => a.ArchiveHash)
|
||||
.Distinct();
|
||||
|
||||
SelectedArchives = shas.Select(sha => ResolveArchive(sha, archives)).ToList();
|
||||
|
||||
}
|
||||
|
||||
private Archive ResolveArchive(string sha, Dictionary<string, IndexedArchive> archives)
|
||||
{
|
||||
if(archives.TryGetValue(sha, out var found))
|
||||
{
|
||||
if (found.IniData == null)
|
||||
Error("No download metadata found for {0}, please use MO2 to query info or add a .meta file and try again.", found.Name);
|
||||
var general = found.IniData.General;
|
||||
if (general == null)
|
||||
Error("No General section in mod metadata found for {0}, please use MO2 to query info or add the info and try again.", found.Name);
|
||||
|
||||
Archive result;
|
||||
|
||||
if (general.modID != null && general.fileID != null && general.gameName != null)
|
||||
{
|
||||
result = new NexusMod() {
|
||||
GameName = general.gameName,
|
||||
FileID = general.fileID,
|
||||
ModID = general.modID};
|
||||
}
|
||||
else if (general.directURL != null)
|
||||
{
|
||||
result = new DirectURLArchive()
|
||||
{
|
||||
URL = general.directURL
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Error("No way to handle archive {0} but it's required by the modpack", found.Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
result.Name = found.Name;
|
||||
result.Hash = found.Hash;
|
||||
result.Meta = found.Meta;
|
||||
|
||||
return result;
|
||||
}
|
||||
Error("No match found for Archive sha: {0} this shouldn't happen", sha);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private Directive RunStack(IEnumerable<Func<RawSourceFile, Directive>> stack, RawSourceFile source)
|
||||
{
|
||||
return (from f in stack
|
||||
@ -176,6 +271,7 @@ namespace Wabbajack
|
||||
IgnoreRegex(Consts.GameFolderFilesDir + "\\\\.*\\.bsa"),
|
||||
IncludeModIniData(),
|
||||
DirectMatch(),
|
||||
IncludePatches(),
|
||||
|
||||
// If we have no match at this point for a game folder file, skip them, we can't do anything about them
|
||||
IgnoreGameFiles(),
|
||||
@ -183,6 +279,30 @@ namespace Wabbajack
|
||||
};
|
||||
}
|
||||
|
||||
private Func<RawSourceFile, Directive> IncludePatches()
|
||||
{
|
||||
var indexed = (from archive in IndexedArchives
|
||||
from entry in archive.Entries
|
||||
select new { archive = archive, entry = entry })
|
||||
.GroupBy(e => Path.GetFileName(e.entry.Path))
|
||||
.ToDictionary(e => e.Key);
|
||||
|
||||
return source =>
|
||||
{
|
||||
if (indexed.TryGetValue(Path.GetFileName(source.Path), out var value))
|
||||
{
|
||||
var found = value.First();
|
||||
|
||||
var e = source.EvolveTo<PatchedFromArchive>();
|
||||
e.From = found.entry.Path;
|
||||
e.ArchiveHash = found.archive.Hash;
|
||||
e.To = source.Path;
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
private Func<RawSourceFile, Directive> IncludeModIniData()
|
||||
{
|
||||
return source =>
|
||||
|
3
Wabbajack/FodyWeavers.xml
Normal file
3
Wabbajack/FodyWeavers.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura />
|
||||
</Weavers>
|
111
Wabbajack/FodyWeavers.xsd
Normal file
111
Wabbajack/FodyWeavers.xsd
Normal file
@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||
<xs:element name="Weavers">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCompression" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCleanup" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
|
||||
@ -11,11 +14,46 @@ namespace Wabbajack
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var modpack = CheckForModPack();
|
||||
if (modpack != null)
|
||||
{
|
||||
Console.WriteLine(modpack);
|
||||
Thread.Sleep(10000);
|
||||
return;
|
||||
}
|
||||
|
||||
var compiler = new Compiler("c:\\Mod Organizer 2", msg => Console.WriteLine(msg), (msg, id, prog) => Console.WriteLine(msg));
|
||||
compiler.LoadArchives();
|
||||
compiler.MO2Profile = "Lexy's Legacy of The Dragonborn Special Edition";
|
||||
compiler.Compile();
|
||||
compiler.PatchExecutable();
|
||||
|
||||
}
|
||||
|
||||
private static string CheckForModPack()
|
||||
{
|
||||
using (var s = File.OpenRead(Assembly.GetExecutingAssembly().Location))
|
||||
{
|
||||
var magic_bytes = Encoding.ASCII.GetBytes(Consts.ModPackMagic);
|
||||
s.Position = s.Length - magic_bytes.Length;
|
||||
using (var br = new BinaryReader(s))
|
||||
{
|
||||
var bytes = br.ReadBytes(magic_bytes.Length);
|
||||
var magic = Encoding.ASCII.GetString(bytes);
|
||||
if (magic != Consts.ModPackMagic)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
s.Position = s.Length - magic_bytes.Length - 8;
|
||||
var start_pos = br.ReadInt64();
|
||||
s.Position = start_pos;
|
||||
long length = br.ReadInt64();
|
||||
|
||||
return br.ReadBytes((int)length).BZip2String();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
72
Wabbajack/Properties/Resources.Designer.cs
generated
Normal file
72
Wabbajack/Properties/Resources.Designer.cs
generated
Normal file
@ -0,0 +1,72 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Wabbajack.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Wabbajack.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string ModPack {
|
||||
get {
|
||||
return ResourceManager.GetString("ModPack", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
123
Wabbajack/Properties/Resources.resx
Normal file
123
Wabbajack/Properties/Resources.resx
Normal file
@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ModPack" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
</root>
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Costura.Fody.4.0.0\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.4.0.0\build\Costura.Fody.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@ -12,6 +13,8 @@
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
@ -33,9 +36,15 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Costura, Version=4.0.0.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Costura.Fody.4.0.0\lib\net40\Costura.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MurmurHash, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\murmurhash.1.0.3\lib\net45\MurmurHash.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
@ -49,6 +58,11 @@
|
||||
<Compile Include="Compiler.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
@ -67,5 +81,19 @@
|
||||
<Name>Wabbajack.Common</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Costura.Fody.4.0.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.4.0.0\build\Costura.Fody.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Fody.5.1.1\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.5.1.1\build\Fody.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Fody.5.1.1\build\Fody.targets" Condition="Exists('..\packages\Fody.5.1.1\build\Fody.targets')" />
|
||||
</Project>
|
@ -1,4 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Costura.Fody" version="4.0.0" targetFramework="net472" />
|
||||
<package id="Fody" version="5.1.1" targetFramework="net472" developmentDependency="true" />
|
||||
<package id="murmurhash" version="1.0.3" targetFramework="net472" />
|
||||
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" />
|
||||
</packages>
|
Loading…
Reference in New Issue
Block a user