Merge remote-tracking branch 'wabbajack-tools/master' into async-exploration

This commit is contained in:
Justin Swanson 2019-12-08 00:55:27 -06:00
commit 86d4003398
23 changed files with 502 additions and 79 deletions

View File

@ -10,6 +10,7 @@ namespace Wabbajack.Common
public static bool TestMode { get; set; } = false;
public static string GameFolderFilesDir = "Game Folder Files";
public static string ManualGameFilesDir = "Manual Game Files";
public static string LOOTFolderFilesDir = "LOOT Config Files";
public static string BSACreationDir = "TEMP_BSA_FILES";

View File

@ -67,19 +67,12 @@ namespace Wabbajack.Common
public List<string> RequiredFiles { get; internal set; }
public bool Disabled { get; internal set; }
public string GameLocation
public string GameLocation(bool steam)
{
get
{
if (Consts.TestMode)
return Directory.GetCurrentDirectory();
if (Consts.TestMode)
return Directory.GetCurrentDirectory();
return (string) Registry.GetValue(GameLocationRegistryKey, "installed path", null)
??
(string) Registry.GetValue(
GameLocationRegistryKey.Replace(@"HKEY_LOCAL_MACHINE\SOFTWARE\",
@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\"), "installed path", null);
}
return steam ? SteamHandler.Instance.Games.FirstOrDefault(g => g.Game == Game)?.InstallDir : GOGHandler.Instance.Games.FirstOrDefault(g => g.Game == Game)?.Path;
}
}

View File

@ -17,6 +17,7 @@ using ICSharpCode.SharpZipLib.BZip2;
using IniParser;
using Newtonsoft.Json;
using ReactiveUI;
using Syroot.Windows.IO;
using Wabbajack.Common.StatusFeed;
using Wabbajack.Common.StatusFeed.Errors;
using YamlDotNet.Serialization;
@ -923,5 +924,28 @@ namespace Wabbajack.Common
}
p.WaitForExit();
}
/// <summary>
/// Writes a file to JSON but in an encrypted format in the user's app local directory.
/// The data will be encrypted so that it can only be read by this machine and this user.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="data"></param>
public static void ToEcryptedJson<T>(this T data, string key)
{
var bytes = Encoding.UTF8.GetBytes(data.ToJSON());
var encoded = ProtectedData.Protect(bytes, Encoding.UTF8.GetBytes(key), DataProtectionScope.LocalMachine);
var path = Path.Combine(KnownFolders.LocalAppData.Path, "Wabbajack", key);
File.WriteAllBytes(path, encoded);
}
public static T FromEncryptedJson<T>(string key)
{
var path = Path.Combine(KnownFolders.LocalAppData.Path, "Wabbajack", key);
var bytes = File.ReadAllBytes(path);
var decoded = ProtectedData.Unprotect(bytes, Encoding.UTF8.GetBytes(key), DataProtectionScope.LocalMachine);
return Encoding.UTF8.GetString(decoded).FromJSONString<T>();
}
}
}

View File

