diff --git a/CHANGELOG.md b/CHANGELOG.md
index 949c39cc..d9729dd5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
### Changelog
+#### Version - 1.0 beta 15 - 1/6/2020
+* Don't delete the download folder when deleting empty folders during an update
+* If `Game Folder Files` exists in the MO2 folder during compilation the Game folder will be ignored as a file source
+
+#### Version - 1.0 beta 14 - 1/6/2020
+* Updating a list twice without starting WJ no longer deletes your modlist
+* .mohidden files will now be correctly detected during binary patching
+* Added support for MO2's new path format
+* Added support for MO2 2.2.2's `portable.txt` feature
+* Added support for VectorPlexus downloads
+* Added a new CLI interface for providing Nexus API key overrides
+* Several UI backend improvements
+
#### Version - 1.0 beta 13 - 1/4/22020
* Several fixes for steam game handling
* Fixes for metrics reporting
diff --git a/Wabbajack.CacheServer/JobQueueEndpoints.cs b/Wabbajack.CacheServer/JobQueueEndpoints.cs
index 8cf23027..f68ce0cb 100644
--- a/Wabbajack.CacheServer/JobQueueEndpoints.cs
+++ b/Wabbajack.CacheServer/JobQueueEndpoints.cs
@@ -4,7 +4,6 @@ using System.Linq;
using System.Linq.Expressions;
using System.Security.Policy;
using System.Threading.Tasks;
-using Windows.Media.Playback;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Nancy;
diff --git a/Wabbajack.CacheServer/ListValidationService.cs b/Wabbajack.CacheServer/ListValidationService.cs
index bcf18a93..0c717b1a 100644
--- a/Wabbajack.CacheServer/ListValidationService.cs
+++ b/Wabbajack.CacheServer/ListValidationService.cs
@@ -158,7 +158,7 @@ namespace Wabbajack.CacheServer
using (var queue = new WorkQueue())
{
- foreach (var list in modlists.Skip(2).Take(1))
+ foreach (var list in modlists)
{
try
{
diff --git a/Wabbajack.Common/CLI.cs b/Wabbajack.Common/CLI.cs
new file mode 100644
index 00000000..20c5081c
--- /dev/null
+++ b/Wabbajack.Common/CLI.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Linq;
+using System.Reflection;
+
+namespace Wabbajack.Common
+{
+ public static class CLIArguments
+ {
+ [CLIOptions("nosettings", HelpText = "Don't load the saved Settings")]
+ public static bool NoSettings { get; set; }
+
+ [CLIOptions("apikey", HelpText = "Manually input an Nexus api key")]
+ public static string ApiKey { get; set; }
+
+ [CLIOptions("install", ShortOption = 'i', HelpText = "Install a ModList via CLI")]
+ public static string InstallPath { get; set; }
+
+ [CLIOptions("help", ShortOption = 'h', HelpText = "Display this message")]
+ public static bool Help { get; set; }
+ }
+
+ public static class CLI
+ {
+ ///
+ /// Parses the argument and sets the properties of
+ ///
+ ///
+ public static void ParseOptions(string[] args)
+ {
+ if (args.Length == 1) return;
+ // get all properties of the class Options
+ typeof(CLIArguments).GetProperties().Do(p =>
+ {
+ var optionAttr = (CLIOptions[])p.GetCustomAttributes(typeof(CLIOptions));
+ if (optionAttr.Length != 1)
+ return;
+
+ var cur = optionAttr[0];
+ if (cur?.Option == null) return;
+
+ FillVariable(cur.Option, ref p, ref args, false);
+ FillVariable(cur.ShortOption, ref p, ref args, true);
+ });
+ }
+
+ public static void DisplayHelpText()
+ {
+ Console.WriteLine("Wabbajack CLI Help Text");
+ Console.WriteLine("{0,-20} | {1,-15} | {2,-30}", "Option", "Short Option", "Help Text");
+
+ typeof(CLIArguments).GetProperties().Do(p =>
+ {
+ var optionAttr = (CLIOptions[])p.GetCustomAttributes(typeof(CLIOptions));
+ if (optionAttr.Length != 1)
+ return;
+
+ var cur = optionAttr[0];
+ if (cur?.Option == null) return;
+
+ var shortText = cur.ShortOption != 0 ? $"-{cur.ShortOption}" : "";
+ var helpText = string.IsNullOrWhiteSpace(cur.HelpText) ? "" : cur.HelpText;
+ Console.WriteLine("{0,-20} | {1,-15} | {2,-30}", $"--{cur.Option}", shortText, helpText);
+ });
+ }
+
+ private static void FillVariable(dynamic option, ref PropertyInfo p, ref string[] args, bool single)
+ {
+ var s = single ? $"-{option}" : $"--{option}";
+
+ if (!args.Any(a => a.Contains(s))) return;
+
+ if (p.PropertyType == typeof(bool))
+ {
+ p.SetValue(p, true);
+ return;
+ }
+
+ var filtered = args.Where(a => a.Contains(s)).ToList();
+ if (filtered.Count != 1) return;
+
+ var arg = filtered[0];
+ arg = arg.Replace($"{s}=", "");
+
+ if(p.PropertyType == typeof(string))
+ p.SetValue(p, arg);
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Property)]
+ public class CLIOptions : Attribute
+ {
+ // --option, long name of the option. Eg: --output
+ public string Option;
+ // -shortOption, short name of the option. Eg: -o
+ public char ShortOption;
+ // text to be displayed when --help is called
+ public string HelpText;
+
+ public CLIOptions(string option)
+ {
+ Option = option;
+ }
+ }
+}
diff --git a/Wabbajack.Common/DynamicIniData.cs b/Wabbajack.Common/DynamicIniData.cs
index 7757f40f..594058b5 100644
--- a/Wabbajack.Common/DynamicIniData.cs
+++ b/Wabbajack.Common/DynamicIniData.cs
@@ -1,6 +1,11 @@
-using System.Dynamic;
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Numerics;
+using System.Text;
using System.Text.RegularExpressions;
using IniParser;
+using IniParser.Exceptions;
using IniParser.Model;
namespace Wabbajack.Common
@@ -44,10 +49,58 @@ namespace Wabbajack.Common
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = _coll[binder.Name];
- if (result is string) result = Regex.Unescape(((string) result).Trim('"'));
+ if (result is string) result = Interpret((string)result);
return true;
}
+ private static string Interpret(string s)
+ {
+ if (s.StartsWith("@ByteArray(") && s.EndsWith(")"))
+ {
+ return UnescapeUTF8(s.Substring("@ByteArray(".Length, s.Length - "@ByteArray(".Length - ")".Length));
+ }
+
+ return UnescapeString(s);
+ }
+
+ private static string UnescapeString(string s)
+ {
+ return Regex.Unescape(s.Trim('"'));
+ }
+
+ private static string UnescapeUTF8(string s)
+ {
+ List acc = new List();
+ for (var i = 0; i < s.Length; i++)
+ {
+ var c = s[i];
+ switch (c)
+ {
+ case '\\':
+ i++;
+ var nc = s[i];
+ switch (nc)
+ {
+ case '\\':
+ acc.Add((byte)'\\');
+ break;
+ case 'x':
+ var chrs = s[i + 1] + s[i + 2].ToString();
+ i += 2;
+ acc.Add(Convert.ToByte(chrs, 16));
+ break;
+ default:
+ throw new ParsingException($"Not a valid escape characer {nc}");
+ }
+ break;
+ default:
+ acc.Add((byte)c);
+ break;
+ }
+ }
+ return Encoding.UTF8.GetString(acc.ToArray());
+ }
+
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
if (indexes.Length > 1)
@@ -61,4 +114,4 @@ namespace Wabbajack.Common
return true;
}
}
-}
\ No newline at end of file
+}
diff --git a/Wabbajack.Common/ExtensionManager.cs b/Wabbajack.Common/ExtensionManager.cs
index 1f0f5075..f8e2dd6b 100644
--- a/Wabbajack.Common/ExtensionManager.cs
+++ b/Wabbajack.Common/ExtensionManager.cs
@@ -19,7 +19,7 @@ namespace Wabbajack.Common
{
{"", "Wabbajack"},
{"FriendlyTypeName", "Wabbajack"},
- {"shell\\open\\command", "\"{appPath}\" -i \"%1\""},
+ {"shell\\open\\command", "\"{appPath}\" -i=\"%1\""},
};
private static readonly Dictionary ExtList = new Dictionary
@@ -34,7 +34,7 @@ namespace Wabbajack.Common
var tempKey = progIDKey?.OpenSubKey("shell\\open\\command");
if (progIDKey == null || tempKey == null) return true;
var value = tempKey.GetValue("");
- return value == null || value.ToString().Equals($"\"{appPath}\" -i \"%1\"");
+ return value == null || value.ToString().Equals($"\"{appPath}\" -i=\"%1\"");
}
public static bool IsAssociated()
diff --git a/Wabbajack.Common/GameMetaData.cs b/Wabbajack.Common/GameMetaData.cs
index 25646945..d30ed10c 100644
--- a/Wabbajack.Common/GameMetaData.cs
+++ b/Wabbajack.Common/GameMetaData.cs
@@ -178,7 +178,7 @@ namespace Wabbajack.Common
MO2Name = "New Vegas",
MO2ArchiveName = "falloutnv",
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\falloutnv",
- SteamIDs = new List {22380},
+ SteamIDs = new List {22380, 22490}, // normal and RU version
RequiredFiles = new List
{
"FalloutNV.exe"
diff --git a/Wabbajack.Common/Utils.cs b/Wabbajack.Common/Utils.cs
index 7f80149e..377bf7e0 100644
--- a/Wabbajack.Common/Utils.cs
+++ b/Wabbajack.Common/Utils.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Data.HashFunction.xxHash;
using System.Diagnostics;
@@ -365,16 +366,26 @@ namespace Wabbajack.Common
public static void ToCERAS(this T obj, string filename, SerializerConfig config)
{
+ byte[] final;
+ final = ToCERAS(obj, config);
+ File.WriteAllBytes(filename, final);
+ }
+
+ public static byte[] ToCERAS(this T obj, SerializerConfig config)
+ {
+ byte[] final;
var ceras = new CerasSerializer(config);
byte[] buffer = null;
ceras.Serialize(obj, ref buffer);
- using(var m1 = new MemoryStream(buffer))
+
+ using (var m1 = new MemoryStream(buffer))
using (var m2 = new MemoryStream())
{
BZip2.Compress(m1, m2, false, 9);
m2.Seek(0, SeekOrigin.Begin);
- File.WriteAllBytes(filename, m2.ToArray());
+ final = m2.ToArray();
}
+ return final;
}
public static T FromCERAS(this Stream data, SerializerConfig config)
@@ -1112,6 +1123,20 @@ namespace Wabbajack.Common
return path.ToLower().TrimEnd('\\').StartsWith(parent.ToLower().TrimEnd('\\') + "\\");
}
+ public static HashSet ToHashSet(this IEnumerable coll)
+ {
+ var hs = new HashSet();
+ coll.Do(v => hs.Add(v));
+ return hs;
+ }
+
+ public static HashSet ToHashSet(this T[] coll)
+ {
+ var hs = new HashSet();
+ coll.Do(v => hs.Add(v));
+ return hs;
+ }
+
public class NexusErrorResponse
{
public int code;
diff --git a/Wabbajack.Common/Wabbajack.Common.csproj b/Wabbajack.Common/Wabbajack.Common.csproj
index 430458da..09fecdb0 100644
--- a/Wabbajack.Common/Wabbajack.Common.csproj
+++ b/Wabbajack.Common/Wabbajack.Common.csproj
@@ -5,35 +5,35 @@
AnyCPU;x64
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
\ No newline at end of file
diff --git a/Wabbajack.Lib/AInstaller.cs b/Wabbajack.Lib/AInstaller.cs
index ce4550d4..78dd8851 100644
--- a/Wabbajack.Lib/AInstaller.cs
+++ b/Wabbajack.Lib/AInstaller.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using Alphaleonis.Win32.Filesystem;
using Wabbajack.Common;
@@ -27,13 +28,16 @@ namespace Wabbajack.Lib
public string ModListArchive { get; private set; }
public ModList ModList { get; private set; }
public Dictionary HashedArchives { get; set; }
+
+ public SystemParameters SystemParameters { get; set; }
- public AInstaller(string archive, ModList modList, string outputFolder, string downloadFolder)
+ public AInstaller(string archive, ModList modList, string outputFolder, string downloadFolder, SystemParameters parameters)
{
ModList = modList;
ModListArchive = archive;
OutputFolder = outputFolder;
DownloadFolder = downloadFolder;
+ SystemParameters = parameters;
}
public void Info(string msg)
@@ -108,7 +112,7 @@ namespace Wabbajack.Lib
Info("Building Folder Structure");
ModList.Directives
.Select(d => Path.Combine(OutputFolder, Path.GetDirectoryName(d.To)))
- .ToHashSet()
+ .Distinct()
.Do(f =>
{
if (Directory.Exists(f)) return;
@@ -344,6 +348,10 @@ namespace Wabbajack.Lib
public async Task OptimizeModlist()
{
Utils.Log("Optimizing Modlist directives");
+
+ // Clone the modlist so our changes don't modify the original data
+ ModList = ModList.Clone();
+
var indexed = ModList.Directives.ToDictionary(d => d.To);
UpdateTracker.NextStep("Looking for files to delete");
@@ -377,6 +385,33 @@ namespace Wabbajack.Lib
.Where(d => d != null)
.Do(d => indexed.Remove(d.To));
+ Utils.Log("Cleaning empty folders");
+ var expectedFolders = indexed.Keys
+ // We ignore the last part of the path, so we need a dummy file name
+ .Append(Path.Combine(DownloadFolder, "_"))
+ .SelectMany(path =>
+ {
+ // Get all the folders and all the folder parents
+ // so for foo\bar\baz\qux.txt this emits ["foo", "foo\\bar", "foo\\bar\\baz"]
+ var split = path.Split('\\');
+ return Enumerable.Range(1, split.Length - 1).Select(t => string.Join("\\", split.Take(t)));
+ }).Distinct()
+ .Select(p => Path.Combine(OutputFolder, p))
+ .ToHashSet();
+
+ try
+ {
+ Directory.EnumerateDirectories(OutputFolder, DirectoryEnumerationOptions.Recursive)
+ .Where(p => !expectedFolders.Contains(p))
+ .OrderByDescending(p => p.Length)
+ .Do(p => Directory.Delete(p));
+ }
+ catch (Exception)
+ {
+ // ignored because it's not worth throwing a fit over
+ Utils.Log("Error when trying to clean empty folders. This doesn't really matter.");
+ }
+
UpdateTracker.NextStep("Updating Modlist");
Utils.Log($"Optimized {ModList.Directives.Count} directives to {indexed.Count} required");
var requiredArchives = indexed.Values.OfType()
diff --git a/Wabbajack.Lib/CerasConfig.cs b/Wabbajack.Lib/CerasConfig.cs
index 3997c8bf..7afa8d53 100644
--- a/Wabbajack.Lib/CerasConfig.cs
+++ b/Wabbajack.Lib/CerasConfig.cs
@@ -28,7 +28,8 @@ namespace Wabbajack.Lib
typeof(BSAStateObject), typeof(BSAFileStateObject), typeof(BA2StateObject), typeof(BA2DX10EntryState),
typeof(BA2FileEntryState), typeof(MediaFireDownloader.State), typeof(ArchiveMeta),
typeof(PropertyFile), typeof(SteamMeta), typeof(SteamWorkshopDownloader), typeof(SteamWorkshopDownloader.State),
- typeof(LoversLabDownloader.State), typeof(GameFileSourceDownloader.State)
+ typeof(LoversLabDownloader.State), typeof(GameFileSourceDownloader.State), typeof(VectorPlexusDownloader.State),
+ typeof(DeadlyStreamDownloader.State)
},
};
diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs b/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs
index 4f28c933..c39240af 100644
--- a/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs
+++ b/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs
@@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps
public IgnoreDisabledMods(ACompiler compiler) : base(compiler)
{
_mo2Compiler = (MO2Compiler) compiler;
- var alwaysEnabled = _mo2Compiler.ModInis.Where(f => IsAlwaysEnabled(f.Value)).Select(f => f.Key).ToHashSet();
+ var alwaysEnabled = _mo2Compiler.ModInis.Where(f => IsAlwaysEnabled(f.Value)).Select(f => f.Key).Distinct();
_allEnabledMods = _mo2Compiler.SelectedProfiles
.SelectMany(p => File.ReadAllLines(Path.Combine(_mo2Compiler.MO2Folder, "profiles", p, "modlist.txt")))
diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs b/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs
new file mode 100644
index 00000000..c2dc1cdb
--- /dev/null
+++ b/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs
@@ -0,0 +1,46 @@
+using System.Threading.Tasks;
+using Alphaleonis.Win32.Filesystem;
+using Wabbajack.Common;
+
+namespace Wabbajack.Lib.CompilationSteps
+{
+ public class IgnoreGameFilesIfGameFolderFilesExist : ACompilationStep
+ {
+ private readonly bool _gameFolderFilesExists;
+ private readonly string _gameFolder;
+
+ public IgnoreGameFilesIfGameFolderFilesExist(ACompiler compiler) : base(compiler)
+ {
+ _gameFolderFilesExists = Directory.Exists(Path.Combine(((MO2Compiler)compiler).MO2Folder, Consts.GameFolderFilesDir));
+ _gameFolder = compiler.GamePath;
+ }
+
+ public override async ValueTask Run(RawSourceFile source)
+ {
+ if (_gameFolderFilesExists)
+ {
+ if (source.AbsolutePath.IsInPath(_gameFolder))
+ {
+ var result = source.EvolveTo();
+ result.Reason = $"Ignoring game files because {Consts.GameFolderFilesDir} exists";
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ public override IState GetState()
+ {
+ return new State();
+ }
+
+ public class State : IState
+ {
+ public ICompilationStep CreateStep(ACompiler compiler)
+ {
+ return new IgnoreGameFilesIfGameFolderFilesExist(compiler);
+ }
+ }
+ }
+}
diff --git a/Wabbajack.Lib/CompilationSteps/IncludePatches.cs b/Wabbajack.Lib/CompilationSteps/IncludePatches.cs
index 0677fbbf..48f86990 100644
--- a/Wabbajack.Lib/CompilationSteps/IncludePatches.cs
+++ b/Wabbajack.Lib/CompilationSteps/IncludePatches.cs
@@ -22,8 +22,14 @@ namespace Wabbajack.Lib.CompilationSteps
public override async ValueTask Run(RawSourceFile source)
{
- if (!_indexed.TryGetValue(Path.GetFileName(source.File.Name.ToLower()), out var choices))
- return null;
+ var name = Path.GetFileName(source.File.Name.ToLower());
+ string nameWithoutExt = name;
+ if (Path.GetExtension(name) == ".mohidden")
+ nameWithoutExt = Path.GetFileNameWithoutExtension(name);
+
+ if (!_indexed.TryGetValue(Path.GetFileName(name), out var choices))
+ if (!_indexed.TryGetValue(Path.GetFileName(nameWithoutExt), out choices))
+ return null;
var mod_ini = ((MO2Compiler)_compiler).ModMetas.FirstOrDefault(f => source.Path.StartsWith(f.Key));
var installationFile = mod_ini.Value?.General?.installationFile;
diff --git a/Wabbajack.Lib/Data.cs b/Wabbajack.Lib/Data.cs
index 25c54c89..495fff61 100644
--- a/Wabbajack.Lib/Data.cs
+++ b/Wabbajack.Lib/Data.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using Ceras;
using Compression.BSA;
@@ -120,6 +121,11 @@ namespace Wabbajack.Lib
/// Whether readme is a website
///
public bool ReadmeIsWebsite;
+
+ public ModList Clone()
+ {
+ return new MemoryStream(this.ToCERAS(CerasConfig.Config)).FromCERAS(CerasConfig.Config);
+ }
}
public class Directive
diff --git a/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs b/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs
index 4ec030d8..ee9d5e49 100644
--- a/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs
+++ b/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs
@@ -21,7 +21,9 @@ namespace Wabbajack.Lib.Downloaders
typeof(MegaDownloader.State),
typeof(ModDBDownloader.State),
typeof(NexusDownloader.State),
- typeof(SteamWorkshopDownloader.State)
+ typeof(SteamWorkshopDownloader.State),
+ typeof(VectorPlexusDownloader.State),
+ typeof(DeadlyStreamDownloader.State)
};
public static Dictionary NameToType { get; set; }
public static Dictionary TypeToName { get; set; }
diff --git a/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs b/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs
new file mode 100644
index 00000000..3635b549
--- /dev/null
+++ b/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs
@@ -0,0 +1,155 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Web;
+using Newtonsoft.Json;
+using Wabbajack.Common;
+using Wabbajack.Lib.Validation;
+using File = System.IO.File;
+
+namespace Wabbajack.Lib.Downloaders
+{
+ // IPS4 is the site used by LoversLab, VectorPlexus, etc. the general mechanics of each site are the
+ // same, so we can fairly easily abstract the state.
+ // Pass in the state type via TState
+ public abstract class AbstractIPS4Downloader : AbstractNeedsLoginDownloader, IDownloader
+ where TState : AbstractIPS4Downloader.State, new()
+ where TDownloader : IDownloader
+ {
+ public override string SiteName { get; }
+ public override Uri SiteURL { get; }
+ public async Task GetDownloaderState(dynamic archiveINI)
+ {
+ Uri url = DownloaderUtils.GetDirectURL(archiveINI);
+ if (url == null || url.Host != SiteURL.Host || !url.AbsolutePath.StartsWith("/files/file/")) return null;
+ var id = HttpUtility.ParseQueryString(url.Query)["r"];
+ var file = url.AbsolutePath.Split('/').Last(s => s != "");
+
+ return new TState
+ {
+ FileID = id,
+ FileName = file
+ };
+ }
+
+
+ public class State : AbstractDownloadState where TDownloader : IDownloader
+ {
+ public string FileID { get; set; }
+ public string FileName { get; set; }
+
+ public override object[] PrimaryKey { get => new object[] {FileID, FileName}; }
+
+ public override bool IsWhitelisted(ServerWhitelist whitelist)
+ {
+ return true;
+ }
+
+ public override async Task Download(Archive a, string destination)
+ {
+ var stream = await ResolveDownloadStream();
+ using (var file = File.OpenWrite(destination))
+ {
+ stream.CopyTo(file);
+ }
+ }
+
+ private async Task ResolveDownloadStream()
+ {
+ var downloader = (AbstractNeedsLoginDownloader)(object)DownloadDispatcher.GetInstance();
+
+ TOP:
+ string csrfurl;
+ if (FileID == null)
+ {
+ csrfurl = $"https://{downloader.SiteURL.Host}/files/file/{FileName}/?do=download";
+ }
+ else
+ {
+ csrfurl = $"https://{downloader.SiteURL.Host}/files/file/{FileName}/?do=download&r={FileID}";
+ }
+ var html = await downloader.AuthedClient.GetStringAsync(csrfurl);
+
+ var pattern = new Regex("(?<=csrfKey=).*(?=[&\"\'])|(?<=csrfKey: \").*(?=[&\"\'])");
+ var matches = pattern.Matches(html).Cast();
+
+ var csrfKey = matches.Where(m => m.Length == 32).Select(m => m.ToString()).FirstOrDefault();
+
+ if (csrfKey == null)
+ return null;
+
+ string url;
+ if (FileID == null)
+ url = $"https://{downloader.SiteURL.Host}/files/file/{FileName}/?do=download&confirm=1&t=1&csrfKey={csrfKey}";
+ else
+ url = $"https://{downloader.SiteURL.Host}/files/file/{FileName}/?do=download&r={FileID}&confirm=1&t=1&csrfKey={csrfKey}";
+
+
+ var streamResult = await downloader.AuthedClient.GetAsync(url);
+ if (streamResult.StatusCode != HttpStatusCode.OK)
+ {
+ Utils.Error(new InvalidOperationException(), $"{downloader.SiteName} servers reported an error for file: {FileID}");
+ }
+
+ var content_type = streamResult.Content.Headers.ContentType;
+
+ if (content_type.MediaType == "application/json")
+ {
+ // Sometimes LL hands back a json object telling us to wait until a certain time
+ var times = (await streamResult.Content.ReadAsStringAsync()).FromJSONString();
+ var secs = times.Download - times.CurrentTime;
+ for (int x = 0; x < secs; x++)
+ {
+ Utils.Status($"Waiting for {secs} at the request of {downloader.SiteName}", x * 100 / secs);
+ await Task.Delay(1000);
+ }
+ Utils.Status("Retrying download");
+ goto TOP;
+ }
+
+ return await streamResult.Content.ReadAsStreamAsync();
+ }
+
+ private class WaitResponse
+ {
+ [JsonProperty("download")]
+ public int Download { get; set; }
+ [JsonProperty("currentTime")]
+ public int CurrentTime { get; set; }
+ }
+
+ public override async Task Verify()
+ {
+ var stream = await ResolveDownloadStream();
+ if (stream == null)
+ {
+ return false;
+ }
+
+ stream.Close();
+ return true;
+ }
+
+ public override IDownloader GetDownloader()
+ {
+ return DownloadDispatcher.GetInstance();
+ }
+
+ public override string GetReportEntry(Archive a)
+ {
+ var downloader = (INeedsLogin)GetDownloader();
+ return $"* {((INeedsLogin)GetDownloader()).SiteName} - [{a.Name}](https://{downloader.SiteURL.Host}/files/file/{FileName}/?do=download&r={FileID})";
+ }
+ }
+
+ protected AbstractIPS4Downloader(Uri loginUri, string encryptedKeyName, string cookieDomain) :
+ base(loginUri, encryptedKeyName, cookieDomain, "ips4_member_id")
+ {
+ }
+
+
+ }
+}
diff --git a/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs b/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs
new file mode 100644
index 00000000..1bead261
--- /dev/null
+++ b/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs
@@ -0,0 +1,142 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Reactive.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using ReactiveUI;
+using Wabbajack.Common;
+using Wabbajack.Common.StatusFeed;
+using Wabbajack.Lib.LibCefHelpers;
+using Wabbajack.Lib.WebAutomation;
+
+namespace Wabbajack.Lib.Downloaders
+{
+ public abstract class AbstractNeedsLoginDownloader : INeedsLogin
+ {
+ private readonly Uri _loginUri;
+ private readonly string _encryptedKeyName;
+ private readonly string _cookieDomain;
+ private readonly string _cookieName;
+ internal HttpClient AuthedClient;
+
+ ///
+ /// Sets up all the login facilites needed for a INeedsLogin downloader based on having the user log
+ /// in via a browser
+ ///
+ /// The URI to preset for logging in
+ /// The name of the encrypted JSON key in which to store cookies
+ /// The cookie domain to scan
+ /// The cookie name to wait for
+ public AbstractNeedsLoginDownloader(Uri loginUri,
+ string encryptedKeyName,
+ string cookieDomain,
+ string cookieName)
+ {
+ _loginUri = loginUri;
+ _encryptedKeyName = encryptedKeyName;
+ _cookieDomain = cookieDomain;
+ _cookieName = cookieName;
+
+ TriggerLogin = ReactiveCommand.CreateFromTask(
+ execute: () => Utils.CatchAndLog(async () => await Utils.Log(new RequestSiteLogin(this)).Task),
+ canExecute: IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler));
+ ClearLogin = ReactiveCommand.Create(
+ execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson(_encryptedKeyName)),
+ canExecute: IsLoggedIn.ObserveOn(RxApp.MainThreadScheduler));
+ }
+
+ public ICommand TriggerLogin { get; }
+ public ICommand ClearLogin { get; }
+ public IObservable IsLoggedIn => Utils.HaveEncryptedJsonObservable(_encryptedKeyName);
+ public abstract string SiteName { get; }
+ public virtual string MetaInfo { get; }
+ public abstract Uri SiteURL { get; }
+ public virtual Uri IconUri { get; }
+
+ protected virtual async Task WhileWaiting(IWebDriver browser)
+ {
+ }
+
+ public async Task GetAndCacheCookies(IWebDriver browser, Action updateStatus, CancellationToken cancel)
+ {
+ updateStatus($"Please Log Into {SiteName}");
+ await browser.NavigateTo(_loginUri);
+ var cookies = new Helpers.Cookie[0];
+ while (true)
+ {
+ cancel.ThrowIfCancellationRequested();
+ await WhileWaiting(browser);
+ cookies = (await browser.GetCookies(_cookieDomain));
+ if (cookies.FirstOrDefault(c => c.Name == _cookieName) != null)
+ break;
+ await Task.Delay(500, cancel);
+ }
+
+ cookies.ToEcryptedJson(_encryptedKeyName);
+
+ return cookies;
+ }
+
+ public async Task GetAuthedClient()
+ {
+ Helpers.Cookie[] cookies;
+ try
+ {
+ cookies = Utils.FromEncryptedJson(_encryptedKeyName);
+ if (cookies != null)
+ return Helpers.GetClient(cookies, SiteURL.ToString());
+ }
+ catch (FileNotFoundException) { }
+
+ cookies = await Utils.Log(new RequestSiteLogin(this)).Task;
+ return Helpers.GetClient(cookies, SiteURL.ToString());
+ }
+
+ public async Task Prepare()
+ {
+ AuthedClient = (await GetAuthedClient()) ?? throw new NotLoggedInError(this);
+ }
+
+ public class NotLoggedInError : Exception
+ {
+ public AbstractNeedsLoginDownloader Downloader { get; }
+ public NotLoggedInError(AbstractNeedsLoginDownloader downloader) : base(
+ $"Not logged into {downloader.SiteName}, can't continue")
+ {
+ Downloader = downloader;
+ }
+ }
+
+
+ public class RequestSiteLogin : AUserIntervention
+ {
+ public AbstractNeedsLoginDownloader Downloader { get; }
+ public RequestSiteLogin(AbstractNeedsLoginDownloader downloader)
+ {
+ Downloader = downloader;
+ }
+ public override string ShortDescription => $"Getting {Downloader.SiteName} Login";
+ public override string ExtendedDescription { get; }
+
+ private readonly TaskCompletionSource _source = new TaskCompletionSource();
+ public Task Task => _source.Task;
+
+ public void Resume(Helpers.Cookie[] cookies)
+ {
+ Handled = true;
+ _source.SetResult(cookies);
+ }
+
+ public override void Cancel()
+ {
+ Handled = true;
+ _source.TrySetCanceled();
+ }
+ }
+ }
+
+
+}
diff --git a/Wabbajack.Lib/Downloaders/DeadlyStreamDownloader.cs b/Wabbajack.Lib/Downloaders/DeadlyStreamDownloader.cs
new file mode 100644
index 00000000..2da650c1
--- /dev/null
+++ b/Wabbajack.Lib/Downloaders/DeadlyStreamDownloader.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Wabbajack.Lib.Downloaders
+{
+ public class DeadlyStreamDownloader : AbstractIPS4Downloader
+ {
+ #region INeedsDownload
+ public override string SiteName => "Deadly Stream";
+ public override Uri SiteURL => new Uri("https://www.deadlystream.com");
+ public override Uri IconUri => new Uri("https://www.deadlystream.com/favicon.ico");
+ #endregion
+
+ public DeadlyStreamDownloader() : base(new Uri("https://deadlystream.com/login"), "deadlystream",
+ "deadlystream.com")
+ {
+
+ }
+
+ public class State : State
+ {
+
+ }
+ }
+}
diff --git a/Wabbajack.Lib/Downloaders/DownloadDispatcher.cs b/Wabbajack.Lib/Downloaders/DownloadDispatcher.cs
index f2524f95..143db3d6 100644
--- a/Wabbajack.Lib/Downloaders/DownloadDispatcher.cs
+++ b/Wabbajack.Lib/Downloaders/DownloadDispatcher.cs
@@ -18,6 +18,8 @@ namespace Wabbajack.Lib.Downloaders
new NexusDownloader(),
new MediaFireDownloader(),
new LoversLabDownloader(),
+ new VectorPlexusDownloader(),
+ new DeadlyStreamDownloader(),
new HTTPDownloader(),
new ManualDownloader(),
};
diff --git a/Wabbajack.Lib/Downloaders/GoogleDriveDownloader.cs b/Wabbajack.Lib/Downloaders/GoogleDriveDownloader.cs
index 32bc3488..385f7212 100644
--- a/Wabbajack.Lib/Downloaders/GoogleDriveDownloader.cs
+++ b/Wabbajack.Lib/Downloaders/GoogleDriveDownloader.cs
@@ -1,8 +1,8 @@
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
-using System.Web;
using Wabbajack.Common;
+using Wabbajack.Lib.Exceptions;
using Wabbajack.Lib.Validation;
namespace Wabbajack.Lib.Downloaders
diff --git a/Wabbajack.Lib/Downloaders/HTTPDownloader.cs b/Wabbajack.Lib/Downloaders/HTTPDownloader.cs
index dd353f9b..c9264885 100644
--- a/Wabbajack.Lib/Downloaders/HTTPDownloader.cs
+++ b/Wabbajack.Lib/Downloaders/HTTPDownloader.cs
@@ -7,11 +7,10 @@ using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection.Emit;
using System.Threading.Tasks;
-using System.Web;
-using Windows.Networking.BackgroundTransfer;
using Ceras;
using SharpCompress.Common;
using Wabbajack.Common;
+using Wabbajack.Lib.Exceptions;
using Wabbajack.Lib.Validation;
using File = Alphaleonis.Win32.Filesystem.File;
diff --git a/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs b/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs
index 8ebe3d4c..1e639860 100644
--- a/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs
+++ b/Wabbajack.Lib/Downloaders/LoversLabDownloader.cs
@@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Windows.Input;
+using CefSharp;
using ReactiveUI;
using Wabbajack.Common;
using Wabbajack.Lib.LibCefHelpers;
@@ -21,213 +22,32 @@ using File = Alphaleonis.Win32.Filesystem.File;
namespace Wabbajack.Lib.Downloaders
{
- public class LoversLabDownloader : IDownloader, INeedsLogin
+ public class LoversLabDownloader : AbstractIPS4Downloader
{
- internal HttpClient _authedClient;
-
-
#region INeedsDownload
-
- public ReactiveCommand TriggerLogin { get; }
- public ReactiveCommand ClearLogin { get; }
- public IObservable IsLoggedIn => Utils.HaveEncryptedJsonObservable("loverslabcookies");
- public string SiteName => "Lovers Lab";
- public IObservable MetaInfo => Observable.Return("");
- public Uri SiteURL => new Uri("https://loverslab.com");
- public Uri IconUri => new Uri("https://www.loverslab.com/favicon.ico");
-
-
+ public override string SiteName => "Lovers Lab";
+ public override Uri SiteURL => new Uri("https://www.loverslab.com");
+ public override Uri IconUri => new Uri("https://www.loverslab.com/favicon.ico");
#endregion
- public LoversLabDownloader()
+ public LoversLabDownloader() : base(new Uri("https://www.loverslab.com/login"),
+ "loverslabcookies", "loverslab.com")
{
- TriggerLogin = ReactiveCommand.CreateFromTask(
- execute: () => Utils.CatchAndLog(async () => await Utils.Log(new RequestLoversLabLogin()).Task),
- canExecute: IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler));
- ClearLogin = ReactiveCommand.Create(
- execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson("loverslabcookies")),
- canExecute: IsLoggedIn.ObserveOn(RxApp.MainThreadScheduler));
}
-
-
- public async Task GetDownloaderState(dynamic archive_ini)
+ protected override async Task WhileWaiting(IWebDriver browser)
{
- Uri url = DownloaderUtils.GetDirectURL(archive_ini);
- if (url == null || url.Host != "www.loverslab.com" || !url.AbsolutePath.StartsWith("/files/file/")) return null;
- var id = HttpUtility.ParseQueryString(url.Query)["r"];
- var file = url.AbsolutePath.Split('/').Last(s => s != "");
-
- return new State
- {
- FileID = id,
- FileName = file
- };
- }
-
- public async Task Prepare()
- {
- _authedClient = (await GetAuthedClient()) ?? throw new Exception("not logged into LL, TODO");
- }
-
- public static async Task GetAndCacheLoversLabCookies(IWebDriver browser, Action updateStatus, CancellationToken cancel)
- {
- updateStatus("Please Log Into Lovers Lab");
- await browser.NavigateTo(new Uri("https://www.loverslab.com/login"));
- async Task CleanAds()
- {
- try
- {
- await browser.EvaluateJavaScript(
- "document.querySelectorAll(\".ll_adblock\").forEach(function (itm) { itm.innerHTML = \"\";});");
- }
- catch (Exception ex)
- {
- Utils.Error(ex);
- }
- return false;
- }
- var cookies = new Helpers.Cookie[0];
- while (true)
- {
- cancel.ThrowIfCancellationRequested();
- await CleanAds();
- cookies = (await browser.GetCookies("loverslab.com"));
- if (cookies.FirstOrDefault(c => c.Name == "ips4_member_id") != null)
- break;
- await Task.Delay(500, cancel);
- }
-
- cookies.ToEcryptedJson("loverslabcookies");
-
- return cookies;
- }
-
- public async Task GetAuthedClient()
- {
- Helpers.Cookie[] cookies;
try
{
- cookies = Utils.FromEncryptedJson("loverslabcookies");
- if (cookies != null)
- return Helpers.GetClient(cookies, "https://www.loverslab.com");
+ await browser.EvaluateJavaScript(
+ "document.querySelectorAll(\".ll_adblock\").forEach(function (itm) { itm.innerHTML = \"\";});");
}
- catch (FileNotFoundException) { }
-
- cookies = await Utils.Log(new RequestLoversLabLogin()).Task;
- return Helpers.GetClient(cookies, "https://www.loverslab.com");
- }
-
- public class State : AbstractDownloadState
- {
- public string FileID { get; set; }
- public string FileName { get; set; }
-
- public override object[] PrimaryKey { get => new object[] {FileID, FileName}; }
-
- public override bool IsWhitelisted(ServerWhitelist whitelist)
+ catch (Exception ex)
{
- return true;
- }
-
- public override async Task Download(Archive a, string destination)
- {
- var stream = await ResolveDownloadStream();
- using (var file = File.OpenWrite(destination))
- {
- stream.CopyTo(file);
- }
- }
-
- private async Task ResolveDownloadStream()
- {
- var result = DownloadDispatcher.GetInstance();
- TOP:
- var html = await result._authedClient.GetStringAsync(
- $"https://www.loverslab.com/files/file/{FileName}/?do=download&r={FileID}");
-
- var pattern = new Regex("(?<=csrfKey=).*(?=[&\"\'])");
- var csrfKey = pattern.Matches(html).Cast().Where(m => m.Length == 32).Select(m => m.ToString()).FirstOrDefault();
-
- if (csrfKey == null)
- return null;
-
- var url =
- $"https://www.loverslab.com/files/file/{FileName}/?do=download&r={FileID}&confirm=1&t=1&csrfKey={csrfKey}";
-
- var streamResult = await result._authedClient.GetAsync(url);
- if (streamResult.StatusCode != HttpStatusCode.OK)
- {
- Utils.Error(new InvalidOperationException(), $"LoversLab servers reported an error for file: {FileID}");
- }
-
- var content_type = streamResult.Content.Headers.ContentType;
-
- if (content_type.MediaType == "application/json")
- {
- // Sometimes LL hands back a json object telling us to wait until a certain time
- var times = (await streamResult.Content.ReadAsStringAsync()).FromJSONString();
- var secs = times.download - times.currentTime;
- for (int x = 0; x < secs; x++)
- {
- Utils.Status($"Waiting for {secs} at the request of LoversLab", x * 100 / secs);
- await Task.Delay(1000);
- }
- Utils.Status("Retrying download");
- goto TOP;
- }
-
- return await streamResult.Content.ReadAsStreamAsync();
- }
-
- internal class WaitResponse
- {
- public int download { get; set; }
- public int currentTime { get; set; }
- }
-
- public override async Task Verify()
- {
- var stream = await ResolveDownloadStream();
- if (stream == null)
- {
- return false;
- }
-
- stream.Close();
- return true;
- }
-
- public override IDownloader GetDownloader()
- {
- return DownloadDispatcher.GetInstance();
- }
-
- public override string GetReportEntry(Archive a)
- {
- return $"* Lovers Lab - [{a.Name}](https://www.loverslab.com/files/file/{FileName}/?do=download&r={FileID})";
+ Utils.Error(ex);
}
}
-
- }
-
- public class RequestLoversLabLogin : AUserIntervention
- {
- public override string ShortDescription => "Getting LoversLab information";
- public override string ExtendedDescription { get; }
-
- private readonly TaskCompletionSource _source = new TaskCompletionSource();
- public Task Task => _source.Task;
-
- public void Resume(Helpers.Cookie[] cookies)
+ public class State : State
{
- Handled = true;
- _source.SetResult(cookies);
- }
-
- public override void Cancel()
- {
- Handled = true;
- _source.TrySetCanceled();
}
}
}
diff --git a/Wabbajack.Lib/Downloaders/ManualDownloader.cs b/Wabbajack.Lib/Downloaders/ManualDownloader.cs
index c3352514..988dad42 100644
--- a/Wabbajack.Lib/Downloaders/ManualDownloader.cs
+++ b/Wabbajack.Lib/Downloaders/ManualDownloader.cs
@@ -5,8 +5,8 @@ using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
-using Syroot.Windows.IO;
using Wabbajack.Common;
+using Wabbajack.Common.IO;
using Wabbajack.Lib.Validation;
using File = System.IO.File;
diff --git a/Wabbajack.Lib/Downloaders/NexusDownloader.cs b/Wabbajack.Lib/Downloaders/NexusDownloader.cs
index b2338a34..7acc5875 100644
--- a/Wabbajack.Lib/Downloaders/NexusDownloader.cs
+++ b/Wabbajack.Lib/Downloaders/NexusDownloader.cs
@@ -36,6 +36,11 @@ namespace Wabbajack.Lib.Downloaders
public NexusDownloader()
{
+ if (CLIArguments.ApiKey != null)
+ {
+ CLIArguments.ApiKey.ToEcryptedJson("nexusapikey");
+ }
+
TriggerLogin = ReactiveCommand.CreateFromTask(
execute: () => Utils.CatchAndLog(NexusApiClient.RequestAndCacheAPIKey),
canExecute: IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler));
diff --git a/Wabbajack.Lib/Downloaders/VectorPlexusDownloader.cs b/Wabbajack.Lib/Downloaders/VectorPlexusDownloader.cs
new file mode 100644
index 00000000..43a5d551
--- /dev/null
+++ b/Wabbajack.Lib/Downloaders/VectorPlexusDownloader.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Wabbajack.Lib.Downloaders
+{
+ public class VectorPlexusDownloader : AbstractIPS4Downloader
+ {
+ #region INeedsDownload
+ public override string SiteName => "Vector Plexus";
+ public override Uri SiteURL => new Uri("https://vectorplexus.com");
+ public override Uri IconUri => new Uri("https://www.vectorplexus.com/favicon.ico");
+ #endregion
+
+ public VectorPlexusDownloader() : base(new Uri("https://vectorplexus.com/login"),
+ "vectorplexus", "vectorplexus.com")
+ {
+ }
+ public class State : State
+ {
+ }
+ }
+}
diff --git a/Wabbajack.Lib/Exceptions/HttpException.cs b/Wabbajack.Lib/Exceptions/HttpException.cs
new file mode 100644
index 00000000..41d46b03
--- /dev/null
+++ b/Wabbajack.Lib/Exceptions/HttpException.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Wabbajack.Lib.Exceptions
+{
+ public class HttpException : Exception
+ {
+ public string Reason { get; set; }
+ public int Code { get; set; }
+
+ public HttpException(int code, string reason) : base($"Http Error {code} - {reason}")
+ {
+ Code = code;
+ Reason = reason;
+ }
+
+ }
+}
diff --git a/Wabbajack.Lib/MO2Compiler.cs b/Wabbajack.Lib/MO2Compiler.cs
index f6b682dd..c3f2a80f 100644
--- a/Wabbajack.Lib/MO2Compiler.cs
+++ b/Wabbajack.Lib/MO2Compiler.cs
@@ -1,5 +1,6 @@
using Compression.BSA;
using System;
+using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -159,11 +160,21 @@ namespace Wabbajack.Lib
.Where(p => p.FileExists())
.Select(p => new RawSourceFile(VFS.Index.ByRootPath[p], p.RelativeTo(MO2Folder)));
- var gameFiles = Directory.EnumerateFiles(GamePath, "*", SearchOption.AllDirectories)
- .Where(p => p.FileExists())
- .Select(p => new RawSourceFile(VFS.Index.ByRootPath[p], Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath))));
+ // If Game Folder Files exists, ignore the game folder
+ IEnumerable gameFiles;
+ if (!Directory.Exists(Path.Combine(MO2Folder, Consts.GameFolderFilesDir)))
+ {
+ gameFiles = Directory.EnumerateFiles(GamePath, "*", SearchOption.AllDirectories)
+ .Where(p => p.FileExists())
+ .Select(p => new RawSourceFile(VFS.Index.ByRootPath[p],
+ Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath))));
+ }
+ else
+ {
+ gameFiles = new List();
+ }
+
-
ModMetas = Directory.EnumerateDirectories(Path.Combine(MO2Folder, "mods"))
.Keep(f =>
{
@@ -496,6 +507,7 @@ namespace Wabbajack.Lib
Utils.Log("Generating compilation stack");
return new List
{
+ new IgnoreGameFilesIfGameFolderFilesExist(this),
new IncludePropertyFiles(this),
new IgnoreStartsWith(this,"logs\\"),
new IgnoreStartsWith(this, "downloads\\"),
@@ -536,7 +548,8 @@ namespace Wabbajack.Lib
new IgnoreEndsWith(this, "HavokBehaviorPostProcess.exe"),
// Theme file MO2 downloads somehow
new IgnoreEndsWith(this, "splash.png"),
-
+ // File to force MO2 into portable mode
+ new IgnoreEndsWith(this, "portable.txt"),
new IgnoreEndsWith(this, ".bin"),
new IgnoreEndsWith(this, ".refcache"),
diff --git a/Wabbajack.Lib/MO2Installer.cs b/Wabbajack.Lib/MO2Installer.cs
index 46642daf..8827739f 100644
--- a/Wabbajack.Lib/MO2Installer.cs
+++ b/Wabbajack.Lib/MO2Installer.cs
@@ -30,12 +30,13 @@ namespace Wabbajack.Lib
public string GameFolder { get; set; }
- public MO2Installer(string archive, ModList modList, string outputFolder, string downloadFolder)
+ public MO2Installer(string archive, ModList modList, string outputFolder, string downloadFolder, SystemParameters parameters)
: base(
archive: archive,
modList: modList,
outputFolder: outputFolder,
- downloadFolder: downloadFolder)
+ downloadFolder: downloadFolder,
+ parameters: parameters)
{
}
@@ -44,7 +45,7 @@ namespace Wabbajack.Lib
if (cancel.IsCancellationRequested) return false;
var metric = Metrics.Send("begin_install", ModList.Name);
- ConfigureProcessor(18, await RecommendQueueSize());
+ ConfigureProcessor(19, await RecommendQueueSize());
var game = ModList.GameType.MetaData();
if (GameFolder == null)
@@ -52,10 +53,10 @@ namespace Wabbajack.Lib
if (GameFolder == null)
{
- MessageBox.Show(
+ await Utils.Log(new CriticalFailureIntervention(
$"In order to do a proper install Wabbajack needs to know where your {game.MO2Name} folder resides. We tried looking the" +
"game location up in the windows registry but were unable to find it, please make sure you launch the game once before running this installer. ",
- "Could not find game location", MessageBoxButton.OK);
+ "Could not find game location")).Task;
Utils.Log("Exiting because we couldn't find the game folder.");
return false;
}
@@ -135,6 +136,9 @@ namespace Wabbajack.Lib
UpdateTracker.NextStep("Generating Merges");
await zEditIntegration.GenerateMerges(this);
+ UpdateTracker.NextStep("Set MO2 into portable");
+ ForcePortable();
+
UpdateTracker.NextStep("Updating System-specific ini settings");
SetScreenSizeInPrefs();
@@ -144,6 +148,20 @@ namespace Wabbajack.Lib
return true;
}
+ private void ForcePortable()
+ {
+ var path = Path.Combine(OutputFolder, "portable.txt");
+ if (File.Exists(path)) return;
+
+ try
+ {
+ File.WriteAllText(path, "Created by Wabbajack");
+ }
+ catch (Exception e)
+ {
+ Utils.Error(e, $"Could not create portable.txt in {OutputFolder}");
+ }
+ }
private async Task InstallIncludedDownloadMetas()
{
@@ -173,42 +191,6 @@ namespace Wabbajack.Lib
}
}
- private async Task AskToEndorse()
- {
- var mods = ModList.Archives
- .Select(m => m.State)
- .OfType()
- .GroupBy(f => (f.GameName, f.ModID))
- .Select(mod => mod.First())
- .ToArray();
-
- var result = MessageBox.Show(
- $"Installation has completed, but you have installed {mods.Length} from the Nexus, would you like to" +
- " endorse these mods to show support to the authors? It will only take a few moments.", "Endorse Mods?",
- MessageBoxButton.YesNo, MessageBoxImage.Question);
-
- if (result != MessageBoxResult.Yes) return;
-
- // Shuffle mods so that if we hit a API limit we don't always miss the same mods
- var r = new Random();
- for (var i = 0; i < mods.Length; i++)
- {
- var a = r.Next(mods.Length);
- var b = r.Next(mods.Length);
- var tmp = mods[a];
- mods[a] = mods[b];
- mods[b] = tmp;
- }
-
- await mods.PMap(Queue, async mod =>
- {
- var client = await NexusApiClient.Get();
- var er = await client.EndorseMod(mod);
- Utils.Log($"Endorsed {mod.GameName} - {mod.ModID} - Result: {er.message}");
- });
- Info("Done! You may now exit the application!");
- }
-
private async Task BuildBSAs()
{
var bsas = ModList.Directives.OfType().ToList();
@@ -299,8 +281,8 @@ namespace Wabbajack.Lib
if (data.Sections["Display"]["iSize W"] != null && data.Sections["Display"]["iSize H"] != null)
{
- data.Sections["Display"]["iSize W"] = SystemParameters.PrimaryScreenWidth.ToString(CultureInfo.CurrentCulture);
- data.Sections["Display"]["iSize H"] = SystemParameters.PrimaryScreenHeight.ToString(CultureInfo.CurrentCulture);
+ data.Sections["Display"]["iSize W"] = SystemParameters.ScreenWidth.ToString(CultureInfo.CurrentCulture);
+ data.Sections["Display"]["iSize H"] = SystemParameters.ScreenHeight.ToString(CultureInfo.CurrentCulture);
}
parser.WriteFile(file, data);
diff --git a/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs b/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs
index c42f54c8..038777a8 100644
--- a/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs
+++ b/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs
@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
+using System.Drawing;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
-using System.Windows.Media.Imaging;
using Newtonsoft.Json;
using Wabbajack.Common;
using File = System.IO.File;
@@ -45,7 +45,7 @@ namespace Wabbajack.Lib.ModListRegistry
public string ImageUri { get; set; }
[JsonIgnore]
- public BitmapImage Image { get; set; }
+ public Bitmap Image { get; set; }
[JsonProperty("readme")]
public string Readme { get; set; }
diff --git a/Wabbajack.Lib/NexusApi/NexusApi.cs b/Wabbajack.Lib/NexusApi/NexusApi.cs
index 4365a6c3..c4e417bb 100644
--- a/Wabbajack.Lib/NexusApi/NexusApi.cs
+++ b/Wabbajack.Lib/NexusApi/NexusApi.cs
@@ -8,17 +8,12 @@ using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Security.Authentication;
-using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Lib.Downloaders;
-using Wabbajack.Lib.LibCefHelpers;
using WebSocketSharp;
using static Wabbajack.Lib.NexusApi.NexusApiUtils;
using System.Threading;
-using CefSharp;
-using CefSharp.Handler;
-using Newtonsoft.Json;
using Wabbajack.Lib.WebAutomation;
namespace Wabbajack.Lib.NexusApi
diff --git a/Wabbajack.Lib/Properties/AssemblyInfo.cs b/Wabbajack.Lib/Properties/AssemblyInfo.cs
deleted file mode 100644
index 5d41ef04..00000000
--- a/Wabbajack.Lib/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Wabbajack.Lib")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Wabbajack.Lib")]
-[assembly: AssemblyCopyright("Copyright © 2019")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("0a820830-a298-497d-85e0-e9a89efef5fe")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Wabbajack.Lib/ReportBuilder.cs b/Wabbajack.Lib/ReportBuilder.cs
index 8096a162..8c64570d 100644
--- a/Wabbajack.Lib/ReportBuilder.cs
+++ b/Wabbajack.Lib/ReportBuilder.cs
@@ -129,7 +129,7 @@ namespace Wabbajack.Lib
.Concat(lst.Directives
.OfType()
.Select(f => (f.To, "patched", SizeForID(f.PatchID))))
- .ToHashSet()
+ .Distinct()
.OrderByDescending(f => f.Item3);
NoWrapText("\n\n### Summary of inlined files in this installer");
diff --git a/Wabbajack.Lib/StatusMessages/CriticalFailureIntervention.cs b/Wabbajack.Lib/StatusMessages/CriticalFailureIntervention.cs
new file mode 100644
index 00000000..2062e95a
--- /dev/null
+++ b/Wabbajack.Lib/StatusMessages/CriticalFailureIntervention.cs
@@ -0,0 +1,27 @@
+using System.Threading.Tasks;
+using Wabbajack.Common;
+
+namespace Wabbajack.Lib
+{
+ ///
+ /// This should probably be replaced with an error, but this is just to get messageboxes out of the .Lib library
+ ///
+ public class CriticalFailureIntervention : AUserIntervention
+ {
+ private TaskCompletionSource _source = new TaskCompletionSource();
+ public Task Task => _source.Task;
+
+ public CriticalFailureIntervention(string description, string title)
+ {
+ ExtendedDescription = description;
+ ShortDescription = title;
+ }
+ public override string ShortDescription { get; }
+ public override string ExtendedDescription { get; }
+ public override void Cancel()
+ {
+ Handled = true;
+ _source.SetResult(ConfirmationIntervention.Choice.Abort);
+ }
+ }
+}
diff --git a/Wabbajack.Lib/StatusMessages/YesNoIntervention.cs b/Wabbajack.Lib/StatusMessages/YesNoIntervention.cs
new file mode 100644
index 00000000..b37a2be6
--- /dev/null
+++ b/Wabbajack.Lib/StatusMessages/YesNoIntervention.cs
@@ -0,0 +1,15 @@
+using Wabbajack.Common;
+
+namespace Wabbajack.Lib
+{
+ public class YesNoIntervention : ConfirmationIntervention
+ {
+ public YesNoIntervention(string description, string title)
+ {
+ ExtendedDescription = description;
+ ShortDescription = title;
+ }
+ public override string ShortDescription { get; }
+ public override string ExtendedDescription { get; }
+ }
+}
diff --git a/Wabbajack.Lib/SystemParameters.cs b/Wabbajack.Lib/SystemParameters.cs
new file mode 100644
index 00000000..ceec3d09
--- /dev/null
+++ b/Wabbajack.Lib/SystemParameters.cs
@@ -0,0 +1,8 @@
+namespace Wabbajack.Lib
+{
+ public class SystemParameters
+ {
+ public int ScreenHeight { get; set; }
+ public int ScreenWidth { get; set; }
+ }
+}
diff --git a/Wabbajack.Lib/VortexCompiler.cs b/Wabbajack.Lib/VortexCompiler.cs
index 14957fd0..adb0bb49 100644
--- a/Wabbajack.Lib/VortexCompiler.cs
+++ b/Wabbajack.Lib/VortexCompiler.cs
@@ -6,9 +6,9 @@ using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
-using Microsoft.WindowsAPICodePack.Shell;
using Newtonsoft.Json;
using Wabbajack.Common;
+using Wabbajack.Common.IO;
using Wabbajack.Common.StoreHandlers;
using Wabbajack.Lib.CompilationSteps;
using Wabbajack.Lib.NexusApi;
diff --git a/Wabbajack.Lib/VortexInstaller.cs b/Wabbajack.Lib/VortexInstaller.cs
index fad6aeeb..2d86df47 100644
--- a/Wabbajack.Lib/VortexInstaller.cs
+++ b/Wabbajack.Lib/VortexInstaller.cs
@@ -20,12 +20,13 @@ namespace Wabbajack.Lib
public override ModManager ModManager => ModManager.Vortex;
- public VortexInstaller(string archive, ModList modList, string outputFolder, string downloadFolder)
+ public VortexInstaller(string archive, ModList modList, string outputFolder, string downloadFolder, SystemParameters parameters)
: base(
archive: archive,
modList: modList,
outputFolder: outputFolder,
- downloadFolder: downloadFolder)
+ downloadFolder: downloadFolder,
+ parameters: parameters)
{
#if DEBUG
// TODO: only for testing
@@ -39,11 +40,15 @@ namespace Wabbajack.Lib
{
if (cancel.IsCancellationRequested) return false;
var metric = Metrics.Send("begin_install", ModList.Name);
- MessageBox.Show(
+ var result = await Utils.Log(new YesNoIntervention(
"Vortex Support is still experimental and may produce unexpected results. " +
"If anything fails go to the special vortex support channels on the discord. @erri120#2285 " +
- "for support.", "Warning",
- MessageBoxButton.OK);
+ "for support.", "Continue with experimental feature?")).Task;
+ if (result == ConfirmationIntervention.Choice.Abort)
+ {
+ Utils.Log("Exiting at request of user");
+ return false;
+ }
if (cancel.IsCancellationRequested) return false;
ConfigureProcessor(10, await RecommendQueueSize());
@@ -108,11 +113,11 @@ namespace Wabbajack.Lib
if (!ModList.Directives.Any(d => d.To.StartsWith(Consts.ManualGameFilesDir)))
return;
- var result = MessageBox.Show("Some mods from this ModList must be installed directly into " +
+ var result = await Utils.Log(new YesNoIntervention("Some mods from this ModList must be installed directly into " +
"the game folder. Do you want to do this manually or do you want Wabbajack " +
- "to do this for you?", "Question", MessageBoxButton.YesNo);
+ "to do this for you?", "Install game folder mods?")).Task;
- if (result != MessageBoxResult.Yes)
+ if (result != ConfirmationIntervention.Choice.Continue)
return;
var manualFilesDir = Path.Combine(OutputFolder, Consts.ManualGameFilesDir);
@@ -167,12 +172,13 @@ namespace Wabbajack.Lib
if (!ModList.Directives.Any(s => s is SteamMeta))
return;
- var result = MessageBox.Show("The ModList you are installing requires Steam Workshop Items to exist. " +
- "You can check the Workshop Items in the manifest of this ModList. Wabbajack can start Steam for you " +
- "and download the Items automatically. Do you want to proceed with this step?",
- "Warning", MessageBoxButton.YesNo);
+ var result = await Utils.Log(new YesNoIntervention(
+ "The ModList you are installing requires Steam Workshop Items to exist. " +
+ "You can check the Workshop Items in the manifest of this ModList. Wabbajack can start Steam for you " +
+ "and download the Items automatically. Do you want to proceed with this step?",
+ "Download Steam Workshop Items?")).Task;
- if (result != MessageBoxResult.Yes)
+ if (result != ConfirmationIntervention.Choice.Continue)
return;
await ModList.Directives.OfType()
diff --git a/Wabbajack.Lib/Wabbajack.Lib.csproj b/Wabbajack.Lib/Wabbajack.Lib.csproj
index a78d73b3..57b0cf35 100644
--- a/Wabbajack.Lib/Wabbajack.Lib.csproj
+++ b/Wabbajack.Lib/Wabbajack.Lib.csproj
@@ -1,248 +1,74 @@
-
-
-
- Debug
- AnyCPU
- {0A820830-A298-497D-85E0-E9A89EFEF5FE}
- Library
- Properties
- Wabbajack.Lib
- Wabbajack.Lib
- v4.8
- 512
- true
-
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
- x64
- CS1998
- CS4014
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
- CS1998
- CS4014
-
-
- true
- bin\x64\Debug\
- DEBUG;TRACE
- full
- x64
- 7.3
- prompt
- MinimumRecommendedRules.ruleset
- false
- CS4014
- CS1998
-
-
- bin\x64\Release\
- TRACE
- true
- pdbonly
- x64
- 7.3
- prompt
- MinimumRecommendedRules.ruleset
- CS4014
- CS1998
-
-
-
- ..\..\..\Users\tbald\.nuget\packages\mongodb.bson\2.10.0\lib\net452\MongoDB.Bson.dll
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {ff5d892f-8ff4-44fc-8f7f-cd58f307ad1b}
- Compression.BSA
-
-
- {b3f3fb6e-b9eb-4f49-9875-d78578bc7ae5}
- Wabbajack.Common
-
-
- {5D6A2EAF-6604-4C51-8AE2-A746B4BC5E3E}
- Wabbajack.VirtualFileSystem
-
-
-
-
-
-
-
-
-
-
- 2.2.6
-
-
- 75.1.143
-
-
- 4.1.7
-
-
- 0.15.1
-
-
- 1.11.17
-
-
- 1.7.1
-
-
- 1.1.3.3
-
-
- 6.0.0
-
-
- 2.1.0
-
-
- 12.0.3
-
-
- 11.1.1
-
-
- 11.1.1
-
-
- 0.24.0
-
-
- 1.2.1
-
-
- 4.3.2
-
-
- 1.0.4
-
-
- 8.0.0
-
-
-
-
+
+
+ netstandard2.0
+ AnyCPU;x64
+
+
+
+ 75.1.143
+
+
+ 75.1.143
+
+
+ 4.1.7
+
+
+ 0.15.1
+
+
+ 6.0.6
+
+
+ 2.2.2.1
+
+
+ 1.11.17
+
+
+ 1.7.1
+
+
+ 4.7.0
+
+
+ 2.1.0
+
+
+ 11.1.6
+
+
+ 11.1.6
+
+
+ 0.24.0
+
+
+ 1.7.0
+
+
+ 4.7.0
+
+
+ 4.3.4
+
+
+ 1.0.1
+
+
+ 1.0.0
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Wabbajack.Lib/WebAutomation/CefSharpWrapper.cs b/Wabbajack.Lib/WebAutomation/CefSharpWrapper.cs
index 84fa7c37..fcb9e919 100644
--- a/Wabbajack.Lib/WebAutomation/CefSharpWrapper.cs
+++ b/Wabbajack.Lib/WebAutomation/CefSharpWrapper.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Runtime.Remoting.Channels;
using System.Text;
using System.Threading.Tasks;
using CefSharp;
diff --git a/Wabbajack.Test/ACompilerTest.cs b/Wabbajack.Test/ACompilerTest.cs
index 58f2d5cd..19b8fd07 100644
--- a/Wabbajack.Test/ACompilerTest.cs
+++ b/Wabbajack.Test/ACompilerTest.cs
@@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.LibCefHelpers;
+using Wabbajack.Util;
namespace Wabbajack.Test
{
@@ -57,7 +58,8 @@ namespace Wabbajack.Test
archive: compiler.ModListOutputFile,
modList: modlist,
outputFolder: utils.InstallFolder,
- downloadFolder: utils.DownloadsFolder);
+ downloadFolder: utils.DownloadsFolder,
+ parameters: SystemParametersConstructor.Create());
installer.WarnOnOverwrite = false;
installer.GameFolder = utils.GameFolder;
await installer.Begin();
diff --git a/Wabbajack.Test/AVortexCompilerTest.cs b/Wabbajack.Test/AVortexCompilerTest.cs
index 1f9c7466..a8691415 100644
--- a/Wabbajack.Test/AVortexCompilerTest.cs
+++ b/Wabbajack.Test/AVortexCompilerTest.cs
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wabbajack.Common;
using Wabbajack.Lib;
+using Wabbajack.Util;
namespace Wabbajack.Test
{
@@ -67,7 +68,8 @@ namespace Wabbajack.Test
archive: vortexCompiler.ModListOutputFile,
modList: modList,
outputFolder: utils.InstallFolder,
- downloadFolder: utils.DownloadsFolder)
+ downloadFolder: utils.DownloadsFolder,
+ parameters: SystemParametersConstructor.Create())
{
GameFolder = utils.GameFolder,
};
diff --git a/Wabbajack.Test/DownloaderTests.cs b/Wabbajack.Test/DownloaderTests.cs
index 395a0be8..9d6fd0c0 100644
--- a/Wabbajack.Test/DownloaderTests.cs
+++ b/Wabbajack.Test/DownloaderTests.cs
@@ -310,6 +310,34 @@ namespace Wabbajack.Test
Assert.AreEqual(File.ReadAllText(filename), "Cheese for Everyone!");
}
+
+ [TestMethod]
+ public async Task VectorPlexusDownload()
+ {
+ await DownloadDispatcher.GetInstance().Prepare();
+ var ini = @"[General]
+ directURL=https://vectorplexus.com/files/file/290-wabbajack-test-file";
+
+ var state = (AbstractDownloadState)await DownloadDispatcher.ResolveArchive(ini.LoadIniString());
+
+ Assert.IsNotNull(state);
+
+ /*var url_state = DownloadDispatcher.ResolveArchive("https://www.loverslab.com/files/file/11116-test-file-for-wabbajack-integration/?do=download&r=737123&confirm=1&t=1");
+ Assert.AreEqual("http://build.wabbajack.org/WABBAJACK_TEST_FILE.txt",
+ ((HTTPDownloader.State)url_state).Url);
+ */
+ var converted = state.ViaJSON();
+ Assert.IsTrue(await converted.Verify());
+ var filename = Guid.NewGuid().ToString();
+
+ Assert.IsTrue(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List() }));
+
+ await converted.Download(new Archive { Name = "Vector Plexus Test.zip" }, filename);
+
+ Assert.AreEqual("eSIyd+KOG3s=", Utils.FileHash(filename));
+
+ Assert.AreEqual(File.ReadAllText(filename), "Cheese for Everyone!");
+ }
[TestMethod]
public async Task GameFileSourceDownload()
diff --git a/Wabbajack.Test/EndToEndTests.cs b/Wabbajack.Test/EndToEndTests.cs
index d120ede9..1a9da3ef 100644
--- a/Wabbajack.Test/EndToEndTests.cs
+++ b/Wabbajack.Test/EndToEndTests.cs
@@ -8,6 +8,7 @@ using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
+using Wabbajack.Util;
namespace Wabbajack.Test
{
@@ -153,7 +154,8 @@ namespace Wabbajack.Test
archive: compiler.ModListOutputFile,
modList: modlist,
outputFolder: utils.InstallFolder,
- downloadFolder: utils.DownloadsFolder);
+ downloadFolder: utils.DownloadsFolder,
+ parameters: SystemParametersConstructor.Create());
installer.GameFolder = utils.GameFolder;
await installer.Begin();
}
diff --git a/Wabbajack.Test/FilePickerTests.cs b/Wabbajack.Test/FilePickerTests.cs
index 5aff8958..33f8b16c 100644
--- a/Wabbajack.Test/FilePickerTests.cs
+++ b/Wabbajack.Test/FilePickerTests.cs
@@ -9,6 +9,7 @@ using DynamicData;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wabbajack.Common;
using Wabbajack.Lib;
+using Wabbajack.UI;
namespace Wabbajack.Test
{
diff --git a/Wabbajack.Test/IniTests.cs b/Wabbajack.Test/IniTests.cs
new file mode 100644
index 00000000..6ad26a1d
--- /dev/null
+++ b/Wabbajack.Test/IniTests.cs
@@ -0,0 +1,27 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Wabbajack.Common;
+
+namespace Wabbajack.Test
+{
+ [TestClass]
+ public class IniTests
+ {
+
+ [TestMethod]
+ public void TestByteArrayParsing()
+ {
+ Assert.AreEqual("bar", @"[General]
+ foo = bar".LoadIniString().General.foo);
+
+ Assert.AreEqual("baz\\bar", @"[General]
+ foo = baz\\bar".LoadIniString().General.foo);
+
+ Assert.AreEqual("bar", @"[General]
+ foo = @ByteArray(bar)".LoadIniString().General.foo);
+
+ Assert.AreEqual("foo\\h̴̹͚̎é̶̘͙̐l̶͕̔͑p̴̯̋͂m̶̞̮͘͠e̸͉͙͆̄\\baz", @"[General]
+ foo = @ByteArray(foo\\\x68\xcc\xb4\xcc\x8e\xcc\xb9\xcd\x9a\x65\xcc\xb6\xcd\x81\xcc\x90\xcc\x98\xcd\x99\x6c\xcc\xb6\xcc\x94\xcd\x91\xcd\x95\x70\xcc\xb4\xcc\x8b\xcd\x82\xcc\xaf\x6d\xcc\xb6\xcd\x98\xcd\xa0\xcc\x9e\xcc\xae\x65\xcc\xb8\xcd\x86\xcc\x84\xcd\x89\xcd\x99\\baz)".LoadIniString().General.foo);
+ }
+
+ }
+}
diff --git a/Wabbajack.Test/SanityTests.cs b/Wabbajack.Test/SanityTests.cs
index 411ad9d3..053c28c6 100644
--- a/Wabbajack.Test/SanityTests.cs
+++ b/Wabbajack.Test/SanityTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -31,6 +32,44 @@ namespace Wabbajack.Test
utils.VerifyInstalledFile(mod, @"Data\scripts\test.pex");
}
+
+ [TestMethod]
+ public async Task TestDirectMatchFromGameFolder()
+ {
+
+ var profile = utils.AddProfile();
+ var mod = utils.AddMod();
+ var test_pex = utils.AddGameFile(@"enbstuff\test.pex", 10);
+
+ utils.Configure();
+
+ utils.AddManualDownload(
+ new Dictionary {{"/baz/biz.pex", File.ReadAllBytes(test_pex)}});
+
+ await CompileAndInstall(profile);
+
+ utils.VerifyInstalledGameFile(@"enbstuff\test.pex");
+ }
+
+ [TestMethod]
+ public async Task TestDirectMatchIsIgnoredWhenGameFolderFilesOverrideExists()
+ {
+
+ var profile = utils.AddProfile();
+ var mod = utils.AddMod();
+ var test_pex = utils.AddGameFile(@"enbstuff\test.pex", 10);
+
+ utils.Configure();
+
+ Directory.CreateDirectory(Path.Combine(utils.MO2Folder, Consts.GameFolderFilesDir));
+
+ utils.AddManualDownload(
+ new Dictionary {{"/baz/biz.pex", File.ReadAllBytes(test_pex)}});
+
+ await CompileAndInstall(profile);
+
+ Assert.IsFalse(File.Exists(Path.Combine(utils.InstallFolder, Consts.GameFolderFilesDir, @"enbstuff\test.pex")));
+ }
[TestMethod]
public async Task TestDuplicateFilesAreCopied()
@@ -87,6 +126,11 @@ namespace Wabbajack.Test
var extra_path = utils.PathOfInstalledFile(mod, @"something_i_made.foo");
File.WriteAllText(extra_path, "bleh");
+ var extra_folder = Path.Combine(Path.GetDirectoryName(utils.PathOfInstalledFile(mod, @"something_i_made.foo")), "folder_i_made");
+ Directory.CreateDirectory(extra_folder);
+
+ Assert.IsTrue(Directory.Exists(extra_folder));
+
var unchanged_modified = File.GetLastWriteTime(unchanged_path);
var modified_modified = File.GetLastWriteTime(modified_path);
@@ -105,6 +149,7 @@ namespace Wabbajack.Test
Assert.AreEqual(unchanged_modified, File.GetLastWriteTime(unchanged_path));
Assert.AreNotEqual(modified_modified, File.GetLastWriteTime(modified_path));
Assert.IsFalse(File.Exists(extra_path));
+ Assert.IsFalse(Directory.Exists(extra_folder));
}
diff --git a/Wabbajack.Test/TestUtils.cs b/Wabbajack.Test/TestUtils.cs
index 0f68c4f0..1ad33df3 100644
--- a/Wabbajack.Test/TestUtils.cs
+++ b/Wabbajack.Test/TestUtils.cs
@@ -177,7 +177,26 @@ namespace Wabbajack.Test
Assert.Fail($"Index {x} of {mod}\\{file} are not the same");
}
}
+
+ public void VerifyInstalledGameFile(string file)
+ {
+ var src = Path.Combine(GameFolder, file);
+ Assert.IsTrue(File.Exists(src), src);
+ var dest = Path.Combine(InstallFolder, Consts.GameFolderFilesDir, file);
+ Assert.IsTrue(File.Exists(dest), dest);
+
+ var src_data = File.ReadAllBytes(src);
+ var dest_data = File.ReadAllBytes(dest);
+
+ Assert.AreEqual(src_data.Length, dest_data.Length);
+
+ for(int x = 0; x < src_data.Length; x++)
+ {
+ if (src_data[x] != dest_data[x])
+ Assert.Fail($"Index {x} of {Consts.GameFolderFilesDir}\\{file} are not the same");
+ }
+ }
public string PathOfInstalledFile(string mod, string file)
{
return Path.Combine(InstallFolder, "mods", mod, file);
@@ -185,12 +204,15 @@ namespace Wabbajack.Test
public void VerifyAllFiles()
{
+ var skip_files = new HashSet {"portable.txt"};
foreach (var dest_file in Directory.EnumerateFiles(InstallFolder, "*", DirectoryEnumerationOptions.Recursive))
{
var rel_file = dest_file.RelativeTo(InstallFolder);
if (rel_file.StartsWith(Consts.LOOTFolderFilesDir) || rel_file.StartsWith(Consts.GameFolderFilesDir))
continue;
- Assert.IsTrue(File.Exists(Path.Combine(MO2Folder, rel_file)), $"Only in Destination: {rel_file}");
+
+ if (!skip_files.Contains(rel_file))
+ Assert.IsTrue(File.Exists(Path.Combine(MO2Folder, rel_file)), $"Only in Destination: {rel_file}");
}
var skip_extensions = new HashSet {".txt", ".ini"};
@@ -215,5 +237,15 @@ namespace Wabbajack.Test
}
}
}
+
+ public string AddGameFile(string path, int i)
+ {
+ var full_path = Path.Combine(GameFolder, path);
+ var dir = Path.GetDirectoryName(full_path);
+ if (!Directory.Exists(dir))
+ Directory.CreateDirectory(dir);
+ GenerateRandomFileData(full_path, i);
+ return full_path;
+ }
}
}
diff --git a/Wabbajack.Test/Wabbajack.Test.csproj b/Wabbajack.Test/Wabbajack.Test.csproj
index 1b15cf8d..581ff503 100644
--- a/Wabbajack.Test/Wabbajack.Test.csproj
+++ b/Wabbajack.Test/Wabbajack.Test.csproj
@@ -127,6 +127,7 @@
+
diff --git a/Wabbajack.Test/ZEditIntegrationTests.cs b/Wabbajack.Test/ZEditIntegrationTests.cs
index a4b19234..4d683aba 100644
--- a/Wabbajack.Test/ZEditIntegrationTests.cs
+++ b/Wabbajack.Test/ZEditIntegrationTests.cs
@@ -73,6 +73,8 @@ namespace Wabbajack.Test
});
+
+
var modlist = await CompileAndInstall(profile);
var directive = modlist.Directives.Where(m => m.To == $"mods\\{moddest}\\merged.esp").FirstOrDefault();
diff --git a/Wabbajack/App.xaml.cs b/Wabbajack/App.xaml.cs
index 1daefbf8..ad59de68 100644
--- a/Wabbajack/App.xaml.cs
+++ b/Wabbajack/App.xaml.cs
@@ -14,7 +14,9 @@ namespace Wabbajack
{
public App()
{
- // Do initialization in MainWindow ctor
+ CLI.ParseOptions(Environment.GetCommandLineArgs());
+ if(CLIArguments.Help)
+ CLI.DisplayHelpText();
}
}
}
diff --git a/Wabbajack/Properties/AssemblyInfo.cs b/Wabbajack/Properties/AssemblyInfo.cs
index 02512be1..5a44f29b 100644
--- a/Wabbajack/Properties/AssemblyInfo.cs
+++ b/Wabbajack/Properties/AssemblyInfo.cs
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.9.13.0")]
-[assembly: AssemblyFileVersion("0.9.13.0")]
+[assembly: AssemblyVersion("0.9.15.0")]
+[assembly: AssemblyFileVersion("0.9.15.0")]
diff --git a/Wabbajack.Lib/UI/FilePickerVM.cs b/Wabbajack/UI/FilePickerVM.cs
similarity index 99%
rename from Wabbajack.Lib/UI/FilePickerVM.cs
rename to Wabbajack/UI/FilePickerVM.cs
index b087fe19..9295660c 100644
--- a/Wabbajack.Lib/UI/FilePickerVM.cs
+++ b/Wabbajack/UI/FilePickerVM.cs
@@ -10,7 +10,7 @@ using System.Reactive.Linq;
using System.Windows.Input;
using Wabbajack.Lib;
-namespace Wabbajack.Lib
+namespace Wabbajack.UI
{
public class FilePickerVM : ViewModel
{
diff --git a/Wabbajack.Lib/UI/UIUtils.cs b/Wabbajack/UI/UIUtils.cs
similarity index 98%
rename from Wabbajack.Lib/UI/UIUtils.cs
rename to Wabbajack/UI/UIUtils.cs
index 5c0bb5c5..6c2de57c 100644
--- a/Wabbajack.Lib/UI/UIUtils.cs
+++ b/Wabbajack/UI/UIUtils.cs
@@ -8,7 +8,7 @@ using System.Windows.Forms;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
-namespace Wabbajack.Lib
+namespace Wabbajack.UI
{
public static class UIUtils
{
diff --git a/Wabbajack/Util/SystemParametersConstructor.cs b/Wabbajack/Util/SystemParametersConstructor.cs
new file mode 100644
index 00000000..e8b3ddbc
--- /dev/null
+++ b/Wabbajack/Util/SystemParametersConstructor.cs
@@ -0,0 +1,17 @@
+using MahApps.Metro.Controls;
+using Wabbajack.Lib;
+
+namespace Wabbajack.Util
+{
+ public static class SystemParametersConstructor
+ {
+ public static SystemParameters Create()
+ {
+ return new SystemParameters
+ {
+ ScreenWidth = (int)System.Windows.SystemParameters.PrimaryScreenWidth,
+ ScreenHeight = (int)System.Windows.SystemParameters.PrimaryScreenHeight
+ };
+ }
+ }
+}
diff --git a/Wabbajack/View Models/Compilers/CompilerVM.cs b/Wabbajack/View Models/Compilers/CompilerVM.cs
index afa07298..409e7906 100644
--- a/Wabbajack/View Models/Compilers/CompilerVM.cs
+++ b/Wabbajack/View Models/Compilers/CompilerVM.cs
@@ -15,6 +15,7 @@ using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.Common.StatusFeed;
using Wabbajack.Lib;
+using Wabbajack.UI;
namespace Wabbajack
{
diff --git a/Wabbajack/View Models/Compilers/MO2CompilerVM.cs b/Wabbajack/View Models/Compilers/MO2CompilerVM.cs
index eba68e75..a80c826c 100644
--- a/Wabbajack/View Models/Compilers/MO2CompilerVM.cs
+++ b/Wabbajack/View Models/Compilers/MO2CompilerVM.cs
@@ -9,6 +9,7 @@ using System.Reactive.Linq;
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Lib;
+using Wabbajack.UI;
namespace Wabbajack
{
diff --git a/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs b/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs
index 0f487134..2d94c524 100644
--- a/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs
+++ b/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs
@@ -6,6 +6,7 @@ using Microsoft.WindowsAPICodePack.Dialogs;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.Lib;
+using Wabbajack.UI;
namespace Wabbajack
{
diff --git a/Wabbajack/View Models/Compilers/VortexCompilerVM.cs b/Wabbajack/View Models/Compilers/VortexCompilerVM.cs
index d87ff48b..30a7567a 100644
--- a/Wabbajack/View Models/Compilers/VortexCompilerVM.cs
+++ b/Wabbajack/View Models/Compilers/VortexCompilerVM.cs
@@ -11,6 +11,7 @@ using ReactiveUI.Fody.Helpers;
using Wabbajack.Common;
using Wabbajack.Common.StoreHandlers;
using Wabbajack.Lib;
+using Wabbajack.UI;
namespace Wabbajack
{
diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs
index 95294956..3106794e 100644
--- a/Wabbajack/View Models/Installers/InstallerVM.cs
+++ b/Wabbajack/View Models/Installers/InstallerVM.cs
@@ -21,6 +21,7 @@ using Wabbajack.Common.StatusFeed;
using System.Reactive;
using System.Collections.Generic;
using System.Windows.Input;
+using Wabbajack.UI;
namespace Wabbajack
{
@@ -99,12 +100,10 @@ namespace Wabbajack
{
if (Path.GetDirectoryName(Assembly.GetEntryAssembly().Location.ToLower()) == KnownFolders.Downloads.Path.ToLower())
{
- MessageBox.Show(
+ Utils.Log(new CriticalFailureIntervention(
"Wabbajack is running inside your Downloads folder. This folder is often highly monitored by antivirus software and these can often " +
"conflict with the operations Wabbajack needs to perform. Please move this executable outside of your Downloads folder and then restart the app.",
- "Cannot run inside Downloads",
- MessageBoxButton.OK,
- MessageBoxImage.Error);
+ "Cannot run inside Downloads")).Task.Wait();
Environment.Exit(1);
}
diff --git a/Wabbajack/View Models/Installers/MO2InstallerVM.cs b/Wabbajack/View Models/Installers/MO2InstallerVM.cs
index a0909b96..f32b5a1d 100644
--- a/Wabbajack/View Models/Installers/MO2InstallerVM.cs
+++ b/Wabbajack/View Models/Installers/MO2InstallerVM.cs
@@ -11,6 +11,8 @@ using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.Common;
using Wabbajack.Lib;
+using Wabbajack.UI;
+using Wabbajack.Util;
namespace Wabbajack
{
@@ -148,7 +150,8 @@ namespace Wabbajack
archive: Parent.ModListLocation.TargetPath,
modList: Parent.ModList.SourceModList,
outputFolder: Location.TargetPath,
- downloadFolder: DownloadLocation.TargetPath);
+ downloadFolder: DownloadLocation.TargetPath,
+ parameters: SystemParametersConstructor.Create());
await Task.Run(async () =>
{
diff --git a/Wabbajack/View Models/Installers/VortexInstallerVM.cs b/Wabbajack/View Models/Installers/VortexInstallerVM.cs
index efc91e02..6952ee86 100644
--- a/Wabbajack/View Models/Installers/VortexInstallerVM.cs
+++ b/Wabbajack/View Models/Installers/VortexInstallerVM.cs
@@ -8,6 +8,7 @@ using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.Common;
using Wabbajack.Lib;
+using Wabbajack.Util;
namespace Wabbajack
{
@@ -66,7 +67,8 @@ namespace Wabbajack
archive: Parent.ModListLocation.TargetPath,
modList: Parent.ModList.SourceModList,
outputFolder: staging,
- downloadFolder: download);
+ downloadFolder: download,
+ parameters: SystemParametersConstructor.Create());
await Task.Run(async () =>
{
diff --git a/Wabbajack/View Models/MainWindowVM.cs b/Wabbajack/View Models/MainWindowVM.cs
index 360d50b9..ff55e8ae 100644
--- a/Wabbajack/View Models/MainWindowVM.cs
+++ b/Wabbajack/View Models/MainWindowVM.cs
@@ -130,16 +130,16 @@ namespace Wabbajack
.Select(active => !SettingsPane.IsValueCreated || !object.ReferenceEquals(active, SettingsPane.Value)),
execute: () => NavigateTo(SettingsPane.Value));
}
+
private static bool IsStartingFromModlist(out string modlistPath)
{
- string[] args = Environment.GetCommandLineArgs();
- if (args.Length != 3 || !args[1].Contains("-i"))
+ if (CLIArguments.InstallPath == null)
{
modlistPath = default;
return false;
}
- modlistPath = args[2];
+ modlistPath = CLIArguments.InstallPath;
return true;
}
diff --git a/Wabbajack/View Models/ModListVM.cs b/Wabbajack/View Models/ModListVM.cs
index 264771d6..17bb3076 100644
--- a/Wabbajack/View Models/ModListVM.cs
+++ b/Wabbajack/View Models/ModListVM.cs
@@ -8,6 +8,7 @@ using System.Reactive.Linq;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.Lib;
+using Wabbajack.UI;
namespace Wabbajack
{
diff --git a/Wabbajack/View Models/ModVM.cs b/Wabbajack/View Models/ModVM.cs
index 9b72d593..9e2344b7 100644
--- a/Wabbajack/View Models/ModVM.cs
+++ b/Wabbajack/View Models/ModVM.cs
@@ -8,6 +8,7 @@ using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
+using Wabbajack.UI;
namespace Wabbajack
{
diff --git a/Wabbajack/View Models/ModeSelectionVM.cs b/Wabbajack/View Models/ModeSelectionVM.cs
index 33330252..cb8b3f8f 100644
--- a/Wabbajack/View Models/ModeSelectionVM.cs
+++ b/Wabbajack/View Models/ModeSelectionVM.cs
@@ -6,6 +6,7 @@ using System.Reactive.Linq;
using System.Windows.Input;
using Wabbajack.Common;
using Wabbajack.Lib;
+using Wabbajack.UI;
namespace Wabbajack
{
diff --git a/Wabbajack/View Models/UserInterventionHandlers.cs b/Wabbajack/View Models/UserInterventionHandlers.cs
index 3694bbb8..88c5e731 100644
--- a/Wabbajack/View Models/UserInterventionHandlers.cs
+++ b/Wabbajack/View Models/UserInterventionHandlers.cs
@@ -8,6 +8,7 @@ using System.Windows;
using System.Windows.Threading;
using ReactiveUI;
using Wabbajack.Common;
+using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.WebAutomation;
@@ -65,14 +66,27 @@ namespace Wabbajack
c.Resume(key);
});
break;
- case RequestLoversLabLogin c:
+ case AbstractNeedsLoginDownloader.RequestSiteLogin c:
await WrapBrowserJob(msg, async (vm, cancel) =>
{
await vm.Driver.WaitForInitialized();
- var data = await LoversLabDownloader.GetAndCacheLoversLabCookies(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token);
+ var data = await c.Downloader.GetAndCacheCookies(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token);
c.Resume(data);
});
break;
+ case YesNoIntervention c:
+ var result = MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.YesNo,
+ MessageBoxImage.Question);
+ if (result == MessageBoxResult.Yes)
+ c.Confirm();
+ else
+ c.Cancel();
+ break;
+ case CriticalFailureIntervention c:
+ MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.OK,
+ MessageBoxImage.Error);
+ c.Cancel();
+ break;
case ConfirmationIntervention c:
break;
default:
diff --git a/Wabbajack/Views/Common/FilePicker.xaml.cs b/Wabbajack/Views/Common/FilePicker.xaml.cs
index 601f9203..d1a78e34 100644
--- a/Wabbajack/Views/Common/FilePicker.xaml.cs
+++ b/Wabbajack/Views/Common/FilePicker.xaml.cs
@@ -2,6 +2,7 @@
using System.Windows.Controls;
using System.Windows.Data;
using Wabbajack.Lib;
+using Wabbajack.UI;
namespace Wabbajack
{
diff --git a/Wabbajack/Views/MainWindow.xaml.cs b/Wabbajack/Views/MainWindow.xaml.cs
index 5cf16005..5491cba0 100644
--- a/Wabbajack/Views/MainWindow.xaml.cs
+++ b/Wabbajack/Views/MainWindow.xaml.cs
@@ -48,9 +48,7 @@ namespace Wabbajack
}).FireAndForget();
// Load settings
- string[] args = Environment.GetCommandLineArgs();
- if ((args.Length > 1 && args[1] == "nosettings")
- || !MainSettings.TryLoadTypicalSettings(out var settings))
+ if (CLIArguments.NoSettings || !MainSettings.TryLoadTypicalSettings(out var settings))
{
_settings = new MainSettings();
RunWhenLoaded(DefaultSettings);
diff --git a/Wabbajack/Wabbajack.csproj b/Wabbajack/Wabbajack.csproj
index 5e8640e3..87da201b 100644
--- a/Wabbajack/Wabbajack.csproj
+++ b/Wabbajack/Wabbajack.csproj
@@ -172,6 +172,9 @@
MSBuild:Compile
Designer
+
+
+
@@ -555,7 +558,7 @@
11.1.1
- 11.1.1
+ 11.1.6
11.1.1