feat: add uninstall code

This commit is contained in:
rjindael 2023-08-01 22:59:28 -07:00
parent f68ce87cb5
commit 7890fa380a
No known key found for this signature in database
GPG Key ID: D069369C906CCF31
9 changed files with 103 additions and 20 deletions

View File

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Reflection; using System.Reflection;
using Microsoft.Win32; using Microsoft.Win32;
@ -15,14 +16,69 @@ namespace Kiseki.Launcher.Windows
Directory.CreateDirectory(Directories.Base); Directory.CreateDirectory(Directories.Base);
} }
// TODO: Implement this public static void Uninstall(bool quiet = false)
public static void Register()
{ {
using (RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{Constants.PROJECT_NAME}")) 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())
{ {
applicationKey.SetValue("InstallLocation", Directories.Base); 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
Directory.Delete(Directories.Logs, true);
Directory.Delete(Directories.Versions, true);
Directory.Delete(Directories.License);
// 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 \"{Directories.Application}\"";
if (Directory.GetFiles(Directories.Base, "*", SearchOption.AllDirectories).Length == 1)
{
// We're the only file in the directory, so we can delete the entire directory
command += $" && rmdir \"{Directories.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);
}
}
public static void Register()
{
using RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Constants.PROJECT_NAME}"); using RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Constants.PROJECT_NAME}");
uninstallKey.SetValue("DisplayIcon", $"{Directories.Application},0"); uninstallKey.SetValue("DisplayIcon", $"{Directories.Application},0");
@ -42,10 +98,18 @@ namespace Kiseki.Launcher.Windows
uninstallKey.SetValue("URLUpdateInfo", $"https://github.com/{Constants.PROJECT_REPOSITORY}/releases/latest"); uninstallKey.SetValue("URLUpdateInfo", $"https://github.com/{Constants.PROJECT_REPOSITORY}/releases/latest");
} }
// TODO: Implement this
public static void Unregister() public static void Unregister()
{ {
Registry.CurrentUser.DeleteSubKey($@"Software\{Constants.PROJECT_NAME}"); try
{
Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Constants.PROJECT_NAME}");
}
catch
{
#if DEBUG
throw;
#endif
}
} }
#endregion #endregion

View File

@ -47,6 +47,12 @@ namespace Kiseki.Launcher.Windows
return; return;
} }
if (args[0] == "uninstall")
{
Launcher.Uninstall(args[0] == "-quiet");
return;
}
ApplicationConfiguration.Initialize(); ApplicationConfiguration.Initialize();
Application.Run(new MainWindow(args[0])); Application.Run(new MainWindow(args[0]));
} }

View File

@ -6,17 +6,17 @@ namespace Kiseki.Launcher.Windows
{ {
public const string PROTOCOL_KEY = "kiseki"; public const string PROTOCOL_KEY = "kiseki";
public void Register(string key, string name, string handler) public static void Register()
{ {
string arguments = $"\"{handler}\" \"%1\""; string arguments = $"\"{Directories.Application}\" \"%1\"";
RegistryKey uriKey = Registry.CurrentUser.CreateSubKey(@$"Software\Classes\{key}"); RegistryKey uriKey = Registry.CurrentUser.CreateSubKey(@$"Software\Classes\{PROTOCOL_KEY}");
RegistryKey uriIconKey = uriKey.CreateSubKey("DefaultIcon"); RegistryKey uriIconKey = uriKey.CreateSubKey("DefaultIcon");
RegistryKey uriCommandKey = uriKey.CreateSubKey(@"shell\open\command"); RegistryKey uriCommandKey = uriKey.CreateSubKey(@"shell\open\command");
if (uriKey.GetValue("") is null) if (uriKey.GetValue("") is null)
{ {
uriKey.SetValue("", $"URL: {name} Protocol"); uriKey.SetValue("", $"URL: {Constants.PROJECT_NAME} Protocol");
uriKey.SetValue("URL Protocol", ""); uriKey.SetValue("URL Protocol", "");
} }
@ -30,9 +30,9 @@ namespace Kiseki.Launcher.Windows
uriCommandKey.Close(); uriCommandKey.Close();
} }
public void Unregister(string key) public static void Unregister()
{ {
Registry.CurrentUser.DeleteSubKeyTree(@$"Software\Classes\{key}"); Registry.CurrentUser.DeleteSubKeyTree(@$"Software\Classes\{PROTOCOL_KEY}");
} }
} }
} }

