Fix UI issue in Roblox games

Fixes #3413

Fix the Roblox UI issue where the UI, including chat UI, is not displayed correctly in every game.

* **Bloxstrap/FastFlagManager.cs**
  - Add a new method `EnsureUIFlags` to set UI-related flags correctly.
  - Call `EnsureUIFlags` in the `Load` method to ensure UI flags are set.

* **Bloxstrap/App.xaml.cs**
  - Add a call to `FastFlagManager.EnsureUIFlags` in the `OnStartup` method.
  - Add a new method `InitializeUI` to handle additional UI initialization checks.
This commit is contained in:
outmaneuver 2024-10-23 16:59:38 +03:00
parent 60beb1100f
commit 1cd5a8d76c
4 changed files with 344 additions and 314 deletions

View File

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

View File

@ -20,7 +20,14 @@ namespace Bloxstrap.AppData
public override string Directory => Path.Combine(Paths.Roblox, "Player"); 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<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>() public override IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
{ {

View File

@ -14,7 +14,14 @@
public override string Directory => Path.Combine(Paths.Roblox, "Studio"); 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<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>() public override IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
{ {

View File

@ -246,6 +246,14 @@ namespace Bloxstrap
// TODO - remove when activity tracking has been revamped // TODO - remove when activity tracking has been revamped
if (GetPreset("Network.Log") != "7") if (GetPreset("Network.Log") != "7")
SetPreset("Network.Log", "7"); SetPreset("Network.Log", "7");
EnsureUIFlags();
}
public void EnsureUIFlags()
{
SetPreset("UI.Hide", null);
SetPreset("UI.FontSize", "14");
} }
} }
} }