Skip to content

Commit 1cc8147

Browse files
committed
Merge branch 'main' of https://github.com/pizzaboxer/bloxstrap into wip
2 parents 3e12ed8 + 055695e commit 1cc8147

19 files changed

+441
-92
lines changed

Bloxstrap/App.xaml.cs

+39-3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ public partial class App : Application
6565
);
6666

6767
private static bool _showingExceptionDialog = false;
68+
69+
private static string? _webUrl = null;
70+
public static string WebUrl
71+
{
72+
get {
73+
if (_webUrl != null)
74+
return _webUrl;
75+
76+
string url = ConstructBloxstrapWebUrl();
77+
if (Settings.Loaded) // only cache if settings are done loading
78+
_webUrl = url;
79+
return url;
80+
}
81+
}
6882

6983
public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
7084
{
@@ -126,6 +140,25 @@ public static void FinalizeExceptionHandling(Exception ex, bool log = true)
126140
Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
127141
}
128142

143+
public static string ConstructBloxstrapWebUrl()
144+
{
145+
// dont let user switch web environment if debug mode is not on
146+
if (Settings.Prop.WebEnvironment == WebEnvironment.Production || !Settings.Prop.DeveloperMode)
147+
return "bloxstraplabs.com";
148+
149+
string? sub = Settings.Prop.WebEnvironment.GetDescription();
150+
return $"web-{sub}.bloxstraplabs.com";
151+
}
152+
153+
public static bool CanSendLogs()
154+
{
155+
// non developer mode always uses production
156+
if (!Settings.Prop.DeveloperMode || Settings.Prop.WebEnvironment == WebEnvironment.Production)
157+
return IsProductionBuild;
158+
159+
return true;
160+
}
161+
129162
public static async Task<GithubRelease?> GetLatestRelease()
130163
{
131164
const string LOG_IDENT = "App::GetLatestRelease";
@@ -157,7 +190,7 @@ public static async void SendStat(string key, string value)
157190

158191
try
159192
{
160-
await HttpClient.GetAsync($"https://bloxstraplabs.com/metrics/post?key={key}&value={value}");
193+
await HttpClient.GetAsync($"https://{WebUrl}/metrics/post?key={key}&value={value}");
161194
}
162195
catch (Exception ex)
163196
{
@@ -167,13 +200,13 @@ public static async void SendStat(string key, string value)
167200

168201
public static async void SendLog()
169202
{
170-
if (!Settings.Prop.EnableAnalytics || !IsProductionBuild)
203+
if (!Settings.Prop.EnableAnalytics || !CanSendLogs())
171204
return;
172205

173206
try
174207
{
175208
await HttpClient.PostAsync(
176-
$"https://bloxstraplabs.com/metrics/post-exception",
209+
$"https://{WebUrl}/metrics/post-exception",
177210
new StringContent(Logger.AsDocument)
178211
);
179212
}
@@ -347,6 +380,9 @@ protected override void OnStartup(StartupEventArgs e)
347380
Settings.Save();
348381
}
349382

383+
Logger.WriteLine(LOG_IDENT, $"Developer mode: {Settings.Prop.DeveloperMode}");
384+
Logger.WriteLine(LOG_IDENT, $"Web environment: {Settings.Prop.WebEnvironment}");
385+
350386
Locale.Set(Settings.Prop.Locale);
351387

352388
if (!LaunchSettings.BypassUpdateCheck)

Bloxstrap/Bootstrapper.cs

+56-20
Original file line numberDiff line numberDiff line change
@@ -313,11 +313,14 @@ public async Task Run()
313313

314314
if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelTokenSource.IsCancellationRequested)
315315
{
316-
// show some balloon tips
317-
if (!_packageExtractionSuccess)
318-
Frontend.ShowBalloonTip(Strings.Bootstrapper_ExtractionFailed_Title, Strings.Bootstrapper_ExtractionFailed_Message, ToolTipIcon.Warning);
319-
else if (!allModificationsApplied)
320-
Frontend.ShowBalloonTip(Strings.Bootstrapper_ModificationsFailed_Title, Strings.Bootstrapper_ModificationsFailed_Message, ToolTipIcon.Warning);
316+
if (!App.LaunchSettings.QuietFlag.Active)
317+
{
318+
// show some balloon tips
319+
if (!_packageExtractionSuccess)
320+
Frontend.ShowBalloonTip(Strings.Bootstrapper_ExtractionFailed_Title, Strings.Bootstrapper_ExtractionFailed_Message, ToolTipIcon.Warning);
321+
else if (!allModificationsApplied)
322+
Frontend.ShowBalloonTip(Strings.Bootstrapper_ModificationsFailed_Title, Strings.Bootstrapper_ModificationsFailed_Message, ToolTipIcon.Warning);
323+
}
321324

322325
StartRoblox();
323326
}
@@ -490,21 +493,48 @@ private bool IsEligibleForBackgroundUpdate()
490493
}
491494
}
492495

