Merge pull request #1230 from bluepilledgreat/feature/app-cleanup

App cleanup
This commit is contained in:
pizzaboxer 2024-02-07 15:17:29 +00:00 committed by GitHub
commit 62ae4c3e89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 322 additions and 180 deletions

View File

@ -27,12 +27,7 @@ namespace Bloxstrap
public static bool IsSetupComplete { get; set; } = true; public static bool IsSetupComplete { get; set; } = true;
public static bool IsFirstRun { get; set; } = true; public static bool IsFirstRun { get; set; } = true;
public static bool IsQuiet { get; private set; } = false; public static LaunchSettings LaunchSettings { get; private set; } = null!;
public static bool IsUninstall { get; private set; } = false;
public static bool IsNoLaunch { get; private set; } = false;
public static bool IsUpgrade { get; private set; } = false;
public static bool IsMenuLaunch { get; private set; } = false;
public static string[] LaunchArgs { get; private set; } = null!;
public static BuildMetadataAttribute BuildMetadata = Assembly.GetExecutingAssembly().GetCustomAttribute<BuildMetadataAttribute>()!; public static BuildMetadataAttribute BuildMetadata = Assembly.GetExecutingAssembly().GetCustomAttribute<BuildMetadataAttribute>()!;
public static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2]; public static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2];
@ -96,13 +91,22 @@ namespace Bloxstrap
_showingExceptionDialog = true; _showingExceptionDialog = true;
if (!IsQuiet) if (!LaunchSettings.IsQuiet)
Frontend.ShowExceptionDialog(exception); Frontend.ShowExceptionDialog(exception);
Terminate(ErrorCode.ERROR_INSTALL_FAILURE); Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
#endif #endif
} }
private void StartupFinished()
{
const string LOG_IDENT = "App::StartupFinished";
Logger.WriteLine(LOG_IDENT, "Successfully reached end of main thread. Terminating...");
Terminate();
}
protected override void OnStartup(StartupEventArgs e) protected override void OnStartup(StartupEventArgs e)
{ {
const string LOG_IDENT = "App::OnStartup"; const string LOG_IDENT = "App::OnStartup";
@ -122,48 +126,11 @@ namespace Bloxstrap
// see https://aka.ms/applicationconfiguration. // see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize(); ApplicationConfiguration.Initialize();
LaunchArgs = e.Args; LaunchSettings = new LaunchSettings(e.Args);
#if DEBUG
Logger.WriteLine(LOG_IDENT, $"Arguments: {string.Join(' ', LaunchArgs)}");
#endif
HttpClient.Timeout = TimeSpan.FromSeconds(30); HttpClient.Timeout = TimeSpan.FromSeconds(30);
HttpClient.DefaultRequestHeaders.Add("User-Agent", ProjectRepository); HttpClient.DefaultRequestHeaders.Add("User-Agent", ProjectRepository);
if (LaunchArgs.Length > 0)
{
if (Array.IndexOf(LaunchArgs, "-preferences") != -1 || Array.IndexOf(LaunchArgs, "-menu") != -1)
{
Logger.WriteLine(LOG_IDENT, "Started with IsMenuLaunch flag");
IsMenuLaunch = true;
}
if (Array.IndexOf(LaunchArgs, "-quiet") != -1)
{
Logger.WriteLine(LOG_IDENT, "Started with IsQuiet flag");
IsQuiet = true;
}
if (Array.IndexOf(LaunchArgs, "-uninstall") != -1)
{
Logger.WriteLine(LOG_IDENT, "Started with IsUninstall flag");
IsUninstall = true;
}
if (Array.IndexOf(LaunchArgs, "-nolaunch") != -1)
{
Logger.WriteLine(LOG_IDENT, "Started with IsNoLaunch flag");
IsNoLaunch = true;
}
if (Array.IndexOf(LaunchArgs, "-upgrade") != -1)
{
Logger.WriteLine(LOG_IDENT, "Bloxstrap started with IsUpgrade flag");
IsUpgrade = true;
}
}
using (var checker = new InstallChecker()) using (var checker = new InstallChecker())
{ {
checker.Check(); checker.Check();
@ -175,7 +142,7 @@ namespace Bloxstrap
// just in case the user decides to cancel the install // just in case the user decides to cancel the install
if (!IsFirstRun) if (!IsFirstRun)
{ {
Logger.Initialize(IsUninstall); Logger.Initialize(LaunchSettings.IsUninstall);
if (!Logger.Initialized) if (!Logger.Initialized)
{ {
@ -188,18 +155,15 @@ namespace Bloxstrap
FastFlags.Load(); FastFlags.Load();
} }
if (!IsUninstall && !IsMenuLaunch) if (!LaunchSettings.IsUninstall && !LaunchSettings.IsMenuLaunch)
NotifyIcon = new(); NotifyIcon = new();
#if !DEBUG #if !DEBUG
if (!IsUninstall && !IsFirstRun) if (!LaunchSettings.IsUninstall && !IsFirstRun)
InstallChecker.CheckUpgrade(); InstallChecker.CheckUpgrade();
#endif #endif
string commandLine = ""; if (LaunchSettings.IsMenuLaunch)
LaunchMode? launchMode = null;
if (IsMenuLaunch)
{ {
Process? menuProcess = Process.GetProcesses().Where(x => x.MainWindowTitle == $"{ProjectName} Menu").FirstOrDefault(); Process? menuProcess = Process.GetProcesses().Where(x => x.MainWindowTitle == $"{ProjectName} Menu").FirstOrDefault();
@ -211,7 +175,7 @@ namespace Bloxstrap
} }
else else
{ {
if (Process.GetProcessesByName(ProjectName).Length > 1 && !IsQuiet) if (Process.GetProcessesByName(ProjectName).Length > 1 && !LaunchSettings.IsQuiet)
Frontend.ShowMessageBox( Frontend.ShowMessageBox(
Bloxstrap.Resources.Strings.Menu_AlreadyRunning, Bloxstrap.Resources.Strings.Menu_AlreadyRunning,
MessageBoxImage.Information MessageBoxImage.Information
@ -219,152 +183,95 @@ namespace Bloxstrap
Frontend.ShowMenu(); Frontend.ShowMenu();
} }
}
else if (LaunchArgs.Length > 0)
{
if (LaunchArgs[0].StartsWith("roblox-player:"))
{
commandLine = ProtocolHandler.ParseUri(LaunchArgs[0]);
launchMode = LaunchMode.Player; StartupFinished();
} return;
else if (LaunchArgs[0].StartsWith("roblox:"))
{
if (Settings.Prop.UseDisableAppPatch)
Frontend.ShowMessageBox(
Bloxstrap.Resources.Strings.Bootstrapper_DeeplinkTempEnabled,
MessageBoxImage.Information
);
commandLine = $"--app --deeplink {LaunchArgs[0]}";
launchMode = LaunchMode.Player;
}
else if (LaunchArgs[0].StartsWith("roblox-studio:"))
{
commandLine = ProtocolHandler.ParseUri(LaunchArgs[0]);
if (!commandLine.Contains("-startEvent"))
commandLine += " -startEvent www.roblox.com/robloxQTStudioStartedEvent";
launchMode = LaunchMode.Studio;
}
else if (LaunchArgs[0].StartsWith("roblox-studio-auth:"))
{
commandLine = HttpUtility.UrlDecode(LaunchArgs[0]);
launchMode = LaunchMode.StudioAuth;
}
else if (LaunchArgs[0] == "-ide")
{
launchMode = LaunchMode.Studio;
if (LaunchArgs.Length >= 2)
commandLine = $"-task EditFile -localPlaceFile \"{LaunchArgs[1]}\"";
}
else
{
commandLine = "--app";
launchMode = LaunchMode.Player;
}
}
else
{
commandLine = "--app";
launchMode = LaunchMode.Player;
} }
if (launchMode != null) if (!IsFirstRun)
ShouldSaveConfigs = true;
// start bootstrapper and show the bootstrapper modal if we're not running silently
Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
Bootstrapper bootstrapper = new(LaunchSettings.RobloxLaunchArgs, LaunchSettings.RobloxLaunchMode);
IBootstrapperDialog? dialog = null;
if (!LaunchSettings.IsQuiet)
{ {
if (!IsFirstRun) Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper dialog");
ShouldSaveConfigs = true; dialog = Settings.Prop.BootstrapperStyle.GetNew();
bootstrapper.Dialog = dialog;
dialog.Bootstrapper = bootstrapper;
}
// start bootstrapper and show the bootstrapper modal if we're not running silently // handle roblox singleton mutex for multi-instance launching
Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper"); // note we're handling it here in the main thread and NOT in the
Bootstrapper bootstrapper = new(commandLine, (LaunchMode)launchMode); // bootstrapper as handling mutexes in async contexts suuuuuucks
IBootstrapperDialog? dialog = null;
if (!IsQuiet) Mutex? singletonMutex = null;
if (Settings.Prop.MultiInstanceLaunching && LaunchSettings.RobloxLaunchMode == LaunchMode.Player)
{
Logger.WriteLine(LOG_IDENT, "Creating singleton mutex");
try
{ {
Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper dialog"); Mutex.OpenExisting("ROBLOX_singletonMutex");
dialog = Settings.Prop.BootstrapperStyle.GetNew(); Logger.WriteLine(LOG_IDENT, "Warning - singleton mutex already exists!");
bootstrapper.Dialog = dialog;
dialog.Bootstrapper = bootstrapper;
} }
catch
// 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 && launchMode == LaunchMode.Player)
{ {
Logger.WriteLine(LOG_IDENT, "Creating singleton mutex"); // create the singleton mutex before the game client does
singletonMutex = new Mutex(true, "ROBLOX_singletonMutex");
try
{
Mutex.OpenExisting("ROBLOX_singletonMutex");
Logger.WriteLine(LOG_IDENT, "Warning - singleton mutex already exists!");
}
catch
{
// create the singleton mutex before the game client does
singletonMutex = new Mutex(true, "ROBLOX_singletonMutex");
}
} }
}
Task bootstrapperTask = Task.Run(async () => await bootstrapper.Run()).ContinueWith(t => Task bootstrapperTask = Task.Run(async () => await bootstrapper.Run()).ContinueWith(t =>
{ {
Logger.WriteLine(LOG_IDENT, "Bootstrapper task has finished"); Logger.WriteLine(LOG_IDENT, "Bootstrapper task has finished");
// notifyicon is blocking main thread, must be disposed here // notifyicon is blocking main thread, must be disposed here
NotifyIcon?.Dispose(); NotifyIcon?.Dispose();
if (t.IsFaulted) if (t.IsFaulted)
Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper"); Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
if (t.Exception is null) if (t.Exception is null)
return; return;
Logger.WriteException(LOG_IDENT, t.Exception); Logger.WriteException(LOG_IDENT, t.Exception);
Exception exception = t.Exception; Exception exception = t.Exception;
#if !DEBUG #if !DEBUG
if (t.Exception.GetType().ToString() == "System.AggregateException") if (t.Exception.GetType().ToString() == "System.AggregateException")
exception = t.Exception.InnerException!; exception = t.Exception.InnerException!;
#endif #endif
FinalizeExceptionHandling(exception, false); FinalizeExceptionHandling(exception, false);
}); });
// this ordering is very important as all wpf windows are shown as modal dialogs, mess it up and you'll end up blocking input to one of them // this ordering is very important as all wpf windows are shown as modal dialogs, mess it up and you'll end up blocking input to one of them
dialog?.ShowBootstrapper(); dialog?.ShowBootstrapper();
if (!IsNoLaunch && Settings.Prop.EnableActivityTracking) if (!LaunchSettings.IsNoLaunch && Settings.Prop.EnableActivityTracking)
NotifyIcon?.InitializeContextMenu(); NotifyIcon?.InitializeContextMenu();
Logger.WriteLine(LOG_IDENT, "Waiting for bootstrapper task to finish"); Logger.WriteLine(LOG_IDENT, "Waiting for bootstrapper task to finish");
bootstrapperTask.Wait(); bootstrapperTask.Wait();
if (singletonMutex is not null) if (singletonMutex is not null)
{ {
Logger.WriteLine(LOG_IDENT, "We have singleton mutex ownership! Running in background until all Roblox processes are closed"); Logger.WriteLine(LOG_IDENT, "We have singleton mutex ownership! Running in background until all Roblox processes are closed");
// we've got ownership of the roblox singleton mutex! // we've got ownership of the roblox singleton mutex!
// if we stop running, everything will screw up once any more roblox instances launched // if we stop running, everything will screw up once any more roblox instances launched
while (Process.GetProcessesByName("RobloxPlayerBeta").Any()) while (Process.GetProcessesByName("RobloxPlayerBeta").Any())
Thread.Sleep(5000); Thread.Sleep(5000);
}
} }
Logger.WriteLine(LOG_IDENT, "Successfully reached end of main thread. Terminating..."); StartupFinished();
Terminate();
} }
} }
} }

