More Installer stuff and modularisation.

This commit is contained in:
Unnoen 2022-01-17 00:46:16 +11:00
parent 365970bef9
commit 2ada4621b8
No known key found for this signature in database
GPG Key ID: 8F8E42252BA20553
19 changed files with 684 additions and 123 deletions

View File

@ -4,6 +4,9 @@ using Fluxor;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Wabbajack.App.Blazor.Models;
using Wabbajack.App.Blazor.Utility;
using Wabbajack.DTOs;
using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.App.Blazor
@ -12,7 +15,7 @@ namespace Wabbajack.App.Blazor
{
private readonly IServiceProvider _serviceProvider;
private readonly IHost _host;
public App()
{
_host = Host.CreateDefaultBuilder(Array.Empty<string>())
@ -28,10 +31,12 @@ namespace Wabbajack.App.Blazor
services.AddOSIntegrated();
services.AddFluxor(o => o.ScanAssemblies(typeof(App).Assembly));
services.AddBlazorWebView();
services.AddAllSingleton<ILoggerProvider, LoggerProvider>();
services.AddTransient<MainWindow>();
services.AddSingleton<SystemParametersConstructor>();
return services;
}
private void OnStartup(object sender, StartupEventArgs e)
{
MainWindow mainWindow = _serviceProvider.GetRequiredService<MainWindow>();

View File

@ -0,0 +1,22 @@
<div id="info-block">
<div class="title">@Title</div>
<div class="subtitle">@Subtitle</div>
<div class="comment">@Comment</div>
<div class="description">@Description</div>
</div>
@code {
[Parameter]
public string Title { get; set; }
[Parameter]
public string Subtitle { get; set; }
[Parameter]
public string Comment { get; set; }
[Parameter]
public string Description { get; set; }
}

View File

@ -0,0 +1,27 @@
#info-block {
width: 50%;
height: fit-content;
margin: 1rem;
.title {
font-size: 5rem;
font-weight: 100;
}
.subtitle {
margin-left: 10px;
font-size: 2rem;
font-weight: 100;
}
.comment {
margin-left: 20px;
color: rgba(255, 255, 255, 0.75);
}
.description {
margin-left: 20px;
margin-top: 20px;
color: rgba(255, 255, 255, 0.5);
}
}

View File

@ -0,0 +1,5 @@
<h3>Logger</h3>
@code {
}

View File

@ -45,7 +45,7 @@ namespace Wabbajack.App.Blazor.Components
dispose.Dispose();
AbsolutePath path = KnownFolders.EntryPoint.Combine("downloaded_mod_lists", Metadata.Links.MachineURL).WithExtension(Ext.Wabbajack);
_dispatcher.Dispatch(new UpdateInstallState(InstallState.InstallStateEnum.Configuration, null, path));
_dispatcher.Dispatch(new UpdateInstallState(InstallState.InstallStateEnum.Configuration, null, path, null, null));
NavigationManager.NavigateTo("/configure");
}

View File