@ -75,12 +75,16 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Syroot.KnownFolders">
<HintPath>..\..\..\Users\tbald\.nuget\packages\syroot.windows.io.knownfolders\1.2.1\lib\net452\Syroot.KnownFolders.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Numerics" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Security" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" />
@ -169,6 +173,9 @@
<PackageReference Include="ReactiveUI">
<Version>10.5.30</Version>
</PackageReference>
<PackageReference Include="Syroot.Windows.IO.KnownFolders">
<Version>1.2.1</Version>
</PackageReference>
<PackageReference Include="System.Data.HashFunction.xxHash">
<Version>2.0.0</Version>
</PackageReference>

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Alphaleonis.Win32.Filesystem;
using Wabbajack.Common;
using Xilium.CefGlue;
namespace Wabbajack.Lib.LibCefHelpers
{
public static class Helpers
{
/// <summary>
/// We bundle the cef libs inside the .exe, we need to extract them before loading any wpf code that requires them
/// </summary>
public static void ExtractLibs()
{
if (File.Exists("cefglue.7z") && File.Exists("libcef.dll")) return;
using (var fs = File.OpenWrite("cefglue.7z"))
using (var rs = Assembly.GetExecutingAssembly().GetManifestResourceStream("Wabbajack.Lib.LibCefHelpers.cefglue.7z"))
{
rs.CopyTo(fs);
Utils.Log("Extracting libCef files");
}
using (var wq = new WorkQueue(1))
FileExtractor.ExtractAll(wq, "cefglue.7z", ".");
}
public static async Task<Cookie[]> GetCookies(string domainEnding)
{
var manager = CefCookieManager.GetGlobal(null);
var visitor = new CookieVisitor();
if (!manager.VisitAllCookies(visitor))
return new Cookie[0];
var cc = await visitor.Task;
return (await visitor.Task).Where(c => c.Domain.EndsWith(domainEnding)).ToArray();
}
private class CookieVisitor : CefCookieVisitor
{
TaskCompletionSource<List<Cookie>> _source = new TaskCompletionSource<List<Cookie>>();
public Task<List<Cookie>> Task => _source.Task;
public List<Cookie> Cookies { get; } = new List<Cookie>();
protected override bool Visit(CefCookie cookie, int count, int total, out bool delete)
{
Cookies.Add(new Cookie
{
Name = cookie.Name,
Value = cookie.Value,
Domain = cookie.Domain,
Path = cookie.Path
});
if (count == total)
_source.SetResult(Cookies);
delete = false;
return true;
}
protected override void Dispose(bool disposing)
{
if (disposing)
_source.SetResult(Cookies);
}
}
public class Cookie
{
public string Name { get; set; }
public string Value { get; set; }
public string Domain { get; set; }
public string Path { get; set; }
}
}
}

Binary file not shown.

View File

