mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-16 10:11:30 -07:00
379 lines
14 KiB
C#
379 lines
14 KiB
C#
using System.Windows;
|
|
|
|
using Windows.Win32;
|
|
using Windows.Win32.Foundation;
|
|
|
|
using Bloxstrap.UI.Elements.Dialogs;
|
|
using Bloxstrap.Enums;
|
|
|
|
namespace Bloxstrap
|
|
{
|
|
public static class LaunchHandler
|
|
{
|
|
public static void ProcessNextAction(NextAction action, bool isUnfinishedInstall = false)
|
|
{
|
|
const string LOG_IDENT = "LaunchHandler::ProcessNextAction";
|
|
|
|
switch (action)
|
|
{
|
|
case NextAction.LaunchSettings:
|
|
App.Logger.WriteLine(LOG_IDENT, "Opening settings");
|
|
LaunchSettings();
|
|
break;
|
|
|
|
case NextAction.LaunchRoblox:
|
|
App.Logger.WriteLine(LOG_IDENT, "Opening Roblox");
|
|
LaunchRoblox(LaunchMode.Player);
|
|
break;
|
|
|
|
case NextAction.LaunchRobloxStudio:
|
|
App.Logger.WriteLine(LOG_IDENT, "Opening Roblox Studio");
|
|
LaunchRoblox(LaunchMode.Studio);
|
|
break;
|
|
|
|
default:
|
|
App.Logger.WriteLine(LOG_IDENT, "Closing");
|
|
App.Terminate(isUnfinishedInstall ? ErrorCode.ERROR_INSTALL_USEREXIT : ErrorCode.ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void ProcessLaunchArgs()
|
|
{
|
|
const string LOG_IDENT = "LaunchHandler::ProcessLaunchArgs";
|
|
|
|
// this order is specific
|
|
|
|
if (App.LaunchSettings.UninstallFlag.Active)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Opening uninstaller");
|
|
LaunchUninstaller();
|
|
}
|
|
else if (App.LaunchSettings.MenuFlag.Active)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Opening settings");
|
|
LaunchSettings();
|
|
}
|
|
else if (App.LaunchSettings.WatcherFlag.Active)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Opening watcher");
|
|
LaunchWatcher();
|
|
}
|
|
else if (App.LaunchSettings.MultiInstanceWatcherFlag.Active)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Opening multi-instance watcher");
|
|
LaunchMultiInstanceWatcher();
|
|
}
|
|
else if (App.LaunchSettings.BackgroundUpdaterFlag.Active)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Opening background updater");
|
|
LaunchBackgroundUpdater();
|
|
}
|
|
else if (App.LaunchSettings.RobloxLaunchMode != LaunchMode.None)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, $"Opening bootstrapper ({App.LaunchSettings.RobloxLaunchMode})");
|
|
LaunchRoblox(App.LaunchSettings.RobloxLaunchMode);
|
|
}
|
|
else if (!App.LaunchSettings.QuietFlag.Active)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Opening menu");
|
|
LaunchMenu();
|
|
}
|
|
else
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Closing - quiet flag active");
|
|
App.Terminate();
|
|
}
|
|
}
|
|
|
|
public static void LaunchInstaller()
|
|
{
|
|
using var interlock = new InterProcessLock("Installer");
|
|
|
|
if (!interlock.IsAcquired)
|
|
{
|
|
Frontend.ShowMessageBox(Strings.Dialog_AlreadyRunning_Installer, MessageBoxImage.Stop);
|
|
App.Terminate();
|
|
return;
|
|
}
|
|
|
|
if (App.LaunchSettings.UninstallFlag.Active)
|
|
{
|
|
Frontend.ShowMessageBox(Strings.Bootstrapper_FirstRunUninstall, MessageBoxImage.Error);
|
|
App.Terminate(ErrorCode.ERROR_INVALID_FUNCTION);
|
|
return;
|
|
}
|
|
|
|
if (App.LaunchSettings.QuietFlag.Active)
|
|
{
|
|
var installer = new Installer();
|
|
|
|
if (!installer.CheckInstallLocation())
|
|
App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
|
|
|
|
installer.DoInstall();
|
|
|
|
interlock.Dispose();
|
|
|
|
ProcessLaunchArgs();
|
|
}
|
|
else
|
|
{
|
|
#if QA_BUILD
|
|
Frontend.ShowMessageBox("You are about to install a QA build of Bloxstrap. The red window border indicates that this is a QA build.\n\nQA builds are handled completely separately of your standard installation, like a virtual environment.", MessageBoxImage.Information);
|
|
#endif
|
|
|
|
new LanguageSelectorDialog().ShowDialog();
|
|
|
|
var installer = new UI.Elements.Installer.MainWindow();
|
|
installer.ShowDialog();
|
|
|
|
interlock.Dispose();
|
|
|
|
ProcessNextAction(installer.CloseAction, !installer.Finished);
|
|
}
|
|
|
|
}
|
|
|
|
public static void LaunchUninstaller()
|
|
{
|
|
using var interlock = new InterProcessLock("Uninstaller");
|
|
|
|
if (!interlock.IsAcquired)
|
|
{
|
|
Frontend.ShowMessageBox(Strings.Dialog_AlreadyRunning_Uninstaller, MessageBoxImage.Stop);
|
|
App.Terminate();
|
|
return;
|
|
}
|
|
|
|
bool confirmed = false;
|
|
bool keepData = true;
|
|
|
|
if (App.LaunchSettings.QuietFlag.Active)
|
|
{
|
|
confirmed = true;
|
|
}
|
|
else
|
|
{
|
|
var dialog = new UninstallerDialog();
|
|
dialog.ShowDialog();
|
|
|
|
confirmed = dialog.Confirmed;
|
|
keepData = dialog.KeepData;
|
|
}
|
|
|
|
if (!confirmed)
|
|
{
|
|
App.Terminate();
|
|
return;
|
|
}
|
|
|
|
Installer.DoUninstall(keepData);
|
|
|
|
Frontend.ShowMessageBox(Strings.Bootstrapper_SuccessfullyUninstalled, MessageBoxImage.Information);
|
|
|
|
App.Terminate();
|
|
}
|
|
|
|
public static void LaunchSettings()
|
|
{
|
|
const string LOG_IDENT = "LaunchHandler::LaunchSettings";
|
|
|
|
using var interlock = new InterProcessLock("Settings");
|
|
|
|
if (interlock.IsAcquired)
|
|
{
|
|
bool showAlreadyRunningWarning = Process.GetProcessesByName(App.ProjectName).Length > 1;
|
|
|
|
var window = new UI.Elements.Settings.MainWindow(showAlreadyRunningWarning);
|
|
|
|
// typically we'd use Show(), but we need to block to ensure IPL stays in scope
|
|
window.ShowDialog();
|
|
}
|
|
else
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Found an already existing menu window");
|
|
|
|
var process = Utilities.GetProcessesSafe().Where(x => x.MainWindowTitle == Strings.Menu_Title).FirstOrDefault();
|
|
|
|
if (process is not null)
|
|
PInvoke.SetForegroundWindow((HWND)process.MainWindowHandle);
|
|
|
|
App.Terminate();
|
|
}
|
|
}
|
|
|
|
public static void LaunchMenu()
|
|
{
|
|
var dialog = new LaunchMenuDialog();
|
|
dialog.ShowDialog();
|
|
|
|
ProcessNextAction(dialog.CloseAction);
|
|
}
|
|
|
|
public static void LaunchRoblox(LaunchMode launchMode)
|
|
{
|
|
const string LOG_IDENT = "LaunchHandler::LaunchRoblox";
|
|
|
|
if (launchMode == LaunchMode.None)
|
|
throw new InvalidOperationException("No Roblox launch mode set");
|
|
|
|
if (!File.Exists(Path.Combine(Paths.System, "mfplat.dll")))
|
|
{
|
|
Frontend.ShowMessageBox(Strings.Bootstrapper_WMFNotFound, MessageBoxImage.Error);
|
|
|
|
if (!App.LaunchSettings.QuietFlag.Active)
|
|
Utilities.ShellExecute("https://support.microsoft.com/en-us/topic/media-feature-pack-list-for-windows-n-editions-c1c6fffa-d052-8338-7a79-a4bb980a700a");
|
|
|
|
App.Terminate(ErrorCode.ERROR_FILE_NOT_FOUND);
|
|
}
|
|
|
|
if (App.Settings.Prop.ConfirmLaunches && Mutex.TryOpenExisting("ROBLOX_singletonMutex", out var _) && !App.Settings.Prop.MultiInstanceLaunching)
|
|
{
|
|
// this currently doesn't work very well since it relies on checking the existence of the singleton mutex
|
|
// which often hangs around for a few seconds after the window closes
|
|
// it would be better to have this rely on the activity tracker when we implement IPC in the planned refactoring
|
|
|
|
var result = Frontend.ShowMessageBox(Strings.Bootstrapper_ConfirmLaunch, MessageBoxImage.Warning, MessageBoxButton.YesNo);
|
|
|
|
if (result != MessageBoxResult.Yes)
|
|
{
|
|
App.Terminate();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// start bootstrapper and show the bootstrapper modal if we're not running silently
|
|
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
|
|
App.Bootstrapper = new Bootstrapper(launchMode);
|
|
IBootstrapperDialog? dialog = null;
|
|
|
|
if (!App.LaunchSettings.QuietFlag.Active)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper dialog");
|
|
dialog = App.Settings.Prop.BootstrapperStyle.GetNew();
|
|
App.Bootstrapper.Dialog = dialog;
|
|
dialog.Bootstrapper = App.Bootstrapper;
|
|
}
|
|
|
|
Task.Run(App.Bootstrapper.Run).ContinueWith(t =>
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Bootstrapper task has finished");
|
|
|
|
if (t.IsFaulted)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
|
|
|
|
if (t.Exception is not null)
|
|
App.FinalizeExceptionHandling(t.Exception);
|
|
}
|
|
|
|
App.Terminate();
|
|
});
|
|
|
|
dialog?.ShowBootstrapper();
|
|
|
|
App.Logger.WriteLine(LOG_IDENT, "Exiting");
|
|
}
|
|
|
|
public static void LaunchWatcher()
|
|
{
|
|
const string LOG_IDENT = "LaunchHandler::LaunchWatcher";
|
|
|
|
// this whole topology is a bit confusing, bear with me:
|
|
// main thread: strictly UI only, handles showing of the notification area icon, context menu, server details dialog
|
|
// - server information task: queries server location, invoked if either the explorer notification is shown or the server details dialog is opened
|
|
// - discord rpc thread: handles rpc connection with discord
|
|
// - discord rich presence tasks: handles querying and displaying of game information, invoked on activity watcher events
|
|
// - watcher task: runs activity watcher + waiting for roblox to close, terminates when it has
|
|
|
|
var watcher = new Watcher();
|
|
|
|
Task.Run(watcher.Run).ContinueWith(t =>
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Watcher task has finished");
|
|
|
|
watcher.Dispose();
|
|
|
|
if (t.IsFaulted)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the watcher");
|
|
|
|
if (t.Exception is not null)
|
|
App.FinalizeExceptionHandling(t.Exception);
|
|
}
|
|
|
|
App.Terminate();
|
|
});
|
|
}
|
|
|
|
public static void LaunchMultiInstanceWatcher()
|
|
{
|
|
const string LOG_IDENT = "LaunchHandler::LaunchMultiInstanceWatcher";
|
|
|
|
App.Logger.WriteLine(LOG_IDENT, "Starting multi-instance watcher");
|
|
|
|
Task.Run(MultiInstanceWatcher.Run).ContinueWith(t =>
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Multi instance watcher task has finished");
|
|
|
|
if (t.IsFaulted)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the multi-instance watcher");
|
|
|
|
if (t.Exception is not null)
|
|
App.FinalizeExceptionHandling(t.Exception);
|
|
}
|
|
|
|
App.Terminate();
|
|
});
|
|
}
|
|
|
|
public static void LaunchBackgroundUpdater()
|
|
{
|
|
const string LOG_IDENT = "LaunchHandler::LaunchBackgroundUpdater";
|
|
|
|
// Activate some LaunchFlags we need
|
|
App.LaunchSettings.QuietFlag.Active = true;
|
|
App.LaunchSettings.NoLaunchFlag.Active = true;
|
|
|
|
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
|
|
App.Bootstrapper = new Bootstrapper(LaunchMode.Player)
|
|
{
|
|
MutexName = "Bloxstrap-BackgroundUpdater",
|
|
QuitIfMutexExists = true
|
|
};
|
|
|
|
CancellationTokenSource cts = new CancellationTokenSource();
|
|
|
|
Task.Run(() =>
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Started event waiter");
|
|
using (EventWaitHandle handle = new EventWaitHandle(false, EventResetMode.AutoReset, "Bloxstrap-BackgroundUpdaterKillEvent"))
|
|
handle.WaitOne();
|
|
|
|
App.Logger.WriteLine(LOG_IDENT, "Received close event, killing it all!");
|
|
App.Bootstrapper.Cancel();
|
|
}, cts.Token);
|
|
|
|
Task.Run(App.Bootstrapper.Run).ContinueWith(t =>
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "Bootstrapper task has finished");
|
|
cts.Cancel(); // stop event waiter
|
|
|
|
if (t.IsFaulted)
|
|
{
|
|
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
|
|
|
|
if (t.Exception is not null)
|
|
App.FinalizeExceptionHandling(t.Exception);
|
|
}
|
|
|
|
App.Terminate();
|
|
});
|
|
|
|
App.Logger.WriteLine(LOG_IDENT, "Exiting");
|
|
}
|
|
}
|
|
}
|