namespace Kiseki.Launcher.Windows; using System.Diagnostics; using System.Reflection; using Microsoft.Win32; using Syroot.Windows.IO; public class Bootstrapper : Interfaces.IBootstrapper { public readonly static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2]; private readonly string Payload; private readonly Dictionary Arguments = new(); public event EventHandler? OnHeadingChange; public event EventHandler? OnProgressBarAdd; public event EventHandler? OnProgressBarStateChange; public event EventHandler? OnError; public Bootstrapper(string payload) { Payload = payload; } public bool Initialize() { if (!Helpers.Base64.IsBase64String(Payload)) { Error($"Failed to launch {Constants.PROJECT_NAME}", $"Try launching {Constants.PROJECT_NAME} from the website again."); return false; } // { mode, version, ticket, joinscript } string[] pieces = Helpers.Base64.ConvertBase64ToString(Payload).Split("|"); if (pieces.Length != 4) { Error($"Failed to launch {Constants.PROJECT_NAME}", $"Try launching {Constants.PROJECT_NAME} from the website again."); return false; } Arguments["Mode"] = pieces[0]; Arguments["Version"] = pieces[1]; Arguments["Ticket"] = pieces[2]; Arguments["JoinScript"] = pieces[3]; return true; } public void Run() { // } public void Abort() { // } #region MainWindow protected virtual void HeadingChange(string heading) { OnHeadingChange!.Invoke(this, heading); } protected virtual void ProgressBarAdd(int value) { OnProgressBarAdd!.Invoke(this, value); } protected virtual void ProgressBarStateChange(Enums.ProgressBarState state) { OnProgressBarStateChange!.Invoke(this, state); } protected virtual void Error(string heading, string text) { // Ugly hack for now (I don't want to derive EventHandler just for this) OnError!.Invoke(this, new string[] { heading, text }); } #endregion #region Installation public static void Install() { // Cleanup our registry entries beforehand (if they even exist) Protocol.Unregister(); Register(); // Create paths Directory.CreateDirectory(Paths.Base); Directory.CreateDirectory(Paths.Versions); Directory.CreateDirectory(Paths.Logs); // Copy ourselves if (!File.Exists(Paths.Application)) File.Copy(Application.ExecutablePath, Paths.Application, true); // Register us and our protocol handler system-wide Register(); Protocol.Register(); // Create shortcuts if (File.Exists(Path.Combine(Paths.StartMenu, $"{Constants.PROJECT_NAME}.lnk"))) File.Delete(Path.Combine(Paths.StartMenu, $"{Constants.PROJECT_NAME}.lnk")); if (File.Exists(Path.Combine(Paths.Desktop, $"{Constants.PROJECT_NAME}.lnk"))) File.Delete(Path.Combine(Paths.Desktop, $"{Constants.PROJECT_NAME}.lnk")); ShellLink.Shortcut.CreateShortcut(Paths.Application, "", Paths.Application, 0).WriteToFile(Path.Combine(Paths.StartMenu, $"{Constants.PROJECT_NAME}.lnk")); ShellLink.Shortcut.CreateShortcut(Paths.Application, "", Paths.Application, 0).WriteToFile(Path.Combine(Paths.Desktop, $"{Constants.PROJECT_NAME}.lnk")); // We're finished MessageBox.Show($"Sucessfully installed {Constants.PROJECT_NAME}!", Constants.PROJECT_NAME, MessageBoxButtons.OK, MessageBoxIcon.Information); Environment.Exit((int)Win32.ErrorCode.ERROR_SUCCESS); } public static void Uninstall(bool quiet = false) { DialogResult answer = quiet ? DialogResult.Yes : MessageBox.Show($"Are you sure you want to uninstall {Constants.PROJECT_NAME}?", Constants.PROJECT_NAME, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (answer != DialogResult.Yes) Environment.Exit((int)Win32.ErrorCode.ERROR_CANCELLED); // Close active processes if (Process.GetProcessesByName($"{Constants.PROJECT_NAME}.Player").Any() || Process.GetProcessesByName($"{Constants.PROJECT_NAME}.Studio").Any()) { answer = quiet ? DialogResult.Yes : MessageBox.Show($"Kiseki is currently running. Would you like to close Kiseki now?", Constants.PROJECT_NAME, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (answer != DialogResult.Yes) Environment.Exit((int)Win32.ErrorCode.ERROR_CANCELLED); try { foreach (Process process in Process.GetProcessesByName($"{Constants.PROJECT_NAME}.Player")) process.Kill(); foreach (Process process in Process.GetProcessesByName($"{Constants.PROJECT_NAME}.Studio")) process.Kill(); } catch { Environment.Exit((int)Win32.ErrorCode.ERROR_INTERNAL_ERROR); } } // Delete all files if (Directory.Exists(Paths.Logs)) Directory.Delete(Paths.Logs, true); if (Directory.Exists(Paths.Versions)) Directory.Delete(Paths.Versions, true); if (File.Exists(Paths.License)) File.Delete(Paths.License); // Delete our shortcuts if (File.Exists(Path.Combine(Paths.StartMenu, $"{Constants.PROJECT_NAME}.lnk"))) File.Delete(Path.Combine(Paths.StartMenu, $"{Constants.PROJECT_NAME}.lnk")); if (File.Exists(Path.Combine(Paths.Desktop, $"{Constants.PROJECT_NAME}.lnk"))) File.Delete(Path.Combine(Paths.Desktop, $"{Constants.PROJECT_NAME}.lnk")); // Cleanup our registry entries Unregister(); Protocol.Unregister(); answer = quiet ? DialogResult.OK : MessageBox.Show($"Sucessfully uninstalled {Constants.PROJECT_NAME}!", Constants.PROJECT_NAME, MessageBoxButtons.OK, MessageBoxIcon.Information); if (answer == DialogResult.OK || answer == DialogResult.Cancel) { string command = $"del /Q \"{Paths.Application}\""; if (Directory.GetFiles(Paths.Base, "*", SearchOption.AllDirectories).Length == 1) { // We're the only file in the directory, so we can delete the entire directory command += $" && rmdir \"{Paths.Base}\""; } Process.Start(new ProcessStartInfo() { FileName = "cmd.exe", Arguments = $"/c timeout 5 && {command}", UseShellExecute = true, WindowStyle = ProcessWindowStyle.Hidden }); Environment.Exit((int)Win32.ErrorCode.ERROR_SUCCESS); } } #endregion #region Registration public static void Register() { using RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Constants.PROJECT_NAME}"); uninstallKey.SetValue("NoModify", 1); uninstallKey.SetValue("NoRepair", 1); uninstallKey.SetValue("DisplayIcon", $"{Paths.Application},0"); uninstallKey.SetValue("DisplayName", Constants.PROJECT_NAME); uninstallKey.SetValue("DisplayVersion", Version); if (uninstallKey.GetValue("InstallDate") is null) uninstallKey.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd")); uninstallKey.SetValue("InstallLocation", Paths.Base); uninstallKey.SetValue("Publisher", Constants.PROJECT_NAME); uninstallKey.SetValue("QuietUninstallString", $"\"{Paths.Application}\" -uninstall -quiet"); uninstallKey.SetValue("UninstallString", $"\"{Paths.Application}\" -uninstall"); uninstallKey.SetValue("URLInfoAbout", $"https://github.com/{Constants.PROJECT_REPOSITORY}"); uninstallKey.SetValue("URLUpdateInfo", $"https://github.com/{Constants.PROJECT_REPOSITORY}/releases/latest"); } public static void Unregister() { try { Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Constants.PROJECT_NAME}"); } catch { #if DEBUG throw; #endif } } #endregion #region Licensing public static bool License() { if (!File.Exists(Paths.License)) { if (!AskForLicense(Paths.License)) { // User doesn't want to license this launcher return false; } } // Load the license... while (!Web.LoadLicense(File.ReadAllText(Paths.License))) { // ...and if it's corrupt, keep asking for a new one. File.Delete(Paths.License); MessageBox.Show($"Corrupt license file! Please verify the contents of your license file (it should be named \"license.bin\".)", Constants.PROJECT_NAME, MessageBoxButtons.OK, MessageBoxIcon.Error); AskForLicense(Paths.License, false); } return true; } public static void Unlicense() { if (File.Exists(Paths.License)) { File.Delete(Paths.License); } } private static bool AskForLicense(string licensePath, bool showDialog = true) { DialogResult answer = showDialog ? MessageBox.Show($"{Constants.PROJECT_NAME} is currently undergoing maintenance and requires a license in order to access the test site. Would you like to look for the license file now?", Constants.PROJECT_NAME, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) : DialogResult.Yes; if (answer == DialogResult.Yes) { using OpenFileDialog dialog = new() { Title = "Select your license file", Filter = "License files (*.bin)|*.bin", InitialDirectory = KnownFolders.Downloads.Path }; if (dialog.ShowDialog() == DialogResult.OK) { File.Copy(dialog.FileName, licensePath, true); } return true; } return false; } #endregion }