@ -44,7 +44,7 @@ namespace Wabbajack.Lib
var game = GameRegistry.Games[ModList.GameType];
if (GameFolder == null)
GameFolder = game.GameLocation;
GameFolder = game.GameLocation(SteamHandler.Instance.Games.Any(g => g.Game == game.Game));
if (GameFolder == null)
{

View File

@ -1,4 +1,4 @@
using ReactiveUI;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -12,10 +12,16 @@ using System.Security.Authentication;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Documents;
using Syroot.Windows.IO;
using Wabbajack.Common;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.LibCefHelpers;
using WebSocketSharp;
using Xilium.CefGlue;
using Xilium.CefGlue.Common;
using Xilium.CefGlue.Common.Handlers;
using Xilium.CefGlue.WPF;
using static Wabbajack.Lib.NexusApi.NexusApiUtils;
using System.Threading;
@ -26,18 +32,13 @@ namespace Wabbajack.Lib.NexusApi
private static readonly string API_KEY_CACHE_FILE = "nexus.key_cache";
private static string _additionalEntropy = "vtP2HF6ezg";
private readonly HttpClient _httpClient;
public HttpClient HttpClient => _httpClient;
public HttpClient HttpClient { get; } = new HttpClient();
#region Authentication
private readonly string _apiKey;
public string ApiKey { get; }
public string ApiKey => _apiKey;
public bool IsAuthenticated => _apiKey != null;
public bool IsAuthenticated => ApiKey != null;
private Task<UserStatus> _userStatus;
@ -71,25 +72,12 @@ namespace Wabbajack.Lib.NexusApi
File.Delete(API_KEY_CACHE_FILE);
}
var cacheFolder = Path.Combine(new KnownFolder(KnownFolderType.LocalAppData).Path, "Wabbajack");
if (!Directory.Exists(cacheFolder))
try
{
Directory.CreateDirectory(cacheFolder);
return Utils.FromEncryptedJson<string>("nexusapikey");
}
var cacheFile = Path.Combine(cacheFolder, _additionalEntropy);
if (File.Exists(cacheFile))
catch (Exception)
{
try
{
return Encoding.UTF8.GetString(
ProtectedData.Unprotect(File.ReadAllBytes(cacheFile),
Encoding.UTF8.GetBytes(_additionalEntropy), DataProtectionScope.CurrentUser));
}
catch (CryptographicException)
{
File.Delete(cacheFile);
}
}
var env_key = Environment.GetEnvironmentVariable("NEXUSAPIKEY");
@ -98,29 +86,8 @@ namespace Wabbajack.Lib.NexusApi
return env_key;
}
// open a web socket to receive the api key
var guid = Guid.NewGuid();
var _websocket = new WebSocket("wss://sso.nexusmods.com")
{
SslConfiguration =
{
EnabledSslProtocols = SslProtocols.Tls12
}
};
var api_key = new TaskCompletionSource<string>();
_websocket.OnMessage += (sender, msg) => { api_key.SetResult(msg.Data); };
_websocket.Connect();
_websocket.Send("{\"id\": \"" + guid + "\", \"appid\": \"" + Consts.AppName + "\"}");
// open a web browser to get user permission
Process.Start($"https://www.nexusmods.com/sso?id={guid}&application=" + Consts.AppName);
// get the api key from the socket and cache it
var result = await api_key.Task;
File.WriteAllBytes(cacheFile, ProtectedData.Protect(Encoding.UTF8.GetBytes(result),
Encoding.UTF8.GetBytes(_additionalEntropy), DataProtectionScope.CurrentUser));
var result = await Utils.Log(new RequestNexusAuthorization()).Task;
result.ToEcryptedJson("nexusapikey");
return result;
}
finally
@ -129,6 +96,60 @@ namespace Wabbajack.Lib.NexusApi
}
}
class RefererHandler : RequestHandler
{
private string _referer;
public RefererHandler(string referer)
{
_referer = referer;
}
protected override bool OnBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, bool userGesture, bool isRedirect)
{
base.OnBeforeBrowse(browser, frame, request, userGesture, isRedirect);
if (request.ReferrerURL == null)
request.SetReferrer(_referer, CefReferrerPolicy.Default);
return false;
}
}
public static async Task<string> SetupNexusLogin(BaseCefBrowser browser, Action<string> updateStatus)
{
updateStatus("Please Log Into the Nexus");
browser.Address = "https://users.nexusmods.com/auth/continue?client_id=nexus&redirect_uri=https://www.nexusmods.com/oauth/callback&response_type=code&referrer=//www.nexusmods.com";
while (true)
{
var cookies = (await Helpers.GetCookies("nexusmods.com"));
if (cookies.FirstOrDefault(c => c.Name == "member_id") != null)
break;
await Task.Delay(500);
}
// open a web socket to receive the api key
var guid = Guid.NewGuid();
var _websocket = new WebSocket("wss://sso.nexusmods.com")
{
SslConfiguration =
{
EnabledSslProtocols = SslProtocols.Tls12
}
};
updateStatus("Please Authorize Wabbajack to Download Mods");
var api_key = new TaskCompletionSource<string>();
_websocket.OnMessage += (sender, msg) => { api_key.SetResult(msg.Data); };
_websocket.Connect();
_websocket.Send("{\"id\": \"" + guid + "\", \"appid\": \"" + Consts.AppName + "\"}");
await Task.Delay(1000);
// open a web browser to get user permission
browser.Address = $"https://www.nexusmods.com/sso?id={guid}&application={Consts.AppName}";
return await api_key.Task;
}
public async Task<UserStatus> GetUserStatus()
{
var url = "https://api.nexusmods.com/v1/users/validate.json";
@ -189,16 +210,14 @@ namespace Wabbajack.Lib.NexusApi
#endregion
private NexusApiClient(string apiKey)
private NexusApiClient(string apiKey = null)
{
_apiKey = apiKey;
_httpClient = new HttpClient();
ApiKey = apiKey;
// set default headers for all requests to the Nexus API
var headers = _httpClient.DefaultRequestHeaders;
var headers = HttpClient.DefaultRequestHeaders;
headers.Add("User-Agent", Consts.UserAgent);
headers.Add("apikey", _apiKey);
headers.Add("apikey", ApiKey);
headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
headers.Add("Application-Name", Consts.AppName);
headers.Add("Application-Version", $"{Assembly.GetEntryAssembly()?.GetName()?.Version ?? new Version(0, 1)}");
@ -215,8 +234,7 @@ namespace Wabbajack.Lib.NexusApi
private async Task<T> Get<T>(string url)
{
HttpResponseMessage response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
var response = await HttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
UpdateRemaining(response);
using (var stream = await response.Content.ReadAsStreamAsync())
@ -327,7 +345,7 @@ namespace Wabbajack.Lib.NexusApi
var content = new FormUrlEncodedContent(new Dictionary<string, string> { { "version", mod.Version } });
using (var stream = await _httpClient.PostStream(url, content))
using (var stream = await HttpClient.PostStream(url, content))
{
return stream.FromJSON<EndorsementResponse>();
}
@ -368,6 +386,7 @@ namespace Wabbajack.Lib.NexusApi
set => _localCacheDir = value;
}
public async Task ClearUpdatedModsInCache()
{
if (!UseLocalCache) return;
@ -393,7 +412,7 @@ namespace Wabbajack.Lib.NexusApi
using (var queue = new WorkQueue())
{
var to_purge = (await Directory.EnumerateFiles(LocalCacheDir, "*.json")
.PMap(queue,f =>
.PMap(queue, f =>
{
Utils.Status("Cleaning Nexus cache for");
var uri = new Uri(Encoding.UTF8.GetString(Path.GetFileNameWithoutExtension(f).FromHex()));
@ -425,8 +444,6 @@ namespace Wabbajack.Lib.NexusApi
File.Delete(f.f);
});
}
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common.StatusFeed;
namespace Wabbajack.Lib.NexusApi
{
public class RequestNexusAuthorization : AStatusMessage, IUserIntervention
{
public override string ShortDescription => "Getting User's Nexus API Key";
public override string ExtendedDescription { get; }
private readonly TaskCompletionSource<string> _source = new TaskCompletionSource<string>();
public Task<string> Task => _source.Task;
public void Resume(string apikey)
{
_source.SetResult(apikey);
}
public void Cancel()
{
_source.SetCanceled();
}
}
}

View File

@ -190,7 +190,15 @@ namespace Wabbajack.Lib
#endif
var replace = f;
replace.Path = Path.Combine("Manual Game Files", element.FullPath.Substring(DownloadsFolder.Length + 1).Replace('|', '\\'));
var name = replace.File.Name;
var archiveName = targetArchive.Name;
var elementPath = element.FullPath.Substring(element.FullPath.IndexOf('|')+1);
var gameToFile = name.Substring(GamePath.Length + 1).Replace(elementPath, "");
if (gameToFile.EndsWith("\\"))
gameToFile = gameToFile.Substring(0, gameToFile.Length - 1);
//replace.Path = replace.Path.Replace(Consts.GameFolderFilesDir, Consts.ManualGameFilesDir);
replace.Path = Path.Combine(Consts.ManualGameFilesDir, archiveName, gameToFile, elementPath);
//replace.Path = Path.Combine(Consts.ManualGameFilesDir, element.FullPath.Substring(DownloadsFolder.Length + 1).Replace('|', '\\'));
AllFiles.RemoveAt(i);
AllFiles.Insert(i, replace);
//AllFiles.Replace(f, replace);
@ -324,7 +332,7 @@ namespace Wabbajack.Lib
.Where(File.Exists)
.Select(async f =>
{
if (Path.GetExtension(f) != ".meta" && !File.Exists($"{f}.meta") && ActiveArchives.Contains(Path.GetFileNameWithoutExtension(f)))
if (Path.GetExtension(f) != ".meta" && Path.GetExtension(f) != ".xxHash" && !File.Exists($"{f}.meta") && ActiveArchives.Contains(Path.GetFileNameWithoutExtension(f)))
{
Utils.Log($"Trying to create meta file for {Path.GetFileName(f)}");
var metaString = "[General]\n" +

View File

@ -1,10 +1,13 @@
using System.Diagnostics;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using System.Windows;
using Wabbajack.Common;
using Directory = Alphaleonis.Win32.Filesystem.Directory;
using DirectoryInfo = Alphaleonis.Win32.Filesystem.DirectoryInfo;
using File = Alphaleonis.Win32.Filesystem.File;
using Path = Alphaleonis.Win32.Filesystem.Path;
@ -76,6 +79,9 @@ namespace Wabbajack.Lib
if (cancel.IsCancellationRequested) return false;
await InstallIncludedFiles();
if (cancel.IsCancellationRequested) return false;
await InstallManualGameFiles();
if (cancel.IsCancellationRequested) return false;
await InstallSteamWorkshopItems();
//InstallIncludedDownloadMetas();
@ -84,6 +90,57 @@ namespace Wabbajack.Lib
return true;
}
private async Task InstallManualGameFiles()
{
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 " +
"the game folder. Do you want to do this manually or do you want Wabbajack " +
"to do this for you?", "Question", MessageBoxButton.YesNo);
if (result != MessageBoxResult.Yes)
return;
var manualFilesDir = Path.Combine(OutputFolder, Consts.ManualGameFilesDir);
var gameFolder = GameInfo.GameLocation(SteamHandler.Instance.Games.Any(g => g.Game == GameInfo.Game));
Info($"Copying files from {manualFilesDir} " +
$"to the game folder at {gameFolder}");
if (!Directory.Exists(manualFilesDir))
{
Info($"{manualFilesDir} does not exist!");
return;
}
await Directory.EnumerateDirectories(manualFilesDir).PMap(Queue, dir =>
{
var dirInfo = new DirectoryInfo(dir);
dirInfo.GetDirectories("*", SearchOption.AllDirectories).Do(d =>
{
var destPath = d.FullName.Replace(dir, gameFolder);
Status($"Creating directory {destPath}");
Directory.CreateDirectory(destPath);
});
dirInfo.GetFiles("*", SearchOption.AllDirectories).Do(f =>
{
var destPath = f.FullName.Replace(dir, gameFolder);
Status($"Copying file {f.FullName} to {destPath}");
try
{
File.Copy(f.FullName, destPath);
}
catch (Exception)
{
Info($"Could not copy file {f.FullName} to {destPath}. The file may already exist, skipping...");
}
});
});
}
private async Task InstallSteamWorkshopItems()
{
//var currentLib = "";

View File

@ -117,6 +117,7 @@
<Compile Include="CompilationSteps\PatchStockESMs.cs" />
<Compile Include="CompilationSteps\Serialization.cs" />
<Compile Include="Downloaders\SteamWorkshopDownloader.cs" />
<Compile Include="LibCefHelpers\Init.cs" />
<Compile Include="MO2Compiler.cs" />
<Compile Include="Data.cs" />
<Compile Include="Downloaders\AbstractDownloadState.cs" />
@ -138,6 +139,7 @@
<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" />
@ -156,6 +158,7 @@
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<EmbeddedResource Include="LibCefHelpers\cefglue.7z" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)\Compression.BSA\Compression.BSA.csproj">
@ -181,6 +184,9 @@
<PackageReference Include="AlphaFS">
<Version>2.2.6</Version>
</PackageReference>
<PackageReference Include="CefGlue.Wpf">
<Version>75.1.28</Version>
</PackageReference>
<PackageReference Include="Ceras">
<Version>4.1.7</Version>
</PackageReference>

View File

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.LibCefHelpers;
namespace Wabbajack.Test
{
@ -14,6 +15,7 @@ namespace Wabbajack.Test
[TestInitialize]
public void TestInitialize()
{
Helpers.ExtractLibs();
Consts.TestMode = true;
utils = new TestUtils();

View File

@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.LibCefHelpers;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.Validation;
using File = Alphaleonis.Win32.Filesystem.File;
@ -17,6 +18,7 @@ namespace Wabbajack.Test
[TestInitialize]
public void Setup()
{
Helpers.ExtractLibs();
}
[TestMethod]

View File

@ -141,6 +141,9 @@
<PackageReference Include="AlphaFS">
<Version>2.2.6</Version>
</PackageReference>
<PackageReference Include="CefGlue.Wpf">
<Version>75.1.28</Version>
</PackageReference>
<PackageReference Include="MSTest.TestAdapter">
<Version>2.0.0</Version>
</PackageReference>

View File

@ -4,6 +4,7 @@ using System.Reflection;
using System.Windows;
using MahApps.Metro;
using Wabbajack.Common;
using Wabbajack.Lib.LibCefHelpers;
namespace Wabbajack
{
@ -14,7 +15,7 @@ namespace Wabbajack
{
public App()
{
// Initialization in MainWindow ctor
Helpers.ExtractLibs();
}
}
}

View File

@ -102,10 +102,14 @@ namespace Wabbajack
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
_listBox.ScrollIntoView(e.NewItems[0]);
_listBox.SelectedItem = e.NewItems[0];
try
{
_listBox.ScrollIntoView(e.NewItems[0]);
_listBox.SelectedItem = e.NewItems[0];
}
catch (ArgumentOutOfRangeException) { }
}
}
}
}
}
}

View File

@ -6,10 +6,13 @@ using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using Wabbajack.Common;
using Wabbajack.Common.StatusFeed;
using Wabbajack.Lib;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.StatusMessages;
namespace Wabbajack
@ -33,15 +36,19 @@ namespace Wabbajack
public readonly Lazy<InstallerVM> Installer;
public readonly Lazy<ModListGalleryVM> Gallery;
public readonly ModeSelectionVM ModeSelectionVM;
public readonly WebBrowserVM WebBrowserVM;
public Dispatcher ViewDispatcher { get; set; }
public MainWindowVM(MainWindow mainWindow, MainSettings settings)
{
MainWindow = mainWindow;
ViewDispatcher = MainWindow.Dispatcher;
Settings = settings;
Installer = new Lazy<InstallerVM>(() => new InstallerVM(this));
Compiler = new Lazy<CompilerVM>(() => new CompilerVM(this));
Gallery = new Lazy<ModListGalleryVM>(() => new ModListGalleryVM(this));
ModeSelectionVM = new ModeSelectionVM(this);
WebBrowserVM = new WebBrowserVM();
// Set up logging
Utils.LogMessages
@ -59,6 +66,10 @@ namespace Wabbajack
.OfType<ConfirmUpdateOfExistingInstall>()
.Subscribe(msg => ConfirmUpdate(msg));
Utils.LogMessages
.OfType<RequestNexusAuthorization>()
.Subscribe(HandleRequestNexusAuthorization);
if (IsStartingFromModlist(out var path))
{
Installer.Value.ModListLocation.TargetPath = path;
@ -71,6 +82,38 @@ namespace Wabbajack
}
}
private void HandleRequestNexusAuthorization(RequestNexusAuthorization msg)
{
ViewDispatcher.InvokeAsync(async () =>
{
var oldPane = ActivePane;
var vm = new WebBrowserVM();
ActivePane = vm;
try
{
vm.BackCommand = ReactiveCommand.Create(() =>
{
ActivePane = oldPane;
msg.Cancel();
});
}
catch (Exception e)
{ }
try
{
var key = await NexusApiClient.SetupNexusLogin(vm.Browser, m => vm.Instructions = m);
msg.Resume(key);
}
catch (Exception ex)
{
msg.Cancel();
}
ActivePane = oldPane;
});
}
private void ConfirmUpdate(ConfirmUpdateOfExistingInstall msg)
{
var result = MessageBox.Show(msg.ExtendedDescription, msg.ShortDescription, MessageBoxButton.OKCancel);

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.Lib;
using Xilium.CefGlue.Common;
using Xilium.CefGlue.WPF;
namespace Wabbajack
{
public class WebBrowserVM : ViewModel
{
[Reactive]
public string Instructions { get; set; }
public WpfCefBrowser Browser { get; }
[Reactive]
public IReactiveCommand BackCommand { get; set; }
public WebBrowserVM(string url = "http://www.wabbajack.org")
{
Browser = new WpfCefBrowser();
Browser.Address = url;
Instructions = "Wabbajack Web Browser";
}
}
}

View File

@ -33,6 +33,9 @@
<DataTemplate DataType="{x:Type local:ModListGalleryVM}">
<local:ModListGalleryView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:WebBrowserVM}">
<local:WebBrowserView />
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</mahapps:MetroWindow>

View File

@ -0,0 +1,71 @@
<UserControl x:Class="Wabbajack.WebBrowserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:wabbajack="clr-namespace:Wabbajack"
xmlns:wpf="clr-namespace:Xilium.CefGlue.WPF;assembly=Xilium.CefGlue.WPF"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Color x:Key="TextBackgroundFill">#92000000</Color>
<SolidColorBrush x:Key="TextBackgroundFillBrush" Color="{StaticResource TextBackgroundFill}" />
<Color x:Key="TextBackgroundHoverFill">#DF000000</Color>
<Style x:Key="BackgroundBlurStyle" TargetType="TextBlock">
<Setter Property="Background" Value="{StaticResource TextBackgroundFillBrush}" />
<Setter Property="Foreground" Value="Transparent" />
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)"
To="{StaticResource TextBackgroundHoverFill}"
Duration="0:0:0.06" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)"
To="{StaticResource TextBackgroundFill}"
Duration="0:0:0.06" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="47" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<wabbajack:TopProgressView
Title="{Binding Instructions}"
Grid.Row="0"
Grid.RowSpan="2"
ShadowMargin="False" />
<Button
x:Name="BackButton"
Grid.Row="0"
Width="30"
Height="30"
Margin="7,5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Command="{Binding BackCommand}"
Style="{StaticResource IconCircleButtonStyle}"
ToolTip="Back to main menu">
<iconPacks:PackIconMaterial Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" Kind="ArrowLeft" />
</Button>
<!-- Do it this way so we can access the browser directly from the VM -->
<ContentControl Grid.Row="1" Content="{Binding Browser}"></ContentControl>
</Grid>
</UserControl>

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for WebBrowser.xaml
/// </summary>
public partial class WebBrowserView : UserControl
{
public WebBrowserView()
{
InitializeComponent();
}
}
}

