diff --git a/Bloxstrap/App.xaml b/Bloxstrap/App.xaml index 28b450e..8f4642f 100644 --- a/Bloxstrap/App.xaml +++ b/Bloxstrap/App.xaml @@ -3,7 +3,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Bloxstrap" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" - ShutdownMode="OnExplicitShutdown"> + ShutdownMode="OnExplicitShutdown" + DispatcherUnhandledException="GlobalExceptionHandler"> diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs index fb76ee9..e0286b0 100644 --- a/Bloxstrap/App.xaml.cs +++ b/Bloxstrap/App.xaml.cs @@ -8,6 +8,8 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; using System.Windows; +using System.Windows.Threading; + using Microsoft.Win32; using Bloxstrap.Dialogs; @@ -77,35 +79,25 @@ namespace Bloxstrap Logger.Initialize(Path.Combine(logdir, $"{ProjectName}_{timestamp}.log")); } + void GlobalExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs e) + { + e.Handled = true; + + Logger.WriteLine("[App::OnStartup] An exception occurred when running the main thread"); + Logger.WriteLine($"[App::OnStartup] {e.Exception}"); + + if (!IsQuiet) + Settings.Prop.BootstrapperStyle.GetNew().ShowError($"{e.Exception.GetType()}: {e.Exception.Message}"); + + Terminate(Bootstrapper.ERROR_INSTALL_FAILURE); + } + protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); Logger.WriteLine($"[App::OnStartup] Starting {ProjectName} v{Version}"); - // todo: remove this once 32-bit support is fully gone - if (!App.IsQuiet && !Environment.Is64BitOperatingSystem) - { - string message = "In the near future, Roblox will no longer support 32-bit Windows devices. To keep playing Roblox, please use a device that is 64-bit compatible."; - - // check if the processor actually supports 64-bit with wmic - // chances are the user just has a 32-bit version of windows installed on 64-bit hardware - Process p = new(); - p.StartInfo.CreateNoWindow = true; - p.StartInfo.UseShellExecute = false; - p.StartInfo.RedirectStandardOutput = true; - p.StartInfo.FileName = "wmic.exe"; - p.StartInfo.Arguments = "cpu get Architecture"; - p.Start(); - - // https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-processor - // architecture type 9 is x64 - if (p.StandardOutput.ReadToEnd().Contains('9')) - message += "\n\nYour computer is running a 32-bit version of Windows but is actually 64-bit compatible. Search online for how to upgrade to a 64-bit version of Windows."; - - ShowMessageBox(message, MessageBoxImage.Warning); - } - // To customize application configuration such as set high DPI settings or default font, // see https://aka.ms/applicationconfiguration. ApplicationConfiguration.Initialize(); @@ -148,6 +140,29 @@ namespace Bloxstrap } } + // todo: remove this once 32-bit support is fully gone + if (!App.IsQuiet && !Environment.Is64BitOperatingSystem) + { + string message = "In the near future, Roblox will no longer support 32-bit Windows devices. To keep playing Roblox, please use a device that is 64-bit compatible."; + + // check if the processor actually supports 64-bit with wmic + // chances are the user just has a 32-bit version of windows installed on 64-bit hardware + Process p = new(); + p.StartInfo.CreateNoWindow = true; + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.FileName = "wmic.exe"; + p.StartInfo.Arguments = "cpu get Architecture"; + p.Start(); + + // https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-processor + // architecture type 9 is x64 + if (p.StandardOutput.ReadToEnd().Contains('9')) + message += "\n\nYour computer is running a 32-bit version of Windows but is actually 64-bit compatible. Search online for how to upgrade to a 64-bit version of Windows."; + + ShowMessageBox(message, MessageBoxImage.Warning); + } + // check if installed using (RegistryKey? registryKey = Registry.CurrentUser.OpenSubKey($@"Software\{ProjectName}")) { @@ -192,151 +207,140 @@ namespace Bloxstrap Settings.Load(); State.Load(); } + #if !DEBUG - try - { - if (!IsUninstall && !IsFirstRun) - Updater.CheckInstalledVersion(); + if (!IsUninstall && !IsFirstRun) + Updater.CheckInstalledVersion(); #endif - string commandLine = ""; + string commandLine = ""; - if (IsMenuLaunch) + if (IsMenuLaunch) + { + Mutex mutex; + + try { - Mutex mutex; - - try - { - mutex = Mutex.OpenExisting("Bloxstrap_MenuMutex"); - Logger.WriteLine("[App::OnStartup] Bloxstrap_MenuMutex mutex exists, aborting menu launch..."); - Terminate(); - } - catch - { - // no mutex exists, continue to opening preferences menu - mutex = new(true, "Bloxstrap_MenuMutex"); - } - - if (Utilities.GetProcessCount(ProjectName) > 1) - ShowMessageBox($"{ProjectName} is currently running, likely as a background Roblox process. Please note that not all your changes will immediately apply until you close all currently open Roblox instances.", MessageBoxImage.Information); - - new MainWindow().ShowDialog(); - FastFlags.Save(); + mutex = Mutex.OpenExisting("Bloxstrap_MenuMutex"); + Logger.WriteLine("[App::OnStartup] Bloxstrap_MenuMutex mutex exists, aborting menu launch..."); + Terminate(); } - else if (LaunchArgs.Length > 0) + catch { - if (LaunchArgs[0].StartsWith("roblox-player:")) - { - commandLine = Protocol.ParseUri(LaunchArgs[0]); - } - else if (LaunchArgs[0].StartsWith("roblox:")) - { - commandLine = $"--app --deeplink {LaunchArgs[0]}"; - } - else - { - commandLine = "--app"; - } + // no mutex exists, continue to opening preferences menu + mutex = new(true, "Bloxstrap_MenuMutex"); + } + + if (Utilities.GetProcessCount(ProjectName) > 1) + ShowMessageBox($"{ProjectName} is currently running, likely as a background Roblox process. Please note that not all your changes will immediately apply until you close all currently open Roblox instances.", MessageBoxImage.Information); + + new MainWindow().ShowDialog(); + FastFlags.Save(); + } + else if (LaunchArgs.Length > 0) + { + if (LaunchArgs[0].StartsWith("roblox-player:")) + { + commandLine = Protocol.ParseUri(LaunchArgs[0]); + } + else if (LaunchArgs[0].StartsWith("roblox:")) + { + commandLine = $"--app --deeplink {LaunchArgs[0]}"; } else { commandLine = "--app"; } - - if (!String.IsNullOrEmpty(commandLine)) - { - if (!IsFirstRun) - ShouldSaveConfigs = true; - - DeployManager.SetChannel(Settings.Prop.Channel); - - // start bootstrapper and show the bootstrapper modal if we're not running silently - Logger.WriteLine($"[App::OnStartup] Initializing bootstrapper"); - Bootstrapper bootstrapper = new(commandLine); - IBootstrapperDialog? dialog = null; - - if (!IsQuiet) - { - Logger.WriteLine($"[App::OnStartup] Initializing bootstrapper dialog"); - dialog = Settings.Prop.BootstrapperStyle.GetNew(); - bootstrapper.Dialog = dialog; - dialog.Bootstrapper = bootstrapper; - } - - // handle roblox singleton mutex for multi-instance launching - // note we're handling it here in the main thread and NOT in the - // bootstrapper as handling mutexes in async contexts suuuuuucks - - Mutex? singletonMutex = null; - - if (Settings.Prop.MultiInstanceLaunching) - { - Logger.WriteLine("[App::OnStartup] Creating singleton mutex"); - - try - { - Mutex.OpenExisting("ROBLOX_singletonMutex"); - Logger.WriteLine("[App::OnStartup] Warning - singleton mutex already exists!"); - } - catch - { - // create the singleton mutex before the game client does - singletonMutex = new Mutex(true, "ROBLOX_singletonMutex"); - } - } - - // there's a bug here that i have yet to fix! - // sometimes the task just terminates when the bootstrapper hasn't - // actually finished, causing the bootstrapper to hang indefinitely - // i have no idea how the fuck this happens, but it happens like VERY - // rarely so i'm not too concerned by it - // maybe one day ill find out why it happens - Task bootstrapperTask = Task.Run(() => bootstrapper.Run()).ContinueWith(t => - { - Logger.WriteLine("[App::OnStartup] Bootstrapper task has finished"); - - if (t.IsFaulted) - Logger.WriteLine("[App::OnStartup] An exception occurred when running the bootstrapper"); - - if (t.Exception is null) - return; - - Logger.WriteLine($"[App::OnStartup] {t.Exception}"); - -#if DEBUG - throw t.Exception; -#else - var exception = t.Exception.InnerExceptions.Count >= 1 ? t.Exception.InnerExceptions[0] : t.Exception; - dialog?.ShowError($"{exception.GetType()}: {exception.Message}"); -#endif - }); - - dialog?.ShowBootstrapper(); - bootstrapperTask.Wait(); - - if (singletonMutex is not null) - { - Logger.WriteLine($"[App::OnStartup] We have singleton mutex ownership! Running in background until all Roblox processes are closed"); - - // we've got ownership of the roblox singleton mutex! - // if we stop running, everything will screw up once any more roblox instances launched - while (Utilities.GetProcessCount("RobloxPlayerBeta", false) != 0) - { - Thread.Sleep(5000); - } - } - } -#if !DEBUG } - catch (Exception ex) + else { - Logger.WriteLine("[App::OnStartup] An exception occurred when running the main thread"); - Logger.WriteLine($"[App::OnStartup] {ex}"); + commandLine = "--app"; + } + + if (!String.IsNullOrEmpty(commandLine)) + { + if (!IsFirstRun) + ShouldSaveConfigs = true; + + DeployManager.SetChannel(Settings.Prop.Channel); + + // start bootstrapper and show the bootstrapper modal if we're not running silently + Logger.WriteLine($"[App::OnStartup] Initializing bootstrapper"); + Bootstrapper bootstrapper = new(commandLine); + IBootstrapperDialog? dialog = null; if (!IsQuiet) - Settings.Prop.BootstrapperStyle.GetNew().ShowError($"{ex.GetType()}: {ex.Message}"); - } + { + Logger.WriteLine($"[App::OnStartup] Initializing bootstrapper dialog"); + dialog = Settings.Prop.BootstrapperStyle.GetNew(); + bootstrapper.Dialog = dialog; + dialog.Bootstrapper = bootstrapper; + } + + // handle roblox singleton mutex for multi-instance launching + // note we're handling it here in the main thread and NOT in the + // bootstrapper as handling mutexes in async contexts suuuuuucks + + Mutex? singletonMutex = null; + + if (Settings.Prop.MultiInstanceLaunching) + { + Logger.WriteLine("[App::OnStartup] Creating singleton mutex"); + + try + { + Mutex.OpenExisting("ROBLOX_singletonMutex"); + Logger.WriteLine("[App::OnStartup] Warning - singleton mutex already exists!"); + } + catch + { + // create the singleton mutex before the game client does + singletonMutex = new Mutex(true, "ROBLOX_singletonMutex"); + } + } + + // there's a bug here that i have yet to fix! + // sometimes the task just terminates when the bootstrapper hasn't + // actually finished, causing the bootstrapper to hang indefinitely + // i have no idea how the fuck this happens, but it happens like VERY + // rarely so i'm not too concerned by it + // maybe one day ill find out why it happens + Task bootstrapperTask = Task.Run(() => bootstrapper.Run()).ContinueWith(t => + { + Logger.WriteLine("[App::OnStartup] Bootstrapper task has finished"); + + if (t.IsFaulted) + Logger.WriteLine("[App::OnStartup] An exception occurred when running the bootstrapper"); + + if (t.Exception is null) + return; + + Logger.WriteLine($"[App::OnStartup] {t.Exception}"); + +#if DEBUG + throw t.Exception; +#else + var exception = t.Exception.InnerExceptions.Count >= 1 ? t.Exception.InnerExceptions[0] : t.Exception; + dialog?.ShowError($"{exception.GetType()}: {exception.Message}"); + Terminate(Bootstrapper.ERROR_INSTALL_FAILURE); #endif + }); + + dialog?.ShowBootstrapper(); + bootstrapperTask.Wait(); + + if (singletonMutex is not null) + { + Logger.WriteLine($"[App::OnStartup] We have singleton mutex ownership! Running in background until all Roblox processes are closed"); + + // we've got ownership of the roblox singleton mutex! + // if we stop running, everything will screw up once any more roblox instances launched + while (Utilities.GetProcessCount("RobloxPlayerBeta", false) != 0) + { + Thread.Sleep(5000); + } + } + } Logger.WriteLine($"[App::OnStartup] Successfully reached end of main thread. Terminating...");