mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-10 15:25:42 -07:00
Reintroduce multi-instance launching (#4888)
This commit is contained in:
parent
49fd8eb2d2
commit
d244f42b49
@ -471,21 +471,48 @@ namespace Bloxstrap
|
||||
}
|
||||
}
|
||||
|
||||
private static void LaunchMultiInstanceWatcher()
|
||||
{
|
||||
const string LOG_IDENT = "Bootstrapper::LaunchMultiInstanceWatcher";
|
||||
|
||||
if (Utilities.DoesMutexExist("ROBLOX_singletonMutex"))
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Roblox singleton mutex already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
using EventWaitHandle initEventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, "Bloxstrap-MultiInstanceWatcherInitialisationFinished");
|
||||
Process.Start(Paths.Process, "-multiinstancewatcher");
|
||||
|
||||
bool initSuccess = initEventHandle.WaitOne(TimeSpan.FromSeconds(2));
|
||||
if (initSuccess)
|
||||
App.Logger.WriteLine(LOG_IDENT, "Initialisation finished signalled, continuing.");
|
||||
else
|
||||
App.Logger.WriteLine(LOG_IDENT, "Did not receive the initialisation finished signal, continuing.");
|
||||
}
|
||||
|
||||
private void StartRoblox()
|
||||
{
|
||||
const string LOG_IDENT = "Bootstrapper::StartRoblox";
|
||||
|
||||
SetStatus(Strings.Bootstrapper_Status_Starting);
|
||||
|
||||
if (_launchMode == LaunchMode.Player && App.Settings.Prop.ForceRobloxLanguage)
|
||||
if (_launchMode == LaunchMode.Player)
|
||||
{
|
||||
var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)", RegexOptions.CultureInvariant);
|
||||
// this needs to be done before roblox launches
|
||||
if (App.Settings.Prop.MultiInstanceLaunching)
|
||||
LaunchMultiInstanceWatcher();
|
||||
|
||||
if (match.Groups.Count == 2)
|
||||
_launchCommandLine = _launchCommandLine.Replace(
|
||||
"robloxLocale:en_us",
|
||||
$"robloxLocale:{match.Groups[1].Value}",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
if (App.Settings.Prop.ForceRobloxLanguage)
|
||||
{
|
||||
var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)", RegexOptions.CultureInvariant);
|
||||
|
||||
if (match.Groups.Count == 2)
|
||||
_launchCommandLine = _launchCommandLine.Replace(
|
||||
"robloxLocale:en_us",
|
||||
$"robloxLocale:{match.Groups[1].Value}",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo()
|
||||
|
@ -59,6 +59,11 @@ namespace Bloxstrap
|
||||
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");
|
||||
@ -223,7 +228,7 @@ namespace Bloxstrap
|
||||
App.Terminate(ErrorCode.ERROR_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (App.Settings.Prop.ConfirmLaunches && Mutex.TryOpenExisting("ROBLOX_singletonMutex", out var _))
|
||||
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
|
||||
@ -302,6 +307,28 @@ namespace Bloxstrap
|
||||
});
|
||||
}
|
||||
|
||||
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";
|
||||
|
@ -12,33 +12,35 @@ namespace Bloxstrap
|
||||
{
|
||||
public class LaunchSettings
|
||||
{
|
||||
public LaunchFlag MenuFlag { get; } = new("preferences,menu,settings");
|
||||
public LaunchFlag MenuFlag { get; } = new("preferences,menu,settings");
|
||||
|
||||
public LaunchFlag WatcherFlag { get; } = new("watcher");
|
||||
public LaunchFlag WatcherFlag { get; } = new("watcher");
|
||||
|
||||
public LaunchFlag BackgroundUpdaterFlag { get; } = new("backgroundupdater");
|
||||
public LaunchFlag MultiInstanceWatcherFlag { get; } = new("multiinstancewatcher");
|
||||
|
||||
public LaunchFlag QuietFlag { get; } = new("quiet");
|
||||
public LaunchFlag BackgroundUpdaterFlag { get; } = new("backgroundupdater");
|
||||
|
||||
public LaunchFlag UninstallFlag { get; } = new("uninstall");
|
||||
public LaunchFlag QuietFlag { get; } = new("quiet");
|
||||
|
||||
public LaunchFlag NoLaunchFlag { get; } = new("nolaunch");
|
||||
public LaunchFlag UninstallFlag { get; } = new("uninstall");
|
||||
|
||||
public LaunchFlag NoLaunchFlag { get; } = new("nolaunch");
|
||||
|
||||
public LaunchFlag TestModeFlag { get; } = new("testmode");
|
||||
public LaunchFlag TestModeFlag { get; } = new("testmode");
|
||||
|
||||
public LaunchFlag NoGPUFlag { get; } = new("nogpu");
|
||||
public LaunchFlag NoGPUFlag { get; } = new("nogpu");
|
||||
|
||||
public LaunchFlag UpgradeFlag { get; } = new("upgrade");
|
||||
public LaunchFlag UpgradeFlag { get; } = new("upgrade");
|
||||
|
||||
public LaunchFlag PlayerFlag { get; } = new("player");
|
||||
public LaunchFlag PlayerFlag { get; } = new("player");
|
||||
|
||||
public LaunchFlag StudioFlag { get; } = new("studio");
|
||||
public LaunchFlag StudioFlag { get; } = new("studio");
|
||||
|
||||
public LaunchFlag VersionFlag { get; } = new("version");
|
||||
public LaunchFlag VersionFlag { get; } = new("version");
|
||||
|
||||
public LaunchFlag ChannelFlag { get; } = new("channel");
|
||||
public LaunchFlag ChannelFlag { get; } = new("channel");
|
||||
|
||||
public LaunchFlag ForceFlag { get; } = new("force");
|
||||
public LaunchFlag ForceFlag { get; } = new("force");
|
||||
|
||||
#if DEBUG
|
||||
public bool BypassUpdateCheck => true;
|
||||
|
@ -11,6 +11,7 @@ namespace Bloxstrap.Models.Persistable
|
||||
public string BootstrapperIconCustomLocation { get; set; } = "";
|
||||
public Theme Theme { get; set; } = Theme.Default;
|
||||
public bool CheckForUpdates { get; set; } = true;
|
||||
public bool MultiInstanceLaunching { get; set; } = false;
|
||||
public bool ConfirmLaunches { get; set; } = false;
|
||||
public string Locale { get; set; } = "nil";
|
||||
public bool ForceRobloxLanguage { get; set; } = false;
|
||||
|
68
Bloxstrap/MultiInstanceWatcher.cs
Normal file
68
Bloxstrap/MultiInstanceWatcher.cs
Normal file
@ -0,0 +1,68 @@
|
||||
namespace Bloxstrap
|
||||
{
|
||||
internal static class MultiInstanceWatcher
|
||||
{
|
||||
private static int GetOpenProcessesCount()
|
||||
{
|
||||
const string LOG_IDENT = "MultiInstanceWatcher::GetOpenProcessesCount";
|
||||
|
||||
try
|
||||
{
|
||||
// prevent any possible race conditions by checking for bloxstrap processes too
|
||||
int count = Process.GetProcesses().Count(x => x.ProcessName is "RobloxPlayerBeta" or "Bloxstrap");
|
||||
count -= 1; // ignore the current process
|
||||
return count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// everything process related can error at any time
|
||||
App.Logger.WriteException(LOG_IDENT, ex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void FireInitialisedEvent()
|
||||
{
|
||||
using EventWaitHandle initEventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, "Bloxstrap-MultiInstanceWatcherInitialisationFinished");
|
||||
initEventHandle.Set();
|
||||
}
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
const string LOG_IDENT = "MultiInstanceWatcher::Run";
|
||||
|
||||
// try to get the mutex
|
||||
bool acquiredMutex;
|
||||
using Mutex mutex = new Mutex(false, "ROBLOX_singletonMutex");
|
||||
try
|
||||
{
|
||||
acquiredMutex = mutex.WaitOne(0);
|
||||
}
|
||||
catch (AbandonedMutexException)
|
||||
{
|
||||
acquiredMutex = true;
|
||||
}
|
||||
|
||||
if (!acquiredMutex)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Client singleton mutex is already acquired");
|
||||
FireInitialisedEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, "Acquired mutex!");
|
||||
FireInitialisedEvent();
|
||||
|
||||
// watch for alive processes
|
||||
int count;
|
||||
do
|
||||
{
|
||||
Thread.Sleep(5000);
|
||||
count = GetOpenProcessesCount();
|
||||
}
|
||||
while (count == -1 || count > 0); // redo if -1 (one of the Process apis failed)
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, "All Roblox related processes have closed, exiting!");
|
||||
}
|
||||
}
|
||||
}
|
18
Bloxstrap/Resources/Strings.Designer.cs
generated
18
Bloxstrap/Resources/Strings.Designer.cs
generated
@ -3425,6 +3425,24 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Allows for having more than one Roblox game client instance open simultaneously..
|
||||
/// </summary>
|
||||
public static string Menu_Integrations_MultiInstanceLaunching_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Integrations.MultiInstanceLaunching.Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Allow multi-instance launching.
|
||||
/// </summary>
|
||||
public static string Menu_Integrations_MultiInstanceLaunching_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Integrations.MultiInstanceLaunching.Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to When in-game, you'll be able to see where your server is located via [ipinfo.io]({0})..
|
||||
/// </summary>
|
||||
|
@ -1487,4 +1487,10 @@ Defaulting to {1}.</value>
|
||||
<value>Custom Theme {0}</value>
|
||||
<comment>{0} is a string (e.g. '1', '1-1234')</comment>
|
||||
</data>
|
||||
<data name="Menu.Integrations.MultiInstanceLaunching.Title" xml:space="preserve">
|
||||
<value>Allow multi-instance launching</value>
|
||||
</data>
|
||||
<data name="Menu.Integrations.MultiInstanceLaunching.Description" xml:space="preserve">
|
||||
<value>Allows for having more than one Roblox game client instance open simultaneously.</value>
|
||||
</data>
|
||||
</root>
|
@ -66,6 +66,14 @@
|
||||
<ui:ToggleSwitch IsChecked="{Binding DiscordAccountOnProfile, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
|
||||
<TextBlock Text="{x:Static resources:Strings.Common_Miscellaneous}" FontSize="20" FontWeight="Medium" Margin="0,16,0,0" />
|
||||
|
||||
<controls:OptionControl
|
||||
Header="{x:Static resources:Strings.Menu_Integrations_MultiInstanceLaunching_Title}"
|
||||
Description="{x:Static resources:Strings.Menu_Integrations_MultiInstanceLaunching_Description}">
|
||||
<ui:ToggleSwitch IsChecked="{Binding MultiInstanceLaunchingEnabled, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
|
||||
<TextBlock Text="{x:Static resources:Strings.Menu_Integrations_Custom_Title}" FontSize="20" FontWeight="Medium" Margin="0,16,0,0" />
|
||||
<TextBlock Text="{x:Static resources:Strings.Menu_Integrations_Custom_Description}" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<Grid Margin="0,8,0,0">
|
||||
|
@ -125,6 +125,12 @@ namespace Bloxstrap.UI.ViewModels.Settings
|
||||
set => App.Settings.Prop.UseDisableAppPatch = value;
|
||||
}
|
||||
|
||||
public bool MultiInstanceLaunchingEnabled
|
||||
{
|
||||
get => App.Settings.Prop.MultiInstanceLaunching;
|
||||
set => App.Settings.Prop.MultiInstanceLaunching = value;
|
||||
}
|
||||
|
||||
public ObservableCollection<CustomIntegration> CustomIntegrations
|
||||
{
|
||||
get => App.Settings.Prop.CustomIntegrations;
|
||||
|
Loading…
Reference in New Issue
Block a user