View File

@ -4,13 +4,24 @@ namespace Kiseki.Launcher.Windows
{ {
public static class Win32 public static class Win32
{ {
[DllImport("shell32", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] // https://learn.microsoft.com/en-us/windows/win32/msi/error-codes
private static extern string SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, nint hToken = default); // https://i-logic.com/serial/errorcodes.htm
public enum ErrorCode
{
ERROR_SUCCESS = 0,
ERROR_INSTALL_USEREXIT = 1602,
ERROR_INSTALL_FAILURE = 1603,
ERROR_CANCELLED = 1223,
ERROR_INTERNAL_ERROR = 1359
}
// https://www.codeproject.com/Articles/878605/Getting-All-Special-Folders-in-NET // https://www.codeproject.com/Articles/878605/Getting-All-Special-Folders-in-NET
public static string GetDownloadsPath() public static string GetDownloadsPath()
{ {
return SHGetKnownFolderPath(new("374DE290-123F-4565-9164-39C4925E467B"), 0); return SHGetKnownFolderPath(new("374DE290-123F-4565-9164-39C4925E467B"), 0);
} }
[DllImport("shell32", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
private static extern string SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, nint hToken = default);
} }
} }

View File

@ -49,6 +49,7 @@ namespace Kiseki.Launcher
{ {
PageHeadingChange("Downloading Kiseki..."); PageHeadingChange("Downloading Kiseki...");
ProgressBarStateChange(ProgressBarState.Normal); ProgressBarStateChange(ProgressBarState.Normal);
marquee = false; marquee = false;
} }
@ -60,6 +61,7 @@ namespace Kiseki.Launcher
await Task.Delay(2800); await Task.Delay(2800);
yield return 4; yield return 4;
await Task.Delay(200); await Task.Delay(200);
} }

View File

@ -9,6 +9,7 @@ namespace Kiseki.Launcher.Helpers
try try
{ {
string json = Web.HttpClient.GetStringAsync(url).Result; string json = Web.HttpClient.GetStringAsync(url).Result;
return JsonSerializer.Deserialize<T>(json); return JsonSerializer.Deserialize<T>(json);
} }
catch catch

View File

@ -3,6 +3,7 @@ namespace Kiseki.Launcher
public interface ILauncher public interface ILauncher
{ {
static abstract void Install(); static abstract void Install();
static abstract void Uninstall(bool quiet = false);
static abstract void Register(); static abstract void Register();
static abstract void Unregister(); static abstract void Unregister();
} }

View File

@ -2,7 +2,7 @@ namespace Kiseki.Launcher
{ {
public interface IProtocol public interface IProtocol
{ {
void Register(string key, string name, string handler); static abstract void Register();
void Unregister(string key); static abstract void Unregister();
} }
} }

View File

@ -22,12 +22,11 @@ namespace Kiseki.Launcher
CurrentUrl = IsInMaintenance ? $"{MAINTENANCE_DOMAIN}.{BASE_URL}" : BASE_URL; CurrentUrl = IsInMaintenance ? $"{MAINTENANCE_DOMAIN}.{BASE_URL}" : BASE_URL;
int response = CheckHealth(); int response = CheckHealth();
if (response != RESPONSE_SUCCESS) if (response != RESPONSE_SUCCESS)
{ {
if (response == RESPONSE_MAINTENANCE) if (response == RESPONSE_MAINTENANCE)
{
IsInMaintenance = true; IsInMaintenance = true;
}
return false; return false;
} }
@ -55,10 +54,9 @@ namespace Kiseki.Launcher
HttpClient.DefaultRequestHeaders.Clear(); HttpClient.DefaultRequestHeaders.Clear();
var headers = JsonSerializer.Deserialize<Dictionary<string, string>>(license)!; var headers = JsonSerializer.Deserialize<Dictionary<string, string>>(license)!;
for (int i = 0; i < headers.Count; i++) for (int i = 0; i < headers.Count; i++)
{
HttpClient.DefaultRequestHeaders.Add(headers.ElementAt(i).Key, headers.ElementAt(i).Value); HttpClient.DefaultRequestHeaders.Add(headers.ElementAt(i).Key, headers.ElementAt(i).Value);
}
} }
catch catch
{ {