@ -1,21 +1,59 @@
using System;
using Fluxor;
using Microsoft.Extensions.Logging;
using Wabbajack.App.Blazor.Models;
using Wabbajack.App.Blazor.Utility;
using Wabbajack.Common;
using Wabbajack.Installer;
using Wabbajack.Paths.IO;
namespace Wabbajack.App.Blazor
{
public partial class MainWindow
{
private readonly ILogger<MainWindow> _logger;
private readonly IStore _store;
private readonly ILogger<MainWindow> _logger;
private readonly LoggerProvider _loggerProvider;
private readonly IStore _store;
private readonly SystemParametersConstructor _systemParams;
public MainWindow(ILogger<MainWindow> logger, IStore store, IServiceProvider serviceProvider)
public MainWindow(ILogger<MainWindow> logger, IStore store, IServiceProvider serviceProvider, LoggerProvider loggerProvider,
SystemParametersConstructor systemParams)
{
_logger = logger;
_store = store;
_logger = logger;
_store = store;
_loggerProvider = loggerProvider;
_systemParams = systemParams;
_store.InitializeAsync().Wait();
Resources.Add("services", serviceProvider);
InitializeComponent();
try
{
// TODO: Not sure how to set this up.
//_logger.LogInformation("Wabbajack Build - {Sha}", ThisAssembly.Git.Sha);
_logger.LogInformation("Running in {EntryPoint}", KnownFolders.EntryPoint);
SystemParameters p = _systemParams.Create();
_logger.LogInformation("Detected Windows Version: {Version}", Environment.OSVersion.VersionString);
_logger.LogInformation(
"System settings - ({MemorySize} RAM) ({PageSize} Page), Display: {ScreenWidth} x {ScreenHeight} ({Vram} VRAM - VideoMemorySizeMb={ENBVRam})",
p.SystemMemorySize.ToFileSizeString(), p.SystemPageSize.ToFileSizeString(), p.ScreenWidth, p.ScreenHeight,
p.VideoMemorySize.ToFileSizeString(), p.EnbLEVRAMSize);
if (p.SystemPageSize == 0)
_logger.LogInformation(
"Page file is disabled! Consider increasing to 20000MB. A disabled page file can cause crashes and poor in-game performance");
else if (p.SystemPageSize < 2e+10)
_logger.LogInformation(
"Page file below recommended! Consider increasing to 20000MB. A suboptimal page file can cause crashes and poor in-game performance");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during Main Window startup.");
Environment.Exit(-1);
}
}
}

View File

@ -0,0 +1,6 @@
namespace Wabbajack.App.Blazor.Models;
public class Install
{
}

View File

@ -0,0 +1,149 @@
using System;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Text;
using System.Threading;
using DynamicData;
using Microsoft.Extensions.Logging;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.App.Blazor.Models;
public class LoggerProvider : ILoggerProvider
{
private readonly RelativePath _appName;
private readonly Configuration _configuration;
private readonly CompositeDisposable _disposables;
private readonly Stream _logFile;
private readonly StreamWriter _logStream;
public readonly ReadOnlyObservableCollection<ILogMessage> _messagesFiltered;
private readonly DateTime _startupTime;
private long _messageId;
private readonly SourceCache<ILogMessage, long> _messageLog = new(m => m.MessageId);
private readonly Subject<ILogMessage> _messages = new();
public LoggerProvider(Configuration configuration)
{
_startupTime = DateTime.UtcNow;
_configuration = configuration;
_configuration.LogLocation.CreateDirectory();
_disposables = new CompositeDisposable();
// Messages
// .ObserveOnGuiThread()
// .Subscribe(m => _messageLog.AddOrUpdate(m));
Messages.Subscribe(LogToFile);
_messageLog.Connect()
.Bind(out _messagesFiltered)
.Subscribe();
_appName = typeof(LoggerProvider).Assembly.Location.ToAbsolutePath().FileName;
LogPath = _configuration.LogLocation.Combine($"{_appName}.current.log");
_logFile = LogPath.Open(FileMode.Append, FileAccess.Write);
_logStream = new StreamWriter(_logFile, Encoding.UTF8);
}
public IObservable<ILogMessage> Messages => _messages;
public AbsolutePath LogPath { get; }
public ReadOnlyObservableCollection<ILogMessage> MessageLog => _messagesFiltered;
public void Dispose()
{
_disposables.Dispose();
}
public ILogger CreateLogger(string categoryName)
{
return new Logger(this, categoryName);
}
private void LogToFile(ILogMessage logMessage)
{
var line = $"[{logMessage.TimeStamp - _startupTime}] {logMessage.LongMessage}";
lock (_logStream)
{
_logStream.Write(line);
_logStream.Flush();
}
}
private long NextMessageId()
{
return Interlocked.Increment(ref _messageId);
}
public class Logger : ILogger
{
private readonly string _categoryName;
private readonly LoggerProvider _provider;
private ImmutableList<object> Scopes = ImmutableList<object>.Empty;
public Logger(LoggerProvider provider, string categoryName)
{
_categoryName = categoryName;
_provider = provider;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
Func<TState, Exception?, string> formatter)
{
Debug.WriteLine($"{logLevel} - {formatter(state, exception)}");
_provider._messages.OnNext(new LogMessage<TState>(DateTime.UtcNow, _provider.NextMessageId(), logLevel,
eventId, state, exception, formatter));
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public IDisposable BeginScope<TState>(TState state)
{
Scopes = Scopes.Add(state);
return Disposable.Create(() => Scopes = Scopes.Remove(state));
}
}
public interface ILogMessage
{
long MessageId { get; }
string ShortMessage { get; }
DateTime TimeStamp { get; }
string LongMessage { get; }
}
private record LogMessage<TState>(DateTime TimeStamp, long MessageId, LogLevel LogLevel, EventId EventId,
TState State, Exception? Exception, Func<TState, Exception?, string> Formatter) : ILogMessage
{
public string ShortMessage => Formatter(State, Exception);
public string LongMessage
{
get
{
var sb = new StringBuilder();
sb.AppendLine(ShortMessage);
if (Exception != null)
{
sb.Append("Exception: ");
sb.Append(Exception);
}
return sb.ToString();
}
}
}
}

View File

@ -4,12 +4,10 @@
<div id="content">
<div class="image" style="background-image: url('@Image');"></div>
<div class="list">
<div class="info">
<div class="name">@Name</div>
<div class="author">@Author</div>
<div class="version">@Version</div>
<div class="description">@Description</div>
</div>
@if (Name != string.Empty)
{
<InfoBlock Title="@Name" Subtitle="@Author" Comment="@Version.ToString()" Description="@Description"/>
}
<div class="modlist-image" style="background-image: url('@Image');"></div>
</div>
<div class="settings">
@ -20,9 +18,9 @@
<span>Download Location</span>
</div>
<div class="paths">
<span class="modlist-file">D:\Programs\Wabbajack\downloaded_mod_lists\magnum_opus.wabbajack</span>
<span class="install-location"></span>
<span class="download-location"></span>
<span class="modlist-file">@ModListPath</span>
<span class="install-location" @onclick="SelectInstallFolder">@InstallPath</span>
<span class="download-location" @onclick="SelectDownloadFolder">@DownloadPath</span>
</div>
</div>
@ -52,4 +50,5 @@
</label>
</div>
</div>
<div class="install" @onclick="NavigateInstall">Install</div>
</div>

View File

@ -1,13 +1,19 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Fluxor;
using Microsoft.AspNetCore.Components;
using Microsoft.WindowsAPICodePack.Dialogs;
using Wabbajack.App.Blazor.Store;
using Wabbajack.DTOs;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Installer;
using Wabbajack.Paths;
using Ookii.Dialogs.Wpf;
using Wabbajack.App.Blazor.Utility;
namespace Wabbajack.App.Blazor.Pages
{
@ -18,14 +24,20 @@ namespace Wabbajack.App.Blazor.Pages
[Inject] private DTOSerializer _dtos { get; set; }
[Inject] private IDispatcher _dispatcher { get; set; }
private string Name { get; set; }
private string Author { get; set; }
private string Description { get; set; }
private Version Version { get; set; }
private string Image { get; set; }
private string Name { get; set; } = "";
private string Author { get; set; } = "";
private string Description { get; set; } = "";
private Version Version { get; set; } = Version.Parse("0.0.0");
private string Image { get; set; } = "";
private ModList ModList { get; set; }
private AbsolutePath ModListPath { get; set; }
private AbsolutePath InstallPath { get; set; }
private AbsolutePath DownloadPath { get; set; }
protected override async Task OnInitializedAsync()
{
// var Location = KnownFolders.EntryPoint.Combine("downloaded_mod_lists", machineURL).WithExtension(Ext.Wabbajack);
await CheckValidInstallPath();
await base.OnInitializedAsync();
@ -33,23 +45,56 @@ namespace Wabbajack.App.Blazor.Pages
private async Task CheckValidInstallPath()
{
if (_installState.Value.CurrentPath == null) return;
if (_installState.Value.CurrentModListPath == null) return;
var listPath = (AbsolutePath)_installState.Value.CurrentPath;
ModList modList = await StandardInstaller.LoadFromFile(_dtos, listPath);
_dispatcher.Dispatch(new UpdateInstallState(InstallState.InstallStateEnum.Configuration, modList, listPath));
ModListPath = (AbsolutePath)_installState.Value.CurrentModListPath;
ModList = await StandardInstaller.LoadFromFile(_dtos, ModListPath);
_dispatcher.Dispatch(new UpdateInstallState(InstallState.InstallStateEnum.Configuration, ModList, ModListPath, null, null));
Name = _installState.Value.CurrentModList.Name;
Author = _installState.Value.CurrentModList.Author;
Description = _installState.Value.CurrentModList.Description;
Version = _installState.Value.CurrentModList.Version;
ModListPath = (AbsolutePath)_installState.Value.CurrentModListPath;
Name = _installState.Value.CurrentModlist.Name;
Author = _installState.Value.CurrentModlist.Author;
Description = _installState.Value.CurrentModlist.Description;
Version = _installState.Value.CurrentModlist.Version;
var imagepath = (AbsolutePath)_installState.Value.CurrentPath;
Stream image = await StandardInstaller.ModListImageStream(imagepath);
Stream image = await StandardInstaller.ModListImageStream(ModListPath);
await using var reader = new MemoryStream();
await image.CopyToAsync(reader);
Image = $"data:image/png;base64,{Convert.ToBase64String(reader.ToArray())}";
}
private async void SelectInstallFolder()
{
try
{
AbsolutePath? thing = await Dialog.ShowDialogNonBlocking(true);
if (thing != null) InstallPath = (AbsolutePath)thing;
StateHasChanged();
}
catch (Exception ex)
{
Debug.Print(ex.Message);
}
}
private async void SelectDownloadFolder()
{
try
{
AbsolutePath? thing = await Dialog.ShowDialogNonBlocking(true);
if (thing != null) DownloadPath = (AbsolutePath)thing;
StateHasChanged();
}
catch (Exception ex)
{
Debug.Print(ex.Message);
}
}
private void NavigateInstall()
{
_dispatcher.Dispatch(new UpdateInstallState(InstallState.InstallStateEnum.Configuration, ModList, ModListPath, InstallPath, DownloadPath));
NavigationManager.NavigateTo("installing");
}
}
}

View File

@ -12,6 +12,7 @@ $checkbox-background-checked: purple;
padding: 0.25rem;
margin: 0.25rem;
white-space: pre;
cursor: pointer;
}
#content {
@ -40,34 +41,6 @@ $checkbox-background-checked: purple;
width: 100%;
align-items: center;
.info {
width: 50%;
height: fit-content;
margin: 1rem;
.name {
font-size: 5rem;
font-weight: 100;
}
.author {
margin-left: 10px;
font-size: 2rem;
font-weight: 100;
}
.version {
margin-left: 20px;
color: rgba(255, 255, 255, 0.75);
}
.description {
margin-left: 20px;
margin-top: 20px;
color: rgba(255, 255, 255, 0.5);
}
}
.modlist-image {
background-repeat: no-repeat;
background-size: contain;
@ -79,6 +52,7 @@ $checkbox-background-checked: purple;
}
.settings {
font-size: 0.9rem;
display: flex;
align-items: center;
width: 100%;
@ -167,4 +141,11 @@ $checkbox-background-checked: purple;
}
}
}
.install {
width: 300px;
height: 50px;
background-color: green;
cursor: pointer;
}
}

