From e500ef0d265262116ae15c9a0d112054a05a7840 Mon Sep 17 00:00:00 2001 From: rjindael Date: Wed, 2 Aug 2023 16:50:17 -0700 Subject: [PATCH] feat: launcher auto-updating --- Kiseki.Launcher.Windows/Bootstrapper.cs | 72 +++++++++++++++++++-- Kiseki.Launcher.Windows/MainWindow.cs | 6 +- Kiseki.Launcher.Windows/Win32.cs | 4 +- Kiseki.Launcher/Enums/PackageType.cs | 7 -- Kiseki.Launcher/Helpers/Base64.cs | 2 +- Kiseki.Launcher/Interfaces/IBootstrapper.cs | 2 +- Kiseki.Launcher/Models/ClientRelease.cs | 21 ++++++ 7 files changed, 94 insertions(+), 20 deletions(-) delete mode 100644 Kiseki.Launcher/Enums/PackageType.cs create mode 100644 Kiseki.Launcher/Models/ClientRelease.cs diff --git a/Kiseki.Launcher.Windows/Bootstrapper.cs b/Kiseki.Launcher.Windows/Bootstrapper.cs index 4e6ae9e..17174ae 100644 --- a/Kiseki.Launcher.Windows/Bootstrapper.cs +++ b/Kiseki.Launcher.Windows/Bootstrapper.cs @@ -1,10 +1,13 @@ namespace Kiseki.Launcher.Windows; using System.Diagnostics; +using System.Net; using System.Reflection; -using Microsoft.Win32; +using Kiseki.Launcher.Helpers; +using Kiseki.Launcher.Models; +using Microsoft.Win32; using Syroot.Windows.IO; public class Bootstrapper : Interfaces.IBootstrapper @@ -15,7 +18,7 @@ public class Bootstrapper : Interfaces.IBootstrapper private readonly Dictionary Arguments = new(); public event EventHandler? OnHeadingChange; - public event EventHandler? OnProgressBarAdd; + public event EventHandler? OnProgressBarSet; public event EventHandler? OnProgressBarStateChange; public event EventHandler? OnError; @@ -46,9 +49,66 @@ public class Bootstrapper : Interfaces.IBootstrapper return true; } - public void Run() + public async void Run() { - // + // Check for updates + HeadingChange("Checking for updates..."); + + // Check for a new launcher release from GitHub + var release = await Http.GetJson($"https://api.github.com/repos/{Constants.PROJECT_REPOSITORY}/releases/latest"); + bool launcherUpToDate = true; + + // TODO: We can remove this check once we do our first release. + if (release is not null && release.Assets is not null) + { + launcherUpToDate = Version == release.TagName[1..]; + + if (!launcherUpToDate) + { + // Update the launcher + HeadingChange("Getting the latest launcher..."); + ProgressBarStateChange(Enums.ProgressBarState.Normal); + + // TODO: This needs to be rewritten. It's a mess. + // REF: https://stackoverflow.com/a/9459441 + Thread thread = new(() => { + using WebClient client = new(); + + client.DownloadProgressChanged += (_, e) => { + double bytesIn = double.Parse(e.BytesReceived.ToString()); + double totalBytes = double.Parse(e.TotalBytesToReceive.ToString()); + double percentage = bytesIn / totalBytes * 100; + + ProgressBarSet(int.Parse(Math.Truncate(percentage).ToString())); + }; + + client.DownloadFileCompleted += (_, _) => { + HeadingChange("Installing the latest launcher..."); + ProgressBarStateChange(Enums.ProgressBarState.Marquee); + + // Rename Kiseki.Launcher.exe.new -> Kiseki.Launcher.exe, and launch it with our payload + string command = $"del /Q \"{Paths.Application}\" && move /Y \"{Paths.Application}.new\" \"{Paths.Application}\" && start \"\" \"{Paths.Application}\" {Payload}"; + + Process.Start(new ProcessStartInfo() + { + FileName = "cmd.exe", + Arguments = $"/c timeout 1 && {command}", + UseShellExecute = true, + WindowStyle = ProcessWindowStyle.Hidden + }); + + Environment.Exit((int)Win32.ErrorCode.ERROR_SUCCESS); + }; + + client.DownloadFileAsync(new Uri(release.Assets[0].BrowserDownloadUrl), $"{Paths.Application}.new"); + }); + + thread.Start(); + + return; + } + } + } public void Abort() @@ -63,9 +123,9 @@ public class Bootstrapper : Interfaces.IBootstrapper OnHeadingChange!.Invoke(this, heading); } - protected virtual void ProgressBarAdd(int value) + protected virtual void ProgressBarSet(int value) { - OnProgressBarAdd!.Invoke(this, value); + OnProgressBarSet!.Invoke(this, value); } protected virtual void ProgressBarStateChange(Enums.ProgressBarState state) diff --git a/Kiseki.Launcher.Windows/MainWindow.cs b/Kiseki.Launcher.Windows/MainWindow.cs index 440d6e8..2880d97 100644 --- a/Kiseki.Launcher.Windows/MainWindow.cs +++ b/Kiseki.Launcher.Windows/MainWindow.cs @@ -13,7 +13,7 @@ public class MainWindow : Form { Bootstrapper = new Bootstrapper(payload); Bootstrapper.OnHeadingChange += Bootstrapper_HeadingChanged; - Bootstrapper.OnProgressBarAdd += Bootstrapper_ProgressBarAdded; + Bootstrapper.OnProgressBarSet += Bootstrapper_ProgressBarSet; Bootstrapper.OnProgressBarStateChange += Bootstrapper_ProgressBarStateChanged; Bootstrapper.OnError += Bootstrapper_Errored; @@ -72,9 +72,9 @@ public class MainWindow : Form Page.Heading = heading; } - private void Bootstrapper_ProgressBarAdded(object? sender, int value) + private void Bootstrapper_ProgressBarSet(object? sender, int value) { - Page.ProgressBar!.Value += value; + Page.ProgressBar!.Value = value; } private void Bootstrapper_ProgressBarStateChanged(object? sender, Enums.ProgressBarState state) diff --git a/Kiseki.Launcher.Windows/Win32.cs b/Kiseki.Launcher.Windows/Win32.cs index 2764883..269fdbb 100644 --- a/Kiseki.Launcher.Windows/Win32.cs +++ b/Kiseki.Launcher.Windows/Win32.cs @@ -2,8 +2,8 @@ namespace Kiseki.Launcher.Windows; public static class Win32 { - // Ref: https://learn.microsoft.com/en-us/windows/win32/msi/error-codes - // Ref: https://i-logic.com/serial/errorcodes.htm + // REF: https://learn.microsoft.com/en-us/windows/win32/msi/error-codes + // REF: https://i-logic.com/serial/errorcodes.htm public enum ErrorCode { ERROR_SUCCESS = 0, diff --git a/Kiseki.Launcher/Enums/PackageType.cs b/Kiseki.Launcher/Enums/PackageType.cs deleted file mode 100644 index c7a6bdc..0000000 --- a/Kiseki.Launcher/Enums/PackageType.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Kiseki.Launcher.Enums; - -public enum PackageType -{ - Bootstrapper, - Client, -} \ No newline at end of file diff --git a/Kiseki.Launcher/Helpers/Base64.cs b/Kiseki.Launcher/Helpers/Base64.cs index 5f1a297..f1ba731 100644 --- a/Kiseki.Launcher/Helpers/Base64.cs +++ b/Kiseki.Launcher/Helpers/Base64.cs @@ -4,7 +4,7 @@ using System.Text; public static class Base64 { - // Source: https://stackoverflow.com/a/54143400 + // REF: https://stackoverflow.com/a/54143400 public static bool IsBase64String(string base64) { Span buffer = new(new byte[base64.Length]); diff --git a/Kiseki.Launcher/Interfaces/IBootstrapper.cs b/Kiseki.Launcher/Interfaces/IBootstrapper.cs index 7d22c93..23a824d 100644 --- a/Kiseki.Launcher/Interfaces/IBootstrapper.cs +++ b/Kiseki.Launcher/Interfaces/IBootstrapper.cs @@ -4,7 +4,7 @@ public interface IBootstrapper { // These connect to MainWindow event EventHandler? OnHeadingChange; - event EventHandler? OnProgressBarAdd; + event EventHandler? OnProgressBarSet; event EventHandler? OnProgressBarStateChange; event EventHandler? OnError; diff --git a/Kiseki.Launcher/Models/ClientRelease.cs b/Kiseki.Launcher/Models/ClientRelease.cs new file mode 100644 index 0000000..2a6c30a --- /dev/null +++ b/Kiseki.Launcher/Models/ClientRelease.cs @@ -0,0 +1,21 @@ +namespace Kiseki.Launcher.Models; + +using System.Text.Json.Serialization; + +public class ClientRelease +{ + [JsonPropertyName("checksums")] + public Dictionary Checksums { get; set; } = null!; + + [JsonPropertyName("asset")] + public ClientReleaseAsset Asset { get; set; } = null!; +} + +public class ClientReleaseAsset +{ + [JsonPropertyName("url")] + public string Url { get; set; } = null!; + + [JsonPropertyName("checksum")] + public string Checksum { get; set; } = null!; +} \ No newline at end of file