496+
private static void LaunchMultiInstanceWatcher()
497+
{
498+
const string LOG_IDENT = "Bootstrapper::LaunchMultiInstanceWatcher";
499+
500+
if (Utilities.DoesMutexExist("ROBLOX_singletonMutex"))
501+
{
502+
App.Logger.WriteLine(LOG_IDENT, "Roblox singleton mutex already exists");
503+
return;
504+
}
505+
506+
using EventWaitHandle initEventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, "Bloxstrap-MultiInstanceWatcherInitialisationFinished");
507+
Process.Start(Paths.Process, "-multiinstancewatcher");
508+
509+
bool initSuccess = initEventHandle.WaitOne(TimeSpan.FromSeconds(2));
510+
if (initSuccess)
511+
App.Logger.WriteLine(LOG_IDENT, "Initialisation finished signalled, continuing.");
512+
else
513+
App.Logger.WriteLine(LOG_IDENT, "Did not receive the initialisation finished signal, continuing.");
514+
}
515+
493516
private void StartRoblox()
494517
{
495518
const string LOG_IDENT = "Bootstrapper::StartRoblox";
496519

497520
SetStatus(Strings.Bootstrapper_Status_Starting);
498521

499-
if (_launchMode == LaunchMode.Player && App.Settings.Prop.ForceRobloxLanguage)
522+
if (_launchMode == LaunchMode.Player)
500523
{
501-
var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)", RegexOptions.CultureInvariant);
524+
// this needs to be done before roblox launches
525+
if (App.Settings.Prop.MultiInstanceLaunching)
526+
LaunchMultiInstanceWatcher();
527+
528+
if (App.Settings.Prop.ForceRobloxLanguage)
529+
{
530+
var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)", RegexOptions.CultureInvariant);
502531

503-
if (match.Groups.Count == 2)
504-
_launchCommandLine = _launchCommandLine.Replace(
505-
"robloxLocale:en_us",
506-
$"robloxLocale:{match.Groups[1].Value}",
507-
StringComparison.OrdinalIgnoreCase);
532+
if (match.Groups.Count == 2)
533+
_launchCommandLine = _launchCommandLine.Replace(
534+
"robloxLocale:en_us",
535+
$"robloxLocale:{match.Groups[1].Value}",
536+
StringComparison.OrdinalIgnoreCase);
537+
}
508538
}
509539

510540
var startInfo = new ProcessStartInfo()
@@ -848,6 +878,12 @@ public static void CleanupVersionsFolder()
848878
return;
849879
}
850880

881+
if (!Directory.Exists(Paths.Versions))
882+
{
883+
App.Logger.WriteLine(LOG_IDENT, "Versions directory does not exist, skipping cleanup.");
884+
return;
885+
}
886+
851887
foreach (string dir in Directory.GetDirectories(Paths.Versions))
852888
{
853889
string dirName = Path.GetFileName(dir);
@@ -866,7 +902,7 @@ public static void CleanupVersionsFolder()
866902
{
867903
Directory.Delete(dir, true);
868904
}
869-
catch (IOException ex)
905+
catch (Exception ex)
870906
{
871907
App.Logger.WriteLine(LOG_IDENT, $"Failed to delete {dir}");
872908
App.Logger.WriteException(LOG_IDENT, ex);
@@ -894,21 +930,19 @@ private void MigrateCompatibilityFlags()
894930
}
895931
}
896932