View File

@ -2,6 +2,9 @@
@namespace Wabbajack.App.Blazor.Pages
@using Wabbajack.Networking.WabbajackClientApi;
@using Wabbajack.DTOs
@using System.Diagnostics
@using Microsoft.Extensions.Logging
@using Wabbajack.App.Blazor.Models
@inject Client _client
@ -14,24 +17,31 @@
@code {
List<ModlistMetadata> _listItems = new();
[Inject]
private ILogger<Gallery> _logger { get; set; }
// [Inject]
// private LoggerProvider _loggerProvider { get; set; }
[Inject]
private Services.OSIntegrated.Configuration _configuration { get; set; }
protected override async Task<Task> OnAfterRenderAsync(bool firstRender)
private List<ModlistMetadata> _listItems { get; set; } = new() { };
protected override async Task OnInitializedAsync()
{
if (!firstRender) return base.OnAfterRenderAsync(firstRender);
try
{
_logger.LogInformation("Getting modlists...");
ModlistMetadata[] modLists = await _client.LoadLists();
_listItems = modLists.ToList();
_listItems.AddRange(modLists.ToList());
StateHasChanged();
}
catch (Exception ex)
{
Console.WriteLine(ex);
//TODO: Figure out why an exception is thrown on first navigation.
Debug.Print(ex.Message);
_logger.LogError(ex, "Error while loading lists");
}
return base.OnAfterRenderAsync(firstRender);
await base.OnInitializedAsync();
}
}