View File

@ -123,7 +123,7 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, "Running bootstrapper"); App.Logger.WriteLine(LOG_IDENT, "Running bootstrapper");
if (App.IsUninstall) if (App.LaunchSettings.IsUninstall)
{ {
Uninstall(); Uninstall();
return; return;
@ -226,9 +226,9 @@ namespace Bloxstrap
await mutex.ReleaseAsync(); await mutex.ReleaseAsync();
if (App.IsFirstRun && App.IsNoLaunch) if (App.IsFirstRun && App.LaunchSettings.IsNoLaunch)
Dialog?.ShowSuccess(Resources.Strings.Bootstrapper_SuccessfullyInstalled); Dialog?.ShowSuccess(Resources.Strings.Bootstrapper_SuccessfullyInstalled);
else if (!App.IsNoLaunch && !_cancelFired) else if (!App.LaunchSettings.IsNoLaunch && !_cancelFired)
await StartRoblox(); await StartRoblox();
} }
@ -302,7 +302,7 @@ namespace Bloxstrap
MessageBoxImage.Error MessageBoxImage.Error
); );
if (!App.IsQuiet) if (!App.LaunchSettings.IsQuiet)
Utilities.ShellExecute("https://support.microsoft.com/en-us/topic/media-feature-pack-list-for-windows-n-editions-c1c6fffa-d052-8338-7a79-a4bb980a700a"); Utilities.ShellExecute("https://support.microsoft.com/en-us/topic/media-feature-pack-list-for-windows-n-editions-c1c6fffa-d052-8338-7a79-a4bb980a700a");
Dialog?.CloseBootstrapper(); Dialog?.CloseBootstrapper();
@ -655,7 +655,7 @@ namespace Bloxstrap
FileName = downloadLocation, FileName = downloadLocation,
}; };
foreach (string arg in App.LaunchArgs) foreach (string arg in App.LaunchSettings.Args)
startInfo.ArgumentList.Add(arg); startInfo.ArgumentList.Add(arg);
App.Settings.Save(); App.Settings.Save();