897-
private void KillRunningRobloxInDirectory(string path)
933+
private static void KillRobloxPlayers()
898934
{
899-
const string LOG_IDENT = "Bootstrapper::KillRunningRobloxInDirectory";
935+
const string LOG_IDENT = "Bootstrapper::KillRobloxPlayers";
900936

901937
List<Process> processes = new List<Process>();
902-
processes.AddRange(Process.GetProcessesByName(IsStudioLaunch ? "RobloxStudioBeta" : "RobloxPlayerBeta"));
903-
processes.AddRange(Process.GetProcessesByName("RobloxCrashHandler"));
938+
processes.AddRange(Process.GetProcessesByName("RobloxPlayerBeta"));
939+
processes.AddRange(Process.GetProcessesByName("RobloxCrashHandler")); // roblox studio doesnt depend on crash handler being open, so this should be fine
904940

905941
foreach (Process process in processes)
906942
{
907943
try
908944
{
909-
string? processPath = process.MainModule?.FileName;
910-
if (processPath != null && processPath.StartsWith(path))
911-
process.Kill();
945+
process.Kill();
912946
}
913947
catch (Exception ex)
914948
{
@@ -1130,6 +1164,8 @@ private async Task UpgradeRoblox()
11301164

11311165
App.Logger.WriteLine(LOG_IDENT, $"Registered as {totalSize} KB");
11321166

1167+
App.State.Prop.ForceReinstall = false;
1168+
11331169
App.State.Save();
11341170
App.RobloxState.Save();
11351171

Bloxstrap/Enums/WebEnvironment.cs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.ComponentModel;
2+
3+
namespace Bloxstrap.Enums
4+
{
5+
[JsonConverter(typeof(JsonStringEnumConverter))]
6+
public enum WebEnvironment
7+
{
8+
[Description("prod")]
9+
Production,
10+
11+
[Description("stage")]
12+
Staging,
13+
14+
[Description("dev")]
15+
Dev,
16+
17+
[Description("pizza")]
18+
DevPizza,
19+
20+
[Description("matt")]
21+
DevMatt,
22+
23+
[Description("local")]
24+
Local
25+
}
26+
}
+27-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,36 @@
1-
namespace Bloxstrap.Extensions
1+
using System.Text;
2+
3+
namespace Bloxstrap.Extensions
24
{
35
static class CustomThemeTemplateEx
46
{
7+
const string EXAMPLES_URL = "https://github.com/bloxstraplabs/custom-bootstrapper-examples";
8+
59
public static string GetFileName(this CustomThemeTemplate template)
610
{
711
return $"CustomBootstrapperTemplate_{template}.xml";
812
}
13+
14+
public static string GetFileContents(this CustomThemeTemplate template)
15+
{
16+
string contents = Encoding.UTF8.GetString(Resource.Get(template.GetFileName()).Result);
17+
18+
switch (template)
19+
{
20+
case CustomThemeTemplate.Blank:
21+
{
22+
string moreText = string.Format(Strings.CustomTheme_Templates_Blank_MoreExamples, EXAMPLES_URL);
23+
return contents.Replace("{0}", Strings.CustomTheme_Templates_Blank_UIElements).Replace("{1}", moreText);
24+
}
25+
case CustomThemeTemplate.Simple:
26+
{
27+
string moreText = string.Format(Strings.CustomTheme_Templates_Simple_MoreExamples, EXAMPLES_URL);
28+
return contents.Replace("{0}", moreText);
29+
}
30+
default:
31+
Debug.Assert(false);
32+
return contents;
33+
}
34+
}
935
}
1036
}

Bloxstrap/Extensions/TEnumEx.cs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.ComponentModel;
2+
using System.Reflection;
3+
4+
namespace Bloxstrap.Extensions
5+
{
6+
internal static class TEnumEx
7+
{
8+
public static string? GetDescription<TEnum>(this TEnum e)
9+
{
10+
string? enumName = e?.ToString();
11+
if (enumName == null)
12+
return null;
13+
14+
FieldInfo? field = e?.GetType().GetField(enumName);
15+
if (field == null)
16+
return null;
17+
18+
DescriptionAttribute? attribute = field.GetCustomAttribute<DescriptionAttribute>();
19+
return attribute?.Description;
20+
}
21+
}
22+
}

Bloxstrap/Installer.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ namespace Bloxstrap
55
{
66
internal class Installer
77
{
8+
/// <summary>
9+
/// Should this version automatically open the release notes page?
10+
/// Recommended for major updates only.
11+
/// </summary>
12+
private const bool OpenReleaseNotes = true;
13+
814
private static string DesktopShortcut => Path.Combine(Paths.Desktop, $"{App.ProjectName}.lnk");
915

1016
private static string StartMenuShortcut => Path.Combine(Paths.WindowsStartMenu, $"{App.ProjectName}.lnk");
@@ -593,10 +599,10 @@ public static void HandleUpgrade()
593599
App.RobloxState.Prop.Studio = App.State.Prop.GetDeprecatedStudio()!;
594600

595601
if (App.State.Prop.GetDeprecatedPlayerModManifest() != null)
596-
App.RobloxState.Prop.ModManifest = App.State.Prop.GetDeprecatedPlayerModManifest()!;
602+
App.RobloxState.Prop.PlayerModManifest = App.State.Prop.GetDeprecatedPlayerModManifest()!;
597603

598604
if (App.State.Prop.GetDeprecatedStudioModManifest() != null)
599-
App.RobloxState.Prop.ModManifest = App.State.Prop.GetDeprecatedStudioModManifest()!;
605+
App.RobloxState.Prop.StudioModManifest = App.State.Prop.GetDeprecatedStudioModManifest()!;
600606
}
601607

602608
App.Settings.Save();

Bloxstrap/JsonManager.cs

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ namespace Bloxstrap
1313
/// </summary>
1414
public string? LastFileHash { get; private set; }
1515

16+
public bool Loaded { get; set; } = false;
17+
1618
public virtual string ClassName => typeof(T).Name;
1719

1820
public virtual string FileLocation => Path.Combine(Paths.Base, $"{ClassName}.json");
@@ -35,6 +37,7 @@ public virtual void Load(bool alertFailure = true)
3537
throw new ArgumentNullException("Deserialization returned null");
3638

3739
Prop = settings;
40+
Loaded = true;
3841
LastFileHash = MD5Hash.FromString(contents);
3942

4043
App.Logger.WriteLine(LOG_IDENT, "Loaded successfully!");

Bloxstrap/LaunchHandler.cs

+28-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ public static void ProcessLaunchArgs()
5959
App.Logger.WriteLine(LOG_IDENT, "Opening watcher");
6060
LaunchWatcher();
6161
}
62+
else if (App.LaunchSettings.MultiInstanceWatcherFlag.Active)
63+
{
64+
App.Logger.WriteLine(LOG_IDENT, "Opening multi-instance watcher");
65+
LaunchMultiInstanceWatcher();
66+
}
6267
else if (App.LaunchSettings.BackgroundUpdaterFlag.Active)
6368
{
6469
App.Logger.WriteLine(LOG_IDENT, "Opening background updater");
@@ -223,7 +228,7 @@ public static void LaunchRoblox(LaunchMode launchMode)
223228
App.Terminate(ErrorCode.ERROR_FILE_NOT_FOUND);
224229
}
225230

226-
if (App.Settings.Prop.ConfirmLaunches && Mutex.TryOpenExisting("ROBLOX_singletonMutex", out var _))
231+
if (App.Settings.Prop.ConfirmLaunches && Mutex.TryOpenExisting("ROBLOX_singletonMutex", out var _) && !App.Settings.Prop.MultiInstanceLaunching)
227232
{
228233
// this currently doesn't work very well since it relies on checking the existence of the singleton mutex
229234
// which often hangs around for a few seconds after the window closes
@@ -302,6 +307,28 @@ public static void LaunchWatcher()
302307
});
303308
}
304309

310+
public static void LaunchMultiInstanceWatcher()
311+
{
312+
const string LOG_IDENT = "LaunchHandler::LaunchMultiInstanceWatcher";
313+
314+
App.Logger.WriteLine(LOG_IDENT, "Starting multi-instance watcher");
315+
316+
Task.Run(MultiInstanceWatcher.Run).ContinueWith(t =>
317+
{
318+
App.Logger.WriteLine(LOG_IDENT, "Multi instance watcher task has finished");
319+
320+
if (t.IsFaulted)
321+
{
322+
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the multi-instance watcher");
323+
324+
if (t.Exception is not null)
325+
App.FinalizeExceptionHandling(t.Exception);
326+
}
327+
328+
App.Terminate();
329+
});
330+
}
331+
305332
public static void LaunchBackgroundUpdater()
306333
{
307334
const string LOG_IDENT = "LaunchHandler::LaunchBackgroundUpdater";

0 commit comments

Comments
 (0)