View File

@ -1,14 +1,11 @@
@page "/installing"
@namespace Wabbajack.App.Blazor.Pages
@using System.Diagnostics
@using Wabbajack.Common
@using Wabbajack.Paths.IO
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
<div id="content">
<div class="image"></div>
<div class="info">
<div class="step">Downloading</div>
<div class="step">@StatusText</div>
<div class="description">Downloading missing archives...</div>
<div class="number">16 of 228 archives downloaded</div>
<div class="number">128.27 GB remaining</div>
@ -27,21 +24,4 @@
<div class="mod-author">Simon Magus and colinswrath</div>
<div class="mod-info">Manbeast is a complete overhaul of Skyrims Werewolf system designed to balance existing Werewolf mechanics and add powerful new lycanthropic abilities to the game.</div>
</div>
</div>
@code {
[Parameter]
public string machineURL { get; set; }
protected override void OnInitialized()
{
var Location = KnownFolders.EntryPoint.Combine("downloaded_mod_lists", machineURL).WithExtension(Ext.Wabbajack);
if (Location.FileExists())
{
Debug.Print("IT EXISTS I GUESS");
}
base.OnInitialized();
}
}
</div>

View File

@ -0,0 +1,82 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Shell;
using Fluxor;
using Microsoft.AspNetCore.Components;
using Wabbajack.App.Blazor.Store;
using Wabbajack.App.Blazor.Utility;
using Wabbajack.Downloaders.GameFile;
using Wabbajack.DTOs;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Installer;
using Wabbajack.Paths;
namespace Wabbajack.App.Blazor.Pages
{
public partial class Installing
{
[Inject] private NavigationManager NavigationManager { get; set; }
[Inject] private IState<InstallState> _installState { get; set; }
[Inject] private DTOSerializer _dtos { get; set; }
[Inject] private IDispatcher _dispatcher { get; set; }
[Inject] private IServiceProvider _serviceProvider { get; set; }
[Inject] private SystemParametersConstructor _parametersConstructor { get; set; }
[Inject] private IGameLocator _gameLocator { get; set; }
private ModList modlist { get; set; }
private string StatusText { get; set; }
protected override async Task OnInitializedAsync()
{
modlist = _installState.Value.CurrentModList;
Task.Run(BeginInstall);
await base.OnInitializedAsync();
}
private async Task BeginInstall()
{
// var postfix = (await ModListLocation.TargetPath.ToString().Hash()).ToHex();
// await _settingsManager.Save(InstallSettingsPrefix + postfix, new SavedInstallSettings
// {
// ModListLocation = ModListLocation.TargetPath,
// InstallLocation = Installer.Location.TargetPath,
// DownloadLoadction = Installer.DownloadLocation.TargetPath,
// Metadata = ModlistMetadata
// });
try
{
var installer = StandardInstaller.Create(_serviceProvider, new InstallerConfiguration
{
Game = modlist.GameType,
Downloads = (AbsolutePath)_installState.Value.CurrentDownloadPath,
Install = (AbsolutePath)_installState.Value.CurrentInstallPath,
ModList = modlist,
ModlistArchive = (AbsolutePath)_installState.Value.CurrentModListPath,
SystemParameters = _parametersConstructor.Create(),
GameFolder = _gameLocator.GameLocation(modlist.GameType)
});
installer.OnStatusUpdate = update =>
{
if (StatusText != update.StatusText)
{
StatusText = update.StatusText;
InvokeAsync(StateHasChanged);
}
};
await installer.Begin(CancellationToken.None);
}
catch (Exception ex)
{
Debug.Print(ex.Message);
}
}
}
}