View File

@ -124,7 +124,7 @@ namespace Bloxstrap
App.BaseDirectory = Path.Combine(Paths.LocalAppData, App.ProjectName); App.BaseDirectory = Path.Combine(Paths.LocalAppData, App.ProjectName);
App.Logger.Initialize(true); App.Logger.Initialize(true);
if (App.IsQuiet) if (App.LaunchSettings.IsQuiet)
return; return;
App.IsSetupComplete = false; App.IsSetupComplete = false;
@ -159,7 +159,7 @@ namespace Bloxstrap
MessageBoxResult result; MessageBoxResult result;
// silently upgrade version if the command line flag is set or if we're launching from an auto update // silently upgrade version if the command line flag is set or if we're launching from an auto update
if (App.IsUpgrade || isAutoUpgrade) if (App.LaunchSettings.IsUpgrade || isAutoUpgrade)
{ {
result = MessageBoxResult.Yes; result = MessageBoxResult.Yes;
} }
@ -238,7 +238,7 @@ namespace Bloxstrap
(_, _) => Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/releases/tag/v{currentVersionInfo.ProductVersion}") (_, _) => Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/releases/tag/v{currentVersionInfo.ProductVersion}")
); );
} }
else if (!App.IsQuiet) else if (!App.LaunchSettings.IsQuiet)
{ {
Frontend.ShowMessageBox( Frontend.ShowMessageBox(
string.Format(Resources.Strings.InstallChecker_Updated, currentVersionInfo.ProductVersion), string.Format(Resources.Strings.InstallChecker_Updated, currentVersionInfo.ProductVersion),

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap
{
public class InterProcessLock : IDisposable
{
public Mutex Mutex { get; private set; }
public bool IsAcquired { get; private set; }
public InterProcessLock(string name, TimeSpan timeout)
{
Mutex = new Mutex(false, "Bloxstrap-" + name);
IsAcquired = Mutex.WaitOne(timeout);
}
public void Dispose()
{
if (IsAcquired)
{
Mutex.ReleaseMutex();
IsAcquired = false;
}
}
}
}

181
Bloxstrap/LaunchSettings.cs Normal file
View File

@ -0,0 +1,181 @@
using Bloxstrap.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Windows;
namespace Bloxstrap
{
public class LaunchSettings
{
[LaunchFlag(new[] { "-preferences", "-menu" })]
public bool IsMenuLaunch { get; private set; } = false;
[LaunchFlag("-quiet")]
public bool IsQuiet { get; private set; } = false;
[LaunchFlag("-uninstall")]
public bool IsUninstall { get; private set; } = false;
[LaunchFlag("-nolaunch")]
public bool IsNoLaunch { get; private set; } = false;
[LaunchFlag("-upgrade")]
public bool IsUpgrade { get; private set; } = false;
public LaunchMode RobloxLaunchMode { get; private set; } = LaunchMode.Player;
public string RobloxLaunchArgs { get; private set; } = "--app";
/// <summary>
/// Original launch arguments
/// </summary>
public string[] Args { get; private set; }
private Dictionary<string, PropertyInfo>? _flagMap;
// pizzaboxer wanted this
private void ParseLaunchFlagProps()
{
_flagMap = new Dictionary<string, PropertyInfo>();
foreach (var prop in typeof(LaunchSettings).GetProperties())
{
var attr = prop.GetCustomAttribute<LaunchFlagAttribute>();
if (attr == null)
continue;
if (!string.IsNullOrEmpty(attr.Name))
{
_flagMap[attr.Name] = prop;
}
else
{
foreach (var name in attr.Names!)
_flagMap[name] = prop;
}
}
}
private void ParseFlag(string arg)
{
const string LOG_IDENT = "LaunchSettings::ParseFlag";
arg = arg.ToLowerInvariant();
if (_flagMap!.ContainsKey(arg))
{
var prop = _flagMap[arg];
prop.SetValue(this, true);
App.Logger.WriteLine(LOG_IDENT, $"Started with {prop.Name} flag");
}
}
private void ParseRoblox(string arg, ref int i)
{
if (arg.StartsWith("roblox-player:"))
{
RobloxLaunchArgs = ProtocolHandler.ParseUri(arg);
RobloxLaunchMode = LaunchMode.Player;
}
else if (arg.StartsWith("roblox:"))
{
if (App.Settings.Prop.UseDisableAppPatch)
Frontend.ShowMessageBox(
Resources.Strings.Bootstrapper_DeeplinkTempEnabled,
MessageBoxImage.Information
);
RobloxLaunchArgs = $"--app --deeplink {arg}";
RobloxLaunchMode = LaunchMode.Player;
}
else if (arg.StartsWith("roblox-studio:"))
{
RobloxLaunchArgs = ProtocolHandler.ParseUri(arg);
if (!RobloxLaunchArgs.Contains("-startEvent"))
RobloxLaunchArgs += " -startEvent www.roblox.com/robloxQTStudioStartedEvent";
RobloxLaunchMode = LaunchMode.Studio;
}
else if (arg.StartsWith("roblox-studio-auth:"))
{
RobloxLaunchArgs = HttpUtility.UrlDecode(arg);
RobloxLaunchMode = LaunchMode.StudioAuth;
}
else if (arg == "-ide")
{
RobloxLaunchMode = LaunchMode.Studio;
if (Args.Length >= 2)
{
string pathArg = Args[i + 1];
if (pathArg.StartsWith('-'))
return; // likely a launch flag, ignore it.
i++; // path arg
RobloxLaunchArgs = $"-task EditFile -localPlaceFile \"{pathArg}\"";
}
}
}
private void Parse()
{
const string LOG_IDENT = "LaunchSettings::Parse";
App.Logger.WriteLine(LOG_IDENT, "Parsing launch arguments");
#if DEBUG
App.Logger.WriteLine(LOG_IDENT, $"Launch arguments: {string.Join(' ', Args)}");
#endif
if (Args.Length == 0)
{
App.Logger.WriteLine(LOG_IDENT, "No launch arguments to parse");
return;
}
int idx = 0;
string firstArg = Args[0];
// check & handle roblox arg
if (!firstArg.StartsWith('-') || firstArg == "-ide")
{
ParseRoblox(firstArg, ref idx);
idx++; // roblox arg
}
// check if there are any launch flags
if (idx > Args.Length - 1)
return;
App.Logger.WriteLine(LOG_IDENT, "Parsing launch flags");
// map out launch flags
ParseLaunchFlagProps();
// parse any launch flags
for (int i = idx; i < Args.Length; i++)
ParseFlag(Args[i]);
// cleanup flag map
_flagMap!.Clear();
_flagMap = null;
}
public LaunchSettings(string[] args)
{
Args = args;
Parse();
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.Models.Attributes
{
public class LaunchFlagAttribute : Attribute
{
public string? Name { get; private set; }
public string[]? Names { get; private set; }
public LaunchFlagAttribute(string name)
{
Name = name;
}
public LaunchFlagAttribute(string[] names)
{
Names = names;
}
}
}

View File

@ -12,7 +12,7 @@ namespace Bloxstrap.UI
public static MessageBoxResult ShowMessageBox(string message, MessageBoxImage icon = MessageBoxImage.None, MessageBoxButton buttons = MessageBoxButton.OK, MessageBoxResult defaultResult = MessageBoxResult.None) public static MessageBoxResult ShowMessageBox(string message, MessageBoxImage icon = MessageBoxImage.None, MessageBoxButton buttons = MessageBoxButton.OK, MessageBoxResult defaultResult = MessageBoxResult.None)
{ {
if (App.IsQuiet) if (App.LaunchSettings.IsQuiet)
return defaultResult; return defaultResult;
switch (App.Settings.Prop.BootstrapperStyle) switch (App.Settings.Prop.BootstrapperStyle)