View File

@ -162,6 +162,10 @@
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="Xilium.CefGlue.WPF, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\Users\tbald\.nuget\packages\cefglue.wpf\75.1.28\lib\net472\x64\Xilium.CefGlue.WPF.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
@ -169,6 +173,7 @@
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Converters\EqualsToBoolConverter.cs" />
<Compile Include="View Models\WebBrowserVM.cs" />
<Compile Include="Views\Installers\MO2InstallerConfigView.xaml.cs">
<DependentUpon>MO2InstallerConfigView.xaml</DependentUpon>
</Compile>
@ -247,6 +252,9 @@
<Compile Include="Views\Installers\VortexInstallerConfigView.xaml.cs">
<DependentUpon>VortexInstallerConfigView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\WebBrowserView.xaml.cs">
<DependentUpon>WebBrowserView.xaml</DependentUpon>
</Compile>
<Page Include="Views\Installers\MO2InstallerConfigView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -333,6 +341,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\WebBrowserView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
@ -411,6 +423,9 @@
<PackageReference Include="AlphaFS">
<Version>2.2.6</Version>
</PackageReference>
<PackageReference Include="CefGlue.Wpf">
<Version>75.1.28</Version>
</PackageReference>
<PackageReference Include="CommonMark.NET">
<Version>0.15.1</Version>
</PackageReference>