diff --git a/Wabbajack.Common/BSDiff.cs b/Wabbajack.Common/BSDiff.cs
new file mode 100644
index 00000000..c51eaa19
--- /dev/null
+++ b/Wabbajack.Common/BSDiff.cs
@@ -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
+ {
+ ///
+ /// 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 */
+ 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;
+ }
+
+ ///
+ /// 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 (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;
+ }
+
+ ///
+ /// 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");
+ byte[] 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
+ 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;
+ }
+}
diff --git a/Wabbajack.Common/Consts.cs b/Wabbajack.Common/Consts.cs
index 4e839f10..d112471f 100644
--- a/Wabbajack.Common/Consts.cs
+++ b/Wabbajack.Common/Consts.cs
@@ -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!";
}
}
diff --git a/Wabbajack.Common/Data.cs b/Wabbajack.Common/Data.cs
index 0c463b37..46f98712 100644
--- a/Wabbajack.Common/Data.cs
+++ b/Wabbajack.Common/Data.cs
@@ -94,7 +94,7 @@ namespace Wabbajack.Common
public string From;
}
- public class PatchedArchive : FromArchive
+ public class PatchedFromArchive : FromArchive
{
///
/// The file to apply to the source file to patch it
@@ -112,6 +112,18 @@ namespace Wabbajack.Common
/// Human friendly name of this archive
///
public string Name;
+
+ ///
+ /// Meta INI for the downloaded archive
+ ///
+ public string Meta;
+ }
+
+ public class NexusMod : Archive
+ {
+ public string GameName;
+ public string ModID;
+ public string FileID;
}
///
@@ -157,6 +169,7 @@ namespace Wabbajack.Common
{
public dynamic IniData;
public string Name;
+ public string Meta;
}
///
diff --git a/Wabbajack.Common/Utils.cs b/Wabbajack.Common/Utils.cs
index 0c450a15..7b7ad49d 100644
--- a/Wabbajack.Common/Utils.cs
+++ b/Wabbajack.Common/Utils.cs
@@ -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);
}
+ ///
+ /// Returns the string compressed via BZip2
+ ///
+ ///
+ ///
+ 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();
+ }
+ }
+
+ ///
+ /// Returns the string compressed via BZip2
+ ///
+ ///
+ ///
+ 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();
+ }
+ }
+ }
+
}
}
diff --git a/Wabbajack.Common/Wabbajack.Common.csproj b/Wabbajack.Common/Wabbajack.Common.csproj
index e1410ca9..095b9fc2 100644
--- a/Wabbajack.Common/Wabbajack.Common.csproj
+++ b/Wabbajack.Common/Wabbajack.Common.csproj
@@ -32,9 +32,15 @@
4
+
+ ..\packages\SharpZipLib.1.1.0\lib\net45\ICSharpCode.SharpZipLib.dll
+
..\packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll
+
+ ..\packages\murmurhash.1.0.3\lib\net45\MurmurHash.dll
+
..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll
@@ -48,6 +54,7 @@
+
diff --git a/Wabbajack.Common/packages.config b/Wabbajack.Common/packages.config
index d4d1fb83..3f87e68c 100644
--- a/Wabbajack.Common/packages.config
+++ b/Wabbajack.Common/packages.config
@@ -3,8 +3,6 @@
-
-
-
+
\ No newline at end of file
diff --git a/Wabbajack/Compiler.cs b/Wabbajack/Compiler.cs
index 38cb8d1c..6503545b 100644
--- a/Wabbajack/Compiler.cs
+++ b/Wabbajack/Compiler.cs
@@ -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 Log_Fn { get; }
public Action Progress_Function { get; }
+ public List InstallDirectives { get; private set; }
+ public List SelectedArchives { get; private set; }
+
public List 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 log_fn, Action 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()
+ .Select(a => a.ArchiveHash)
+ .Distinct();
+
+ SelectedArchives = shas.Select(sha => ResolveArchive(sha, archives)).ToList();
+
+ }
+
+ private Archive ResolveArchive(string sha, Dictionary 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> 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 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();
+ e.From = found.entry.Path;
+ e.ArchiveHash = found.archive.Hash;
+ e.To = source.Path;
+ return e;
+ }
+ return null;
+ };
+ }
+
private Func IncludeModIniData()
{
return source =>
diff --git a/Wabbajack/FodyWeavers.xml b/Wabbajack/FodyWeavers.xml
new file mode 100644
index 00000000..5029e706
--- /dev/null
+++ b/Wabbajack/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/Wabbajack/FodyWeavers.xsd b/Wabbajack/FodyWeavers.xsd
new file mode 100644
index 00000000..44a53744
--- /dev/null
+++ b/Wabbajack/FodyWeavers.xsd
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+ A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks
+
+
+
+
+ A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.
+
+
+
+
+ A list of unmanaged 32 bit assembly names to include, delimited with line breaks.
+
+
+
+
+ A list of unmanaged 64 bit assembly names to include, delimited with line breaks.
+
+
+
+
+ The order of preloaded assemblies, delimited with line breaks.
+
+
+
+
+
+ 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.
+
+
+
+
+ Controls if .pdbs for reference assemblies are also embedded.
+
+
+
+
+ Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.
+
+
+
+
+ As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.
+
+
+
+
+ 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.
+
+
+
+
+ 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.
+
+
+
+
+ A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |
+
+
+
+
+ A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.
+
+
+
+
+ A list of unmanaged 32 bit assembly names to include, delimited with |.
+
+
+
+
+ A list of unmanaged 64 bit assembly names to include, delimited with |.
+
+
+
+
+ The order of preloaded assemblies, delimited with |.
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/Wabbajack/Program.cs b/Wabbajack/Program.cs
index 8c749794..06f2e4f4 100644
--- a/Wabbajack/Program.cs
+++ b/Wabbajack/Program.cs
@@ -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();
+
+ }
+ }
+ }
}
}
diff --git a/Wabbajack/Properties/Resources.Designer.cs b/Wabbajack/Properties/Resources.Designer.cs
new file mode 100644
index 00000000..ab5f14c9
--- /dev/null
+++ b/Wabbajack/Properties/Resources.Designer.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+namespace Wabbajack.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // 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() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [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;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to .
+ ///
+ internal static string ModPack {
+ get {
+ return ResourceManager.GetString("ModPack", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Wabbajack/Properties/Resources.resx b/Wabbajack/Properties/Resources.resx
new file mode 100644
index 00000000..c1567b33
--- /dev/null
+++ b/Wabbajack/Properties/Resources.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+
+
\ No newline at end of file
diff --git a/Wabbajack/Wabbajack.csproj b/Wabbajack/Wabbajack.csproj
index 97dfcb7d..5a4f2e42 100644
--- a/Wabbajack/Wabbajack.csproj
+++ b/Wabbajack/Wabbajack.csproj
@@ -1,5 +1,6 @@
+
Debug
@@ -12,6 +13,8 @@
512
true
true
+
+
x64
@@ -33,9 +36,15 @@
4
+
+ ..\packages\Costura.Fody.4.0.0\lib\net40\Costura.dll
+
..\packages\murmurhash.1.0.3\lib\net45\MurmurHash.dll
+
+ ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll
+
@@ -49,6 +58,11 @@
+
+ True
+ True
+ Resources.resx
+
@@ -67,5 +81,19 @@
Wabbajack.Common
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+ 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}.
+
+
+
+
+
\ No newline at end of file
diff --git a/Wabbajack/packages.config b/Wabbajack/packages.config
index 94093d76..b65cc07a 100644
--- a/Wabbajack/packages.config
+++ b/Wabbajack/packages.config
@@ -1,4 +1,7 @@
+
+
+
\ No newline at end of file