diff --git a/Bloxstrap/App.xaml b/Bloxstrap/App.xaml
deleted file mode 100644
index ae4bfac..0000000
--- a/Bloxstrap/App.xaml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
- pack://application:,,,/Resources/Fonts/#Rubik Light
-
-
-
-
-
-
-
-
-
diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs
deleted file mode 100644
index 691d868..0000000
--- a/Bloxstrap/App.xaml.cs
+++ /dev/null
@@ -1,359 +0,0 @@
-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 bool IsStudioVisible => !String.IsNullOrEmpty(App.State.Prop.Studio.VersionGuid);
-
- 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);
- }
- }
-
- public static void AssertWindowsOSVersion()
- {
- const string LOG_IDENT = "App::AssertWindowsOSVersion";
-
- int major = Environment.OSVersion.Version.Major;
- if (major < 10) // Windows 10 and newer only
- {
- Logger.WriteLine(LOG_IDENT, $"Detected unsupported Windows version ({Environment.OSVersion.Version}).");
-
- if (!LaunchSettings.QuietFlag.Active)
- Frontend.ShowMessageBox(Strings.App_OSDeprecation_Win7_81, MessageBoxImage.Error);
-
- Terminate(ErrorCode.ERROR_INVALID_FUNCTION);
- }
- }
-
- 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, $"OSVersion: {Environment.OSVersion}");
-
- 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);
- Logger.WriteLine(LOG_IDENT, "Not installed, launching the installer");
- AssertWindowsOSVersion(); // prevent new installs from unsupported operating systems
- LaunchHandler.LaunchInstaller();
- }
- else
- {
- Paths.Initialize(installLocation);
-
- Logger.WriteLine(LOG_IDENT, "Entering main logic");
-
- // ensure executable is in the install directory
- if (Paths.Process != Paths.Application && !File.Exists(Paths.Application))
- {
- Logger.WriteLine(LOG_IDENT, "Copying to install directory");
- 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
- Logger.WriteLine(LOG_IDENT, "Startup finished");
- }
- }
-}
diff --git a/Bloxstrap/AppData/CommonAppData.cs b/Bloxstrap/AppData/CommonAppData.cs
deleted file mode 100644
index 1e1b425..0000000
--- a/Bloxstrap/AppData/CommonAppData.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Bloxstrap.AppData
-{
- public abstract class CommonAppData
- {
- // in case a new package is added, you can find the corresponding directory
- // by opening the stock bootstrapper in a hex editor
- private IReadOnlyDictionary _commonMap { get; } = new Dictionary()
- {
- { "Libraries.zip", @"" },
- { "redist.zip", @"" },
- { "shaders.zip", @"shaders\" },
- { "ssl.zip", @"ssl\" },
-
- // the runtime installer is only extracted if it needs installing
- { "WebView2.zip", @"" },
- { "WebView2RuntimeInstaller.zip", @"WebView2RuntimeInstaller\" },
-
- { "content-avatar.zip", @"content\avatar\" },
- { "content-configs.zip", @"content\configs\" },
- { "content-fonts.zip", @"content\fonts\" },
- { "content-sky.zip", @"content\sky\" },
- { "content-sounds.zip", @"content\sounds\" },
- { "content-textures2.zip", @"content\textures\" },
- { "content-models.zip", @"content\models\" },
-
- { "content-textures3.zip", @"PlatformContent\pc\textures\" },
- { "content-terrain.zip", @"PlatformContent\pc\terrain\" },
- { "content-platform-fonts.zip", @"PlatformContent\pc\fonts\" },
- { "content-platform-dictionaries.zip", @"PlatformContent\pc\shared_compression_dictionaries\" },
-
- { "extracontent-luapackages.zip", @"ExtraContent\LuaPackages\" },
- { "extracontent-translations.zip", @"ExtraContent\translations\" },
- { "extracontent-models.zip", @"ExtraContent\models\" },
- { "extracontent-textures.zip", @"ExtraContent\textures\" },
- { "extracontent-places.zip", @"ExtraContent\places\" },
- };
-
- public virtual string ExecutableName { get; } = null!;
-
- public string Directory => Path.Combine(Paths.Versions, State.VersionGuid);
-
- public string ExecutablePath => Path.Combine(Directory, ExecutableName);
-
- public virtual AppState State { get; } = null!;
-
- public virtual IReadOnlyDictionary PackageDirectoryMap { get; set; }
-
-
- public CommonAppData()
- {
- if (PackageDirectoryMap is null)
- {
- PackageDirectoryMap = _commonMap;
- return;
- }
-
- var merged = new Dictionary();
-
- foreach (var entry in _commonMap)
- merged[entry.Key] = entry.Value;
-
- foreach (var entry in PackageDirectoryMap)
- merged[entry.Key] = entry.Value;
-
- PackageDirectoryMap = merged;
- }
- }
-}
diff --git a/Bloxstrap/AppData/IAppData.cs b/Bloxstrap/AppData/IAppData.cs
deleted file mode 100644
index b19aa95..0000000
--- a/Bloxstrap/AppData/IAppData.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace Bloxstrap.AppData
-{
- internal interface IAppData
- {
- string ProductName { get; }
-
- string BinaryType { get; }
-
- string RegistryName { get; }
-
- string ExecutableName { get; }
-
- string Directory { get; }
-
- string ExecutablePath { get; }
-
- AppState State { get; }
-
- IReadOnlyDictionary PackageDirectoryMap { get; set; }
- }
-}
diff --git a/Bloxstrap/AppData/RobloxPlayerData.cs b/Bloxstrap/AppData/RobloxPlayerData.cs
deleted file mode 100644
index 3c4f728..0000000
--- a/Bloxstrap/AppData/RobloxPlayerData.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Bloxstrap.AppData
-{
- public class RobloxPlayerData : CommonAppData, IAppData
- {
- public string ProductName => "Roblox";
-
- public string BinaryType => "WindowsPlayer";
-
- public string RegistryName => "RobloxPlayer";
-
- public override string ExecutableName => "RobloxPlayerBeta.exe";
-
- public override AppState State => App.State.Prop.Player;
-
- public override IReadOnlyDictionary PackageDirectoryMap { get; set; } = new Dictionary()
- {
- { "RobloxApp.zip", @"" }
- };
- }
-}
diff --git a/Bloxstrap/AppData/RobloxStudioData.cs b/Bloxstrap/AppData/RobloxStudioData.cs
deleted file mode 100644
index 2ada1c2..0000000
--- a/Bloxstrap/AppData/RobloxStudioData.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-namespace Bloxstrap.AppData
-{
- public class RobloxStudioData : CommonAppData, IAppData
- {
- public string ProductName => "Roblox Studio";
-
- public string BinaryType => "WindowsStudio64";
-
- public string RegistryName => "RobloxStudio";
-
- public override string ExecutableName => "RobloxStudioBeta.exe";
-
- public override AppState State => App.State.Prop.Studio;
-
- public override IReadOnlyDictionary PackageDirectoryMap { get; set; } = new Dictionary()
- {
- { "RobloxStudio.zip", @"" },
- { "LibrariesQt5.zip", @"" },
-
- { "content-studio_svg_textures.zip", @"content\studio_svg_textures\"},
- { "content-qt_translations.zip", @"content\qt_translations\" },
- { "content-api-docs.zip", @"content\api_docs\" },
-
- { "extracontent-scripts.zip", @"ExtraContent\scripts\" },
-
- { "BuiltInPlugins.zip", @"BuiltInPlugins\" },
- { "BuiltInStandalonePlugins.zip", @"BuiltInStandalonePlugins\" },
-
- { "ApplicationConfig.zip", @"ApplicationConfig\" },
- { "Plugins.zip", @"Plugins\" },
- { "Qml.zip", @"Qml\" },
- { "StudioFonts.zip", @"StudioFonts\" },
- { "RibbonConfig.zip", @"RibbonConfig\" }
- };
- }
-}
diff --git a/Bloxstrap/AssemblyInfo.cs b/Bloxstrap/AssemblyInfo.cs
deleted file mode 100644
index 8b5504e..0000000
--- a/Bloxstrap/AssemblyInfo.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Windows;
-
-[assembly: ThemeInfo(
- ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
- //(used if a resource is not found in the page,
- // or application resource dictionaries)
- ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
- //(used if a resource is not found in the page,
- // app, or any theme specific resource dictionaries)
-)]
diff --git a/Bloxstrap/Bloxstrap.csproj b/Bloxstrap/Bloxstrap.csproj
deleted file mode 100644
index 8e88aa5..0000000
--- a/Bloxstrap/Bloxstrap.csproj
+++ /dev/null
@@ -1,114 +0,0 @@
-
-
-
- WinExe
- net6.0-windows
- enable
- true
- True
- Bloxstrap.ico
- 2.9.0
- 2.9.0
- app.manifest
- true
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- all
-
-
-
-
-
-
-
-
-
-
-
-
- <_Parameter1>$([System.DateTime]::UtcNow.ToString("s"))Z
- <_Parameter2>$(COMPUTERNAME)\$(USERNAME)
- <_Parameter3>$(CommitHash)
- <_Parameter4>$(CommitRef)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @(ProjectionMetadataWinmd->'%(FullPath)','|')
- @(ProjectionDocs->'%(FullPath)','|')
-
-
-
-
-
-
-
-
-
-
-
- True
- True
- Strings.resx
-
-
-
-
-
- PublicResXFileCodeGenerator
- Strings.Designer.cs
-
-
-
-
diff --git a/Bloxstrap/Bloxstrap.ico b/Bloxstrap/Bloxstrap.ico
deleted file mode 100644
index a9a04b9..0000000
Binary files a/Bloxstrap/Bloxstrap.ico and /dev/null differ
diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs
deleted file mode 100644
index da21e53..0000000
--- a/Bloxstrap/Bootstrapper.cs
+++ /dev/null
@@ -1,1272 +0,0 @@
-// To debug the automatic updater:
-// - Uncomment the definition below
-// - Publish the executable
-// - Launch the executable (click no when it asks you to upgrade)
-// - Launch Roblox (for testing web launches, run it from the command prompt)
-// - To re-test the same executable, delete it from the installation folder
-
-// #define DEBUG_UPDATER
-
-#if DEBUG_UPDATER
-#warning "Automatic updater debugging is enabled"
-#endif
-
-using System.ComponentModel;
-using System.Data;
-using System.Windows;
-using System.Windows.Forms;
-using System.Windows.Shell;
-
-using Microsoft.Win32;
-
-using Bloxstrap.AppData;
-using Bloxstrap.RobloxInterfaces;
-using Bloxstrap.UI.Elements.Bootstrapper.Base;
-
-using ICSharpCode.SharpZipLib.Zip;
-
-namespace Bloxstrap
-{
- public class Bootstrapper
- {
- #region Properties
- private const int ProgressBarMaximum = 10000;
-
- private const double TaskbarProgressMaximumWpf = 1; // this can not be changed. keep it at 1.
- private const int TaskbarProgressMaximumWinForms = WinFormsDialogBase.TaskbarProgressMaximum;
-
- private const string AppSettings =
- "\r\n" +
- "\r\n" +
- " content\r\n" +
- " http://www.roblox.com\r\n" +
- "\r\n";
-
- private readonly FastZipEvents _fastZipEvents = new();
- private readonly CancellationTokenSource _cancelTokenSource = new();
-
- private readonly IAppData AppData;
- private readonly LaunchMode _launchMode;
-
- private string _launchCommandLine = App.LaunchSettings.RobloxLaunchArgs;
- private string _latestVersionGuid = null!;
- private string _latestVersionDirectory = null!;
- private PackageManifest _versionPackageManifest = null!;
-
- private bool _isInstalling = false;
- private double _progressIncrement;
- private double _taskbarProgressIncrement;
- private double _taskbarProgressMaximum;
- private long _totalDownloadedBytes = 0;
-
- private bool _mustUpgrade => String.IsNullOrEmpty(AppData.State.VersionGuid) || !File.Exists(AppData.ExecutablePath);
- private bool _noConnection = false;
-
- private AsyncMutex? _mutex;
-
- private int _appPid = 0;
-
- public IBootstrapperDialog? Dialog = null;
-
- public bool IsStudioLaunch => _launchMode != LaunchMode.Player;
- #endregion
-
- #region Core
- public Bootstrapper(LaunchMode launchMode)
- {
- _launchMode = launchMode;
-
- // https://github.com/icsharpcode/SharpZipLib/blob/master/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs/#L669-L680
- // exceptions don't get thrown if we define events without actually binding to the failure events. probably a bug. ¯\_(ツ)_/¯
- _fastZipEvents.FileFailure += (_, e) => throw e.Exception;
- _fastZipEvents.DirectoryFailure += (_, e) => throw e.Exception;
- _fastZipEvents.ProcessFile += (_, e) => e.ContinueRunning = !_cancelTokenSource.IsCancellationRequested;
-
- AppData = IsStudioLaunch ? new RobloxStudioData() : new RobloxPlayerData();
- Deployment.BinaryType = AppData.BinaryType;
- }
-
- private void SetStatus(string message)
- {
- App.Logger.WriteLine("Bootstrapper::SetStatus", message);
-
- message = message.Replace("{product}", AppData.ProductName);
-
- if (Dialog is not null)
- Dialog.Message = message;
- }
-
- private void UpdateProgressBar()
- {
- if (Dialog is null)
- return;
-
- // UI progress
- int progressValue = (int)Math.Floor(_progressIncrement * _totalDownloadedBytes);
-
- // bugcheck: if we're restoring a file from a package, it'll incorrectly increment the progress beyond 100
- // too lazy to fix properly so lol
- progressValue = Math.Clamp(progressValue, 0, ProgressBarMaximum);
-
- Dialog.ProgressValue = progressValue;
-
- // taskbar progress
- double taskbarProgressValue = _taskbarProgressIncrement * _totalDownloadedBytes;
- taskbarProgressValue = Math.Clamp(taskbarProgressValue, 0, _taskbarProgressMaximum);
-
- Dialog.TaskbarProgressValue = taskbarProgressValue;
- }
-
- private void HandleConnectionError(Exception exception)
- {
- const string LOG_IDENT = "Bootstrapper::HandleConnectionError";
-
- _noConnection = true;
-
- App.Logger.WriteLine(LOG_IDENT, "Connectivity check failed");
- App.Logger.WriteException(LOG_IDENT, exception);
-
- string message = Strings.Dialog_Connectivity_BadConnection;
-
- if (exception is AggregateException)
- exception = exception.InnerException!;
-
- // https://gist.github.com/pizzaboxer/4b58303589ee5b14cc64397460a8f386
- if (exception is HttpRequestException && exception.InnerException is null)
- message = String.Format(Strings.Dialog_Connectivity_RobloxDown, "[status.roblox.com](https://status.roblox.com)");
-
- if (_mustUpgrade)
- message += $"\n\n{Strings.Dialog_Connectivity_RobloxUpgradeNeeded}\n\n{Strings.Dialog_Connectivity_TryAgainLater}";
- else
- message += $"\n\n{Strings.Dialog_Connectivity_RobloxUpgradeSkip}";
-
- Frontend.ShowConnectivityDialog(
- String.Format(Strings.Dialog_Connectivity_UnableToConnect, "Roblox"),
- message,
- _mustUpgrade ? MessageBoxImage.Error : MessageBoxImage.Warning,
- exception);
-
- if (_mustUpgrade)
- App.Terminate(ErrorCode.ERROR_CANCELLED);
- }
-
- public async Task Run()
- {
- const string LOG_IDENT = "Bootstrapper::Run";
-
- App.Logger.WriteLine(LOG_IDENT, "Running bootstrapper");
-
- // this is now always enabled as of v2.8.0
- if (Dialog is not null)
- Dialog.CancelEnabled = true;
-
- SetStatus(Strings.Bootstrapper_Status_Connecting);
-
- var connectionResult = await Deployment.InitializeConnectivity();
-
- App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished");
-
- if (connectionResult is not null)
- HandleConnectionError(connectionResult);
-
-#if (!DEBUG || DEBUG_UPDATER) && !QA_BUILD
- if (App.Settings.Prop.CheckForUpdates && !App.LaunchSettings.UpgradeFlag.Active)
- {
- bool updatePresent = await CheckForUpdates();
-
- if (updatePresent)
- return;
- }
-#endif
-
- App.AssertWindowsOSVersion();
-
- // ensure only one instance of the bootstrapper is running at the time
- // so that we don't have stuff like two updates happening simultaneously
-
- bool mutexExists = false;
-
- try
- {
- Mutex.OpenExisting("Bloxstrap-Bootstrapper").Close();
- App.Logger.WriteLine(LOG_IDENT, "Bloxstrap-Bootstrapper mutex exists, waiting...");
- SetStatus(Strings.Bootstrapper_Status_WaitingOtherInstances);
- mutexExists = true;
- }
- catch (Exception)
- {
- // no mutex exists
- }
-
- // wait for mutex to be released if it's not yet
- await using var mutex = new AsyncMutex(false, "Bloxstrap-Bootstrapper");
- await mutex.AcquireAsync(_cancelTokenSource.Token);
-
- _mutex = mutex;
-
- // reload our configs since they've likely changed by now
- if (mutexExists)
- {
- App.Settings.Load();
- App.State.Load();
- }
-
- if (!_noConnection)
- {
- try
- {
- await GetLatestVersionInfo();
- }
- catch (Exception ex)
- {
- HandleConnectionError(ex);
- }
- }
-
- if (!_noConnection)
- {
- if (AppData.State.VersionGuid != _latestVersionGuid || _mustUpgrade)
- await UpgradeRoblox();
-
- if (_cancelTokenSource.IsCancellationRequested)
- return;
-
- // we require deployment details for applying modifications for a worst case scenario,
- // where we'd need to restore files from a package that isn't present on disk and needs to be redownloaded
- await ApplyModifications();
- }
-
- // check registry entries for every launch, just in case the stock bootstrapper changes it back
-
- if (IsStudioLaunch)
- WindowsRegistry.RegisterStudio();
- else
- WindowsRegistry.RegisterPlayer();
-
- if (_launchMode != LaunchMode.Player)
- await mutex.ReleaseAsync();
-
- if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelTokenSource.IsCancellationRequested)
- StartRoblox();
-
- await mutex.ReleaseAsync();
-
- Dialog?.CloseBootstrapper();
- }
-
- ///
- /// Will throw whatever HttpClient can throw
- ///
- ///
- private async Task GetLatestVersionInfo()
- {
- const string LOG_IDENT = "Bootstrapper::GetLatestVersionInfo";
-
- // before we do anything, we need to query our channel
- // if it's set in the launch uri, we need to use it and set the registry key for it
- // else, check if the registry key for it exists, and use it
-
- using var key = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\ROBLOX Corporation\\Environments\\{AppData.RegistryName}\\Channel");
-
- var match = Regex.Match(
- App.LaunchSettings.RobloxLaunchArgs,
- "channel:([a-zA-Z0-9-_]+)",
- RegexOptions.IgnoreCase | RegexOptions.CultureInvariant
- );
-
- if (match.Groups.Count == 2)
- {
- Deployment.Channel = match.Groups[1].Value.ToLowerInvariant();
- }
- else if (key.GetValue("www.roblox.com") is string value && !String.IsNullOrEmpty(value))
- {
- Deployment.Channel = value.ToLowerInvariant();
- }
-
- if (String.IsNullOrEmpty(Deployment.Channel))
- Deployment.Channel = Deployment.DefaultChannel;
-
- App.Logger.WriteLine(LOG_IDENT, $"Got channel as {Deployment.DefaultChannel}");
-
- if (!Deployment.IsDefaultChannel)
- App.SendStat("robloxChannel", Deployment.Channel);
-
- ClientVersion clientVersion;
-
- try
- {
- clientVersion = await Deployment.GetInfo();
- }
- catch (InvalidChannelException ex)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Resetting channel from {Deployment.Channel} because {ex.StatusCode}");
-
- Deployment.Channel = Deployment.DefaultChannel;
- clientVersion = await Deployment.GetInfo();
- }
-
- key.SetValueSafe("www.roblox.com", Deployment.IsDefaultChannel ? "" : Deployment.Channel);
-
- _latestVersionGuid = clientVersion.VersionGuid;
- _latestVersionDirectory = Path.Combine(Paths.Versions, _latestVersionGuid);
-
- string pkgManifestUrl = Deployment.GetLocation($"/{_latestVersionGuid}-rbxPkgManifest.txt");
- var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl);
-
- _versionPackageManifest = new(pkgManifestData);
- }
-
- private void StartRoblox()
- {
- const string LOG_IDENT = "Bootstrapper::StartRoblox";
-
- SetStatus(Strings.Bootstrapper_Status_Starting);
-
- if (_launchMode == LaunchMode.Player && 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()
- {
- FileName = AppData.ExecutablePath,
- Arguments = _launchCommandLine,
- WorkingDirectory = AppData.Directory
- };
-
- if (_launchMode == LaunchMode.Player && ShouldRunAsAdmin())
- {
- startInfo.Verb = "runas";
- startInfo.UseShellExecute = true;
- }
- else if (_launchMode == LaunchMode.StudioAuth)
- {
- Process.Start(startInfo);
- return;
- }
-
- string? logFileName = null;
-
- string rbxDir = Path.Combine(Paths.LocalAppData, "Roblox");
- if (!Directory.Exists(rbxDir))
- Directory.CreateDirectory(rbxDir);
-
- string rbxLogDir = Path.Combine(rbxDir, "logs");
- if (!Directory.Exists(rbxLogDir))
- Directory.CreateDirectory(rbxLogDir);
-
- var logWatcher = new FileSystemWatcher()
- {
- Path = rbxLogDir,
- Filter = "*.log",
- EnableRaisingEvents = true
- };
-
- var logCreatedEvent = new AutoResetEvent(false);
-
- logWatcher.Created += (_, e) =>
- {
- logWatcher.EnableRaisingEvents = false;
- logFileName = e.FullPath;
- logCreatedEvent.Set();
- };
-
- // v2.2.0 - byfron will trip if we keep a process handle open for over a minute, so we're doing this now
- try
- {
- using var process = Process.Start(startInfo)!;
- _appPid = process.Id;
- }
- catch (Win32Exception ex) when (ex.NativeErrorCode == 1223)
- {
- // 1223 = ERROR_CANCELLED, gets thrown if a UAC prompt is cancelled
- return;
- }
- catch (Exception)
- {
- // attempt a reinstall on next launch
- File.Delete(AppData.ExecutablePath);
- throw;
- }
-
- App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {_appPid}), waiting for log file");
-
- logCreatedEvent.WaitOne(TimeSpan.FromSeconds(15));
-
- if (String.IsNullOrEmpty(logFileName))
- {
- App.Logger.WriteLine(LOG_IDENT, "Unable to identify log file");
- Frontend.ShowPlayerErrorDialog();
- return;
- }
- else
- {
- App.Logger.WriteLine(LOG_IDENT, $"Got log file as {logFileName}");
- }
-
- _mutex?.ReleaseAsync();
-
- if (IsStudioLaunch)
- return;
-
- var autoclosePids = new List();
-
- // launch custom integrations now
- foreach (var integration in App.Settings.Prop.CustomIntegrations)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Launching custom integration '{integration.Name}' ({integration.Location} {integration.LaunchArgs} - autoclose is {integration.AutoClose})");
-
- int pid = 0;
-
- try
- {
- var process = Process.Start(new ProcessStartInfo
- {
- FileName = integration.Location,
- Arguments = integration.LaunchArgs.Replace("\r\n", " "),
- WorkingDirectory = Path.GetDirectoryName(integration.Location),
- UseShellExecute = true
- })!;
-
- pid = process.Id;
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Failed to launch integration '{integration.Name}'!");
- App.Logger.WriteLine(LOG_IDENT, ex.Message);
- }
-
- if (integration.AutoClose && pid != 0)
- autoclosePids.Add(pid);
- }
-
- if (App.Settings.Prop.EnableActivityTracking || App.LaunchSettings.TestModeFlag.Active || autoclosePids.Any())
- {
- using var ipl = new InterProcessLock("Watcher", TimeSpan.FromSeconds(5));
-
- var watcherData = new WatcherData
- {
- ProcessId = _appPid,
- LogFile = logFileName,
- AutoclosePids = autoclosePids
- };
-
- string watcherDataArg = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(watcherData)));
-
- string args = $"-watcher \"{watcherDataArg}\"";
-
- if (App.LaunchSettings.TestModeFlag.Active)
- args += " -testmode";
-
- if (ipl.IsAcquired)
- Process.Start(Paths.Process, args);
- }
-
- // allow for window to show, since the log is created pretty far beforehand
- Thread.Sleep(1000);
- }
-
- private bool ShouldRunAsAdmin()
- {
- foreach (var root in WindowsRegistry.Roots)
- {
- using var key = root.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers");
-
- if (key is null)
- continue;
-
- string? flags = (string?)key.GetValue(AppData.ExecutablePath);
-
- if (flags is not null && flags.Contains("RUNASADMIN", StringComparison.OrdinalIgnoreCase))
- return true;
- }
-
- return false;
- }
-
- public void Cancel()
- {
- const string LOG_IDENT = "Bootstrapper::Cancel";
-
- if (_cancelTokenSource.IsCancellationRequested)
- return;
-
- App.Logger.WriteLine(LOG_IDENT, "Cancelling launch...");
-
- _cancelTokenSource.Cancel();
-
- if (Dialog is not null)
- Dialog.CancelEnabled = false;
-
- if (_isInstalling)
- {
- try
- {
- // clean up install
- if (Directory.Exists(_latestVersionDirectory))
- Directory.Delete(_latestVersionDirectory, true);
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, "Could not fully clean up installation!");
- App.Logger.WriteException(LOG_IDENT, ex);
- }
- }
- else if (_appPid != 0)
- {
- try
- {
- using var process = Process.GetProcessById(_appPid);
- process.Kill();
- }
- catch (Exception) { }
- }
-
- Dialog?.CloseBootstrapper();
-
- App.SoftTerminate(ErrorCode.ERROR_CANCELLED);
- }
-#endregion
-
- #region App Install
- private async Task CheckForUpdates()
- {
- const string LOG_IDENT = "Bootstrapper::CheckForUpdates";
-
- // don't update if there's another instance running (likely running in the background)
- // i don't like this, but there isn't much better way of doing it /shrug
- if (Process.GetProcessesByName(App.ProjectName).Length > 1)
- {
- App.Logger.WriteLine(LOG_IDENT, $"More than one Bloxstrap instance running, aborting update check");
- return false;
- }
-
- App.Logger.WriteLine(LOG_IDENT, "Checking for updates...");
-
-#if !DEBUG_UPDATER
- var releaseInfo = await App.GetLatestRelease();
-
- if (releaseInfo is null)
- return false;
-
- var versionComparison = Utilities.CompareVersions(App.Version, releaseInfo.TagName);
-
- // check if we aren't using a deployed build, so we can update to one if a new version comes out
- if (App.IsProductionBuild && versionComparison == VersionComparison.Equal || versionComparison == VersionComparison.GreaterThan)
- {
- App.Logger.WriteLine(LOG_IDENT, "No updates found");
- return false;
- }
-
- if (Dialog is not null)
- Dialog.CancelEnabled = false;
-
- string version = releaseInfo.TagName;
-#else
- string version = App.Version;
-#endif
-
- SetStatus(Strings.Bootstrapper_Status_UpgradingBloxstrap);
-
- try
- {
-#if DEBUG_UPDATER
- string downloadLocation = Path.Combine(Paths.TempUpdates, "Bloxstrap.exe");
-
- Directory.CreateDirectory(Paths.TempUpdates);
-
- File.Copy(Paths.Process, downloadLocation, true);
-#else
- var asset = releaseInfo.Assets![0];
-
- string downloadLocation = Path.Combine(Paths.TempUpdates, asset.Name);
-
- Directory.CreateDirectory(Paths.TempUpdates);
-
- App.Logger.WriteLine(LOG_IDENT, $"Downloading {releaseInfo.TagName}...");
-
- if (!File.Exists(downloadLocation))
- {
- var response = await App.HttpClient.GetAsync(asset.BrowserDownloadUrl);
-
- await using var fileStream = new FileStream(downloadLocation, FileMode.OpenOrCreate, FileAccess.Write);
- await response.Content.CopyToAsync(fileStream);
- }
-#endif
-
- App.Logger.WriteLine(LOG_IDENT, $"Starting {version}...");
-
- ProcessStartInfo startInfo = new()
- {
- FileName = downloadLocation,
- };
-
- startInfo.ArgumentList.Add("-upgrade");
-
- foreach (string arg in App.LaunchSettings.Args)
- startInfo.ArgumentList.Add(arg);
-
- if (_launchMode == LaunchMode.Player && !startInfo.ArgumentList.Contains("-player"))
- startInfo.ArgumentList.Add("-player");
- else if (_launchMode == LaunchMode.Studio && !startInfo.ArgumentList.Contains("-studio"))
- startInfo.ArgumentList.Add("-studio");
-
- App.Settings.Save();
-
- new InterProcessLock("AutoUpdater");
-
- Process.Start(startInfo);
-
- return true;
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the auto-updater");
- App.Logger.WriteException(LOG_IDENT, ex);
-
- Frontend.ShowMessageBox(
- string.Format(Strings.Bootstrapper_AutoUpdateFailed, version),
- MessageBoxImage.Information
- );
-
- Utilities.ShellExecute(App.ProjectDownloadLink);
- }
-
- return false;
- }
- #endregion
-
- #region Roblox Install
- private void CleanupVersionsFolder()
- {
- const string LOG_IDENT = "Bootstrapper::CleanupVersionsFolder";
-
- foreach (string dir in Directory.GetDirectories(Paths.Versions))
- {
- string dirName = Path.GetFileName(dir);
-
- if (dirName != App.State.Prop.Player.VersionGuid && dirName != App.State.Prop.Studio.VersionGuid)
- {
- try
- {
- Directory.Delete(dir, true);
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Failed to delete {dir}");
- App.Logger.WriteException(LOG_IDENT, ex);
- }
- }
- }
- }
-
- private void MigrateCompatibilityFlags()
- {
- const string LOG_IDENT = "Bootstrapper::MigrateCompatibilityFlags";
-
- string oldClientLocation = Path.Combine(Paths.Versions, AppData.State.VersionGuid, AppData.ExecutableName);
- string newClientLocation = Path.Combine(_latestVersionDirectory, AppData.ExecutableName);
-
- // move old compatibility flags for the old location
- using RegistryKey appFlagsKey = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers");
- string? appFlags = appFlagsKey.GetValue(oldClientLocation) as string;
-
- if (appFlags is not null)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Migrating app compatibility flags from {oldClientLocation} to {newClientLocation}...");
- appFlagsKey.SetValueSafe(newClientLocation, appFlags);
- appFlagsKey.DeleteValueSafe(oldClientLocation);
- }
- }
-
- private static void KillRobloxPlayers()
- {
- const string LOG_IDENT = "Bootstrapper::KillRobloxPlayers";
-
- List processes = new List();
- processes.AddRange(Process.GetProcessesByName("RobloxPlayerBeta"));
- processes.AddRange(Process.GetProcessesByName("RobloxCrashHandler")); // roblox studio doesnt depend on crash handler being open, so this should be fine
-
- foreach (Process process in processes)
- {
- try
- {
- process.Kill();
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Failed to close process {process.Id}");
- App.Logger.WriteException(LOG_IDENT, ex);
- }
- }
- }
-
- private async Task UpgradeRoblox()
- {
- const string LOG_IDENT = "Bootstrapper::UpgradeRoblox";
-
- if (String.IsNullOrEmpty(AppData.State.VersionGuid))
- SetStatus(Strings.Bootstrapper_Status_Installing);
- else
- SetStatus(Strings.Bootstrapper_Status_Upgrading);
-
- Directory.CreateDirectory(Paths.Base);
- Directory.CreateDirectory(Paths.Downloads);
- Directory.CreateDirectory(Paths.Versions);
-
- _isInstalling = true;
-
- // make sure nothing is running before continuing upgrade
- if (!IsStudioLaunch) // TODO: wait for studio processes to close before updating to prevent data loss
- KillRobloxPlayers();
-
- // get a fully clean install
- if (Directory.Exists(_latestVersionDirectory))
- {
- try
- {
- Directory.Delete(_latestVersionDirectory, true);
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to delete the latest version directory");
- App.Logger.WriteException(LOG_IDENT, ex);
- }
- }
-
- Directory.CreateDirectory(_latestVersionDirectory);
-
- var cachedPackageHashes = Directory.GetFiles(Paths.Downloads).Select(x => Path.GetFileName(x));
-
- // package manifest states packed size and uncompressed size in exact bytes
- int totalSizeRequired = 0;
-
- // packed size only matters if we don't already have the package cached on disk
- totalSizeRequired += _versionPackageManifest.Where(x => !cachedPackageHashes.Contains(x.Signature)).Sum(x => x.PackedSize);
- totalSizeRequired += _versionPackageManifest.Sum(x => x.Size);
-
- if (Filesystem.GetFreeDiskSpace(Paths.Base) < totalSizeRequired)
- {
- Frontend.ShowMessageBox(Strings.Bootstrapper_NotEnoughSpace, MessageBoxImage.Error);
- App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
- return;
- }
-
- if (Dialog is not null)
- {
- Dialog.ProgressStyle = ProgressBarStyle.Continuous;
- Dialog.TaskbarProgressState = TaskbarItemProgressState.Normal;
-
- Dialog.ProgressMaximum = ProgressBarMaximum;
-
- // compute total bytes to download
- int totalPackedSize = _versionPackageManifest.Sum(package => package.PackedSize);
- _progressIncrement = (double)ProgressBarMaximum / totalPackedSize;
-
- if (Dialog is WinFormsDialogBase)
- _taskbarProgressMaximum = (double)TaskbarProgressMaximumWinForms;
- else
- _taskbarProgressMaximum = (double)TaskbarProgressMaximumWpf;
-
- _taskbarProgressIncrement = _taskbarProgressMaximum / (double)totalPackedSize;
- }
-
- var extractionTasks = new List();
-
- foreach (var package in _versionPackageManifest)
- {
- if (_cancelTokenSource.IsCancellationRequested)
- return;
-
- // download all the packages synchronously
- await DownloadPackage(package);
-
- // we'll extract the runtime installer later if we need to
- if (package.Name == "WebView2RuntimeInstaller.zip")
- continue;
-
- // extract the package async immediately after download
- extractionTasks.Add(Task.Run(() => ExtractPackage(package), _cancelTokenSource.Token));
- }
-
- if (_cancelTokenSource.IsCancellationRequested)
- return;
-
- if (Dialog is not null)
- {
- Dialog.ProgressStyle = ProgressBarStyle.Marquee;
- Dialog.TaskbarProgressState = TaskbarItemProgressState.Indeterminate;
- SetStatus(Strings.Bootstrapper_Status_Configuring);
- }
-
- await Task.WhenAll(extractionTasks);
-
- App.Logger.WriteLine(LOG_IDENT, "Writing AppSettings.xml...");
- await File.WriteAllTextAsync(Path.Combine(_latestVersionDirectory, "AppSettings.xml"), AppSettings);
-
- if (_cancelTokenSource.IsCancellationRequested)
- return;
-
- if (App.State.Prop.PromptWebView2Install)
- {
- using var hklmKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
- using var hkcuKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
-
- if (hklmKey is not null || hkcuKey is not null)
- {
- // reset prompt state if the user has it installed
- App.State.Prop.PromptWebView2Install = true;
- }
- else
- {
- var result = Frontend.ShowMessageBox(Strings.Bootstrapper_WebView2NotFound, MessageBoxImage.Warning, MessageBoxButton.YesNo, MessageBoxResult.Yes);
-
- if (result != MessageBoxResult.Yes)
- {
- App.State.Prop.PromptWebView2Install = false;
- }
- else
- {
- App.Logger.WriteLine(LOG_IDENT, "Installing WebView2 runtime...");
-
- var package = _versionPackageManifest.Find(x => x.Name == "WebView2RuntimeInstaller.zip");
-
- if (package is null)
- {
- App.Logger.WriteLine(LOG_IDENT, "Aborted runtime install because package does not exist, has WebView2 been added in this Roblox version yet?");
- return;
- }
-
- string baseDirectory = Path.Combine(_latestVersionDirectory, AppData.PackageDirectoryMap[package.Name]);
-
- ExtractPackage(package);
-
- SetStatus(Strings.Bootstrapper_Status_InstallingWebView2);
-
- var startInfo = new ProcessStartInfo()
- {
- WorkingDirectory = baseDirectory,
- FileName = Path.Combine(baseDirectory, "MicrosoftEdgeWebview2Setup.exe"),
- Arguments = "/silent /install"
- };
-
- await Process.Start(startInfo)!.WaitForExitAsync();
-
- App.Logger.WriteLine(LOG_IDENT, "Finished installing runtime");
-
- Directory.Delete(baseDirectory, true);
- }
- }
- }
-
- // finishing and cleanup
-
- MigrateCompatibilityFlags();
-
- AppData.State.VersionGuid = _latestVersionGuid;
-
- AppData.State.PackageHashes.Clear();
-
- foreach (var package in _versionPackageManifest)
- AppData.State.PackageHashes.Add(package.Name, package.Signature);
-
- CleanupVersionsFolder();
-
- var allPackageHashes = new List();
-
- allPackageHashes.AddRange(App.State.Prop.Player.PackageHashes.Values);
- allPackageHashes.AddRange(App.State.Prop.Studio.PackageHashes.Values);
-
- foreach (string hash in cachedPackageHashes)
- {
- if (!allPackageHashes.Contains(hash))
- {
- App.Logger.WriteLine(LOG_IDENT, $"Deleting unused package {hash}");
-
- try
- {
- File.Delete(Path.Combine(Paths.Downloads, hash));
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Failed to delete {hash}!");
- App.Logger.WriteException(LOG_IDENT, ex);
- }
- }
- }
-
- App.Logger.WriteLine(LOG_IDENT, "Registering approximate program size...");
-
- int distributionSize = _versionPackageManifest.Sum(x => x.Size + x.PackedSize) / 1024;
-
- AppData.State.Size = distributionSize;
-
- int totalSize = App.State.Prop.Player.Size + App.State.Prop.Studio.Size;
-
- using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey))
- {
- uninstallKey.SetValueSafe("EstimatedSize", totalSize);
- }
-
- App.Logger.WriteLine(LOG_IDENT, $"Registered as {totalSize} KB");
-
- App.State.Save();
-
- _isInstalling = false;
- }
-
- private async Task ApplyModifications()
- {
- const string LOG_IDENT = "Bootstrapper::ApplyModifications";
-
- SetStatus(Strings.Bootstrapper_Status_ApplyingModifications);
-
- // handle file mods
- App.Logger.WriteLine(LOG_IDENT, "Checking file mods...");
-
- // manifest has been moved to State.json
- File.Delete(Path.Combine(Paths.Base, "ModManifest.txt"));
-
- List modFolderFiles = new();
-
- Directory.CreateDirectory(Paths.Modifications);
-
- // check custom font mod
- // instead of replacing the fonts themselves, we'll just alter the font family manifests
-
- string modFontFamiliesFolder = Path.Combine(Paths.Modifications, "content\\fonts\\families");
-
- if (File.Exists(Paths.CustomFont))
- {
- App.Logger.WriteLine(LOG_IDENT, "Begin font check");
-
- Directory.CreateDirectory(modFontFamiliesFolder);
-
- const string path = "rbxasset://fonts/CustomFont.ttf";
-
- // lets make sure the content/fonts/families path exists in the version directory
- string contentFolder = Path.Combine(_latestVersionDirectory, "content");
- Directory.CreateDirectory(contentFolder);
-
- string fontsFolder = Path.Combine(contentFolder, "fonts");
- Directory.CreateDirectory(fontsFolder);
-
- string familiesFolder = Path.Combine(fontsFolder, "families");
- Directory.CreateDirectory(familiesFolder);
-
- foreach (string jsonFilePath in Directory.GetFiles(familiesFolder))
- {
- string jsonFilename = Path.GetFileName(jsonFilePath);
- string modFilepath = Path.Combine(modFontFamiliesFolder, jsonFilename);
-
- if (File.Exists(modFilepath))
- continue;
-
- App.Logger.WriteLine(LOG_IDENT, $"Setting font for {jsonFilename}");
-
- var fontFamilyData = JsonSerializer.Deserialize(File.ReadAllText(jsonFilePath));
-
- if (fontFamilyData is null)
- continue;
-
- bool shouldWrite = false;
-
- foreach (var fontFace in fontFamilyData.Faces)
- {
- if (fontFace.AssetId != path)
- {
- fontFace.AssetId = path;
- shouldWrite = true;
- }
- }
-
- if (shouldWrite)
- File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true }));
- }
-
- App.Logger.WriteLine(LOG_IDENT, "End font check");
- }
- else if (Directory.Exists(modFontFamiliesFolder))
- {
- Directory.Delete(modFontFamiliesFolder, true);
- }
-
- foreach (string file in Directory.GetFiles(Paths.Modifications, "*.*", SearchOption.AllDirectories))
- {
- if (_cancelTokenSource.IsCancellationRequested)
- return;
-
- // get relative directory path
- string relativeFile = file.Substring(Paths.Modifications.Length + 1);
-
- // v1.7.0 - README has been moved to the preferences menu now
- if (relativeFile == "README.txt")
- {
- File.Delete(file);
- continue;
- }
-
- if (!App.Settings.Prop.UseFastFlagManager && String.Equals(relativeFile, "ClientSettings\\ClientAppSettings.json", StringComparison.OrdinalIgnoreCase))
- continue;
-
- if (relativeFile.EndsWith(".lock"))
- continue;
-
- modFolderFiles.Add(relativeFile);
-
- string fileModFolder = Path.Combine(Paths.Modifications, relativeFile);
- string fileVersionFolder = Path.Combine(_latestVersionDirectory, relativeFile);
-
- if (File.Exists(fileVersionFolder) && MD5Hash.FromFile(fileModFolder) == MD5Hash.FromFile(fileVersionFolder))
- {
- App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} already exists in the version folder, and is a match");
- continue;
- }
-
- Directory.CreateDirectory(Path.GetDirectoryName(fileVersionFolder)!);
-
- Filesystem.AssertReadOnly(fileVersionFolder);
- File.Copy(fileModFolder, fileVersionFolder, true);
- Filesystem.AssertReadOnly(fileVersionFolder);
-
- App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} has been copied to the version folder");
- }
-
- // the manifest is primarily here to keep track of what files have been
- // deleted from the modifications folder, so that we know when to restore the original files from the downloaded packages
- // now check for files that have been deleted from the mod folder according to the manifest
-
- var fileRestoreMap = new Dictionary>();
-
- foreach (string fileLocation in App.State.Prop.ModManifest)
- {
- if (modFolderFiles.Contains(fileLocation))
- continue;
-
- var packageMapEntry = AppData.PackageDirectoryMap.SingleOrDefault(x => !String.IsNullOrEmpty(x.Value) && fileLocation.StartsWith(x.Value));
- string packageName = packageMapEntry.Key;
-
- // package doesn't exist, likely mistakenly placed file
- if (String.IsNullOrEmpty(packageName))
- {
- App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod but does not belong to a package");
-
- string versionFileLocation = Path.Combine(_latestVersionDirectory, fileLocation);
-
- if (File.Exists(versionFileLocation))
- File.Delete(versionFileLocation);
-
- continue;
- }
-
- string fileName = fileLocation.Substring(packageMapEntry.Value.Length);
-
- if (!fileRestoreMap.ContainsKey(packageName))
- fileRestoreMap[packageName] = new();
-
- fileRestoreMap[packageName].Add(fileName);
-
- App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod, restoring from {packageName}");
- }
-
- foreach (var entry in fileRestoreMap)
- {
- var package = _versionPackageManifest.Find(x => x.Name == entry.Key);
-
- if (package is not null)
- {
- if (_cancelTokenSource.IsCancellationRequested)
- return;
-
- await DownloadPackage(package);
- ExtractPackage(package, entry.Value);
- }
- }
-
- App.State.Prop.ModManifest = modFolderFiles;
- App.State.Save();
-
- App.Logger.WriteLine(LOG_IDENT, $"Finished checking file mods");
- }
-
- private async Task DownloadPackage(Package package)
- {
- string LOG_IDENT = $"Bootstrapper::DownloadPackage.{package.Name}";
-
- if (_cancelTokenSource.IsCancellationRequested)
- return;
-
- Directory.CreateDirectory(Paths.Downloads);
-
- string packageUrl = Deployment.GetLocation($"/{_latestVersionGuid}-{package.Name}");
- string robloxPackageLocation = Path.Combine(Paths.LocalAppData, "Roblox", "Downloads", package.Signature);
-
- if (File.Exists(package.DownloadPath))
- {
- var file = new FileInfo(package.DownloadPath);
-
- string calculatedMD5 = MD5Hash.FromFile(package.DownloadPath);
-
- if (calculatedMD5 != package.Signature)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Package is corrupted ({calculatedMD5} != {package.Signature})! Deleting and re-downloading...");
- file.Delete();
- }
- else
- {
- App.Logger.WriteLine(LOG_IDENT, $"Package is already downloaded, skipping...");
-
- _totalDownloadedBytes += package.PackedSize;
- UpdateProgressBar();
-
- return;
- }
- }
- else if (File.Exists(robloxPackageLocation))
- {
- // let's cheat! if the stock bootstrapper already previously downloaded the file,
- // then we can just copy the one from there
-
- App.Logger.WriteLine(LOG_IDENT, $"Found existing copy at '{robloxPackageLocation}'! Copying to Downloads folder...");
- File.Copy(robloxPackageLocation, package.DownloadPath);
-
- _totalDownloadedBytes += package.PackedSize;
- UpdateProgressBar();
-
- return;
- }
-
- if (File.Exists(package.DownloadPath))
- return;
-
- const int maxTries = 5;
-
- App.Logger.WriteLine(LOG_IDENT, "Downloading...");
-
- var buffer = new byte[4096];
-
- for (int i = 1; i <= maxTries; i++)
- {
- if (_cancelTokenSource.IsCancellationRequested)
- return;
-
- int totalBytesRead = 0;
-
- try
- {
- var response = await App.HttpClient.GetAsync(packageUrl, HttpCompletionOption.ResponseHeadersRead, _cancelTokenSource.Token);
- await using var stream = await response.Content.ReadAsStreamAsync(_cancelTokenSource.Token);
- await using var fileStream = new FileStream(package.DownloadPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Delete);
-
- while (true)
- {
- if (_cancelTokenSource.IsCancellationRequested)
- {
- stream.Close();
- fileStream.Close();
- return;
- }
-
- int bytesRead = await stream.ReadAsync(buffer, _cancelTokenSource.Token);
-
- if (bytesRead == 0)
- break;
-
- totalBytesRead += bytesRead;
-
- await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead), _cancelTokenSource.Token);
-
- _totalDownloadedBytes += bytesRead;
- UpdateProgressBar();
- }
-
- string hash = MD5Hash.FromStream(fileStream);
-
- if (hash != package.Signature)
- throw new ChecksumFailedException($"Failed to verify download of {packageUrl}\n\nExpected hash: {package.Signature}\nGot hash: {hash}");
-
- App.Logger.WriteLine(LOG_IDENT, $"Finished downloading! ({totalBytesRead} bytes total)");
- break;
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, $"An exception occurred after downloading {totalBytesRead} bytes. ({i}/{maxTries})");
- App.Logger.WriteException(LOG_IDENT, ex);
-
- if (ex.GetType() == typeof(ChecksumFailedException))
- {
- App.SendStat("packageDownloadState", "httpFail");
-
- Frontend.ShowConnectivityDialog(
- Strings.Dialog_Connectivity_UnableToDownload,
- String.Format(Strings.Dialog_Connectivity_UnableToDownloadReason, "[https://github.com/bloxstraplabs/bloxstrap/wiki/Bloxstrap-is-unable-to-download-Roblox](https://github.com/bloxstraplabs/bloxstrap/wiki/Bloxstrap-is-unable-to-download-Roblox)"),
- MessageBoxImage.Error,
- ex
- );
-
- App.Terminate(ErrorCode.ERROR_CANCELLED);
- }
- else if (i >= maxTries)
- throw;
-
- if (File.Exists(package.DownloadPath))
- File.Delete(package.DownloadPath);
-
- _totalDownloadedBytes -= totalBytesRead;
- UpdateProgressBar();
-
- // attempt download over HTTP
- // this isn't actually that unsafe - signatures were fetched earlier over HTTPS
- // so we've already established that our signatures are legit, and that there's very likely no MITM anyway
- if (ex.GetType() == typeof(IOException) && !packageUrl.StartsWith("http://"))
- {
- App.Logger.WriteLine(LOG_IDENT, "Retrying download over HTTP...");
- packageUrl = packageUrl.Replace("https://", "http://");
- }
- }
- }
- }
-
- private void ExtractPackage(Package package, List? files = null)
- {
- const string LOG_IDENT = "Bootstrapper::ExtractPackage";
-
- string? packageDir = AppData.PackageDirectoryMap.GetValueOrDefault(package.Name);
-
- if (packageDir is null)
- {
- App.Logger.WriteLine(LOG_IDENT, $"WARNING: {package.Name} was not found in the package map!");
- return;
- }
-
- string packageFolder = Path.Combine(_latestVersionDirectory, packageDir);
- string? fileFilter = null;
-
- // for sharpziplib, each file in the filter needs to be a regex
- if (files is not null)
- {
- var regexList = new List();
-
- foreach (string file in files)
- regexList.Add("^" + file.Replace("\\", "\\\\") + "$");
-
- fileFilter = String.Join(';', regexList);
- }
-
- App.Logger.WriteLine(LOG_IDENT, $"Extracting {package.Name}...");
-
- var fastZip = new FastZip(_fastZipEvents);
-
- fastZip.ExtractZip(package.DownloadPath, packageFolder, fileFilter);
-
- App.Logger.WriteLine(LOG_IDENT, $"Finished extracting {package.Name}");
- }
- #endregion
- }
-}
diff --git a/Bloxstrap/Enums/BootstrapperIcon.cs b/Bloxstrap/Enums/BootstrapperIcon.cs
deleted file mode 100644
index 4125cc6..0000000
--- a/Bloxstrap/Enums/BootstrapperIcon.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace Bloxstrap.Enums
-{
- public enum BootstrapperIcon
- {
- [EnumName(StaticName = "Bloxstrap")]
- IconBloxstrap,
- [EnumName(StaticName = "2008")]
- Icon2008,
- [EnumName(StaticName = "2011")]
- Icon2011,
- IconEarly2015,
- IconLate2015,
- [EnumName(StaticName = "2017")]
- Icon2017,
- [EnumName(StaticName = "2019")]
- Icon2019,
- [EnumName(StaticName = "2022")]
- Icon2022,
- [EnumName(FromTranslation = "Common.Custom")]
- IconCustom,
- [EnumName(FromTranslation = "Enums.BootstrapperStyle.ClassicFluentDialog")]
- IconBloxstrapClassic
- }
-}
diff --git a/Bloxstrap/Enums/BootstrapperStyle.cs b/Bloxstrap/Enums/BootstrapperStyle.cs
deleted file mode 100644
index 5c5f6fd..0000000
--- a/Bloxstrap/Enums/BootstrapperStyle.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Bloxstrap.Enums
-{
- public enum BootstrapperStyle
- {
- VistaDialog,
- LegacyDialog2008,
- LegacyDialog2011,
- ProgressDialog,
- ClassicFluentDialog,
- ByfronDialog,
- [EnumName(StaticName = "Bloxstrap")]
- FluentDialog,
- FluentAeroDialog
- }
-}
diff --git a/Bloxstrap/Enums/CursorType.cs b/Bloxstrap/Enums/CursorType.cs
deleted file mode 100644
index 8b0fa60..0000000
--- a/Bloxstrap/Enums/CursorType.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Bloxstrap.Enums
-{
- public enum CursorType
- {
- [EnumSort(Order = 1)]
- [EnumName(FromTranslation = "Common.Default")]
- Default,
-
- [EnumSort(Order = 3)]
- From2006,
-
- [EnumSort(Order = 2)]
- From2013
- }
-}
diff --git a/Bloxstrap/Enums/EmojiType.cs b/Bloxstrap/Enums/EmojiType.cs
deleted file mode 100644
index cdefefd..0000000
--- a/Bloxstrap/Enums/EmojiType.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Bloxstrap.Enums
-{
- public enum EmojiType
- {
- Default,
- Catmoji,
- Windows11,
- Windows10,
- Windows8
- }
-}
diff --git a/Bloxstrap/Enums/ErrorCode.cs b/Bloxstrap/Enums/ErrorCode.cs
deleted file mode 100644
index 4d830dd..0000000
--- a/Bloxstrap/Enums/ErrorCode.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Bloxstrap.Enums
-{
- // https://learn.microsoft.com/en-us/windows/win32/msi/error-codes
- // https://i-logic.com/serial/errorcodes.htm
- // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/705fb797-2175-4a90-b5a3-3918024b10b8
- // just the ones that we're interested in
-
- public enum ErrorCode
- {
- ERROR_SUCCESS = 0,
- ERROR_INVALID_FUNCTION = 1,
- ERROR_FILE_NOT_FOUND = 2,
-
- ERROR_CANCELLED = 1223,
- ERROR_INSTALL_USEREXIT = 1602,
- ERROR_INSTALL_FAILURE = 1603,
-
- CO_E_APPNOTFOUND = -2147221003
- }
-}
diff --git a/Bloxstrap/Enums/FlagPresets/InGameMenuVersion.cs b/Bloxstrap/Enums/FlagPresets/InGameMenuVersion.cs
deleted file mode 100644
index 30bbc70..0000000
--- a/Bloxstrap/Enums/FlagPresets/InGameMenuVersion.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Bloxstrap.Enums.FlagPresets
-{
- public enum InGameMenuVersion
- {
- [EnumName(FromTranslation = "Common.Default")]
- Default,
- V1,
- V2,
- V4,
- V4Chrome
- }
-}
diff --git a/Bloxstrap/Enums/FlagPresets/LightingMode.cs b/Bloxstrap/Enums/FlagPresets/LightingMode.cs
deleted file mode 100644
index 88ba4da..0000000
--- a/Bloxstrap/Enums/FlagPresets/LightingMode.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Bloxstrap.Enums.FlagPresets
-{
- public enum LightingMode
- {
- Default,
- Voxel,
- ShadowMap,
- Future
- }
-}
diff --git a/Bloxstrap/Enums/FlagPresets/MSAAMode.cs b/Bloxstrap/Enums/FlagPresets/MSAAMode.cs
deleted file mode 100644
index e5ae281..0000000
--- a/Bloxstrap/Enums/FlagPresets/MSAAMode.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Bloxstrap.Enums.FlagPresets
-{
- public enum MSAAMode
- {
- [EnumName(FromTranslation = "Common.Automatic")]
- Default,
- [EnumName(StaticName = "1x")]
- x1,
- [EnumName(StaticName = "2x")]
- x2,
- [EnumName(StaticName = "4x")]
- x4
- }
-}
diff --git a/Bloxstrap/Enums/FlagPresets/RenderingMode.cs b/Bloxstrap/Enums/FlagPresets/RenderingMode.cs
deleted file mode 100644
index 082d301..0000000
--- a/Bloxstrap/Enums/FlagPresets/RenderingMode.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Bloxstrap.Enums.FlagPresets
-{
- public enum RenderingMode
- {
- [EnumName(FromTranslation = "Common.Automatic")]
- Default,
- D3D11,
- D3D10,
- }
-}
diff --git a/Bloxstrap/Enums/FlagPresets/TextureQuality.cs b/Bloxstrap/Enums/FlagPresets/TextureQuality.cs
deleted file mode 100644
index 9610135..0000000
--- a/Bloxstrap/Enums/FlagPresets/TextureQuality.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Bloxstrap.Enums.FlagPresets
-{
- public enum TextureQuality
- {
- [EnumName(FromTranslation = "Common.Automatic")]
- Default,
- Level0,
- Level1,
- Level2,
- Level3
- }
-}
diff --git a/Bloxstrap/Enums/GenericTriState.cs b/Bloxstrap/Enums/GenericTriState.cs
deleted file mode 100644
index c8b406b..0000000
--- a/Bloxstrap/Enums/GenericTriState.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Bloxstrap.Enums
-{
- public enum GenericTriState
- {
- Successful,
- Failed,
- Unknown
- }
-}
diff --git a/Bloxstrap/Enums/LaunchMode.cs b/Bloxstrap/Enums/LaunchMode.cs
deleted file mode 100644
index c045d4f..0000000
--- a/Bloxstrap/Enums/LaunchMode.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Bloxstrap.Enums
-{
- public enum LaunchMode
- {
- None,
- Player,
- Studio,
- StudioAuth
- }
-}
diff --git a/Bloxstrap/Enums/NextAction.cs b/Bloxstrap/Enums/NextAction.cs
deleted file mode 100644
index 607029e..0000000
--- a/Bloxstrap/Enums/NextAction.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Bloxstrap.Enums
-{
- public enum NextAction
- {
- Terminate,
- LaunchSettings,
- LaunchRoblox,
- LaunchRobloxStudio
- }
-}
diff --git a/Bloxstrap/Enums/ServerType.cs b/Bloxstrap/Enums/ServerType.cs
deleted file mode 100644
index 70386d6..0000000
--- a/Bloxstrap/Enums/ServerType.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Bloxstrap.Enums
-{
- public enum ServerType
- {
- Public,
- Private,
- Reserved
- }
-}
diff --git a/Bloxstrap/Enums/Theme.cs b/Bloxstrap/Enums/Theme.cs
deleted file mode 100644
index f3cd71c..0000000
--- a/Bloxstrap/Enums/Theme.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Bloxstrap.Enums
-{
- public enum Theme
- {
- [EnumName(FromTranslation = "Common.SystemDefault")]
- Default,
- Light,
- Dark
- }
-}
diff --git a/Bloxstrap/Enums/VersionComparison.cs b/Bloxstrap/Enums/VersionComparison.cs
deleted file mode 100644
index 8f65958..0000000
--- a/Bloxstrap/Enums/VersionComparison.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Bloxstrap.Enums
-{
- enum VersionComparison
- {
- LessThan = -1,
- Equal = 0,
- GreaterThan = 1
- }
-}
diff --git a/Bloxstrap/Exceptions/AssertionException.cs b/Bloxstrap/Exceptions/AssertionException.cs
deleted file mode 100644
index 5555baa..0000000
--- a/Bloxstrap/Exceptions/AssertionException.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Bloxstrap.Exceptions
-{
- internal class AssertionException : Exception
- {
- public AssertionException(string message)
- : base($"{message}\n\nThis is very likely just an off-chance error. Please report this first, and then start {App.ProjectName} again.")
- {
- }
- }
-}
diff --git a/Bloxstrap/Exceptions/ChecksumFailedException.cs b/Bloxstrap/Exceptions/ChecksumFailedException.cs
deleted file mode 100644
index 95d8af2..0000000
--- a/Bloxstrap/Exceptions/ChecksumFailedException.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Bloxstrap.Exceptions
-{
- internal class ChecksumFailedException : Exception
- {
- public ChecksumFailedException(string message) : base(message)
- {
- }
- }
-}
diff --git a/Bloxstrap/Exceptions/InvalidChannelException.cs b/Bloxstrap/Exceptions/InvalidChannelException.cs
deleted file mode 100644
index eff6d79..0000000
--- a/Bloxstrap/Exceptions/InvalidChannelException.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Bloxstrap.Exceptions
-{
- public class InvalidChannelException : Exception
- {
- public HttpStatusCode? StatusCode;
-
- public InvalidChannelException(HttpStatusCode? statusCode) : base()
- => StatusCode = statusCode;
- }
-}
diff --git a/Bloxstrap/Exceptions/InvalidHTTPResponseException.cs b/Bloxstrap/Exceptions/InvalidHTTPResponseException.cs
deleted file mode 100644
index f6b45a0..0000000
--- a/Bloxstrap/Exceptions/InvalidHTTPResponseException.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Bloxstrap.Exceptions
-{
- internal class InvalidHTTPResponseException : Exception
- {
- public InvalidHTTPResponseException(string message) : base(message) { }
- }
-}
diff --git a/Bloxstrap/Extensions/BootstrapperIconEx.cs b/Bloxstrap/Extensions/BootstrapperIconEx.cs
deleted file mode 100644
index 943cad6..0000000
--- a/Bloxstrap/Extensions/BootstrapperIconEx.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System.Drawing;
-
-namespace Bloxstrap.Extensions
-{
- static class BootstrapperIconEx
- {
- public static IReadOnlyCollection Selections => new BootstrapperIcon[]
- {
- BootstrapperIcon.IconBloxstrap,
- BootstrapperIcon.Icon2022,
- BootstrapperIcon.Icon2019,
- BootstrapperIcon.Icon2017,
- BootstrapperIcon.IconLate2015,
- BootstrapperIcon.IconEarly2015,
- BootstrapperIcon.Icon2011,
- BootstrapperIcon.Icon2008,
- BootstrapperIcon.IconBloxstrapClassic,
- BootstrapperIcon.IconCustom
- };
-
- // small note on handling icon sizes
- // i'm using multisize icon packs here with sizes 16, 24, 32, 48, 64 and 128
- // use this for generating multisize packs: https://www.aconvert.com/icon/
-
- public static Icon GetIcon(this BootstrapperIcon icon)
- {
- const string LOG_IDENT = "BootstrapperIconEx::GetIcon";
-
- // load the custom icon file
- if (icon == BootstrapperIcon.IconCustom)
- {
- Icon? customIcon = null;
- string location = App.Settings.Prop.BootstrapperIconCustomLocation;
-
- if (String.IsNullOrEmpty(location))
- {
- App.Logger.WriteLine(LOG_IDENT, "Warning: custom icon is not set.");
- }
- else
- {
- try
- {
- customIcon = new Icon(location);
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Failed to load custom icon!");
- App.Logger.WriteException(LOG_IDENT, ex);
- }
- }
-
- return customIcon ?? Properties.Resources.IconBloxstrap;
- }
-
- return icon switch
- {
- BootstrapperIcon.IconBloxstrap => Properties.Resources.IconBloxstrap,
- BootstrapperIcon.Icon2008 => Properties.Resources.Icon2008,
- BootstrapperIcon.Icon2011 => Properties.Resources.Icon2011,
- BootstrapperIcon.IconEarly2015 => Properties.Resources.IconEarly2015,
- BootstrapperIcon.IconLate2015 => Properties.Resources.IconLate2015,
- BootstrapperIcon.Icon2017 => Properties.Resources.Icon2017,
- BootstrapperIcon.Icon2019 => Properties.Resources.Icon2019,
- BootstrapperIcon.Icon2022 => Properties.Resources.Icon2022,
- BootstrapperIcon.IconBloxstrapClassic => Properties.Resources.IconBloxstrapClassic,
- _ => Properties.Resources.IconBloxstrap
- };
- }
- }
-}
diff --git a/Bloxstrap/Extensions/BootstrapperStyleEx.cs b/Bloxstrap/Extensions/BootstrapperStyleEx.cs
deleted file mode 100644
index 3802264..0000000
--- a/Bloxstrap/Extensions/BootstrapperStyleEx.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace Bloxstrap.Extensions
-{
- static class BootstrapperStyleEx
- {
- public static IBootstrapperDialog GetNew(this BootstrapperStyle bootstrapperStyle) => Frontend.GetBootstrapperDialog(bootstrapperStyle);
-
- public static IReadOnlyCollection Selections => new BootstrapperStyle[]
- {
- BootstrapperStyle.FluentDialog,
- BootstrapperStyle.FluentAeroDialog,
- BootstrapperStyle.ClassicFluentDialog,
- BootstrapperStyle.ByfronDialog,
- BootstrapperStyle.ProgressDialog,
- BootstrapperStyle.LegacyDialog2011,
- BootstrapperStyle.LegacyDialog2008,
- BootstrapperStyle.VistaDialog
- };
- }
-}
diff --git a/Bloxstrap/Extensions/DateTimeEx.cs b/Bloxstrap/Extensions/DateTimeEx.cs
deleted file mode 100644
index 0dc8a24..0000000
--- a/Bloxstrap/Extensions/DateTimeEx.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Bloxstrap.Extensions
-{
- static class DateTimeEx
- {
- public static string ToFriendlyString(this DateTime dateTime)
- {
- return dateTime.ToString("dddd, d MMMM yyyy 'at' h:mm:ss tt", CultureInfo.InvariantCulture);
- }
- }
-}
diff --git a/Bloxstrap/Extensions/EmojiTypeEx.cs b/Bloxstrap/Extensions/EmojiTypeEx.cs
deleted file mode 100644
index e1b32fd..0000000
--- a/Bloxstrap/Extensions/EmojiTypeEx.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-namespace Bloxstrap.Extensions
-{
- static class EmojiTypeEx
- {
- public static IReadOnlyDictionary Filenames => new Dictionary
- {
- { EmojiType.Catmoji, "Catmoji.ttf" },
- { EmojiType.Windows11, "Win1122H2SegoeUIEmoji.ttf" },
- { EmojiType.Windows10, "Win10April2018SegoeUIEmoji.ttf" },
- { EmojiType.Windows8, "Win8.1SegoeUIEmoji.ttf" },
- };
-
- public static IReadOnlyDictionary Hashes => new Dictionary
- {
- { EmojiType.Catmoji, "98138f398a8cde897074dd2b8d53eca0" },
- { EmojiType.Windows11, "d50758427673578ddf6c9edcdbf367f5" },
- { EmojiType.Windows10, "d8a7eecbebf9dfdf622db8ccda63aff5" },
- { EmojiType.Windows8, "2b01c6caabbe95afc92aa63b9bf100f3" },
- };
-
- public static string GetHash(this EmojiType emojiType) => Hashes[emojiType];
-
- public static string GetUrl(this EmojiType emojiType)
- {
- if (emojiType == EmojiType.Default)
- return "";
-
- return $"https://github.com/bloxstraplabs/rbxcustom-fontemojis/releases/download/my-phone-is-78-percent/{Filenames[emojiType]}";
- }
- }
-}
diff --git a/Bloxstrap/Extensions/IconEx.cs b/Bloxstrap/Extensions/IconEx.cs
deleted file mode 100644
index 516899d..0000000
--- a/Bloxstrap/Extensions/IconEx.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Drawing;
-using System.Windows.Media.Imaging;
-using System.Windows.Media;
-
-namespace Bloxstrap.Extensions
-{
- public static class IconEx
- {
- public static Icon GetSized(this Icon icon, int width, int height) => new(icon, new Size(width, height));
-
- public static ImageSource GetImageSource(this Icon icon, bool handleException = true)
- {
- using MemoryStream stream = new();
- icon.Save(stream);
-
- if (handleException)
- {
- try
- {
- return BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
- }
- catch (Exception ex)
- {
- App.Logger.WriteException("IconEx::GetImageSource", ex);
- Frontend.ShowMessageBox(String.Format(Strings.Dialog_IconLoadFailed, ex.Message));
- return BootstrapperIcon.IconBloxstrap.GetIcon().GetImageSource(false);
- }
- }
- else
- {
- return BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
- }
- }
- }
-}
diff --git a/Bloxstrap/Extensions/RegistryKeyEx.cs b/Bloxstrap/Extensions/RegistryKeyEx.cs
deleted file mode 100644
index 19efd58..0000000
--- a/Bloxstrap/Extensions/RegistryKeyEx.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Microsoft.Win32;
-
-namespace Bloxstrap.Extensions
-{
- public static class RegistryKeyEx
- {
- public static void SetValueSafe(this RegistryKey registryKey, string? name, object value)
- {
- try
- {
- App.Logger.WriteLine("RegistryKeyEx::SetValueSafe", $"Writing '{value}' to {registryKey}\\{name}");
- registryKey.SetValue(name, value);
- }
- catch (UnauthorizedAccessException)
- {
- Frontend.ShowMessageBox(Strings.Dialog_RegistryWriteError, System.Windows.MessageBoxImage.Error);
- App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
- }
- }
-
- public static void DeleteValueSafe(this RegistryKey registryKey, string name)
- {
- try
- {
- App.Logger.WriteLine("RegistryKeyEx::DeleteValueSafe", $"Deleting {registryKey}\\{name}");
- registryKey.DeleteValue(name);
- }
- catch (UnauthorizedAccessException)
- {
- Frontend.ShowMessageBox(Strings.Dialog_RegistryWriteError, System.Windows.MessageBoxImage.Error);
- App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
- }
- }
- }
-}
diff --git a/Bloxstrap/Extensions/ResourceManagerEx.cs b/Bloxstrap/Extensions/ResourceManagerEx.cs
deleted file mode 100644
index def61c2..0000000
--- a/Bloxstrap/Extensions/ResourceManagerEx.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Resources;
-
-namespace Bloxstrap.Extensions
-{
- static class ResourceManagerEx
- {
- ///
- /// Returns the value of the specified string resource.
- /// If the resource is not found, the resource name will be returned.
- ///
- public static string GetStringSafe(this ResourceManager manager, string name) => manager.GetStringSafe(name, null);
-
- ///
- /// Returns the value of the string resource localized for the specified culture.
- /// If the resource is not found, the resource name will be returned.
- ///
- public static string GetStringSafe(this ResourceManager manager, string name, CultureInfo? culture)
- {
- string? resourceValue = manager.GetString(name, culture);
-
- return resourceValue ?? name;
- }
- }
-}
diff --git a/Bloxstrap/Extensions/ServerTypeEx.cs b/Bloxstrap/Extensions/ServerTypeEx.cs
deleted file mode 100644
index 2b65d8b..0000000
--- a/Bloxstrap/Extensions/ServerTypeEx.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Bloxstrap.Extensions
-{
- static class ServerTypeEx
- {
- public static string ToTranslatedString(this ServerType value) => value switch
- {
- ServerType.Public => Strings.Enums_ServerType_Public,
- ServerType.Private => Strings.Enums_ServerType_Private,
- ServerType.Reserved => Strings.Enums_ServerType_Reserved,
- _ => "?"
- };
- }
-}
diff --git a/Bloxstrap/Extensions/ThemeEx.cs b/Bloxstrap/Extensions/ThemeEx.cs
deleted file mode 100644
index f5fc70c..0000000
--- a/Bloxstrap/Extensions/ThemeEx.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Microsoft.Win32;
-
-namespace Bloxstrap.Extensions
-{
- public static class ThemeEx
- {
- public static Theme GetFinal(this Theme dialogTheme)
- {
- if (dialogTheme != Theme.Default)
- return dialogTheme;
-
- using var key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
-
- if (key?.GetValue("AppsUseLightTheme") is int value && value == 0)
- return Theme.Dark;
-
- return Theme.Light;
- }
- }
-}
diff --git a/Bloxstrap/FastFlagManager.cs b/Bloxstrap/FastFlagManager.cs
deleted file mode 100644
index a69b3e5..0000000
--- a/Bloxstrap/FastFlagManager.cs
+++ /dev/null
@@ -1,263 +0,0 @@
-using Bloxstrap.Enums.FlagPresets;
-
-namespace Bloxstrap
-{
- public class FastFlagManager : JsonManager>
- {
- public override string ClassName => nameof(FastFlagManager);
-
- public override string LOG_IDENT_CLASS => ClassName;
-
- public override string FileLocation => Path.Combine(Paths.Modifications, "ClientSettings\\ClientAppSettings.json");
-
- public bool Changed => !OriginalProp.SequenceEqual(Prop);
-
- public static IReadOnlyDictionary PresetFlags = new Dictionary
- {
- { "Network.Log", "FLogNetwork" },
-
- { "Rendering.Framerate", "DFIntTaskSchedulerTargetFps" },
- { "Rendering.ManualFullscreen", "FFlagHandleAltEnterFullscreenManually" },
- { "Rendering.DisableScaling", "DFFlagDisableDPIScale" },
- { "Rendering.MSAA", "FIntDebugForceMSAASamples" },
- { "Rendering.DisablePostFX", "FFlagDisablePostFx" },
- { "Rendering.ShadowIntensity", "FIntRenderShadowIntensity" },
-
- { "Rendering.Mode.D3D11", "FFlagDebugGraphicsPreferD3D11" },
- { "Rendering.Mode.D3D10", "FFlagDebugGraphicsPreferD3D11FL10" },
-
- { "Rendering.Lighting.Voxel", "DFFlagDebugRenderForceTechnologyVoxel" },
- { "Rendering.Lighting.ShadowMap", "FFlagDebugForceFutureIsBrightPhase2" },
- { "Rendering.Lighting.Future", "FFlagDebugForceFutureIsBrightPhase3" },
-
- { "Rendering.TextureQuality.OverrideEnabled", "DFFlagTextureQualityOverrideEnabled" },
- { "Rendering.TextureQuality.Level", "DFIntTextureQualityOverride" },
- { "Rendering.TerrainTextureQuality", "FIntTerrainArraySliceSize" },
-
- { "UI.Hide", "DFIntCanHideGuiGroupId" },
- { "UI.FontSize", "FIntFontSizePadding" },
-
- { "UI.FullscreenTitlebarDelay", "FIntFullscreenTitleBarTriggerDelayMillis" },
-
- //{ "UI.Menu.Style.V2Rollout", "FIntNewInGameMenuPercentRollout3" },
- //{ "UI.Menu.Style.EnableV4.1", "FFlagEnableInGameMenuControls" },
- //{ "UI.Menu.Style.EnableV4.2", "FFlagEnableInGameMenuModernization" },
- //{ "UI.Menu.Style.EnableV4Chrome", "FFlagEnableInGameMenuChrome" },
- //{ "UI.Menu.Style.ReportButtonCutOff", "FFlagFixReportButtonCutOff" },
-
-
- //{ "UI.Menu.Style.ABTest.1", "FFlagEnableMenuControlsABTest" },
- //{ "UI.Menu.Style.ABTest.2", "FFlagEnableV3MenuABTest3" },
- //{ "UI.Menu.Style.ABTest.3", "FFlagEnableInGameMenuChromeABTest3" },
- //{ "UI.Menu.Style.ABTest.4", "FFlagEnableInGameMenuChromeABTest4" }
- };
-
- public static IReadOnlyDictionary RenderingModes => new Dictionary
- {
- { RenderingMode.Default, "None" },
- { RenderingMode.D3D11, "D3D11" },
- { RenderingMode.D3D10, "D3D10" },
- };
-
- public static IReadOnlyDictionary LightingModes => new Dictionary
- {
- { LightingMode.Default, "None" },
- { LightingMode.Voxel, "Voxel" },
- { LightingMode.ShadowMap, "ShadowMap" },
- { LightingMode.Future, "Future" }
- };
-
- public static IReadOnlyDictionary MSAAModes => new Dictionary
- {
- { MSAAMode.Default, null },
- { MSAAMode.x1, "1" },
- { MSAAMode.x2, "2" },
- { MSAAMode.x4, "4" }
- };
-
- public static IReadOnlyDictionary TextureQualityLevels => new Dictionary
- {
- { TextureQuality.Default, null },
- { TextureQuality.Level0, "0" },
- { TextureQuality.Level1, "1" },
- { TextureQuality.Level2, "2" },
- { TextureQuality.Level3, "3" },
- };
-
- // this is one hell of a dictionary definition lmao
- // since these all set the same flags, wouldn't making this use bitwise operators be better?
- //public static IReadOnlyDictionary> IGMenuVersions => new Dictionary>
- //{
- // {
- // InGameMenuVersion.Default,
- // new Dictionary
- // {
- // { "V2Rollout", null },
- // { "EnableV4", null },
- // { "EnableV4Chrome", null },
- // { "ABTest", null },
- // { "ReportButtonCutOff", null }
- // }
- // },
-
- // {
- // InGameMenuVersion.V1,
- // new Dictionary
- // {
- // { "V2Rollout", "0" },
- // { "EnableV4", "False" },
- // { "EnableV4Chrome", "False" },
- // { "ABTest", "False" },
- // { "ReportButtonCutOff", "False" }
- // }
- // },
-
- // {
- // InGameMenuVersion.V2,
- // new Dictionary
- // {
- // { "V2Rollout", "100" },
- // { "EnableV4", "False" },
- // { "EnableV4Chrome", "False" },
- // { "ABTest", "False" },
- // { "ReportButtonCutOff", null }
- // }
- // },
-
- // {
- // InGameMenuVersion.V4,
- // new Dictionary
- // {
- // { "V2Rollout", "0" },
- // { "EnableV4", "True" },
- // { "EnableV4Chrome", "False" },
- // { "ABTest", "False" },
- // { "ReportButtonCutOff", null }
- // }
- // },
-
- // {
- // InGameMenuVersion.V4Chrome,
- // new Dictionary
- // {
- // { "V2Rollout", "0" },
- // { "EnableV4", "True" },
- // { "EnableV4Chrome", "True" },
- // { "ABTest", "False" },
- // { "ReportButtonCutOff", null }
- // }
- // }
- //};
-
- // all fflags are stored as strings
- // to delete a flag, set the value as null
- public void SetValue(string key, object? value)
- {
- const string LOG_IDENT = "FastFlagManager::SetValue";
-
- if (value is null)
- {
- if (Prop.ContainsKey(key))
- App.Logger.WriteLine(LOG_IDENT, $"Deletion of '{key}' is pending");
-
- Prop.Remove(key);
- }
- else
- {
- if (Prop.ContainsKey(key))
- {
- if (key == Prop[key].ToString())
- return;
-
- App.Logger.WriteLine(LOG_IDENT, $"Changing of '{key}' from '{Prop[key]}' to '{value}' is pending");
- }
- else
- {
- App.Logger.WriteLine(LOG_IDENT, $"Setting of '{key}' to '{value}' is pending");
- }
-
- Prop[key] = value.ToString()!;
- }
- }
-
- // this returns null if the fflag doesn't exist
- public string? GetValue(string key)
- {
- // check if we have an updated change for it pushed first
- if (Prop.TryGetValue(key, out object? value) && value is not null)
- return value.ToString();
-
- return null;
- }
-
- public void SetPreset(string prefix, object? value)
- {
- foreach (var pair in PresetFlags.Where(x => x.Key.StartsWith(prefix)))
- SetValue(pair.Value, value);
- }
-
- public void SetPresetEnum(string prefix, string target, object? value)
- {
- foreach (var pair in PresetFlags.Where(x => x.Key.StartsWith(prefix)))
- {
- if (pair.Key.StartsWith($"{prefix}.{target}"))
- SetValue(pair.Value, value);
- else
- SetValue(pair.Value, null);
- }
- }
-
- public string? GetPreset(string name)
- {
- if (!PresetFlags.ContainsKey(name))
- {
- App.Logger.WriteLine("FastFlagManager::GetPreset", $"Could not find preset {name}");
- Debug.Assert(false, $"Could not find preset {name}");
- return null;
- }
-
- return GetValue(PresetFlags[name]);
- }
-
- public T GetPresetEnum(IReadOnlyDictionary mapping, string prefix, string value) where T : Enum
- {
- foreach (var pair in mapping)
- {
- if (pair.Value == "None")
- continue;
-
- if (GetPreset($"{prefix}.{pair.Value}") == value)
- return pair.Key;
- }
-
- return mapping.First().Key;
- }
-
- public override void Save()
- {
- // convert all flag values to strings before saving
-
- foreach (var pair in Prop)
- Prop[pair.Key] = pair.Value.ToString()!;
-
- base.Save();
-
- // clone the dictionary
- OriginalProp = new(Prop);
- }
-
- public override void Load(bool alertFailure = true)
- {
- base.Load(alertFailure);
-
- // clone the dictionary
- OriginalProp = new(Prop);
-
- if (GetPreset("Network.Log") != "7")
- SetPreset("Network.Log", "7");
-
- if (GetPreset("Rendering.ManualFullscreen") != "False")
- SetPreset("Rendering.ManualFullscreen", "False");
- }
- }
-}
diff --git a/Bloxstrap/GlobalCache.cs b/Bloxstrap/GlobalCache.cs
deleted file mode 100644
index 6977224..0000000
--- a/Bloxstrap/GlobalCache.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Bloxstrap
-{
- public static class GlobalCache
- {
- public static readonly Dictionary ServerLocation = new();
- }
-}
diff --git a/Bloxstrap/GlobalUsings.cs b/Bloxstrap/GlobalUsings.cs
deleted file mode 100644
index c04aec2..0000000
--- a/Bloxstrap/GlobalUsings.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-global using System;
-global using System.Collections.Generic;
-global using System.Diagnostics;
-global using System.Globalization;
-global using System.IO;
-global using System.Text;
-global using System.Text.Json;
-global using System.Text.Json.Serialization;
-global using System.Text.RegularExpressions;
-global using System.Linq;
-global using System.Net;
-global using System.Net.Http;
-global using System.Threading;
-global using System.Threading.Tasks;
-
-global using Bloxstrap.Enums;
-global using Bloxstrap.Exceptions;
-global using Bloxstrap.Extensions;
-global using Bloxstrap.Models;
-global using Bloxstrap.Models.APIs.Config;
-global using Bloxstrap.Models.APIs.GitHub;
-global using Bloxstrap.Models.APIs.Roblox;
-global using Bloxstrap.Models.Attributes;
-global using Bloxstrap.Models.BloxstrapRPC;
-global using Bloxstrap.Models.Entities;
-global using Bloxstrap.Models.Manifest;
-global using Bloxstrap.Models.Persistable;
-global using Bloxstrap.Models.SettingTasks;
-global using Bloxstrap.Models.SettingTasks.Base;
-global using Bloxstrap.Resources;
-global using Bloxstrap.UI;
-global using Bloxstrap.Utility;
\ No newline at end of file
diff --git a/Bloxstrap/HttpClientLoggingHandler.cs b/Bloxstrap/HttpClientLoggingHandler.cs
deleted file mode 100644
index 3eb830b..0000000
--- a/Bloxstrap/HttpClientLoggingHandler.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace Bloxstrap
-{
- internal class HttpClientLoggingHandler : MessageProcessingHandler
- {
- public HttpClientLoggingHandler(HttpMessageHandler innerHandler)
- : base(innerHandler)
- {
- }
-
- protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- App.Logger.WriteLine("HttpClientLoggingHandler::ProcessRequest", $"{request.Method} {request.RequestUri}");
- return request;
- }
-
- protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
- {
- App.Logger.WriteLine("HttpClientLoggingHandler::ProcessResponse", $"{(int)response.StatusCode} {response.ReasonPhrase} {response.RequestMessage!.RequestUri}");
- return response;
- }
- }
-}
diff --git a/Bloxstrap/Installer.cs b/Bloxstrap/Installer.cs
deleted file mode 100644
index 93f40c1..0000000
--- a/Bloxstrap/Installer.cs
+++ /dev/null
@@ -1,624 +0,0 @@
-using System.Windows;
-using Microsoft.Win32;
-
-namespace Bloxstrap
-{
- internal class Installer
- {
- ///
- /// Should this version automatically open the release notes page?
- /// Recommended for major updates only.
- ///
- private const bool OpenReleaseNotes = false;
-
- private static string DesktopShortcut => Path.Combine(Paths.Desktop, $"{App.ProjectName}.lnk");
-
- private static string StartMenuShortcut => Path.Combine(Paths.WindowsStartMenu, $"{App.ProjectName}.lnk");
-
- public string InstallLocation = Path.Combine(Paths.LocalAppData, App.ProjectName);
-
- public bool ExistingDataPresent => File.Exists(Path.Combine(InstallLocation, "Settings.json"));
-
- public bool CreateDesktopShortcuts = true;
-
- public bool CreateStartMenuShortcuts = true;
-
- public bool EnableAnalytics = true;
-
- public bool IsImplicitInstall = false;
-
- public string InstallLocationError { get; set; } = "";
-
- public void DoInstall()
- {
- const string LOG_IDENT = "Installer::DoInstall";
-
- App.Logger.WriteLine(LOG_IDENT, "Beginning installation");
-
- // should've been created earlier from the write test anyway
- Directory.CreateDirectory(InstallLocation);
-
- Paths.Initialize(InstallLocation);
-
- if (!IsImplicitInstall)
- {
- Filesystem.AssertReadOnly(Paths.Application);
-
- try
- {
- File.Copy(Paths.Process, Paths.Application, true);
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, "Could not overwrite executable");
- App.Logger.WriteException(LOG_IDENT, ex);
-
- Frontend.ShowMessageBox(Strings.Installer_Install_CannotOverwrite, MessageBoxImage.Error);
- App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
- }
- }
-
- using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey))
- {
- uninstallKey.SetValueSafe("DisplayIcon", $"{Paths.Application},0");
- uninstallKey.SetValueSafe("DisplayName", App.ProjectName);
-
- uninstallKey.SetValueSafe("DisplayVersion", App.Version);
-
- if (uninstallKey.GetValue("InstallDate") is null)
- uninstallKey.SetValueSafe("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
-
- uninstallKey.SetValueSafe("InstallLocation", Paths.Base);
- uninstallKey.SetValueSafe("NoRepair", 1);
- uninstallKey.SetValueSafe("Publisher", App.ProjectOwner);
- uninstallKey.SetValueSafe("ModifyPath", $"\"{Paths.Application}\" -settings");
- uninstallKey.SetValueSafe("QuietUninstallString", $"\"{Paths.Application}\" -uninstall -quiet");
- uninstallKey.SetValueSafe("UninstallString", $"\"{Paths.Application}\" -uninstall");
- uninstallKey.SetValueSafe("HelpLink", App.ProjectHelpLink);
- uninstallKey.SetValueSafe("URLInfoAbout", App.ProjectSupportLink);
- uninstallKey.SetValueSafe("URLUpdateInfo", App.ProjectDownloadLink);
- }
-
- // only register player, for the scenario where the user installs bloxstrap, closes it,
- // and then launches from the website expecting it to work
- // studio can be implicitly registered when it's first launched manually
- WindowsRegistry.RegisterPlayer();
-
- if (CreateDesktopShortcuts)
- Shortcut.Create(Paths.Application, "", DesktopShortcut);
-
- if (CreateStartMenuShortcuts)
- Shortcut.Create(Paths.Application, "", StartMenuShortcut);
-
- // existing configuration persisting from an earlier install
- App.Settings.Load(false);
- App.State.Load(false);
- App.FastFlags.Load(false);
-
- App.Settings.Prop.EnableAnalytics = EnableAnalytics;
-
- if (App.IsStudioVisible)
- WindowsRegistry.RegisterStudio();
-
- App.Settings.Save();
-
- App.Logger.WriteLine(LOG_IDENT, "Installation finished");
-
- if (!IsImplicitInstall)
- App.SendStat("installAction", "install");
- }
-
- private bool ValidateLocation()
- {
- // prevent from installing to the root of a drive
- if (InstallLocation.Length <= 3)
- return false;
-
- // unc path, just to be safe
- if (InstallLocation.StartsWith("\\\\"))
- return false;
-
- if (InstallLocation.StartsWith(Path.GetTempPath(), StringComparison.InvariantCultureIgnoreCase)
- || InstallLocation.Contains("\\Temp\\", StringComparison.InvariantCultureIgnoreCase))
- return false;
-
- // prevent from installing to a onedrive folder
- if (InstallLocation.Contains("OneDrive", StringComparison.InvariantCultureIgnoreCase))
- return false;
-
- // prevent from installing to an essential user profile folder (e.g. Documents, Downloads, Contacts idk)
- if (String.Compare(Directory.GetParent(InstallLocation)?.FullName, Paths.UserProfile, StringComparison.InvariantCultureIgnoreCase) == 0)
- return false;
-
- // prevent from installing into the program files folder
- if (InstallLocation.Contains("Program Files"))
- return false;
-
- return true;
- }
-
- public bool CheckInstallLocation()
- {
- if (string.IsNullOrEmpty(InstallLocation))
- {
- InstallLocationError = Strings.Menu_InstallLocation_NotSet;
- }
- else if (!ValidateLocation())
- {
- InstallLocationError = Strings.Menu_InstallLocation_CantInstall;
- }
- else
- {
- if (!IsImplicitInstall
- && !InstallLocation.EndsWith(App.ProjectName, StringComparison.InvariantCultureIgnoreCase)
- && Directory.Exists(InstallLocation)
- && Directory.EnumerateFileSystemEntries(InstallLocation).Any())
- {
- string suggestedChange = Path.Combine(InstallLocation, App.ProjectName);
-
- MessageBoxResult result = Frontend.ShowMessageBox(
- String.Format(Strings.Menu_InstallLocation_NotEmpty, suggestedChange),
- MessageBoxImage.Warning,
- MessageBoxButton.YesNoCancel,
- MessageBoxResult.Yes
- );
-
- if (result == MessageBoxResult.Yes)
- InstallLocation = suggestedChange;
- else if (result == MessageBoxResult.Cancel || result == MessageBoxResult.None)
- return false;
- }
-
- try
- {
- // check if we can write to the directory (a bit hacky but eh)
- string testFile = Path.Combine(InstallLocation, $"{App.ProjectName}WriteTest.txt");
-
- Directory.CreateDirectory(InstallLocation);
- File.WriteAllText(testFile, "");
- File.Delete(testFile);
- }
- catch (UnauthorizedAccessException)
- {
- InstallLocationError = Strings.Menu_InstallLocation_NoWritePerms;
- }
- catch (Exception ex)
- {
- InstallLocationError = ex.Message;
- }
- }
-
- return String.IsNullOrEmpty(InstallLocationError);
- }
-
- public static void DoUninstall(bool keepData)
- {
- const string LOG_IDENT = "Installer::DoUninstall";
-
- var processes = new List();
-
- if (!String.IsNullOrEmpty(App.State.Prop.Player.VersionGuid))
- processes.AddRange(Process.GetProcessesByName(App.RobloxPlayerAppName));
-
- if (App.IsStudioVisible)
- processes.AddRange(Process.GetProcessesByName(App.RobloxStudioAppName));
-
- // prompt to shutdown roblox if its currently running
- if (processes.Any())
- {
- var result = Frontend.ShowMessageBox(
- Strings.Bootstrapper_Uninstall_RobloxRunning,
- MessageBoxImage.Information,
- MessageBoxButton.OKCancel,
- MessageBoxResult.OK
- );
-
- if (result != MessageBoxResult.OK)
- {
- App.Terminate(ErrorCode.ERROR_CANCELLED);
- return;
- }
-
- try
- {
- foreach (var process in processes)
- {
- process.Kill();
- process.Close();
- }
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Failed to close process! {ex}");
- }
- }
-
- string robloxFolder = Path.Combine(Paths.LocalAppData, "Roblox");
- bool playerStillInstalled = true;
- bool studioStillInstalled = true;
-
- // check if stock bootstrapper is still installed
- using var playerKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-player");
- var playerFolder = playerKey?.GetValue("InstallLocation");
-
- if (playerKey is null || playerFolder is not string)
- {
- playerStillInstalled = false;
-
- WindowsRegistry.Unregister("roblox");
- WindowsRegistry.Unregister("roblox-player");
- }
- else
- {
- string playerPath = Path.Combine((string)playerFolder, "RobloxPlayerBeta.exe");
-
- WindowsRegistry.RegisterPlayer(playerPath, "%1");
- }
-
- using var studioKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-studio");
- var studioFolder = studioKey?.GetValue("InstallLocation");
-
- if (studioKey is null || studioFolder is not string)
- {
- studioStillInstalled = false;
-
- WindowsRegistry.Unregister("roblox-studio");
- WindowsRegistry.Unregister("roblox-studio-auth");
-
- WindowsRegistry.Unregister("Roblox.Place");
- WindowsRegistry.Unregister(".rbxl");
- WindowsRegistry.Unregister(".rbxlx");
- }
- else
- {
- string studioPath = Path.Combine((string)studioFolder, "RobloxStudioBeta.exe");
- string studioLauncherPath = Path.Combine((string)studioFolder, "RobloxStudioLauncherBeta.exe");
-
- WindowsRegistry.RegisterStudioProtocol(studioPath, "%1");
- WindowsRegistry.RegisterStudioFileClass(studioPath, "-ide \"%1\"");
- }
-
- var cleanupSequence = new List
- {
- () =>
- {
- foreach (var file in Directory.GetFiles(Paths.Desktop).Where(x => x.EndsWith("lnk")))
- {
- var shortcut = ShellLink.Shortcut.ReadFromFile(file);
-
- if (shortcut.ExtraData.EnvironmentVariableDataBlock?.TargetUnicode == Paths.Application)
- File.Delete(file);
- }
- },
-
- () => File.Delete(StartMenuShortcut),
-
- () => Directory.Delete(Paths.Versions, true),
- () => Directory.Delete(Paths.Downloads, true),
-
- () => File.Delete(App.State.FileLocation)
- };
-
- if (!keepData)
- {
- cleanupSequence.AddRange(new List
- {
- () => Directory.Delete(Paths.Modifications, true),
- () => Directory.Delete(Paths.Logs, true),
-
- () => File.Delete(App.Settings.FileLocation)
- });
- }
-
- bool deleteFolder = Directory.GetFiles(Paths.Base).Length <= 3;
-
- if (deleteFolder)
- cleanupSequence.Add(() => Directory.Delete(Paths.Base, true));
-
- if (!playerStillInstalled && !studioStillInstalled && Directory.Exists(robloxFolder))
- cleanupSequence.Add(() => Directory.Delete(robloxFolder, true));
-
- cleanupSequence.Add(() => Registry.CurrentUser.DeleteSubKey(App.UninstallKey));
-
- foreach (var process in cleanupSequence)
- {
- try
- {
- process();
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Encountered exception when running cleanup sequence (#{cleanupSequence.IndexOf(process)})");
- App.Logger.WriteException(LOG_IDENT, ex);
- }
- }
-
- if (Directory.Exists(Paths.Base))
- {
- // this is definitely one of the workaround hacks of all time
-
- string deleteCommand;
-
- if (deleteFolder)
- deleteCommand = $"del /Q \"{Paths.Base}\\*\" && rmdir \"{Paths.Base}\"";
- else
- deleteCommand = $"del /Q \"{Paths.Application}\"";
-
- Process.Start(new ProcessStartInfo()
- {
- FileName = "cmd.exe",
- Arguments = $"/c timeout 5 && {deleteCommand}",
- UseShellExecute = true,
- WindowStyle = ProcessWindowStyle.Hidden
- });
- }
-
- App.SendStat("installAction", "uninstall");
- }
-
- public static void HandleUpgrade()
- {
- const string LOG_IDENT = "Installer::HandleUpgrade";
-
- if (!File.Exists(Paths.Application) || Paths.Process == Paths.Application)
- return;
-
- // 2.0.0 downloads updates to /Updates so lol
- bool isAutoUpgrade = App.LaunchSettings.UpgradeFlag.Active
- || Paths.Process.StartsWith(Path.Combine(Paths.Base, "Updates"))
- || Paths.Process.StartsWith(Path.Combine(Paths.LocalAppData, "Temp"))
- || Paths.Process.StartsWith(Paths.TempUpdates);
-
- var existingVer = FileVersionInfo.GetVersionInfo(Paths.Application).ProductVersion;
- var currentVer = FileVersionInfo.GetVersionInfo(Paths.Process).ProductVersion;
-
- if (MD5Hash.FromFile(Paths.Process) == MD5Hash.FromFile(Paths.Application))
- return;
-
- if (currentVer is not null && existingVer is not null && Utilities.CompareVersions(currentVer, existingVer) == VersionComparison.LessThan)
- {
- var result = Frontend.ShowMessageBox(
- Strings.InstallChecker_VersionLessThanInstalled,
- MessageBoxImage.Question,
- MessageBoxButton.YesNo
- );
-
- if (result != MessageBoxResult.Yes)
- return;
- }
-
- // silently upgrade version if the command line flag is set or if we're launching from an auto update
- if (!isAutoUpgrade)
- {
- var result = Frontend.ShowMessageBox(
- Strings.InstallChecker_VersionDifferentThanInstalled,
- MessageBoxImage.Question,
- MessageBoxButton.YesNo
- );
-
- if (result != MessageBoxResult.Yes)
- return;
- }
-
- App.Logger.WriteLine(LOG_IDENT, "Doing upgrade");
-
- Filesystem.AssertReadOnly(Paths.Application);
-
- using (var ipl = new InterProcessLock("AutoUpdater", TimeSpan.FromSeconds(5)))
- {
- if (!ipl.IsAcquired)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not obtain singleton mutex)");
- return;
- }
- }
-
- // prior to 2.8.0, auto-updating was handled with this... bruteforce method
- // now it's handled with the system mutex you see above, but we need to keep this logic for <2.8.0 versions
- for (int i = 1; i <= 10; i++)
- {
- try
- {
- File.Copy(Paths.Process, Paths.Application, true);
- break;
- }
- catch (Exception ex)
- {
- if (i == 1)
- {
- App.Logger.WriteLine(LOG_IDENT, "Waiting for write permissions to update version");
- }
- else if (i == 10)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not get write permissions after 10 tries/5 seconds)");
- App.Logger.WriteException(LOG_IDENT, ex);
- return;
- }
-
- Thread.Sleep(500);
- }
- }
-
- using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey))
- {
- uninstallKey.SetValueSafe("DisplayVersion", App.Version);
-
- uninstallKey.SetValueSafe("Publisher", App.ProjectOwner);
- uninstallKey.SetValueSafe("HelpLink", App.ProjectHelpLink);
- uninstallKey.SetValueSafe("URLInfoAbout", App.ProjectSupportLink);
- uninstallKey.SetValueSafe("URLUpdateInfo", App.ProjectDownloadLink);
- }
-
- // update migrations
-
- if (existingVer is not null)
- {
- if (Utilities.CompareVersions(existingVer, "2.2.0") == VersionComparison.LessThan)
- {
- string path = Path.Combine(Paths.Integrations, "rbxfpsunlocker");
-
- try
- {
- if (Directory.Exists(path))
- Directory.Delete(path, true);
- }
- catch (Exception ex)
- {
- App.Logger.WriteException(LOG_IDENT, ex);
- }
- }
-
- if (Utilities.CompareVersions(existingVer, "2.3.0") == VersionComparison.LessThan)
- {
- string injectorLocation = Path.Combine(Paths.Modifications, "dxgi.dll");
- string configLocation = Path.Combine(Paths.Modifications, "ReShade.ini");
-
- if (File.Exists(injectorLocation))
- File.Delete(injectorLocation);
-
- if (File.Exists(configLocation))
- File.Delete(configLocation);
- }
-
-
- if (Utilities.CompareVersions(existingVer, "2.5.0") == VersionComparison.LessThan)
- {
- App.FastFlags.SetValue("DFFlagDisableDPIScale", null);
- App.FastFlags.SetValue("DFFlagVariableDPIScale2", null);
- }
-
- if (Utilities.CompareVersions(existingVer, "2.6.0") == VersionComparison.LessThan)
- {
- if (App.Settings.Prop.UseDisableAppPatch)
- {
- try
- {
- File.Delete(Path.Combine(Paths.Modifications, "ExtraContent\\places\\Mobile.rbxl"));
- }
- catch (Exception ex)
- {
- App.Logger.WriteException(LOG_IDENT, ex);
- }
-
- App.Settings.Prop.EnableActivityTracking = true;
- }
-
- if (App.Settings.Prop.BootstrapperStyle == BootstrapperStyle.ClassicFluentDialog)
- App.Settings.Prop.BootstrapperStyle = BootstrapperStyle.FluentDialog;
-
- _ = int.TryParse(App.FastFlags.GetPreset("Rendering.Framerate"), out int x);
- if (x == 0)
- App.FastFlags.SetPreset("Rendering.Framerate", null);
- }
-
- if (Utilities.CompareVersions(existingVer, "2.8.0") == VersionComparison.LessThan)
- {
- if (isAutoUpgrade)
- {
- if (App.LaunchSettings.Args.Length == 0)
- App.LaunchSettings.RobloxLaunchMode = LaunchMode.Player;
-
- string? query = App.LaunchSettings.Args.FirstOrDefault(x => x.Contains("roblox"));
-
- if (query is not null)
- {
- App.LaunchSettings.RobloxLaunchMode = LaunchMode.Player;
- App.LaunchSettings.RobloxLaunchArgs = query;
- }
- }
-
- string oldDesktopPath = Path.Combine(Paths.Desktop, "Play Roblox.lnk");
- string oldStartPath = Path.Combine(Paths.WindowsStartMenu, "Bloxstrap");
-
- if (File.Exists(oldDesktopPath))
- File.Move(oldDesktopPath, DesktopShortcut, true);
-
- if (Directory.Exists(oldStartPath))
- {
- try
- {
- Directory.Delete(oldStartPath, true);
- }
- catch (Exception ex)
- {
- App.Logger.WriteException(LOG_IDENT, ex);
- }
-
- Shortcut.Create(Paths.Application, "", StartMenuShortcut);
- }
-
- Registry.CurrentUser.DeleteSubKeyTree("Software\\Bloxstrap", false);
-
- WindowsRegistry.RegisterPlayer();
-
- App.FastFlags.SetValue("FFlagDisableNewIGMinDUA", null);
- App.FastFlags.SetValue("FFlagFixGraphicsQuality", null);
- }
-
- if (Utilities.CompareVersions(existingVer, "2.8.1") == VersionComparison.LessThan)
- {
- // wipe all escape menu flag presets
- App.FastFlags.SetValue("FIntNewInGameMenuPercentRollout3", null);
- App.FastFlags.SetValue("FFlagEnableInGameMenuControls", null);
- App.FastFlags.SetValue("FFlagEnableInGameMenuModernization", null);
- App.FastFlags.SetValue("FFlagEnableInGameMenuChrome", null);
- App.FastFlags.SetValue("FFlagFixReportButtonCutOff", null);
- App.FastFlags.SetValue("FFlagEnableMenuControlsABTest", null);
- App.FastFlags.SetValue("FFlagEnableV3MenuABTest3", null);
- App.FastFlags.SetValue("FFlagEnableInGameMenuChromeABTest3", null);
- App.FastFlags.SetValue("FFlagEnableInGameMenuChromeABTest4", null);
- }
-
- if (Utilities.CompareVersions(existingVer, "2.8.2") == VersionComparison.LessThan)
- {
- string robloxDirectory = Path.Combine(Paths.Base, "Roblox");
-
- if (Directory.Exists(robloxDirectory))
- {
- try
- {
- Directory.Delete(robloxDirectory, true);
- }
- catch (Exception ex)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to delete the Roblox directory");
- App.Logger.WriteException(LOG_IDENT, ex);
- }
- }
- }
-
- if (Utilities.CompareVersions(existingVer, "2.8.3") == VersionComparison.LessThan)
- {
- // force reinstallation
- App.State.Prop.Player.VersionGuid = "";
- App.State.Prop.Studio.VersionGuid = "";
- }
-
- App.Settings.Save();
- App.FastFlags.Save();
- App.State.Save();
- }
-
- if (currentVer is null)
- return;
-
- App.SendStat("installAction", "upgrade");
-
- if (isAutoUpgrade)
- {
-#pragma warning disable CS0162 // Unreachable code detected
- if (OpenReleaseNotes)
- Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki/Release-notes-for-Bloxstrap-v{currentVer}");
-#pragma warning restore CS0162 // Unreachable code detected
- }
- else
- {
- Frontend.ShowMessageBox(
- string.Format(Strings.InstallChecker_Updated, currentVer),
- MessageBoxImage.Information,
- MessageBoxButton.OK
- );
- }
- }
- }
-}
diff --git a/Bloxstrap/Integrations/ActivityWatcher.cs b/Bloxstrap/Integrations/ActivityWatcher.cs
deleted file mode 100644
index 8132250..0000000
--- a/Bloxstrap/Integrations/ActivityWatcher.cs
+++ /dev/null
@@ -1,389 +0,0 @@
-namespace Bloxstrap.Integrations
-{
- public class ActivityWatcher : IDisposable
- {
- private const string GameMessageEntry = "[FLog::Output] [BloxstrapRPC]";
- private const string GameJoiningEntry = "[FLog::Output] ! Joining game";
-
- // these entries are technically volatile!
- // they only get printed depending on their configured FLog level, which could change at any time
- // while levels being changed is fairly rare, please limit the number of varying number of FLog types you have to use, if possible
-
- private const string GameTeleportingEntry = "[FLog::GameJoinUtil] GameJoinUtil::initiateTeleportToPlace";
- private const string GameJoiningPrivateServerEntry = "[FLog::GameJoinUtil] GameJoinUtil::joinGamePostPrivateServer";
- private const string GameJoiningReservedServerEntry = "[FLog::GameJoinUtil] GameJoinUtil::initiateTeleportToReservedServer";
- private const string GameJoiningUniverseEntry = "[FLog::GameJoinLoadTime] Report game_join_loadtime:";
- private const string GameJoiningUDMUXEntry = "[FLog::Network] UDMUX Address = ";
- private const string GameJoinedEntry = "[FLog::Network] serverId:";
- private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
- private const string GameLeavingEntry = "[FLog::SingleSurfaceApp] leaveUGCGameInternal";
-
- private const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)";
- private const string GameJoiningPrivateServerPattern = @"""accessCode"":""([0-9a-f\-]{36})""";
- private const string GameJoiningUniversePattern = @"universeid:([0-9]+).*userid:([0-9]+)";
- private const string GameJoiningUDMUXPattern = @"UDMUX Address = ([0-9\.]+), Port = [0-9]+ \| RCC Server Address = ([0-9\.]+), Port = [0-9]+";
- private const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|[0-9]+";
- private const string GameMessageEntryPattern = @"\[BloxstrapRPC\] (.*)";
-
- private int _logEntriesRead = 0;
- private bool _teleportMarker = false;
- private bool _reservedTeleportMarker = false;
-
- public event EventHandler? OnLogEntry;
- public event EventHandler? OnGameJoin;
- public event EventHandler? OnGameLeave;
- public event EventHandler? OnLogOpen;
- public event EventHandler? OnAppClose;
- public event EventHandler? OnRPCMessage;
-
- private DateTime LastRPCRequest;
-
- public string LogLocation = null!;
-
- public bool InGame = false;
-
- public ActivityData Data { get; private set; } = new();
-
- ///
- /// Ordered by newest to oldest
- ///
- public List History = new();
-
- public bool IsDisposed = false;
-
- public ActivityWatcher(string? logFile = null)
- {
- if (!String.IsNullOrEmpty(logFile))
- LogLocation = logFile;
- }
-
- public async void Start()
- {
- const string LOG_IDENT = "ActivityWatcher::Start";
-
- // okay, here's the process:
- //
- // - tail the latest log file from %localappdata%\roblox\logs
- // - check for specific lines to determine player's game activity as shown below:
- //
- // - get the place id, job id and machine address from '! Joining game '{{JOBID}}' place {{PLACEID}} at {{MACHINEADDRESS}}' entry
- // - confirm place join with 'serverId: {{MACHINEADDRESS}}|{{MACHINEPORT}}' entry
- // - check for leaves/disconnects with 'Time to disconnect replication data: {{TIME}}' entry
- //
- // we'll tail the log file continuously, monitoring for any log entries that we need to determine the current game activity
-
- FileInfo logFileInfo;
-
- if (String.IsNullOrEmpty(LogLocation))
- {
- string logDirectory = Path.Combine(Paths.LocalAppData, "Roblox\\logs");
-
- if (!Directory.Exists(logDirectory))
- return;
-
- // we need to make sure we're fetching the absolute latest log file
- // if roblox doesn't start quickly enough, we can wind up fetching the previous log file
- // good rule of thumb is to find a log file that was created in the last 15 seconds or so
-
- App.Logger.WriteLine(LOG_IDENT, "Opening Roblox log file...");
-
- while (true)
- {
- logFileInfo = new DirectoryInfo(logDirectory)
- .GetFiles()
- .Where(x => x.Name.Contains("Player", StringComparison.OrdinalIgnoreCase) && x.CreationTime <= DateTime.Now)
- .OrderByDescending(x => x.CreationTime)
- .First();
-
- if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now)
- break;
-
- App.Logger.WriteLine(LOG_IDENT, $"Could not find recent enough log file, waiting... (newest is {logFileInfo.Name})");
- await Task.Delay(1000);
- }
-
- LogLocation = logFileInfo.FullName;
- }
- else
- {
- logFileInfo = new FileInfo(LogLocation);
- }
-
- OnLogOpen?.Invoke(this, EventArgs.Empty);
-
- var logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
-
- App.Logger.WriteLine(LOG_IDENT, $"Opened {LogLocation}");
-
- using var streamReader = new StreamReader(logFileStream);
-
- while (!IsDisposed)
- {
- string? log = await streamReader.ReadLineAsync();
-
- if (log is null)
- await Task.Delay(1000);
- else
- ReadLogEntry(log);
- }
- }
-
- private void ReadLogEntry(string entry)
- {
- const string LOG_IDENT = "ActivityWatcher::ReadLogEntry";
-
- OnLogEntry?.Invoke(this, entry);
-
- _logEntriesRead += 1;
-
- // debug stats to ensure that the log reader is working correctly
- // if more than 1000 log entries have been read, only log per 100 to save on spam
- if (_logEntriesRead <= 1000 && _logEntriesRead % 50 == 0)
- App.Logger.WriteLine(LOG_IDENT, $"Read {_logEntriesRead} log entries");
- else if (_logEntriesRead % 100 == 0)
- App.Logger.WriteLine(LOG_IDENT, $"Read {_logEntriesRead} log entries");
-
- if (entry.Contains(GameLeavingEntry))
- {
- App.Logger.WriteLine(LOG_IDENT, "User is back into the desktop app");
-
- OnAppClose?.Invoke(this, EventArgs.Empty);
-
- if (Data.PlaceId != 0 && !InGame)
- {
- App.Logger.WriteLine(LOG_IDENT, "User appears to be leaving from a cancelled/errored join");
- Data = new();
- }
- }
-
- if (!InGame && Data.PlaceId == 0)
- {
- // We are not in a game, nor are in the process of joining one
-
- if (entry.Contains(GameJoiningPrivateServerEntry))
- {
- // we only expect to be joining a private server if we're not already in a game
-
- Data.ServerType = ServerType.Private;
-
- var match = Regex.Match(entry, GameJoiningPrivateServerPattern);
-
- if (match.Groups.Count != 2)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to assert format for game join private server entry");
- App.Logger.WriteLine(LOG_IDENT, entry);
- return;
- }
-
- Data.AccessCode = match.Groups[1].Value;
- }
- else if (entry.Contains(GameJoiningEntry))
- {
- Match match = Regex.Match(entry, GameJoiningEntryPattern);
-
- if (match.Groups.Count != 4)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Failed to assert format for game join entry");
- App.Logger.WriteLine(LOG_IDENT, entry);
- return;
- }
-
- InGame = false;
- Data.PlaceId = long.Parse(match.Groups[2].Value);
- Data.JobId = match.Groups[1].Value;
- Data.MachineAddress = match.Groups[3].Value;
-
- if (App.Settings.Prop.ShowServerDetails && Data.MachineAddressValid)
- _ = Data.QueryServerLocation();
-
- if (_teleportMarker)
- {
- Data.IsTeleport = true;
- _teleportMarker = false;
- }
-
- if (_reservedTeleportMarker)
- {
- Data.ServerType = ServerType.Reserved;
- _reservedTeleportMarker = false;
- }
-
- App.Logger.WriteLine(LOG_IDENT, $"Joining Game ({Data})");
- }
- }
- else if (!InGame && Data.PlaceId != 0)
- {
- // We are not confirmed to be in a game, but we are in the process of joining one
-
- if (entry.Contains(GameJoiningUniverseEntry))
- {
- var match = Regex.Match(entry, GameJoiningUniversePattern);
-
- if (match.Groups.Count != 3)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to assert format for game join universe entry");
- App.Logger.WriteLine(LOG_IDENT, entry);
- return;
- }
-
- Data.UniverseId = Int64.Parse(match.Groups[1].Value);
- Data.UserId = Int64.Parse(match.Groups[2].Value);
-
- if (History.Any())
- {
- var lastActivity = History.First();
-
- if (Data.UniverseId == lastActivity.UniverseId && Data.IsTeleport)
- Data.RootActivity = lastActivity.RootActivity ?? lastActivity;
- }
- }
- else if (entry.Contains(GameJoiningUDMUXEntry))
- {
- var match = Regex.Match(entry, GameJoiningUDMUXPattern);
-
- if (match.Groups.Count != 3 || match.Groups[2].Value != Data.MachineAddress)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to assert format for game join UDMUX entry");
- App.Logger.WriteLine(LOG_IDENT, entry);
- return;
- }
-
- Data.MachineAddress = match.Groups[1].Value;
-
- if (App.Settings.Prop.ShowServerDetails)
- _ = Data.QueryServerLocation();
-
- App.Logger.WriteLine(LOG_IDENT, $"Server is UDMUX protected ({Data})");
- }
- else if (entry.Contains(GameJoinedEntry))
- {
- Match match = Regex.Match(entry, GameJoinedEntryPattern);
-
- if (match.Groups.Count != 2 || match.Groups[1].Value != Data.MachineAddress)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Failed to assert format for game joined entry");
- App.Logger.WriteLine(LOG_IDENT, entry);
- return;
- }
-
- App.Logger.WriteLine(LOG_IDENT, $"Joined Game ({Data})");
-
- InGame = true;
- Data.TimeJoined = DateTime.Now;
-
- OnGameJoin?.Invoke(this, EventArgs.Empty);
- }
- }
- else if (InGame && Data.PlaceId != 0)
- {
- // We are confirmed to be in a game
-
- if (entry.Contains(GameDisconnectedEntry))
- {
- App.Logger.WriteLine(LOG_IDENT, $"Disconnected from Game ({Data})");
-
- Data.TimeLeft = DateTime.Now;
- History.Insert(0, Data);
-
- InGame = false;
- Data = new();
-
- OnGameLeave?.Invoke(this, EventArgs.Empty);
- }
- else if (entry.Contains(GameTeleportingEntry))
- {
- App.Logger.WriteLine(LOG_IDENT, $"Initiating teleport to server ({Data})");
- _teleportMarker = true;
- }
- else if (entry.Contains(GameJoiningReservedServerEntry))
- {
- _teleportMarker = true;
- _reservedTeleportMarker = true;
- }
- else if (entry.Contains(GameMessageEntry))
- {
- var match = Regex.Match(entry, GameMessageEntryPattern);
-
- if (match.Groups.Count != 2)
- {
- App.Logger.WriteLine(LOG_IDENT, $"Failed to assert format for RPC message entry");
- App.Logger.WriteLine(LOG_IDENT, entry);
- return;
- }
-
- string messagePlain = match.Groups[1].Value;
- Message? message;
-
- App.Logger.WriteLine(LOG_IDENT, $"Received message: '{messagePlain}'");
-
- if ((DateTime.Now - LastRPCRequest).TotalSeconds <= 1)
- {
- App.Logger.WriteLine(LOG_IDENT, "Dropping message as ratelimit has been hit");
- return;
- }
-
- try
- {
- message = JsonSerializer.Deserialize(messagePlain);
- }
- catch (Exception)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization threw an exception)");
- return;
- }
-
- if (message is null)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization returned null)");
- return;
- }
-
- if (string.IsNullOrEmpty(message.Command))
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (Command is empty)");
- return;
- }
-
- if (message.Command == "SetLaunchData")
- {
- string? data;
-
- try
- {
- data = message.Data.Deserialize();
- }
- catch (Exception)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization threw an exception)");
- return;
- }
-
- if (data is null)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization returned null)");
- return;
- }
-
- if (data.Length > 200)
- {
- App.Logger.WriteLine(LOG_IDENT, "Data cannot be longer than 200 characters");
- return;
- }
-
- Data.RPCLaunchData = data;
- }
-
- OnRPCMessage?.Invoke(this, message);
-
- LastRPCRequest = DateTime.Now;
- }
- }
- }
-
- public void Dispose()
- {
- IsDisposed = true;
- GC.SuppressFinalize(this);
- }
- }
-}
diff --git a/Bloxstrap/Integrations/DiscordRichPresence.cs b/Bloxstrap/Integrations/DiscordRichPresence.cs
deleted file mode 100644
index 8d140f9..0000000
--- a/Bloxstrap/Integrations/DiscordRichPresence.cs
+++ /dev/null
@@ -1,344 +0,0 @@
-using System.Windows;
-using Bloxstrap.Models.RobloxApi;
-using DiscordRPC;
-
-namespace Bloxstrap.Integrations
-{
- public class DiscordRichPresence : IDisposable
- {
- private readonly DiscordRpcClient _rpcClient = new("1005469189907173486");
- private readonly ActivityWatcher _activityWatcher;
- private readonly Queue _messageQueue = new();
-
- private DiscordRPC.RichPresence? _currentPresence;
- private DiscordRPC.RichPresence? _originalPresence;
-
- private bool _visible = true;
-
- public DiscordRichPresence(ActivityWatcher activityWatcher)
- {
- const string LOG_IDENT = "DiscordRichPresence";
-
- _activityWatcher = activityWatcher;
-
- _activityWatcher.OnGameJoin += (_, _) => Task.Run(() => SetCurrentGame());
- _activityWatcher.OnGameLeave += (_, _) => Task.Run(() => SetCurrentGame());
- _activityWatcher.OnRPCMessage += (_, message) => ProcessRPCMessage(message);
-
- _rpcClient.OnReady += (_, e) =>
- App.Logger.WriteLine(LOG_IDENT, $"Received ready from user {e.User} ({e.User.ID})");
-
- _rpcClient.OnPresenceUpdate += (_, e) =>
- App.Logger.WriteLine(LOG_IDENT, "Presence updated");
-
- _rpcClient.OnError += (_, e) =>
- App.Logger.WriteLine(LOG_IDENT, $"An RPC error occurred - {e.Message}");
-
- _rpcClient.OnConnectionEstablished += (_, e) =>
- App.Logger.WriteLine(LOG_IDENT, "Established connection with Discord RPC");
-
- //spams log as it tries to connect every ~15 sec when discord is closed so not now
- //_rpcClient.OnConnectionFailed += (_, e) =>
- // App.Logger.WriteLine(LOG_IDENT, "Failed to establish connection with Discord RPC");
-
- _rpcClient.OnClose += (_, e) =>
- App.Logger.WriteLine(LOG_IDENT, $"Lost connection to Discord RPC - {e.Reason} ({e.Code})");
-
- _rpcClient.Initialize();
- }
-
- public void ProcessRPCMessage(Message message, bool implicitUpdate = true)
- {
- const string LOG_IDENT = "DiscordRichPresence::ProcessRPCMessage";
-
- if (message.Command != "SetRichPresence" && message.Command != "SetLaunchData")
- return;
-
- if (_currentPresence is null || _originalPresence is null)
- {
- App.Logger.WriteLine(LOG_IDENT, "Presence is not set, enqueuing message");
- _messageQueue.Enqueue(message);
- return;
- }
-
- // a lot of repeated code here, could this somehow be cleaned up?
-
- if (message.Command == "SetLaunchData")
- {
- _currentPresence.Buttons = GetButtons();
- }
- else if (message.Command == "SetRichPresence")
- {
- Models.BloxstrapRPC.RichPresence? presenceData;
-
- try
- {
- presenceData = message.Data.Deserialize();
- }
- catch (Exception)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization threw an exception)");
- return;
- }
-
- if (presenceData is null)
- {
- App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization returned null)");
- return;
- }
-
- if (presenceData.Details is not null)
- {
- if (presenceData.Details.Length > 128)
- App.Logger.WriteLine(LOG_IDENT, $"Details cannot be longer than 128 characters");
- else if (presenceData.Details == "")
- _currentPresence.Details = _originalPresence.Details;
- else
- _currentPresence.Details = presenceData.Details;
- }
-
- if (presenceData.State is not null)
- {
- if (presenceData.State.Length > 128)
- App.Logger.WriteLine(LOG_IDENT, $"State cannot be longer than 128 characters");
- else if (presenceData.State == "")
- _currentPresence.State = _originalPresence.State;
- else
- _currentPresence.State = presenceData.State;
- }
-
- if (presenceData.TimestampStart == 0)
- _currentPresence.Timestamps.Start = null;
- else if (presenceData.TimestampStart is not null)
- _currentPresence.Timestamps.StartUnixMilliseconds = presenceData.TimestampStart * 1000;
-
- if (presenceData.TimestampEnd == 0)
- _currentPresence.Timestamps.End = null;
- else if (presenceData.TimestampEnd is not null)
- _currentPresence.Timestamps.EndUnixMilliseconds = presenceData.TimestampEnd * 1000;
-
- if (presenceData.SmallImage is not null)
- {
- if (presenceData.SmallImage.Clear)
- {
- _currentPresence.Assets.SmallImageKey = "";
- }
- else if (presenceData.SmallImage.Reset)
- {
- _currentPresence.Assets.SmallImageText = _originalPresence.Assets.SmallImageText;
- _currentPresence.Assets.SmallImageKey = _originalPresence.Assets.SmallImageKey;
- }
- else
- {
- if (presenceData.SmallImage.AssetId is not null)
- _currentPresence.Assets.SmallImageKey = $"https://assetdelivery.roblox.com/v1/asset/?id={presenceData.SmallImage.AssetId}";
-
- if (presenceData.SmallImage.HoverText is not null)
- _currentPresence.Assets.SmallImageText = presenceData.SmallImage.HoverText;
- }
- }
-
- if (presenceData.LargeImage is not null)
- {
- if (presenceData.LargeImage.Clear)
- {
- _currentPresence.Assets.LargeImageKey = "";
- }
- else if (presenceData.LargeImage.Reset)
- {
- _currentPresence.Assets.LargeImageText = _originalPresence.Assets.LargeImageText;
- _currentPresence.Assets.LargeImageKey = _originalPresence.Assets.LargeImageKey;
- }
- else
- {
- if (presenceData.LargeImage.AssetId is not null)
- _currentPresence.Assets.LargeImageKey = $"https://assetdelivery.roblox.com/v1/asset/?id={presenceData.LargeImage.AssetId}";
-
- if (presenceData.LargeImage.HoverText is not null)
- _currentPresence.Assets.LargeImageText = presenceData.LargeImage.HoverText;
- }
- }
- }
-
- if (implicitUpdate)
- UpdatePresence();
- }
-
- public void SetVisibility(bool visible)
- {
- App.Logger.WriteLine("DiscordRichPresence::SetVisibility", $"Setting presence visibility ({visible})");
-
- _visible = visible;
-
- if (_visible)
- UpdatePresence();
- else
- _rpcClient.ClearPresence();
- }
-
- public async Task SetCurrentGame()
- {
- const string LOG_IDENT = "DiscordRichPresence::SetCurrentGame";
-
- if (!_activityWatcher.InGame)
- {
- App.Logger.WriteLine(LOG_IDENT, "Not in game, clearing presence");
-
- _currentPresence = _originalPresence = null;
- _messageQueue.Clear();
-
- UpdatePresence();
- return true;
- }
-
- string icon = "roblox";
- string smallImageText = "Roblox";
- string smallImage = "roblox";
-
-
- var activity = _activityWatcher.Data;
- long placeId = activity.PlaceId;
-
- App.Logger.WriteLine(LOG_IDENT, $"Setting presence for Place ID {placeId}");
-
- // preserve time spent playing if we're teleporting between places in the same universe
- var timeStarted = activity.TimeJoined;
-
- if (activity.RootActivity is not null)
- timeStarted = activity.RootActivity.TimeJoined;
-
- if (activity.UniverseDetails is null)
- {
- try
- {
- await UniverseDetails.FetchSingle(activity.UniverseId);
- }
- catch (Exception ex)
- {
- App.Logger.WriteException(LOG_IDENT, ex);
- Frontend.ShowMessageBox($"{Strings.ActivityWatcher_RichPresenceLoadFailed}\n\n{ex.Message}", MessageBoxImage.Warning);
- return false;
- }
-
- activity.UniverseDetails = UniverseDetails.LoadFromCache(activity.UniverseId);
- }
-
- var universeDetails = activity.UniverseDetails!;
-
- icon = universeDetails.Thumbnail.ImageUrl;
-
- if (App.Settings.Prop.ShowAccountOnRichPresence)
- {
- var userDetails = await UserDetails.Fetch(activity.UserId);
-
- smallImage = userDetails.Thumbnail.ImageUrl;
- smallImageText = $"Playing on {userDetails.Data.DisplayName} (@{userDetails.Data.Name})"; // i.e. "axell (@Axelan_se)"
- }
-
- if (!_activityWatcher.InGame || placeId != activity.PlaceId)
- {
- App.Logger.WriteLine(LOG_IDENT, "Aborting presence set because game activity has changed");
- return false;
- }
-
- string status = _activityWatcher.Data.ServerType switch
- {
- ServerType.Private => "In a private server",
- ServerType.Reserved => "In a reserved server",
- _ => $"by {universeDetails.Data.Creator.Name}" + (universeDetails.Data.Creator.HasVerifiedBadge ? " ☑️" : ""),
- };
-
- string universeName = universeDetails.Data.Name;
-
- if (universeName.Length < 2)
- universeName = $"{universeName}\x2800\x2800\x2800";
-
- _currentPresence = new DiscordRPC.RichPresence
- {
- Details = universeName,
- State = status,
- Timestamps = new Timestamps { Start = timeStarted.ToUniversalTime() },
- Buttons = GetButtons(),
- Assets = new Assets
- {
- LargeImageKey = icon,
- LargeImageText = universeDetails.Data.Name,
- SmallImageKey = smallImage,
- SmallImageText = smallImageText
- }
- };
-
- // this is used for configuration from BloxstrapRPC
- _originalPresence = _currentPresence.Clone();
-
- if (_messageQueue.Any())
- {
- App.Logger.WriteLine(LOG_IDENT, "Processing queued messages");
- ProcessRPCMessage(_messageQueue.Dequeue(), false);
- }
-
- UpdatePresence();
-
- return true;
- }
-
- public Button[] GetButtons()
- {
- var buttons = new List