mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Merge remote-tracking branch 'wabbajack-tools/master' into settings-pane
This commit is contained in:
commit
c5dd5c05bc
13
CHANGELOG.md
13
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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
104
Wabbajack.Common/CLI.cs
Normal file
104
Wabbajack.Common/CLI.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the argument and sets the properties of <see cref="CLIArguments"/>
|
||||
/// </summary>
|
||||
/// <param name="args"><see cref="Environment.GetCommandLineArgs"/></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<byte> acc = new List<byte>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<string, string> ExtList = new Dictionary<string, string>
|
||||
@ -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()
|
||||
|
@ -178,7 +178,7 @@ namespace Wabbajack.Common
|
||||
MO2Name = "New Vegas",
|
||||
MO2ArchiveName = "falloutnv",
|
||||
GameLocationRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\falloutnv",
|
||||
SteamIDs = new List<int> {22380},
|
||||
SteamIDs = new List<int> {22380, 22490}, // normal and RU version
|
||||
RequiredFiles = new List<string>
|
||||
{
|
||||
"FalloutNV.exe"
|
||||
|
@ -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<T>(this T obj, string filename, SerializerConfig config)
|
||||
{
|
||||
byte[] final;
|
||||
final = ToCERAS(obj, config);
|
||||
File.WriteAllBytes(filename, final);
|
||||
}
|
||||
|
||||
public static byte[] ToCERAS<T>(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<T>(this Stream data, SerializerConfig config)
|
||||
@ -1112,6 +1123,20 @@ namespace Wabbajack.Common
|
||||
return path.ToLower().TrimEnd('\\').StartsWith(parent.ToLower().TrimEnd('\\') + "\\");
|
||||
}
|
||||
|
||||
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> coll)
|
||||
{
|
||||
var hs = new HashSet<T>();
|
||||
coll.Do(v => hs.Add(v));
|
||||
return hs;
|
||||
}
|
||||
|
||||
public static HashSet<T> ToHashSet<T>(this T[] coll)
|
||||
{
|
||||
var hs = new HashSet<T>();
|
||||
coll.Do(v => hs.Add(v));
|
||||
return hs;
|
||||
}
|
||||
|
||||
public class NexusErrorResponse
|
||||
{
|
||||
public int code;
|
||||
|
@ -5,35 +5,35 @@
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="7z.dll.gz" />
|
||||
<None Remove="7z.exe.gz" />
|
||||
<None Remove="innounp.exe.gz" />
|
||||
<None Remove="7z.dll.gz" />
|
||||
<None Remove="7z.exe.gz" />
|
||||
<None Remove="innounp.exe.gz" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="7z.dll.gz" />
|
||||
<EmbeddedResource Include="7z.exe.gz" />
|
||||
<EmbeddedResource Include="innounp.exe.gz" />
|
||||
<EmbeddedResource Include="7z.dll.gz" />
|
||||
<EmbeddedResource Include="7z.exe.gz" />
|
||||
<EmbeddedResource Include="innounp.exe.gz" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="KnownFolders\" />
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ceras" Version="4.1.7" />
|
||||
<PackageReference Include="Genbox.AlphaFS" Version="2.2.2.1" />
|
||||
<PackageReference Include="ini-parser-netstandard" Version="2.5.2" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="ReactiveUI" Version="11.1.1" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.2.0" />
|
||||
<PackageReference Include="System.Data.HashFunction.xxHash" Version="2.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.7.0" />
|
||||
<PackageReference Include="System.Security.Principal.Windows" Version="4.7.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="Ceras" Version="4.1.7" />
|
||||
<PackageReference Include="Genbox.AlphaFS" Version="2.2.2.1" />
|
||||
<PackageReference Include="ini-parser-netstandard" Version="2.5.2" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="ReactiveUI" Version="11.1.1" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.2.0" />
|
||||
<PackageReference Include="System.Data.HashFunction.xxHash" Version="2.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.7.0" />
|
||||
<PackageReference Include="System.Security.Principal.Windows" Version="4.7.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Compression.BSA\Compression.BSA.csproj" />
|
||||
<ProjectReference Include="..\OMODExtractor\OMODExtractor.csproj" />
|
||||
<ProjectReference Include="..\Wabbajack.Common.CSP\Wabbajack.Common.CSP.csproj" />
|
||||
<ProjectReference Include="..\Compression.BSA\Compression.BSA.csproj" />
|
||||
<ProjectReference Include="..\OMODExtractor\OMODExtractor.csproj" />
|
||||
<ProjectReference Include="..\Wabbajack.Common.CSP\Wabbajack.Common.CSP.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -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<string, string> 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<FromArchive>()
|
||||
|
@ -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)
|
||||
|
||||
},
|
||||
};
|
||||
|
@ -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")))
|
||||
|
@ -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<Directive> Run(RawSourceFile source)
|
||||
{
|
||||
if (_gameFolderFilesExists)
|
||||
{
|
||||
if (source.AbsolutePath.IsInPath(_gameFolder))
|
||||
{
|
||||
var result = source.EvolveTo<IgnoredDirectly>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,8 +22,14 @@ namespace Wabbajack.Lib.CompilationSteps
|
||||
|
||||
public override async ValueTask<Directive> 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;
|
||||
|
@ -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
|
||||
/// </summary>
|
||||
public bool ReadmeIsWebsite;
|
||||
|
||||
public ModList Clone()
|
||||
{
|
||||
return new MemoryStream(this.ToCERAS(CerasConfig.Config)).FromCERAS<ModList>(CerasConfig.Config);
|
||||
}
|
||||
}
|
||||
|
||||
public class Directive
|
||||
|
@ -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<string, Type> NameToType { get; set; }
|
||||
public static Dictionary<Type, string> TypeToName { get; set; }
|
||||
|
155
Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs
Normal file
155
Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs
Normal file
@ -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<TDownloader, TState> : AbstractNeedsLoginDownloader, IDownloader
|
||||
where TState : AbstractIPS4Downloader<TDownloader, TState>.State<TDownloader>, new()
|
||||
where TDownloader : IDownloader
|
||||
{
|
||||
public override string SiteName { get; }
|
||||
public override Uri SiteURL { get; }
|
||||
public async Task<AbstractDownloadState> 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<TDownloader> : 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<Stream> ResolveDownloadStream()
|
||||
{
|
||||
var downloader = (AbstractNeedsLoginDownloader)(object)DownloadDispatcher.GetInstance<TDownloader>();
|
||||
|
||||
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<Match>();
|
||||
|
||||
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<WaitResponse>();
|
||||
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<bool> Verify()
|
||||
{
|
||||
var stream = await ResolveDownloadStream();
|
||||
if (stream == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stream.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override IDownloader GetDownloader()
|
||||
{
|
||||
return DownloadDispatcher.GetInstance<TDownloader>();
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
142
Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs
Normal file
142
Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs
Normal file
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up all the login facilites needed for a INeedsLogin downloader based on having the user log
|
||||
/// in via a browser
|
||||
/// </summary>
|
||||
/// <param name="loginUri">The URI to preset for logging in</param>
|
||||
/// <param name="encryptedKeyName">The name of the encrypted JSON key in which to store cookies</param>
|
||||
/// <param name="cookieDomain">The cookie domain to scan</param>
|
||||
/// <param name="cookieName">The cookie name to wait for</param>
|
||||
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<bool> 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<Helpers.Cookie[]> GetAndCacheCookies(IWebDriver browser, Action<string> 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<HttpClient> GetAuthedClient()
|
||||
{
|
||||
Helpers.Cookie[] cookies;
|
||||
try
|
||||
{
|
||||
cookies = Utils.FromEncryptedJson<Helpers.Cookie[]>(_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<Helpers.Cookie[]> _source = new TaskCompletionSource<Helpers.Cookie[]>();
|
||||
public Task<Helpers.Cookie[]> Task => _source.Task;
|
||||
|
||||
public void Resume(Helpers.Cookie[] cookies)
|
||||
{
|
||||
Handled = true;
|
||||
_source.SetResult(cookies);
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
{
|
||||
Handled = true;
|
||||
_source.TrySetCanceled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
24
Wabbajack.Lib/Downloaders/DeadlyStreamDownloader.cs
Normal file
24
Wabbajack.Lib/Downloaders/DeadlyStreamDownloader.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public class DeadlyStreamDownloader : AbstractIPS4Downloader<DeadlyStreamDownloader, DeadlyStreamDownloader.State>
|
||||
{
|
||||
#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<DeadlyStreamDownloader>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,8 @@ namespace Wabbajack.Lib.Downloaders
|
||||
new NexusDownloader(),
|
||||
new MediaFireDownloader(),
|
||||
new LoversLabDownloader(),
|
||||
new VectorPlexusDownloader(),
|
||||
new DeadlyStreamDownloader(),
|
||||
new HTTPDownloader(),
|
||||
new ManualDownloader(),
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<LoversLabDownloader, LoversLabDownloader.State>
|
||||
{
|
||||
internal HttpClient _authedClient;
|
||||
|
||||
|
||||
#region INeedsDownload
|
||||
|
||||
public ReactiveCommand<Unit, Unit> TriggerLogin { get; }
|
||||
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
|
||||
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable("loverslabcookies");
|
||||
public string SiteName => "Lovers Lab";
|
||||
public IObservable<string> 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<AbstractDownloadState> 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<Helpers.Cookie[]> GetAndCacheLoversLabCookies(IWebDriver browser, Action<string> updateStatus, CancellationToken cancel)
|
||||
{
|
||||
updateStatus("Please Log Into Lovers Lab");
|
||||
await browser.NavigateTo(new Uri("https://www.loverslab.com/login"));
|
||||
async Task<bool> 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<HttpClient> GetAuthedClient()
|
||||
{
|
||||
Helpers.Cookie[] cookies;
|
||||
try
|
||||
{
|
||||
cookies = Utils.FromEncryptedJson<Helpers.Cookie[]>("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<Stream> ResolveDownloadStream()
|
||||
{
|
||||
var result = DownloadDispatcher.GetInstance<LoversLabDownloader>();
|
||||
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<Match>().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<WaitResponse>();
|
||||
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<bool> Verify()
|
||||
{
|
||||
var stream = await ResolveDownloadStream();
|
||||
if (stream == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stream.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override IDownloader GetDownloader()
|
||||
{
|
||||
return DownloadDispatcher.GetInstance<LoversLabDownloader>();
|
||||
}
|
||||
|
||||
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<Helpers.Cookie[]> _source = new TaskCompletionSource<Helpers.Cookie[]>();
|
||||
public Task<Helpers.Cookie[]> Task => _source.Task;
|
||||
|
||||
public void Resume(Helpers.Cookie[] cookies)
|
||||
public class State : State<LoversLabDownloader>
|
||||
{
|
||||
Handled = true;
|
||||
_source.SetResult(cookies);
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
{
|
||||
Handled = true;
|
||||
_source.TrySetCanceled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
|
22
Wabbajack.Lib/Downloaders/VectorPlexusDownloader.cs
Normal file
22
Wabbajack.Lib/Downloaders/VectorPlexusDownloader.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
public class VectorPlexusDownloader : AbstractIPS4Downloader<VectorPlexusDownloader, VectorPlexusDownloader.State>
|
||||
{
|
||||
#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<VectorPlexusDownloader>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
17
Wabbajack.Lib/Exceptions/HttpException.cs
Normal file
17
Wabbajack.Lib/Exceptions/HttpException.cs
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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<RawSourceFile> 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<RawSourceFile>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
ModMetas = Directory.EnumerateDirectories(Path.Combine(MO2Folder, "mods"))
|
||||
.Keep(f =>
|
||||
{
|
||||
@ -496,6 +507,7 @@ namespace Wabbajack.Lib
|
||||
Utils.Log("Generating compilation stack");
|
||||
return new List<ICompilationStep>
|
||||
{
|
||||
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"),
|
||||
|
||||
|
@ -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<NexusDownloader.State>()
|
||||
.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<CreateBSA>().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);
|
||||
|
@ -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; }
|
||||
|
@ -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
|
||||
|
@ -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")]
|
@ -129,7 +129,7 @@ namespace Wabbajack.Lib
|
||||
.Concat(lst.Directives
|
||||
.OfType<PatchedFromArchive>()
|
||||
.Select(f => (f.To, "patched", SizeForID(f.PatchID))))
|
||||
.ToHashSet()
|
||||
.Distinct()
|
||||
.OrderByDescending(f => f.Item3);
|
||||
|
||||
NoWrapText("\n\n### Summary of inlined files in this installer");
|
||||
|
27
Wabbajack.Lib/StatusMessages/CriticalFailureIntervention.cs
Normal file
27
Wabbajack.Lib/StatusMessages/CriticalFailureIntervention.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
/// <summary>
|
||||
/// This should probably be replaced with an error, but this is just to get messageboxes out of the .Lib library
|
||||
/// </summary>
|
||||
public class CriticalFailureIntervention : AUserIntervention
|
||||
{
|
||||
private TaskCompletionSource<ConfirmationIntervention.Choice> _source = new TaskCompletionSource<ConfirmationIntervention.Choice>();
|
||||
public Task<ConfirmationIntervention.Choice> 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);
|
||||
}
|
||||
}
|
||||
}
|
15
Wabbajack.Lib/StatusMessages/YesNoIntervention.cs
Normal file
15
Wabbajack.Lib/StatusMessages/YesNoIntervention.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
8
Wabbajack.Lib/SystemParameters.cs
Normal file
8
Wabbajack.Lib/SystemParameters.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Wabbajack.Lib
|
||||
{
|
||||
public class SystemParameters
|
||||
{
|
||||
public int ScreenHeight { get; set; }
|
||||
public int ScreenWidth { get; set; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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<SteamMeta>()
|
||||
|
@ -1,248 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{0A820830-A298-497D-85E0-E9A89EFEF5FE}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Wabbajack.Lib</RootNamespace>
|
||||
<AssemblyName>Wabbajack.Lib</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<NoWarn>CS1998</NoWarn>
|
||||
<WarningsAsErrors>CS4014</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn>CS1998</NoWarn>
|
||||
<WarningsAsErrors>CS4014</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors>CS4014</WarningsAsErrors>
|
||||
<NoWarn>CS1998</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<WarningsAsErrors>CS4014</WarningsAsErrors>
|
||||
<NoWarn>CS1998</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="MongoDB.Bson">
|
||||
<HintPath>..\..\..\Users\tbald\.nuget\packages\mongodb.bson\2.10.0\lib\net452\MongoDB.Bson.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Design" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Windows" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ABatchProcessor.cs" />
|
||||
<Compile Include="ACompiler.cs" />
|
||||
<Compile Include="AInstaller.cs" />
|
||||
<Compile Include="CerasConfig.cs" />
|
||||
<Compile Include="CompilationSteps\ACompilationStep.cs" />
|
||||
<Compile Include="CompilationSteps\CompilationErrors\InvalidGameESMError.cs" />
|
||||
<Compile Include="CompilationSteps\DeconstructBSAs.cs" />
|
||||
<Compile Include="CompilationSteps\DirectMatch.cs" />
|
||||
<Compile Include="CompilationSteps\DropAll.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreDisabledMods.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreDisabledVortexMods.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreEndsWith.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreGameFiles.cs" />
|
||||
<Compile Include="CompilationSteps\IgnorePathContains.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreRegex.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreStartsWith.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreVortex.cs" />
|
||||
<Compile Include="CompilationSteps\IgnoreWabbajackInstallCruft.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeAll.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeAllConfigs.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeDummyESPs.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeLootFiles.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeModIniData.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeOtherProfiles.cs" />
|
||||
<Compile Include="CompilationSteps\IncludePatches.cs" />
|
||||
<Compile Include="CompilationSteps\IncludePropertyFiles.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeRegex.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeSteamWorkshopItems.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeStubbedConfigFiles.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeTaggedMods.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeThisProfile.cs" />
|
||||
<Compile Include="CompilationSteps\IncludeVortexDeployment.cs" />
|
||||
<Compile Include="CompilationSteps\IStackStep.cs" />
|
||||
<Compile Include="CompilationSteps\PatchStockESMs.cs" />
|
||||
<Compile Include="CompilationSteps\Serialization.cs" />
|
||||
<Compile Include="Downloaders\GameFileSourceDownloader.cs" />
|
||||
<Compile Include="Downloaders\INeedsLogin.cs" />
|
||||
<Compile Include="Downloaders\LoversLabDownloader.cs" />
|
||||
<Compile Include="Downloaders\SteamWorkshopDownloader.cs" />
|
||||
<Compile Include="Extensions\ReactiveUIExt.cs" />
|
||||
<Compile Include="LibCefHelpers\Init.cs" />
|
||||
<Compile Include="MO2Compiler.cs" />
|
||||
<Compile Include="Data.cs" />
|
||||
<Compile Include="Downloaders\AbstractDownloadState.cs" />
|
||||
<Compile Include="Downloaders\DownloadDispatcher.cs" />
|
||||
<Compile Include="Downloaders\DownloaderUtils.cs" />
|
||||
<Compile Include="Downloaders\DropboxDownloader.cs" />
|
||||
<Compile Include="Downloaders\GoogleDriveDownloader.cs" />
|
||||
<Compile Include="Downloaders\HTTPDownloader.cs" />
|
||||
<Compile Include="Downloaders\IDownloader.cs" />
|
||||
<Compile Include="Downloaders\IUrlDownloader.cs" />
|
||||
<Compile Include="Downloaders\ManualDownloader.cs" />
|
||||
<Compile Include="Downloaders\MediaFireDownloader.cs" />
|
||||
<Compile Include="Downloaders\MEGADownloader.cs" />
|
||||
<Compile Include="Downloaders\ModDBDownloader.cs" />
|
||||
<Compile Include="Downloaders\NexusDownloader.cs" />
|
||||
<Compile Include="IBatchProcessor.cs" />
|
||||
<Compile Include="MO2Installer.cs" />
|
||||
<Compile Include="ModListRegistry\ModListMetadata.cs" />
|
||||
<Compile Include="NexusApi\Dtos.cs" />
|
||||
<Compile Include="NexusApi\NexusApi.cs" />
|
||||
<Compile Include="NexusApi\NexusApiUtils.cs" />
|
||||
<Compile Include="NexusApi\RequestNexusAuthorization.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ReportBuilder.cs" />
|
||||
<Compile Include="StatusMessages\ConfirmUpdateOfExistingInstall.cs" />
|
||||
<Compile Include="UI\FilePickerVM.cs" />
|
||||
<Compile Include="UI\UIUtils.cs" />
|
||||
<Compile Include="Validation\DTOs.cs" />
|
||||
<Compile Include="Validation\ValidateModlist.cs" />
|
||||
<Compile Include="ViewModel.cs" />
|
||||
<Compile Include="VortexCompiler.cs" />
|
||||
<Compile Include="VortexInstaller.cs" />
|
||||
<Compile Include="WebAutomation\CefSharpWrapper.cs" />
|
||||
<Compile Include="WebAutomation\IWebDriver.cs" />
|
||||
<Compile Include="WebAutomation\WebAutomation.cs" />
|
||||
<Compile Include="zEditIntegration.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<EmbeddedResource Include="LibCefHelpers\cefsharp.7z" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(SolutionDir)\Compression.BSA\Compression.BSA.csproj">
|
||||
<Project>{ff5d892f-8ff4-44fc-8f7f-cd58f307ad1b}</Project>
|
||||
<Name>Compression.BSA</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)\Wabbajack.Common\Wabbajack.Common.csproj">
|
||||
<Project>{b3f3fb6e-b9eb-4f49-9875-d78578bc7ae5}</Project>
|
||||
<Name>Wabbajack.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)\Wabbajack.VirtualFileSystem\Wabbajack.VirtualFileSystem.csproj">
|
||||
<Project>{5D6A2EAF-6604-4C51-8AE2-A746B4BC5E3E}</Project>
|
||||
<Name>Wabbajack.VirtualFileSystem</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="css-min.css" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="css.css" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AlphaFS">
|
||||
<Version>2.2.6</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CefSharp.OffScreen">
|
||||
<Version>75.1.143</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Ceras">
|
||||
<Version>4.1.7</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommonMark.NET">
|
||||
<Version>0.15.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="HtmlAgilityPack">
|
||||
<Version>1.11.17</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MegaApiClient">
|
||||
<Version>1.7.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell">
|
||||
<Version>1.1.3.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Toolkit.Wpf.UI.Controls.WebView">
|
||||
<Version>6.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ModuleInit.Fody">
|
||||
<Version>2.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>12.0.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ReactiveUI">
|
||||
<Version>11.1.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ReactiveUI.Fody">
|
||||
<Version>11.1.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SharpCompress">
|
||||
<Version>0.24.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Syroot.Windows.IO.KnownFolders">
|
||||
<Version>1.2.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Reactive">
|
||||
<Version>4.3.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="WebSocketSharpFork">
|
||||
<Version>1.0.4</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="YamlDotNet">
|
||||
<Version>8.0.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CefSharp.Common">
|
||||
<Version>75.1.143</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CefSharp.OffScreen">
|
||||
<Version>75.1.143</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Ceras">
|
||||
<Version>4.1.7</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommonMark.NET">
|
||||
<Version>0.15.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Fody">
|
||||
<Version>6.0.6</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Genbox.AlphaFS">
|
||||
<Version>2.2.2.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="HtmlAgilityPack">
|
||||
<Version>1.11.17</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MegaApiClient">
|
||||
<Version>1.7.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CSharp">
|
||||
<Version>4.7.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ModuleInit.Fody">
|
||||
<Version>2.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ReactiveUI">
|
||||
<Version>11.1.6</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ReactiveUI.Fody">
|
||||
<Version>11.1.6</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SharpCompress">
|
||||
<Version>0.24.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Collections.Immutable">
|
||||
<Version>1.7.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Drawing.Common">
|
||||
<Version>4.7.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Net.Http">
|
||||
<Version>4.3.4</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="WebSocketSharp-netstandard">
|
||||
<Version>1.0.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="YamlDotNet.NetCore">
|
||||
<Version>1.0.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Compression.BSA\Compression.BSA.csproj" />
|
||||
<ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj" />
|
||||
<ProjectReference Include="..\Wabbajack.VirtualFileSystem\Wabbajack.VirtualFileSystem.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="LibCefHelpers\cefsharp.7z" />
|
||||
<EmbeddedResource Include="LibCefHelpers\cefsharp.7z" />
|
||||
<None Remove="css-min.css" />
|
||||
<EmbeddedResource Include="css-min.css" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -310,6 +310,34 @@ namespace Wabbajack.Test
|
||||
|
||||
Assert.AreEqual(File.ReadAllText(filename), "Cheese for Everyone!");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task VectorPlexusDownload()
|
||||
{
|
||||
await DownloadDispatcher.GetInstance<VectorPlexusDownloader>().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<string>() }));
|
||||
|
||||
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()
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using DynamicData;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Lib;
|
||||
using Wabbajack.UI;
|
||||
|
||||
namespace Wabbajack.Test
|
||||
{
|
||||
|
27
Wabbajack.Test/IniTests.cs
Normal file
27
Wabbajack.Test/IniTests.cs
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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<string, byte[]> {{"/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<string, byte[]> {{"/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));
|
||||
}
|
||||
|
||||
|
||||
|
@ -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<string> {"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<string> {".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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,6 +127,7 @@
|
||||
<Compile Include="EndToEndTests.cs" />
|
||||
<Compile Include="Extensions.cs" />
|
||||
<Compile Include="FilePickerTests.cs" />
|
||||
<Compile Include="IniTests.cs" />
|
||||
<Compile Include="MiscTests.cs" />
|
||||
<Compile Include="MO2Tests.cs" />
|
||||
<Compile Include="ModlistMetadataTests.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();
|
||||
|
||||
|
@ -14,7 +14,9 @@ namespace Wabbajack
|
||||
{
|
||||
public App()
|
||||
{
|
||||
// Do initialization in MainWindow ctor
|
||||
CLI.ParseOptions(Environment.GetCommandLineArgs());
|
||||
if(CLIArguments.Help)
|
||||
CLI.DisplayHelpText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")]
|
||||
|
@ -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
|
||||
{
|
@ -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
|
||||
{
|
17
Wabbajack/Util/SystemParametersConstructor.cs
Normal file
17
Wabbajack/Util/SystemParametersConstructor.cs
Normal file
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ using System.Windows.Media.Imaging;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Common.StatusFeed;
|
||||
using Wabbajack.Lib;
|
||||
using Wabbajack.UI;
|
||||
|
||||
namespace Wabbajack
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Lib;
|
||||
using Wabbajack.UI;
|
||||
|
||||
namespace Wabbajack
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using Microsoft.WindowsAPICodePack.Dialogs;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.Lib;
|
||||
using Wabbajack.UI;
|
||||
|
||||
namespace Wabbajack
|
||||
{
|
||||
|
@ -11,6 +11,7 @@ using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Common.StoreHandlers;
|
||||
using Wabbajack.Lib;
|
||||
using Wabbajack.UI;
|
||||
|
||||
namespace Wabbajack
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 () =>
|
||||
{
|
||||
|
@ -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 () =>
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ using System.Reactive.Linq;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Lib;
|
||||
using Wabbajack.UI;
|
||||
|
||||
namespace Wabbajack
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ using Wabbajack.Common;
|
||||
using Wabbajack.Lib;
|
||||
using Wabbajack.Lib.Downloaders;
|
||||
using Wabbajack.Lib.NexusApi;
|
||||
using Wabbajack.UI;
|
||||
|
||||
namespace Wabbajack
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Lib;
|
||||
using Wabbajack.UI;
|
||||
|
||||
namespace Wabbajack
|
||||
{
|
||||
|
@ -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:
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using Wabbajack.Lib;
|
||||
using Wabbajack.UI;
|
||||
|
||||
namespace Wabbajack
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -172,6 +172,9 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Compile Include="UI\FilePickerVM.cs" />
|
||||
<Compile Include="UI\UIUtils.cs" />
|
||||
<Compile Include="Util\SystemParametersConstructor.cs" />
|
||||
<Compile Include="Converters\IntDownCastConverter.cs" />
|
||||
<Compile Include="Converters\CommandConverter.cs" />
|
||||
<Compile Include="Converters\ConverterRegistration.cs" />
|
||||
@ -555,7 +558,7 @@
|
||||
<Version>11.1.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ReactiveUI.Fody">
|
||||
<Version>11.1.1</Version>
|
||||
<Version>11.1.6</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ReactiveUI.WPF">
|
||||
<Version>11.1.1</Version>
|
||||
|
Loading…
Reference in New Issue
Block a user