diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs
index f5bcefb..bbc3b65 100644
--- a/Bloxstrap/App.xaml.cs
+++ b/Bloxstrap/App.xaml.cs
@@ -1,312 +1,320 @@
-using System.Reflection;
-using System.Security.Cryptography;
-using System.Windows;
-using System.Windows.Shell;
-using System.Windows.Threading;
-
-using Microsoft.Win32;
-
-namespace Bloxstrap
-{
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App : Application
- {
-#if QA_BUILD
- public const string ProjectName = "Bloxstrap-QA";
-#else
- public const string ProjectName = "Bloxstrap";
-#endif
- public const string ProjectOwner = "Bloxstrap";
- public const string ProjectRepository = "bloxstraplabs/bloxstrap";
- public const string ProjectDownloadLink = "https://bloxstraplabs.com";
- public const string ProjectHelpLink = "https://github.com/bloxstraplabs/bloxstrap/wiki";
- public const string ProjectSupportLink = "https://github.com/bloxstraplabs/bloxstrap/issues/new";
-
- public const string RobloxPlayerAppName = "RobloxPlayerBeta";
- public const string RobloxStudioAppName = "RobloxStudioBeta";
-
- // simple shorthand for extremely frequently used and long string - this goes under HKCU
- public const string UninstallKey = $@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{ProjectName}";
-
- public static LaunchSettings LaunchSettings { get; private set; } = null!;
-
- public static BuildMetadataAttribute BuildMetadata = Assembly.GetExecutingAssembly().GetCustomAttribute()!;
-
- public static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2];
-
- public static Bootstrapper? Bootstrapper { get; set; } = null!;
-
- public static bool IsActionBuild => !String.IsNullOrEmpty(BuildMetadata.CommitRef);
-
- public static bool IsProductionBuild => IsActionBuild && BuildMetadata.CommitRef.StartsWith("tag", StringComparison.Ordinal);
-
- public static readonly MD5 MD5Provider = MD5.Create();
-
- public static readonly Logger Logger = new();
-
- public static readonly Dictionary PendingSettingTasks = new();
-
- public static readonly JsonManager Settings = new();
-
- public static readonly JsonManager State = new();
-
- public static readonly FastFlagManager FastFlags = new();
-
- public static readonly HttpClient HttpClient = new(
- new HttpClientLoggingHandler(
- new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All }
- )
- );
-
- private static bool _showingExceptionDialog = false;
-
- public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
- {
- int exitCodeNum = (int)exitCode;
-
- Logger.WriteLine("App::Terminate", $"Terminating with exit code {exitCodeNum} ({exitCode})");
-
- Environment.Exit(exitCodeNum);
- }
-
- public static void SoftTerminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
- {
- int exitCodeNum = (int)exitCode;
-
- Logger.WriteLine("App::SoftTerminate", $"Terminating with exit code {exitCodeNum} ({exitCode})");
-
- Current.Dispatcher.Invoke(() => Current.Shutdown(exitCodeNum));
- }
-
- void GlobalExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs e)
- {
- e.Handled = true;
-
- Logger.WriteLine("App::GlobalExceptionHandler", "An exception occurred");
-
- FinalizeExceptionHandling(e.Exception);
- }
-
- public static void FinalizeExceptionHandling(AggregateException ex)
- {
- foreach (var innerEx in ex.InnerExceptions)
- Logger.WriteException("App::FinalizeExceptionHandling", innerEx);
-
- FinalizeExceptionHandling(ex.GetBaseException(), false);
- }
-
- public static void FinalizeExceptionHandling(Exception ex, bool log = true)
- {
- if (log)
- Logger.WriteException("App::FinalizeExceptionHandling", ex);
-
- if (_showingExceptionDialog)
- return;
-
- _showingExceptionDialog = true;
-
- if (Bootstrapper?.Dialog != null)
- {
- if (Bootstrapper.Dialog.TaskbarProgressValue == 0)
- Bootstrapper.Dialog.TaskbarProgressValue = 1; // make sure it's visible
-
- Bootstrapper.Dialog.TaskbarProgressState = TaskbarItemProgressState.Error;
- }
-
- Frontend.ShowExceptionDialog(ex);
-
- Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
- }
-
- public static async Task GetLatestRelease()
- {
- const string LOG_IDENT = "App::GetLatestRelease";
-
- try
- {
- var releaseInfo = await Http.GetJson($"https://api.github.com/repos/{ProjectRepository}/releases/latest");
-
- if (releaseInfo is null || releaseInfo.Assets is null)
- {
- Logger.WriteLine(LOG_IDENT, "Encountered invalid data");
- return null;
- }
-
- return releaseInfo;
- }
- catch (Exception ex)
- {
- Logger.WriteException(LOG_IDENT, ex);
- }
-
- return null;
- }
-
- public static async void SendStat(string key, string value)
- {
- if (!Settings.Prop.EnableAnalytics)
- return;
-
- try
- {
- await HttpClient.GetAsync($"https://bloxstraplabs.com/metrics/post?key={key}&value={value}");
- }
- catch (Exception ex)
- {
- Logger.WriteException("App::SendStat", ex);
- }
- }
-
- protected override void OnStartup(StartupEventArgs e)
- {
- const string LOG_IDENT = "App::OnStartup";
-
- Locale.Initialize();
-
- base.OnStartup(e);
-
- Logger.WriteLine(LOG_IDENT, $"Starting {ProjectName} v{Version}");
-
- string userAgent = $"{ProjectName}/{Version}";
-
- if (IsActionBuild)
- {
- Logger.WriteLine(LOG_IDENT, $"Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from commit {BuildMetadata.CommitHash} ({BuildMetadata.CommitRef})");
-
- if (IsProductionBuild)
- userAgent += $" (Production)";
- else
- userAgent += $" (Artifact {BuildMetadata.CommitHash}, {BuildMetadata.CommitRef})";
- }
- else
- {
- Logger.WriteLine(LOG_IDENT, $"Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from {BuildMetadata.Machine}");
-
-#if QA_BUILD
- userAgent += " (QA)";
-#else
- userAgent += $" (Build {Convert.ToBase64String(Encoding.UTF8.GetBytes(BuildMetadata.Machine))})";
-#endif
- }
-
- Logger.WriteLine(LOG_IDENT, $"Loaded from {Paths.Process}");
- Logger.WriteLine(LOG_IDENT, $"Temp path is {Paths.Temp}");
- Logger.WriteLine(LOG_IDENT, $"WindowsStartMenu path is {Paths.WindowsStartMenu}");
-
- // To customize application configuration such as set high DPI settings or default font,
- // see https://aka.ms/applicationconfiguration.
- ApplicationConfiguration.Initialize();
-
- HttpClient.Timeout = TimeSpan.FromSeconds(30);
- HttpClient.DefaultRequestHeaders.Add("User-Agent", userAgent);
-
- LaunchSettings = new LaunchSettings(e.Args);
-
- // installation check begins here
- using var uninstallKey = Registry.CurrentUser.OpenSubKey(UninstallKey);
- string? installLocation = null;
- bool fixInstallLocation = false;
-
- if (uninstallKey?.GetValue("InstallLocation") is string value)
- {
- if (Directory.Exists(value))
- {
- installLocation = value;
- }
- else
- {
- // check if user profile folder has been renamed
- // honestly, i'll be expecting bugs from this
- var match = Regex.Match(value, @"^[a-zA-Z]:\\Users\\([^\\]+)", RegexOptions.IgnoreCase);
-
- if (match.Success)
- {
- string newLocation = value.Replace(match.Value, Paths.UserProfile, StringComparison.InvariantCultureIgnoreCase);
-
- if (Directory.Exists(newLocation))
- {
- installLocation = newLocation;
- fixInstallLocation = true;
- }
- }
- }
- }
-
- // silently change install location if we detect a portable run
- if (installLocation is null && Directory.GetParent(Paths.Process)?.FullName is string processDir)
- {
- var files = Directory.GetFiles(processDir).Select(x => Path.GetFileName(x)).ToArray();
-
- // check if settings.json and state.json are the only files in the folder
- if (files.Length <= 3 && files.Contains("Settings.json") && files.Contains("State.json"))
- {
- installLocation = processDir;
- fixInstallLocation = true;
- }
- }
-
- if (fixInstallLocation && installLocation is not null)
- {
- var installer = new Installer
- {
- InstallLocation = installLocation,
- IsImplicitInstall = true
- };
-
- if (installer.CheckInstallLocation())
- {
- Logger.WriteLine(LOG_IDENT, $"Changing install location to '{installLocation}'");
- installer.DoInstall();
- }
- else
- {
- // force reinstall
- installLocation = null;
- }
- }
-
- if (installLocation is null)
- {
- Logger.Initialize(true);
- LaunchHandler.LaunchInstaller();
- }
- else
- {
- Paths.Initialize(installLocation);
-
- // ensure executable is in the install directory
- if (Paths.Process != Paths.Application && !File.Exists(Paths.Application))
- File.Copy(Paths.Process, Paths.Application);
-
- Logger.Initialize(LaunchSettings.UninstallFlag.Active);
-
- if (!Logger.Initialized && !Logger.NoWriteMode)
- {
- Logger.WriteLine(LOG_IDENT, "Possible duplicate launch detected, terminating.");
- Terminate();
- }
-
- Settings.Load();
- State.Load();
- FastFlags.Load();
-
- if (!Locale.SupportedLocales.ContainsKey(Settings.Prop.Locale))
- {
- Settings.Prop.Locale = "nil";
- Settings.Save();
- }
-
- Locale.Set(Settings.Prop.Locale);
-
- if (!LaunchSettings.BypassUpdateCheck)
- Installer.HandleUpgrade();
-
- LaunchHandler.ProcessLaunchArgs();
- }
-
- // you must *explicitly* call terminate when everything is done, it won't be called implicitly
- }
- }
-}
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Windows;
+using System.Windows.Shell;
+using System.Windows.Threading;
+
+using Microsoft.Win32;
+
+namespace Bloxstrap
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+#if QA_BUILD
+ public const string ProjectName = "Bloxstrap-QA";
+#else
+ public const string ProjectName = "Bloxstrap";
+#endif
+ public const string ProjectOwner = "Bloxstrap";
+ public const string ProjectRepository = "bloxstraplabs/bloxstrap";
+ public const string ProjectDownloadLink = "https://bloxstraplabs.com";
+ public const string ProjectHelpLink = "https://github.com/bloxstraplabs/bloxstrap/wiki";
+ public const string ProjectSupportLink = "https://github.com/bloxstraplabs/bloxstrap/issues/new";
+
+ public const string RobloxPlayerAppName = "RobloxPlayerBeta";
+ public const string RobloxStudioAppName = "RobloxStudioBeta";
+
+ // simple shorthand for extremely frequently used and long string - this goes under HKCU
+ public const string UninstallKey = $@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{ProjectName}";
+
+ public static LaunchSettings LaunchSettings { get; private set; } = null!;
+
+ public static BuildMetadataAttribute BuildMetadata = Assembly.GetExecutingAssembly().GetCustomAttribute()!;
+
+ public static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2];
+
+ public static Bootstrapper? Bootstrapper { get; set; } = null!;
+
+ public static bool IsActionBuild => !String.IsNullOrEmpty(BuildMetadata.CommitRef);
+
+ public static bool IsProductionBuild => IsActionBuild && BuildMetadata.CommitRef.StartsWith("tag", StringComparison.Ordinal);
+
+ public static readonly MD5 MD5Provider = MD5.Create();
+
+ public static readonly Logger Logger = new();
+
+ public static readonly Dictionary PendingSettingTasks = new();
+
+ public static readonly JsonManager Settings = new();
+
+ public static readonly JsonManager State = new();
+
+ public static readonly FastFlagManager FastFlags = new();
+
+ public static readonly HttpClient HttpClient = new(
+ new HttpClientLoggingHandler(
+ new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All }
+ )
+ );
+
+ private static bool _showingExceptionDialog = false;
+
+ public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
+ {
+ int exitCodeNum = (int)exitCode;
+
+ Logger.WriteLine("App::Terminate", $"Terminating with exit code {exitCodeNum} ({exitCode})");
+
+ Environment.Exit(exitCodeNum);
+ }
+
+ public static void SoftTerminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
+ {
+ int exitCodeNum = (int)exitCode;
+
+ Logger.WriteLine("App::SoftTerminate", $"Terminating with exit code {exitCodeNum} ({exitCode})");
+
+ Current.Dispatcher.Invoke(() => Current.Shutdown(exitCodeNum));
+ }
+
+ void GlobalExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs e)
+ {
+ e.Handled = true;
+
+ Logger.WriteLine("App::GlobalExceptionHandler", "An exception occurred");
+
+ FinalizeExceptionHandling(e.Exception);
+ }
+
+ public static void FinalizeExceptionHandling(AggregateException ex)
+ {
+ foreach (var innerEx in ex.InnerExceptions)
+ Logger.WriteException("App::FinalizeExceptionHandling", innerEx);
+
+ FinalizeExceptionHandling(ex.GetBaseException(), false);
+ }
+
+ public static void FinalizeExceptionHandling(Exception ex, bool log = true)
+ {
+ if (log)
+ Logger.WriteException("App::FinalizeExceptionHandling", ex);
+
+ if (_showingExceptionDialog)
+ return;
+
+ _showingExceptionDialog = true;
+
+ if (Bootstrapper?.Dialog != null)
+ {
+ if (Bootstrapper.Dialog.TaskbarProgressValue == 0)
+ Bootstrapper.Dialog.TaskbarProgressValue = 1; // make sure it's visible
+
+ Bootstrapper.Dialog.TaskbarProgressState = TaskbarItemProgressState.Error;
+ }
+
+ Frontend.ShowExceptionDialog(ex);
+
+ Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
+ }
+
+ public static async Task GetLatestRelease()
+ {
+ const string LOG_IDENT = "App::GetLatestRelease";
+
+ try
+ {
+ var releaseInfo = await Http.GetJson($"https://api.github.com/repos/{ProjectRepository}/releases/latest");
+
+ if (releaseInfo is null || releaseInfo.Assets is null)
+ {
+ Logger.WriteLine(LOG_IDENT, "Encountered invalid data");
+ return null;
+ }
+
+ return releaseInfo;
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteException(LOG_IDENT, ex);
+ }
+
+ return null;
+ }
+
+ public static async void SendStat(string key, string value)
+ {
+ if (!Settings.Prop.EnableAnalytics)
+ return;
+
+ try
+ {
+ await HttpClient.GetAsync($"https://bloxstraplabs.com/metrics/post?key={key}&value={value}");
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteException("App::SendStat", ex);
+ }
+ }
+
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ const string LOG_IDENT = "App::OnStartup";
+
+ Locale.Initialize();
+
+ base.OnStartup(e);
+
+ Logger.WriteLine(LOG_IDENT, $"Starting {ProjectName} v{Version}");
+
+ string userAgent = $"{ProjectName}/{Version}";
+
+ if (IsActionBuild)
+ {
+ Logger.WriteLine(LOG_IDENT, $"Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from commit {BuildMetadata.CommitHash} ({BuildMetadata.CommitRef})");
+
+ if (IsProductionBuild)
+ userAgent += $" (Production)";
+ else
+ userAgent += $" (Artifact {BuildMetadata.CommitHash}, {BuildMetadata.CommitRef})";
+ }
+ else
+ {
+ Logger.WriteLine(LOG_IDENT, $"Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from {BuildMetadata.Machine}");
+
+#if QA_BUILD
+ userAgent += " (QA)";
+#else
+ userAgent += $" (Build {Convert.ToBase64String(Encoding.UTF8.GetBytes(BuildMetadata.Machine))})";
+#endif
+ }
+
+ Logger.WriteLine(LOG_IDENT, $"Loaded from {Paths.Process}");
+ Logger.WriteLine(LOG_IDENT, $"Temp path is {Paths.Temp}");
+ Logger.WriteLine(LOG_IDENT, $"WindowsStartMenu path is {Paths.WindowsStartMenu}");
+
+ // To customize application configuration such as set high DPI settings or default font,
+ // see https://aka.ms/applicationconfiguration.
+ ApplicationConfiguration.Initialize();
+
+ HttpClient.Timeout = TimeSpan.FromSeconds(30);
+ HttpClient.DefaultRequestHeaders.Add("User-Agent", userAgent);
+
+ LaunchSettings = new LaunchSettings(e.Args);
+
+ // installation check begins here
+ using var uninstallKey = Registry.CurrentUser.OpenSubKey(UninstallKey);
+ string? installLocation = null;
+ bool fixInstallLocation = false;
+
+ if (uninstallKey?.GetValue("InstallLocation") is string value)
+ {
+ if (Directory.Exists(value))
+ {
+ installLocation = value;
+ }
+ else
+ {
+ // check if user profile folder has been renamed
+ // honestly, i'll be expecting bugs from this
+ var match = Regex.Match(value, @"^[a-zA-Z]:\\Users\\([^\\]+)", RegexOptions.IgnoreCase);
+
+ if (match.Success)
+ {
+ string newLocation = value.Replace(match.Value, Paths.UserProfile, StringComparison.InvariantCultureIgnoreCase);
+
+ if (Directory.Exists(newLocation))
+ {
+ installLocation = newLocation;
+ fixInstallLocation = true;
+ }
+ }
+ }
+ }
+
+ // silently change install location if we detect a portable run
+ if (installLocation is null && Directory.GetParent(Paths.Process)?.FullName is string processDir)
+ {
+ var files = Directory.GetFiles(processDir).Select(x => Path.GetFileName(x)).ToArray();
+
+ // check if settings.json and state.json are the only files in the folder
+ if (files.Length <= 3 && files.Contains("Settings.json") && files.Contains("State.json"))
+ {
+ installLocation = processDir;
+ fixInstallLocation = true;
+ }
+ }
+
+ if (fixInstallLocation && installLocation is not null)
+ {
+ var installer = new Installer
+ {
+ InstallLocation = installLocation,
+ IsImplicitInstall = true
+ };
+
+ if (installer.CheckInstallLocation())
+ {
+ Logger.WriteLine(LOG_IDENT, $"Changing install location to '{installLocation}'");
+ installer.DoInstall();
+ }
+ else
+ {
+ // force reinstall
+ installLocation = null;
+ }
+ }
+
+ if (installLocation is null)
+ {
+ Logger.Initialize(true);
+ LaunchHandler.LaunchInstaller();
+ }
+ else
+ {
+ Paths.Initialize(installLocation);
+
+ // ensure executable is in the install directory
+ if (Paths.Process != Paths.Application && !File.Exists(Paths.Application))
+ File.Copy(Paths.Process, Paths.Application);
+
+ Logger.Initialize(LaunchSettings.UninstallFlag.Active);
+
+ if (!Logger.Initialized && !Logger.NoWriteMode)
+ {
+ Logger.WriteLine(LOG_IDENT, "Possible duplicate launch detected, terminating.");
+ Terminate();
+ }
+
+ Settings.Load();
+ State.Load();
+ FastFlags.Load();
+
+ if (!Locale.SupportedLocales.ContainsKey(Settings.Prop.Locale))
+ {
+ Settings.Prop.Locale = "nil";
+ Settings.Save();
+ }
+
+ Locale.Set(Settings.Prop.Locale);
+
+ if (!LaunchSettings.BypassUpdateCheck)
+ Installer.HandleUpgrade();
+
+ LaunchHandler.ProcessLaunchArgs();
+ }
+
+ // you must *explicitly* call terminate when everything is done, it won't be called implicitly
+
+ FastFlags.EnsureUIFlags();
+ InitializeUI();
+ }
+
+ public static void InitializeUI()
+ {
+ // Add any additional UI initialization checks here
+ }
+ }
+}
diff --git a/Bloxstrap/AppData/RobloxPlayerData.cs b/Bloxstrap/AppData/RobloxPlayerData.cs
index 923c6a1..b666a4d 100644
--- a/Bloxstrap/AppData/RobloxPlayerData.cs
+++ b/Bloxstrap/AppData/RobloxPlayerData.cs
@@ -20,7 +20,14 @@ namespace Bloxstrap.AppData
public override string Directory => Path.Combine(Paths.Roblox, "Player");
- public AppState State => App.State.Prop.Player;
+ public AppState State
+ {
+ get
+ {
+ App.InitializeUI();
+ return App.State.Prop.Player;
+ }
+ }
public override IReadOnlyDictionary PackageDirectoryMap { get; set; } = new Dictionary()
{
diff --git a/Bloxstrap/AppData/RobloxStudioData.cs b/Bloxstrap/AppData/RobloxStudioData.cs
index 73c630b..82ae344 100644
--- a/Bloxstrap/AppData/RobloxStudioData.cs
+++ b/Bloxstrap/AppData/RobloxStudioData.cs
@@ -14,7 +14,14 @@
public override string Directory => Path.Combine(Paths.Roblox, "Studio");
- public AppState State => App.State.Prop.Studio;
+ public AppState State
+ {
+ get
+ {
+ App.InitializeUI();
+ return App.State.Prop.Studio;
+ }
+ }
public override IReadOnlyDictionary PackageDirectoryMap { get; set; } = new Dictionary()
{
diff --git a/Bloxstrap/FastFlagManager.cs b/Bloxstrap/FastFlagManager.cs
index 284df3d..4a66f44 100644
--- a/Bloxstrap/FastFlagManager.cs
+++ b/Bloxstrap/FastFlagManager.cs
@@ -246,6 +246,14 @@ namespace Bloxstrap
// TODO - remove when activity tracking has been revamped
if (GetPreset("Network.Log") != "7")
SetPreset("Network.Log", "7");
+
+ EnsureUIFlags();
+ }
+
+ public void EnsureUIFlags()
+ {
+ SetPreset("UI.Hide", null);
+ SetPreset("UI.FontSize", "14");
}
}
}