wabbajack/Wabbajack.Launcher/ViewModels/MainWindowViewModel.cs

401 lines
13 KiB
C#
Raw Normal View History

2021-10-02 17:01:09 +00:00
using System;
2021-10-08 13:16:51 +00:00
using System.Collections.Generic;
2021-10-02 17:01:09 +00:00
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
2021-10-08 13:16:51 +00:00
using System.Threading.Tasks;
using JetBrains.Annotations;
using MsBox.Avalonia.Dto;
2021-10-08 13:16:51 +00:00
using ReactiveUI.Fody.Helpers;
using Wabbajack.Common;
using Wabbajack.Downloaders.Http;
2022-06-02 00:12:13 +00:00
using Wabbajack.DTOs.Logins;
using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.Networking.NexusApi;
2021-10-08 13:16:51 +00:00
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
2022-10-08 03:43:44 +00:00
#pragma warning disable SYSLIB0014
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
namespace Wabbajack.Launcher.ViewModels;
public class MainWindowViewModel : ViewModelBase
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
private readonly WebClient _client = new();
private readonly List<string> _errors = new();
private (Version Version, long Size, Func<Task<Uri>> Uri) _version;
2021-10-23 16:51:17 +00:00
public Uri GITHUB_REPO = new("https://api.github.com/repos/wabbajack-tools/wabbajack/releases");
private readonly NexusApi _nexusApi;
private readonly HttpDownloader _downloader;
private readonly ITokenProvider<NexusOAuthState> _tokenProvider;
2021-10-23 16:51:17 +00:00
public MainWindowViewModel(NexusApi nexusApi, HttpDownloader downloader, ITokenProvider<NexusOAuthState> tokenProvider)
2021-10-02 17:01:09 +00:00
{
_nexusApi = nexusApi;
2021-10-23 16:51:17 +00:00
Status = "Checking for new versions";
_downloader = downloader;
2022-06-02 00:12:13 +00:00
_tokenProvider = tokenProvider;
2021-10-23 16:51:17 +00:00
var tsk = CheckForUpdates();
}
[Reactive] public string Status { get; set; }
2021-10-08 13:16:51 +00:00
2021-10-23 16:51:17 +00:00
private async Task CheckForUpdates()
{
2022-06-23 23:28:57 +00:00
await VerifyCurrentLocation();
2021-10-23 16:51:17 +00:00
_client.Headers.Add("user-agent", "Wabbajack Launcher");
Status = "Selecting Release";
try
2021-10-08 13:16:51 +00:00
{
2022-06-02 00:12:13 +00:00
if (_tokenProvider.HaveToken())
{
try
{
_version = await GetNexusReleases(CancellationToken.None);
}
catch (Exception)
{
_errors.Add("Nexus error");
}
2022-06-02 00:12:13 +00:00
}
if (_version == default)
2021-10-23 16:51:17 +00:00
{
_version = await GetGithubRelease(CancellationToken.None);
}
2021-10-08 13:16:51 +00:00
}
2021-10-23 16:51:17 +00:00
catch (Exception ex)
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
_errors.Add(ex.Message);
await FinishAndExit();
}
2021-10-02 17:01:09 +00:00
if (_version == default)
2021-10-23 16:51:17 +00:00
{
_errors.Add("Unable to find releases");
2021-10-23 16:51:17 +00:00
await FinishAndExit();
}
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
Status = "Looking for Updates";
2021-10-02 17:01:09 +00:00
var baseFolder = KnownFolders.CurrentDirectory.Combine(_version.Version.ToString());
2021-10-02 17:01:09 +00:00
if (baseFolder.Combine("Wabbajack.exe").FileExists()) await FinishAndExit();
2021-10-02 17:01:09 +00:00
Status = $"Getting download Uri for {_version.Version}";
var uri = await _version.Uri();
2021-10-02 17:01:09 +00:00
/*
var archive = new Archive()
{
Name = $"{_version.Version}.zip",
Size = _version.Size,
State = new Http {Url = uri}
};
await using var stream = await _downloader.GetChunkedSeekableStream(archive, CancellationToken.None);
var rdr = new ZipReader(stream, true);
var entries = (await rdr.GetFiles()).OrderBy(d => d.FileOffset).ToArray();
foreach (var file in entries)
{
if (file.FileName.EndsWith("/") || file.FileName.EndsWith("\\")) continue;
var relPath = file.FileName.ToRelativePath();
Status = $"Extracting: {relPath.FileName}";
var outPath = baseFolder.Combine(relPath);
if (!outPath.Parent.DirectoryExists())
outPath.Parent.CreateDirectory();
await using var of = outPath.Open(FileMode.Create, FileAccess.Write, FileShare.None);
await rdr.Extract(file, of, CancellationToken.None);
}*/
2021-10-23 16:51:17 +00:00
var wc = new WebClient();
wc.DownloadProgressChanged += (sender, args) => UpdateProgress($"{_version.Version}", sender, args);
Status = $"Downloading {_version.Version} ...";
2021-10-23 16:51:17 +00:00
byte[] data;
try
{
data = await wc.DownloadDataTaskAsync(uri);
2021-10-23 16:51:17 +00:00
}
catch (Exception ex)
{
_errors.Add(ex.Message);
// Something went wrong so fallback to original URL
2021-10-02 17:01:09 +00:00
try
{
data = await wc.DownloadDataTaskAsync(uri);
2021-10-02 17:01:09 +00:00
}
2021-10-23 16:51:17 +00:00
catch (Exception ex2)
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
_errors.Add(ex2.Message);
await FinishAndExit();
throw; // avoid unsigned variable 'data'
2021-10-02 17:01:09 +00:00
}
2021-10-23 16:51:17 +00:00
}
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
try
{
using var zip = new ZipArchive(new MemoryStream(data), ZipArchiveMode.Read);
foreach (var entry in zip.Entries)
2021-10-02 17:01:09 +00:00
{
Status = $"Extracting: {entry.Name}";
var outPath = baseFolder.Combine(entry.FullName.ToRelativePath());
if (!outPath.Parent.DirectoryExists())
outPath.Parent.CreateDirectory();
if (entry.FullName.EndsWith("/") || entry.FullName.EndsWith("\\"))
continue;
await using var o = entry.Open();
await using var of = outPath.Open(FileMode.Create, FileAccess.Write, FileShare.None);
await o.CopyToAsync(of);
2021-10-02 17:01:09 +00:00
}
}
2021-10-23 16:51:17 +00:00
catch (Exception ex)
{
_errors.Add(ex.Message);
}
try
{
await InstallWebView();
}
catch (Exception e)
{
_errors.Add(e.Message);
}
await FinishAndExit();
}
[UriString]
private const string WebViewDownloadLink = "https://go.microsoft.com/fwlink/p/?LinkId=2124703";
private async Task InstallWebView(CancellationToken cancellationToken = default)
{
var setupPath = KnownFolders.WabbajackAppLocal.Combine("MicrosoftEdgeWebview2Setup.exe");
if (setupPath.FileExists()) return;
var wc = new WebClient();
wc.DownloadProgressChanged += (sender, args) => UpdateProgress("WebView2", sender, args);
Status = "Downloading WebView2 Runtime";
byte[] data;
try
{
data = await wc.DownloadDataTaskAsync(WebViewDownloadLink);
}
catch (Exception ex)
2021-10-23 16:51:17 +00:00
{
_errors.Add(ex.Message);
2021-10-23 16:51:17 +00:00
await FinishAndExit();
throw;
2021-10-23 16:51:17 +00:00
}
await setupPath.WriteAllBytesAsync(new Memory<byte>(data), cancellationToken);
var process = new ProcessHelper
{
2022-11-08 10:47:45 +00:00
Path = setupPath,
Arguments = new []{"/silent /install"}
};
await process.Start();
2021-10-23 16:51:17 +00:00
}
2021-10-02 17:01:09 +00:00
2022-06-23 23:28:57 +00:00
private async Task VerifyCurrentLocation()
{
try
{
var entryPoint = KnownFolders.EntryPoint;
if (KnownFolders.IsInSpecialFolder(entryPoint) || entryPoint.Depth <= 1)
{
var msg = MsBox.Avalonia.MessageBoxManager
.GetMessageBoxStandard(new MessageBoxStandardParams()
{
Topmost = true,
ShowInCenter = true,
ContentTitle = "Wabbajack Launcher: Bad startup path",
ContentMessage =
Wabbajack 3.3.0.0 Update (#2416) * added more visible error messages to avoid user confusion added hard drive free space detection, added red error message text, removed overwrite checkbox, added wiki button link extended the error text for starting wabbajack in protected location removed debug code shortened error message to fit in text box * restored warning removed in error, updated changelog, removed debug includes * Update InstallerVM.cs * Update InstallerVM.cs * Update MainWindowViewModel.cs * added json optional flag to only show version number over modlist image in installer view, if the modlist image already contains the title removed debug code change to pascal case and match existing code style update changelog * Fix manual downloads sometimes launching in browser * Fix manual downloads from secure servers * Remove duplicate user agent code * Create configuration project and performance settings * Bind new performance settings to UI * Use performance settings to limit maximum memory per download * Remove unused settings and related classes * Updated CHANGELOG.md * update CHANGELOG.md * moved the existing files popup to an error message , heralding the return of the overwrite install checkbox * added newline * reverted erroneous edit * gogID for fallout4 added * update CHANGELOG.md * Fix deadlock when loading new settings * change folder/directory check logic * update CHANGELOG.md * revert unnecessary change * update CHANGELOG.md * Bump Wabbajack to .NET 7 * Bump ReactiveUI packages & deps * Update GameFinder to 4.0.0 * Update CHANGELOG.md * Update CHANGELOG.md --------- Co-authored-by: JanuarySnow <bobfordiscord12@gmail.com> Co-authored-by: JanuarySnow <85711747+JanuarySnow@users.noreply.github.com> Co-authored-by: UrbanCMC <UrbanCMC@web.de> Co-authored-by: trawzified <55751269+tr4wzified@users.noreply.github.com>
2023-10-12 18:33:06 +00:00
"Cannot start in the root of a drive, or protected folder locations such as Downloads, Desktop etc.\nPlease move Wabbajack to another folder, creating a new folder if necessary ( example : C:\\Wabbajack\\), outside of these locations."
});
var result = await msg.ShowAsync();
Environment.Exit(1);
}
}
catch (Exception ex)
2022-06-23 23:28:57 +00:00
{
Status = ex.Message;
var msg = MsBox.Avalonia.MessageBoxManager
.GetMessageBoxStandard(new MessageBoxStandardParams()
{
Topmost = true,
ShowInCenter = true,
ContentTitle = "Wabbajack Launcher: Error",
ContentMessage = ex.ToString()
});
var result = await msg.ShowAsync();
2022-06-23 23:28:57 +00:00
Environment.Exit(1);
}
}
[ContractAnnotation("=> halt")]
2021-10-23 16:51:17 +00:00
private async Task FinishAndExit()
{
try
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
Status = "Launching...";
2022-09-20 02:03:54 +00:00
var wjFolder = KnownFolders.CurrentDirectory.EnumerateDirectories(recursive:false)
2021-10-23 16:51:17 +00:00
.OrderByDescending(v =>
Version.TryParse(v.FileName.ToString(), out var ver) ? ver : new Version(0, 0, 0, 0))
.FirstOrDefault();
if (wjFolder == default)
{
_errors.Add("No WJ install found");
throw new Exception("No WJ install found");
}
2021-10-23 16:51:17 +00:00
var filename = wjFolder.Combine("Wabbajack.exe");
await CreateBatchFile(filename);
var info = new ProcessStartInfo
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
FileName = filename.ToString(),
Arguments = string.Join(" ",
Environment.GetCommandLineArgs().Skip(1).Select(s => s.Contains(' ') ? '\"' + s + '\"' : s)),
WorkingDirectory = wjFolder.ToString()
};
Process.Start(info);
}
catch (Exception ex)
2021-10-23 16:51:17 +00:00
{
_errors.Add(ex.Message);
2021-10-23 16:51:17 +00:00
if (_errors.Count == 0)
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
Status = "Failed: Unknown error";
await Task.Delay(10000);
2021-10-02 17:01:09 +00:00
}
2021-10-23 16:51:17 +00:00
foreach (var error in _errors)
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
Status = "Failed: " + error;
await Task.Delay(10000);
2021-10-02 17:01:09 +00:00
}
}
2021-10-23 16:51:17 +00:00
finally
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
Environment.Exit(0);
2021-10-02 17:01:09 +00:00
}
2021-10-23 16:51:17 +00:00
}
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
private async Task CreateBatchFile(AbsolutePath filename)
{
try
{
New Wabbajack release (#2479) * added more visible error messages to avoid user confusion added hard drive free space detection, added red error message text, removed overwrite checkbox, added wiki button link extended the error text for starting wabbajack in protected location removed debug code shortened error message to fit in text box * restored warning removed in error, updated changelog, removed debug includes * Update InstallerVM.cs * Update InstallerVM.cs * Update MainWindowViewModel.cs * added json optional flag to only show version number over modlist image in installer view, if the modlist image already contains the title removed debug code change to pascal case and match existing code style update changelog * Fix manual downloads sometimes launching in browser * Fix manual downloads from secure servers * Remove duplicate user agent code * Create configuration project and performance settings * Bind new performance settings to UI * Use performance settings to limit maximum memory per download * Remove unused settings and related classes * Updated CHANGELOG.md * update CHANGELOG.md * moved the existing files popup to an error message , heralding the return of the overwrite install checkbox * added newline * reverted erroneous edit * gogID for fallout4 added * update CHANGELOG.md * Fix deadlock when loading new settings * change folder/directory check logic * update CHANGELOG.md * revert unnecessary change * update CHANGELOG.md * Bump Wabbajack to .NET 7 * Bump ReactiveUI packages & deps * Update GameFinder to 4.0.0 * Update CHANGELOG.md * Update CHANGELOG.md * Don't try to add cookies if array is empty * Start download from scratch if .download_package can't be parsed * Log when application is shutting down * update CHANGELOG.md * exclude the "SP Consent Message" on nexus from the cleared iframes * Actually use the --outputPath compile option if the user provides one It was just not being used, defaulting to the parent of installPath. it still does, if the user does not specify a directory using --outputPath * update Wabbajack.Compression.BSA dependencies * improve log message to include storage space reference * clearing temp folder when the app closes * update logging message * update CHANGELOG.md * update CHANGELOG.md * update logging for possible exceptions thrown when clearing temp folder * fix cloudflare captcha * update Final Fantasy VII: RI metadata * Update CHANGELOG.md * fix error * Update GameRegistry.cs * Updated GameFinder, Added EADesktop module * updated version number in CHANGELOG.md * updated logging code * update CHANGELOG.md * Fix Nexus login (#2476) * Fix Nexus login handler after API keys web page changes * Automatically press request button * Automatically press request API key button * Fix BG3 being named badlursgate3 in Game enum instead of baldursgate3 * Fix WebView2 taking up tons of memory, use single WebView window - still needs refactoring * Remove commented WebView * Add changelog, fix Wabbajack CLI bat pointing to the wrong directory --------- Co-authored-by: JanuarySnow <bobfordiscord12@gmail.com> Co-authored-by: JanuarySnow <85711747+JanuarySnow@users.noreply.github.com> Co-authored-by: UrbanCMC <UrbanCMC@web.de> Co-authored-by: EzioTheDeadPoet <52624146+EzioTheDeadPoet@users.noreply.github.com> Co-authored-by: Marco Antonio Jaguaribe Costa <marco.antonio.costa@gmail.com>
2024-01-14 21:48:34 +00:00
filename = filename.Parent.Combine("cli", "wabbajack-cli.exe");
var data = $"\"{filename}\" %*";
var file = Path.Combine(Directory.GetCurrentDirectory(), "wabbajack-cli.bat");
if (File.Exists(file) && await File.ReadAllTextAsync(file) == data) return;
New Wabbajack release (#2479) * added more visible error messages to avoid user confusion added hard drive free space detection, added red error message text, removed overwrite checkbox, added wiki button link extended the error text for starting wabbajack in protected location removed debug code shortened error message to fit in text box * restored warning removed in error, updated changelog, removed debug includes * Update InstallerVM.cs * Update InstallerVM.cs * Update MainWindowViewModel.cs * added json optional flag to only show version number over modlist image in installer view, if the modlist image already contains the title removed debug code change to pascal case and match existing code style update changelog * Fix manual downloads sometimes launching in browser * Fix manual downloads from secure servers * Remove duplicate user agent code * Create configuration project and performance settings * Bind new performance settings to UI * Use performance settings to limit maximum memory per download * Remove unused settings and related classes * Updated CHANGELOG.md * update CHANGELOG.md * moved the existing files popup to an error message , heralding the return of the overwrite install checkbox * added newline * reverted erroneous edit * gogID for fallout4 added * update CHANGELOG.md * Fix deadlock when loading new settings * change folder/directory check logic * update CHANGELOG.md * revert unnecessary change * update CHANGELOG.md * Bump Wabbajack to .NET 7 * Bump ReactiveUI packages & deps * Update GameFinder to 4.0.0 * Update CHANGELOG.md * Update CHANGELOG.md * Don't try to add cookies if array is empty * Start download from scratch if .download_package can't be parsed * Log when application is shutting down * update CHANGELOG.md * exclude the "SP Consent Message" on nexus from the cleared iframes * Actually use the --outputPath compile option if the user provides one It was just not being used, defaulting to the parent of installPath. it still does, if the user does not specify a directory using --outputPath * update Wabbajack.Compression.BSA dependencies * improve log message to include storage space reference * clearing temp folder when the app closes * update logging message * update CHANGELOG.md * update CHANGELOG.md * update logging for possible exceptions thrown when clearing temp folder * fix cloudflare captcha * update Final Fantasy VII: RI metadata * Update CHANGELOG.md * fix error * Update GameRegistry.cs * Updated GameFinder, Added EADesktop module * updated version number in CHANGELOG.md * updated logging code * update CHANGELOG.md * Fix Nexus login (#2476) * Fix Nexus login handler after API keys web page changes * Automatically press request button * Automatically press request API key button * Fix BG3 being named badlursgate3 in Game enum instead of baldursgate3 * Fix WebView2 taking up tons of memory, use single WebView window - still needs refactoring * Remove commented WebView * Add changelog, fix Wabbajack CLI bat pointing to the wrong directory --------- Co-authored-by: JanuarySnow <bobfordiscord12@gmail.com> Co-authored-by: JanuarySnow <85711747+JanuarySnow@users.noreply.github.com> Co-authored-by: UrbanCMC <UrbanCMC@web.de> Co-authored-by: EzioTheDeadPoet <52624146+EzioTheDeadPoet@users.noreply.github.com> Co-authored-by: Marco Antonio Jaguaribe Costa <marco.antonio.costa@gmail.com>
2024-01-14 21:48:34 +00:00
var parent = Directory.GetParent(file).FullName;
if (!Directory.Exists(file))
Directory.CreateDirectory(parent);
await File.WriteAllTextAsync(file, data);
}
catch (Exception ex)
{
_errors.Add($"Creating Batch File : {ex}");
}
2021-10-23 16:51:17 +00:00
}
2021-10-02 17:01:09 +00:00
private void UpdateProgress(string what, object sender, DownloadProgressChangedEventArgs e)
2021-10-23 16:51:17 +00:00
{
Status = $"Downloading {what} ({e.ProgressPercentage}%)...";
2021-10-23 16:51:17 +00:00
}
2021-10-02 17:01:09 +00:00
private async Task<(Version Version, long Size, Func<Task<Uri>> Uri)> GetGithubRelease(CancellationToken token)
{
var releases = await GetGithubReleases();
var version = releases.Select(r =>
{
if (r.Tag.Split(".").Length == 4 && Version.TryParse(r.Tag, out var v))
return (v, r);
return (new Version(0, 0, 0, 0), r);
})
.OrderByDescending(r => r.Item1)
.FirstOrDefault();
var asset = version.r.Assets.FirstOrDefault(a => a.Name == version.Item1 + ".zip");
if (asset == null)
{
Status = $"Error, no asset found for Github Release {version.r}";
return default;
}
return (version.Item1, asset.Size, async () => asset!.BrowserDownloadUrl);
}
private async Task<Release[]> GetGithubReleases()
2021-10-23 16:51:17 +00:00
{
Status = "Checking GitHub Repository";
var data = await _client.DownloadStringTaskAsync(GITHUB_REPO);
Status = "Parsing Response";
return JsonSerializer.Deserialize<Release[]>(data)!;
}
private async Task<(Version Version, long Size, Func<Task<Uri>> uri)> GetNexusReleases(CancellationToken token)
{
Status = "Checking Nexus for updates";
if (!await _nexusApi.IsPremium(token))
return default;
var data = await _nexusApi.ModFiles("site", 403, token);
Status = "Parsing Response";
//return JsonSerializer.Deserialize<Release[]>(data)!;
2022-10-01 13:02:47 +00:00
var found = data.info.Files.Where(f => f.CategoryId == 5 || f.CategoryId == 3)
.Where(f => f.Name.EndsWith(".zip"))
.Select(f => Version.TryParse(f.Name[..^4], out var version) ? (version, f.SizeInBytes ?? f.Size, f.FileId) : default)
.FirstOrDefault(f => f != default);
if (found == default) return default;
return (found.version, found.Item2, async () =>
{
var link = await _nexusApi.DownloadLink("site", 403, found.FileId, token);
return link.info.First().URI;
});
}
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
private class Release
{
[JsonPropertyName("tag_name")] public string Tag { get; set; }
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
[JsonPropertyName("assets")] public Asset[] Assets { get; set; }
}
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
private class Asset
{
[JsonPropertyName("browser_download_url")]
public Uri BrowserDownloadUrl { get; set; }
2021-10-08 13:16:51 +00:00
2021-10-23 16:51:17 +00:00
[JsonPropertyName("name")] public string Name { get; set; }
[JsonPropertyName("size")] public long Size { get; set; }
2021-10-02 17:01:09 +00:00
}
}