View File

@ -1,9 +1,13 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using DynamicData;
using Fluxor;
using Microsoft.AspNetCore.Components;
using Microsoft.Win32;
using Microsoft.WindowsAPICodePack.Dialogs;
using Wabbajack.App.Blazor.Store;
using Wabbajack.Common;
using Wabbajack.DTOs;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Installer;
@ -17,33 +21,47 @@ namespace Wabbajack.App.Blazor.Pages
[Inject] private IState<InstallState> _installState { get; set; }
[Inject] private IDispatcher _dispatcher { get; set; }
private AbsolutePath _modListPath { get; set; }
private void SelectFile()
{
var file = new OpenFileDialog
using (var dialog = new CommonOpenFileDialog())
{
Filter = "Wabbajack (*.wabbajack)|*.wabbajack",
FilterIndex = 1,
Multiselect = false,
Title = "Wabbajack file for install"
};
try
{
if (file.ShowDialog() != true) return;
var path = file.FileName.ToAbsolutePath();
VerifyFile(path);
}
catch (Exception ex)
{
Debug.Print(ex.Message);
dialog.Multiselect = false;
dialog.Filters.Add(new CommonFileDialogFilter("Wabbajack File", "*" + Ext.Wabbajack));
if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return;
_modListPath = dialog.FileName.ToAbsolutePath();
}
VerifyFile(_modListPath);
}
public async void VerifyFile(AbsolutePath path)
// private void SelectFile()
// {
// var file = new OpenFileDialog
// {
// Filter = "Wabbajack (*.wabbajack)|*.wabbajack",
// FilterIndex = 1,
// Multiselect = false,
// Title = "Wabbajack file for install"
// };
//
// try
// {
// if (file.ShowDialog() != true) return;
// var path = file.FileName.ToAbsolutePath();
// VerifyFile(path);
// }
// catch (Exception ex)
// {
// Debug.Print(ex.Message);
// }
// }
private void VerifyFile(AbsolutePath path)
{
try
{
_dispatcher.Dispatch(new UpdateInstallState(InstallState.InstallStateEnum.Configuration, null, path));
_dispatcher.Dispatch(new UpdateInstallState(InstallState.InstallStateEnum.Configuration, null, path, null, null));
NavigationManager.NavigateTo("/configure");
}
catch (Exception ex)

View File

@ -8,17 +8,21 @@ namespace Wabbajack.App.Blazor.Store;
public class InstallState
{
public InstallStateEnum CurrentInstallState { get; }
public ModList? CurrentModlist { get; }
public AbsolutePath? CurrentPath { get; }
public ModList? CurrentModList { get; }
public AbsolutePath? CurrentModListPath { get; }
public AbsolutePath? CurrentInstallPath { get; }
public AbsolutePath? CurrentDownloadPath { get; }
// Required for initial state.
private InstallState() { }
public InstallState(InstallStateEnum newState, ModList? newModList, AbsolutePath? newPath)
public InstallState(InstallStateEnum newState, ModList? newModList, AbsolutePath? newModListPath, AbsolutePath? newInstallPath, AbsolutePath? newDownloadPath)
{
CurrentInstallState = newState;
CurrentModlist = newModList ?? CurrentModlist;
CurrentPath = newPath ?? CurrentPath;
CurrentModList = newModList ?? CurrentModList;
CurrentModListPath = newModListPath ?? CurrentModListPath;
CurrentInstallPath = newInstallPath ?? CurrentInstallPath;
CurrentDownloadPath = newDownloadPath ?? CurrentDownloadPath;
}
public enum InstallStateEnum
@ -33,20 +37,24 @@ public class InstallState
public class UpdateInstallState
{
public InstallState.InstallStateEnum State { get; }
public ModList? Modlist { get; }
public AbsolutePath? Path { get; }
public InstallState.InstallStateEnum State { get; }
public ModList? ModList { get; }
public AbsolutePath? ModListPath { get; }
public AbsolutePath? InstallPath { get; }
public AbsolutePath? DownloadPath { get; }
public UpdateInstallState(InstallState.InstallStateEnum state, ModList? modlist, AbsolutePath? path)
public UpdateInstallState(InstallState.InstallStateEnum state, ModList? modlist, AbsolutePath? modlistPath, AbsolutePath? installPath, AbsolutePath? downloadPath)
{
State = state;
Modlist = modlist;
Path = path;
State = state;
ModList = modlist;
ModListPath = modlistPath;
InstallPath = installPath;
DownloadPath = downloadPath;
}
}
public static class InstallStateReducers
{
[ReducerMethod]
public static InstallState ReduceChangeInstallState(InstallState state, UpdateInstallState action) => new(action.State, action.Modlist, action.Path);
public static InstallState ReduceChangeInstallState(InstallState state, UpdateInstallState action) => new(action.State, action.ModList, action.ModListPath, action.InstallPath, action.DownloadPath);
}

View File

@ -0,0 +1,25 @@
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.WindowsAPICodePack.Dialogs;
using Wabbajack.Paths;
namespace Wabbajack.App.Blazor.Utility;
public static class Dialog
{
public static async Task<AbsolutePath?> ShowDialogNonBlocking(bool isFolderPicker = false)
{
return await Task.Factory.StartNew(() =>
{
Window newWindow = new();
var dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = isFolderPicker;
dialog.Multiselect = false;
CommonFileDialogResult result = dialog.ShowDialog(newWindow);
return result == CommonFileDialogResult.Ok ? dialog.FileName : null;
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext())
.ContinueWith(result => result.Result?.ToAbsolutePath())
.ConfigureAwait(false);
}
}

View File

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Extensions.Logging;
using PInvoke;
using Silk.NET.Core.Native;
using Silk.NET.DXGI;
using Wabbajack.Common;
using Wabbajack.Installer;
using Wabbajack;
using static PInvoke.User32;
using UnmanagedType = System.Runtime.InteropServices.UnmanagedType;
namespace Wabbajack.App.Blazor.Utility
{
// Much of the GDI code here is taken from : https://github.com/ModOrganizer2/modorganizer/blob/master/src/envmetrics.cpp
// Thanks to MO2 for being good citizens and supporting OSS code
public class SystemParametersConstructor
{
private readonly ILogger<SystemParametersConstructor> _logger;
public SystemParametersConstructor(ILogger<SystemParametersConstructor> logger)
{
_logger = logger;
}
private IEnumerable<(int Width, int Height, bool IsPrimary)> GetDisplays()
{
// Needed to make sure we get the right values from this call
SetProcessDPIAware();
unsafe
{
var col = new List<(int Width, int Height, bool IsPrimary)>();
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
delegate(IntPtr hMonitor, IntPtr hdcMonitor, RECT* lprcMonitor, void* dwData)
{
MONITORINFOEX mi = new MONITORINFOEX();
mi.cbSize = Marshal.SizeOf(mi);
bool success = GetMonitorInfo(hMonitor, (MONITORINFO*)&mi);
if (success)
{
col.Add(((mi.Monitor.right - mi.Monitor.left), (mi.Monitor.bottom - mi.Monitor.top),
mi.Flags == MONITORINFO_Flags.MONITORINFOF_PRIMARY));
}
return true;
}, IntPtr.Zero);
return col;
}
}
public SystemParameters Create()
{
var (width, height, _) = GetDisplays().First(d => d.IsPrimary);
/*using var f = new SharpDX.DXGI.Factory1();
var video_memory = f.Adapters1.Select(a =>
Math.Max(a.Description.DedicatedSystemMemory, (long)a.Description.DedicatedVideoMemory)).Max();*/
var dxgiMemory = 0UL;
unsafe
{
using var api = DXGI.GetApi();
IDXGIFactory1* factory1 = default;
try
{
//https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-createdxgifactory1
SilkMarshal.ThrowHResult(api.CreateDXGIFactory1(SilkMarshal.GuidPtrOf<IDXGIFactory1>(), (void**)&factory1));
uint i = 0u;
while (true)
{
IDXGIAdapter1* adapter1 = default;
//https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgifactory1-enumadapters1
var res = factory1->EnumAdapters1(i, &adapter1);
var exception = Marshal.GetExceptionForHR(res);
if (exception != null) break;
AdapterDesc1 adapterDesc = default;
//https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiadapter1-getdesc1
SilkMarshal.ThrowHResult(adapter1->GetDesc1(&adapterDesc));
var systemMemory = (ulong)adapterDesc.DedicatedSystemMemory;
var videoMemory = (ulong)adapterDesc.DedicatedVideoMemory;
var maxMemory = Math.Max(systemMemory, videoMemory);
if (maxMemory > dxgiMemory)
dxgiMemory = maxMemory;
adapter1->Release();
i++;
}
}
catch (Exception e)
{
_logger.LogError(e, "While getting SystemParameters");
}
finally
{
if (factory1->LpVtbl != (void**)IntPtr.Zero)
factory1->Release();
}
}
var memory = GetMemoryStatus();
return new SystemParameters
{
ScreenWidth = width,
ScreenHeight = height,
VideoMemorySize = (long)dxgiMemory,
SystemMemorySize = (long)memory.ullTotalPhys,
SystemPageSize = (long)memory.ullTotalPageFile - (long)memory.ullTotalPhys
};
}
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool GlobalMemoryStatusEx([In, Out] MEMORYSTATUSEX lpBuffer);
public static MEMORYSTATUSEX GetMemoryStatus()
{
var mstat = new MEMORYSTATUSEX();
GlobalMemoryStatusEx(mstat);
return mstat;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class MEMORYSTATUSEX
{
public uint dwLength;
public uint dwMemoryLoad;
public ulong ullTotalPhys;
public ulong ullAvailPhys;
public ulong ullTotalPageFile;
public ulong ullAvailPageFile;
public ulong ullTotalVirtual;
public ulong ullAvailVirtual;
public ulong ullAvailExtendedVirtual;
public MEMORYSTATUSEX()
{
dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
}
}
}
}

View File

@ -6,6 +6,7 @@
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<PublishSingleFile>True</PublishSingleFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@ -14,11 +15,15 @@
<ItemGroup>
<!-- <PackageReference Include="LibSassBuilder" Version="2.0.1" />-->
<PackageReference Include="DynamicData" Version="7.4.9" />
<PackageReference Include="Fluxor" Version="4.2.2-Alpha" />
<PackageReference Include="Fluxor.Blazor.Web" Version="4.2.2-Alpha" />
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Wpf" Version="6.0.101-preview.11.2349" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.1" />
<PackageReference Include="PInvoke.User32" Version="0.7.104" />
<PackageReference Include="Silk.NET.DXGI" Version="2.12.0" />
</ItemGroup>
<ItemGroup>