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; SendLog(); 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); } } public static async void SendLog() { if (!Settings.Prop.EnableAnalytics || !IsProductionBuild) return; try { await HttpClient.PostAsync( $"https://bloxstraplabs.com/metrics/post-exception", new StringContent(Logger.AsDocument) ); } catch (Exception ex) { Logger.WriteException("App::SendLog", 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 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 } } }