diff --git a/Kiseki.Launcher.Windows/Directories.cs b/Kiseki.Launcher.Windows/Directories.cs index 9eb09ea..8282dc8 100644 --- a/Kiseki.Launcher.Windows/Directories.cs +++ b/Kiseki.Launcher.Windows/Directories.cs @@ -7,6 +7,7 @@ namespace Kiseki.Launcher.Windows public static string Base { get; private set; } = ""; public static string Logs { get; private set; } = ""; public static string Versions { get; private set; } = ""; + public static string License { get; private set; } = ""; public static string Application { get; private set; } = ""; public static void Initialize(string baseDirectory) @@ -15,8 +16,9 @@ namespace Kiseki.Launcher.Windows Logs = Path.Combine(Base, "Logs"); Versions = Path.Combine(Base, "Versions"); - - Application = Path.Combine(Base, $"{Launcher.ProjectName}.exe"); + + License = Path.Combine(Base, "license.bin"); + Application = Path.Combine(Base, $"{Constants.ProjectName}.Launcher.exe"); } } } \ No newline at end of file diff --git a/Kiseki.Launcher.Windows/Launcher.cs b/Kiseki.Launcher.Windows/Launcher.cs index 0cde7b4..a8f7398 100644 --- a/Kiseki.Launcher.Windows/Launcher.cs +++ b/Kiseki.Launcher.Windows/Launcher.cs @@ -6,28 +6,62 @@ namespace Kiseki.Launcher.Windows { public class Launcher : ILauncher { - public const string ProjectName = "Kiseki"; - public const string ProjectRepository = "kiseki-lol/launcher"; - - public readonly static string BaseUrl = "test.kiseki.lol"; // TODO: This should be set dynamically somehow public readonly static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2]; - public static void Install() + public async static void Install() { - + Directory.CreateDirectory(Directories.Base); + int response = await Web.CheckHealth(); + + if (response != Web.RESPONSE_SUCCESS || response != Web.RESPONSE_MAINTENANCE) + { + if (response != Web.RESPONSE_MAINTENANCE) + { + // The Kiseki website is either down or we can't connect to the internet. + // TODO: This is a strange scenario where we need to display an error outside of the controller. Can we do this within the page instead of a message box? + MessageBox.Show($"Failed to connect to the {Constants.ProjectName} website. Please check your internet connection.", Constants.ProjectName, MessageBoxButtons.OK, MessageBoxIcon.Error); + Environment.Exit(0); + } + else + { + // We are in maintenance mode, so let's ask for a license. + AskForLicense(Directories.License); + Web.LoadLicense(File.ReadAllText(Directories.License)); + + // ... try this again; + Install(); + } + } + + // okay, now download the launcher from the Kiseki website... + } + + private static void AskForLicense(string licensePath) + { + using OpenFileDialog dialog = new() + { + Title = "Select your license file", + Filter = "License files (*.bin)|*.bin", + InitialDirectory = Directories.Base + }; + + if (dialog.ShowDialog() == DialogResult.OK) + { + File.Copy(dialog.FileName, licensePath, true); + } } public static void Register() { - using (RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{ProjectName}")) + using (RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{Constants.ProjectName}")) { applicationKey.SetValue("InstallLocation", Directories.Base); } - using RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{ProjectName}"); + using RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Constants.ProjectName}"); uninstallKey.SetValue("DisplayIcon", $"{Directories.Application},0"); - uninstallKey.SetValue("DisplayName", ProjectName); + uninstallKey.SetValue("DisplayName", Constants.ProjectName); uninstallKey.SetValue("DisplayVersion", Version); if (uninstallKey.GetValue("InstallDate") is null) @@ -35,17 +69,17 @@ namespace Kiseki.Launcher.Windows uninstallKey.SetValue("InstallLocation", Directories.Base); uninstallKey.SetValue("NoRepair", 1); - uninstallKey.SetValue("Publisher", ProjectName); + uninstallKey.SetValue("Publisher", Constants.ProjectName); uninstallKey.SetValue("ModifyPath", $"\"{Directories.Application}\" -menu"); uninstallKey.SetValue("QuietUninstallString", $"\"{Directories.Application}\" -uninstall -quiet"); uninstallKey.SetValue("UninstallString", $"\"{Directories.Application}\" -uninstall"); - uninstallKey.SetValue("URLInfoAbout", $"https://github.com/{ProjectRepository}"); - uninstallKey.SetValue("URLUpdateInfo", $"https://github.com/{ProjectRepository}/releases/latest"); + uninstallKey.SetValue("URLInfoAbout", $"https://github.com/{Constants.ProjectRepository}"); + uninstallKey.SetValue("URLUpdateInfo", $"https://github.com/{Constants.ProjectRepository}/releases/latest"); } public static void Unregister() { - Registry.CurrentUser.DeleteSubKey($@"Software\{ProjectName}"); + Registry.CurrentUser.DeleteSubKey($@"Software\{Constants.ProjectName}"); } } } \ No newline at end of file diff --git a/Kiseki.Launcher.Windows/MainWindow.cs b/Kiseki.Launcher.Windows/MainWindow.cs index e4cfb0a..98ec7af 100644 --- a/Kiseki.Launcher.Windows/MainWindow.cs +++ b/Kiseki.Launcher.Windows/MainWindow.cs @@ -9,13 +9,19 @@ namespace Kiseki.Launcher.Windows private readonly TaskDialogPage Page; private readonly Controller Controller; - public MainWindow(string[] args) + public MainWindow(string payload) { - CloseButton = TaskDialogButton.Close; + Controller = new Controller(payload); + Controller.OnPageHeadingChange += Controller_PageHeadingChanged; + Controller.OnProgressBarAdd += Controller_ProgressBarAdded; + Controller.OnProgressBarStateChange += Controller_ProgressBarStateChanged; + Controller.OnErrorShow += Controller_ErrorShown; + Controller.OnLaunch += (s, e) => Environment.Exit(0); + CloseButton = TaskDialogButton.Close; Page = new TaskDialogPage() { - Caption = Launcher.ProjectName, + Caption = Constants.ProjectName, AllowMinimize = true, ProgressBar = new TaskDialogProgressBar() @@ -26,13 +32,10 @@ namespace Kiseki.Launcher.Windows Buttons = { CloseButton } }; - Controller = new Controller(Launcher.BaseUrl, args); - Controller.OnPageHeadingChange += Controller_PageHeadingChanged; - Controller.OnProgressBarChange += Controller_ProgressBarChanged; - Controller.OnProgressBarStateChange += Controller_ProgressBarStateChanged; - - Controller.OnInstall += (s, e) => Launcher.Install(); - Controller.OnLaunch += (s, e) => Environment.Exit(0); + Page.Created += (s, e) => + { + Controller.Start(); + }; Page.Destroyed += (s, e) => { @@ -45,22 +48,23 @@ namespace Kiseki.Launcher.Windows private void CloseButton_Click(object? sender, EventArgs e) { - throw new NotImplementedException(); + Controller.Dispose(); + Environment.Exit(0); } - private void Controller_PageHeadingChanged(object? sender, string Heading) + private void Controller_PageHeadingChanged(object? sender, string heading) { - Page.Heading = Heading; + Page.Heading = heading; } - private void Controller_ProgressBarChanged(object? sender, int Value) + private void Controller_ProgressBarAdded(object? sender, int value) { - Page.ProgressBar!.Value = Value; + Page.ProgressBar!.Value += value; } - private void Controller_ProgressBarStateChanged(object? sender, ProgressBarState State) + private void Controller_ProgressBarStateChanged(object? sender, ProgressBarState state) { - Page.ProgressBar!.State = State switch + Page.ProgressBar!.State = state switch { ProgressBarState.Normal => TaskDialogProgressBarState.Normal, ProgressBarState.Marquee => TaskDialogProgressBarState.Marquee, @@ -68,16 +72,20 @@ namespace Kiseki.Launcher.Windows }; } + private void Controller_ErrorShown(object? sender, string[] texts) + { + Page.Icon = TaskDialogIcon.Error; + Page.Heading = texts[0]; + Page.Text = texts[1]; + + Controller.Dispose(); + } + private void ShowProgressDialog() { TaskDialogIcon logo = new(Resources.IconKiseki); Page.Icon = logo; - Page.Created += (s, e) => - { - Controller.Start(); - }; - TaskDialog.ShowDialog(Page); } } diff --git a/Kiseki.Launcher.Windows/Program.cs b/Kiseki.Launcher.Windows/Program.cs index 346cc7f..db7a784 100644 --- a/Kiseki.Launcher.Windows/Program.cs +++ b/Kiseki.Launcher.Windows/Program.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace Kiseki.Launcher.Windows { internal static class Program @@ -6,10 +8,34 @@ namespace Kiseki.Launcher.Windows static void Main(string[] args) { string parentFolder = Path.GetDirectoryName(AppContext.BaseDirectory)!; - Directories.Initialize(parentFolder.ToLower().Contains(Launcher.ProjectName) ? AppContext.BaseDirectory : Path.Combine(Directories.LocalAppData, Launcher.ProjectName)); + if (parentFolder.ToLower().Contains(Constants.ProjectName)) + { + // Set to the current directory (either user-installed or default; it has "Kiseki" in the path, so that's good enough for us) + Directories.Initialize(AppContext.BaseDirectory); + } + else + { + // Set to the default directory (user likely hasn't installed the launcher yet) + Directories.Initialize(Path.Combine(Directories.LocalAppData, Constants.ProjectName)); + } + + Web.Initialize(); + + if (!File.Exists(Directories.Application)) + { + // The launcher is not installed, so let's install it. + Launcher.Install(); + } + + if (args.Length == 0) + { + // Nothing for us to do :P + Process.Start(Web.Url("/games")); + Environment.Exit(0); + } ApplicationConfiguration.Initialize(); - Application.Run(new MainWindow(args)); + Application.Run(new MainWindow(args[0])); } } } \ No newline at end of file diff --git a/Kiseki.Launcher/Constants.cs b/Kiseki.Launcher/Constants.cs new file mode 100644 index 0000000..c6f2ba5 --- /dev/null +++ b/Kiseki.Launcher/Constants.cs @@ -0,0 +1,8 @@ +namespace Kiseki.Launcher +{ + public static class Constants + { + public const string ProjectName = "Kiseki"; + public const string ProjectRepository = "kiseki-lol/launcher"; + } +} \ No newline at end of file diff --git a/Kiseki.Launcher/Controller.cs b/Kiseki.Launcher/Controller.cs index f1539bb..dc8c649 100644 --- a/Kiseki.Launcher/Controller.cs +++ b/Kiseki.Launcher/Controller.cs @@ -10,54 +10,43 @@ namespace Kiseki.Launcher public class Controller { - private readonly string BaseURL; private readonly Dictionary Arguments = new(); public event EventHandler? OnPageHeadingChange; - public event EventHandler? OnProgressBarChange; + public event EventHandler? OnProgressBarAdd; public event EventHandler? OnProgressBarStateChange; - public event EventHandler? OnInstall; + public event EventHandler? OnErrorShow; public event EventHandler? OnLaunch; - public static readonly HttpClient HttpClient = new(); - - public Controller(string baseURL, string[] args) + public Controller(string payload) { - BaseURL = baseURL; - - if (args.Length == 0) + if (!Base64.IsBase64String(payload)) { - // We are launching for the first time. This means that we should trigger the launcher install process. - Install(); + ErrorShow($"Failed to launch {Constants.ProjectName}", $"Try launching {Constants.ProjectName} from the website again."); + return; } - else + + // TODO: The payload will soon include more members; update this accordingly + payload = Base64.ConvertBase64ToString(payload); + if (payload.Split("|").Length != 2) { - // TODO: handle these more gracefully - if (!Base64.IsBase64String(args[0])) - { - Environment.Exit(0); - } - - // TODO: the payload will soon include more members, such as whether to open the IDE or not (as well as values required for our weird loopback authentication thing) - string payload = Base64.ConvertBase64ToString(args[0]); - if (payload.Split("|").Length != 2) - { - Environment.Exit(0); - } - - Arguments["JoinScriptURL"] = payload.Split("|")[0]; - Arguments["Ticket"] = payload.Split("|")[1]; + ErrorShow($"Failed to launch {Constants.ProjectName}", $"Try launching {Constants.ProjectName} from the website again."); + return; } + + Arguments["JoinScriptURL"] = payload.Split("|")[0]; + Arguments["Ticket"] = payload.Split("|")[1]; } public async void Start() { PageHeadingChange("Connecting to Kiseki..."); - var response = await Http.GetJson($"https://{BaseURL}/api/health"); - if (response is null) + // This is kind of useless, but whatever + int response = await Web.CheckHealth(); + if (response != Web.RESPONSE_SUCCESS) { - PageHeadingChange("Failed to connect"); + ErrorShow($"Failed to connect to {Constants.ProjectName}.", "Please check your internet connection."); return; } @@ -71,18 +60,15 @@ namespace Kiseki.Launcher marquee = false; } - ProgressBarChange(progressValue); + ProgressBarAdd(progressValue); } static async IAsyncEnumerable StreamBackgroundOperationProgressAsync() { await Task.Delay(2800); - for (int i = 0; i <= 100; i += 4) - { - yield return i; - await Task.Delay(200); - } + yield return 4; + await Task.Delay(200); } PageHeadingChange("Installing Kiseki..."); @@ -103,27 +89,27 @@ namespace Kiseki.Launcher // TODO: This will only be called when the user closes the window OR we're done (i.e. the Launched event is called.) } - protected virtual void PageHeadingChange(string Heading) + protected virtual void PageHeadingChange(string heading) { - OnPageHeadingChange!.Invoke(this, Heading); + OnPageHeadingChange!.Invoke(this, heading); } - protected virtual void ProgressBarChange(int Value) + protected virtual void ProgressBarAdd(int value) { - OnProgressBarChange!.Invoke(this, Value); + OnProgressBarAdd!.Invoke(this, value); } - protected virtual void ProgressBarStateChange(ProgressBarState State) + protected virtual void ProgressBarStateChange(ProgressBarState state) { - OnProgressBarStateChange!.Invoke(this, State); + OnProgressBarStateChange!.Invoke(this, state); } - protected virtual void Install() + protected virtual void ErrorShow(string heading, string text) { - OnInstall!.Invoke(this, EventArgs.Empty); + // ugly hack for now (I don't want to derive EventHandler just for this) + OnErrorShow!.Invoke(this, new string[] { heading, text }); } - protected virtual void Launch() { OnLaunch!.Invoke(this, EventArgs.Empty); diff --git a/Kiseki.Launcher/Helpers/Http.cs b/Kiseki.Launcher/Helpers/Http.cs index b9842d7..77bdfe3 100644 --- a/Kiseki.Launcher/Helpers/Http.cs +++ b/Kiseki.Launcher/Helpers/Http.cs @@ -6,7 +6,7 @@ namespace Kiseki.Launcher.Helpers { public static async Task GetJson(string url) { - string json = await Controller.HttpClient.GetStringAsync(url); + string json = await Web.HttpClient.GetStringAsync(url); try { diff --git a/Kiseki.Launcher/Models/Health.cs b/Kiseki.Launcher/Models/Health.cs index e33eee3..97d3fe0 100644 --- a/Kiseki.Launcher/Models/Health.cs +++ b/Kiseki.Launcher/Models/Health.cs @@ -2,6 +2,6 @@ namespace Kiseki.Launcher.Models { public class Health { - public string Response { get; set; } = ""; + public int Response { get; set; } = -1; } } \ No newline at end of file diff --git a/Kiseki.Launcher/Web.cs b/Kiseki.Launcher/Web.cs new file mode 100644 index 0000000..1245bcc --- /dev/null +++ b/Kiseki.Launcher/Web.cs @@ -0,0 +1,33 @@ +namespace Kiseki.Launcher +{ + public static class Web + { + public const int RESPONSE_SUCCESS = 0; + public const int RESPONSE_FAILURE = -1; + + public const int RESPONSE_MAINTENANCE = 1; + + public const string BaseUrl = "kiseki.lol"; + public const string MaintenanceDomain = "test"; + + public static readonly HttpClient HttpClient = new(); + public static string CurrentUrl { get; private set; } = ""; + + public static void Initialize() => CurrentUrl = BaseUrl; + public static string Url(string path) => $"https://{CurrentUrl}/{path}"; + + public static async Task CheckHealth() + { + var response = await Helpers.Http.GetJson(Url("/api/health")); + + return response?.Response ?? RESPONSE_FAILURE; + } + + public static void LoadLicense(string license) + { + CurrentUrl = $"{MaintenanceDomain}.{CurrentUrl}"; + + + } + } +} \ No newline at end of file