diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index 6eaadb6..0302340 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -60,6 +60,7 @@ namespace Bloxstrap private string _latestVersionGuid = null!; private PackageManifest _versionPackageManifest = null!; + private FileManifest _versionFileManifest = null!; private string _versionFolder = null!; private bool _isInstalling = false; @@ -118,10 +119,11 @@ namespace Bloxstrap App.Logger.WriteLine(LOG_IDENT, "Performing connectivity check..."); + SetStatus("Connecting to Roblox..."); + try { await RobloxDeployment.GetInfo(RobloxDeployment.DefaultChannel); - App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished"); } catch (Exception ex) { @@ -132,14 +134,19 @@ namespace Bloxstrap if (ex.GetType() == typeof(HttpResponseException)) message = "Roblox may be down right now. See status.roblox.com for more information. Please try again later."; - - if (ex.GetType() == typeof(AggregateException)) + else if (ex.GetType() == typeof(TaskCanceledException)) + message = "Bloxstrap timed out when trying to connect to three different Roblox deployment mirrors, indicating a poor internet connection. Please try again later."; + else if (ex.GetType() == typeof(AggregateException)) ex = ex.InnerException!; Controls.ShowConnectivityDialog("Roblox", message, ex); App.Terminate(ErrorCode.ERROR_CANCELLED); } + finally + { + App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished"); + } #if !DEBUG if (!App.IsFirstRun && App.Settings.Prop.CheckForUpdates) @@ -153,8 +160,9 @@ namespace Bloxstrap try { - Mutex.OpenExisting("Bloxstrap_BootstrapperMutex").Close(); - App.Logger.WriteLine(LOG_IDENT, "Bloxstrap_BootstrapperMutex mutex exists, waiting..."); + Mutex.OpenExisting("Bloxstrap_SingletonMutex").Close(); + App.Logger.WriteLine(LOG_IDENT, "Bloxstrap_SingletonMutex mutex exists, waiting..."); + SetStatus("Waiting for other instances..."); mutexExists = true; } catch (Exception) @@ -163,7 +171,7 @@ namespace Bloxstrap } // wait for mutex to be released if it's not yet - await using AsyncMutex mutex = new("Bloxstrap_BootstrapperMutex"); + await using var mutex = new AsyncMutex(true, "Bloxstrap_SingletonMutex"); await mutex.AcquireAsync(_cancelTokenSource.Token); // reload our configs since they've likely changed by now @@ -175,8 +183,6 @@ namespace Bloxstrap await CheckLatestVersion(); - CheckInstallMigration(); - // install/update roblox if we're running for the first time, needs updating, or the player location doesn't exist if (App.IsFirstRun || _latestVersionGuid != App.State.Prop.VersionGuid || !File.Exists(_playerLocation)) await InstallLatestVersion(); @@ -214,9 +220,23 @@ namespace Bloxstrap private async Task CheckLatestVersion() { - SetStatus("Connecting to Roblox..."); - - var clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel); + const string LOG_IDENT = "Bootstrapper::CheckLatestVersion"; + + ClientVersion clientVersion; + + try + { + clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel); + } + catch (HttpResponseException ex) + { + if (ex.ResponseMessage.StatusCode != HttpStatusCode.NotFound) + throw; + + App.Logger.WriteLine(LOG_IDENT, $"Reverting enrolled channel to {RobloxDeployment.DefaultChannel} because a WindowsPlayer build does not exist for {App.Settings.Prop.Channel}"); + App.Settings.Prop.Channel = RobloxDeployment.DefaultChannel; + clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel); + } if (clientVersion.IsBehindDefaultChannel) { @@ -245,6 +265,7 @@ namespace Bloxstrap _latestVersionGuid = clientVersion.VersionGuid; _versionFolder = Path.Combine(Paths.Versions, _latestVersionGuid); _versionPackageManifest = await PackageManifest.Get(_latestVersionGuid); + _versionFileManifest = await FileManifest.Get(_latestVersionGuid); } private async Task StartRoblox() @@ -405,6 +426,8 @@ namespace Bloxstrap App.Logger.WriteException(LOG_IDENT, ex); } + Dialog?.CloseBootstrapper(); + App.Terminate(ErrorCode.ERROR_CANCELLED); } #endregion @@ -458,71 +481,6 @@ namespace Bloxstrap App.Logger.WriteLine(LOG_IDENT, $"Registered as {totalSize} KB"); } - private void CheckInstallMigration() - { - const string LOG_IDENT = "Bootstrapper::CheckInstallMigration"; - - // check if we've changed our install location since the last time we started - // in which case, we'll have to copy over all our folders so we don't lose any mods and stuff - - using RegistryKey? applicationKey = Registry.CurrentUser.OpenSubKey($@"Software\{App.ProjectName}", true); - - string? oldInstallLocation = (string?)applicationKey?.GetValue("OldInstallLocation"); - - if (applicationKey is null || oldInstallLocation is null || oldInstallLocation == Paths.Base) - return; - - SetStatus("Migrating install location..."); - - if (Directory.Exists(oldInstallLocation)) - { - App.Logger.WriteLine(LOG_IDENT, $"Moving all files in {oldInstallLocation} to {Paths.Base}..."); - - foreach (string oldFileLocation in Directory.GetFiles(oldInstallLocation, "*.*", SearchOption.AllDirectories)) - { - string relativeFile = oldFileLocation.Substring(oldInstallLocation.Length + 1); - string newFileLocation = Path.Combine(Paths.Base, relativeFile); - string? newDirectory = Path.GetDirectoryName(newFileLocation); - - try - { - if (!String.IsNullOrEmpty(newDirectory)) - Directory.CreateDirectory(newDirectory); - - File.Move(oldFileLocation, newFileLocation, true); - } - catch (Exception ex) - { - App.Logger.WriteLine(LOG_IDENT, $"Failed to move {oldFileLocation} to {newFileLocation}! {ex}"); - } - } - - try - { - Directory.Delete(oldInstallLocation, true); - App.Logger.WriteLine(LOG_IDENT, "Deleted old install location"); - } - catch (Exception ex) - { - App.Logger.WriteLine(LOG_IDENT, $"Failed to delete old install location! {ex}"); - } - } - - applicationKey.DeleteValue("OldInstallLocation"); - - // allow shortcuts to be re-registered - if (Directory.Exists(Paths.StartMenu)) - Directory.Delete(Paths.StartMenu, true); - - if (File.Exists(DesktopShortcutLocation)) - { - File.Delete(DesktopShortcutLocation); - App.Settings.Prop.CreateDesktopIcon = true; - } - - App.Logger.WriteLine(LOG_IDENT, "Finished migrating install location!"); - } - public static void CheckInstall() { const string LOG_IDENT = "Bootstrapper::CheckInstall"; @@ -727,7 +685,7 @@ namespace Bloxstrap // if the folder we're installed to does not end with "Bloxstrap", we're installed to a user-selected folder // in which case, chances are they chose to install to somewhere they didn't really mean to (prior to the added warning in 2.4.0) // if so, we're walking on eggshells and have to ensure we only clean up what we need to clean up - bool cautiousUninstall = !Paths.Base.EndsWith(App.ProjectName); + bool cautiousUninstall = !Paths.Base.ToLower().EndsWith(App.ProjectName.ToLower()); var cleanupSequence = new List { @@ -1022,6 +980,12 @@ namespace Bloxstrap { const string LOG_IDENT = "Bootstrapper::ApplyModifications"; + if (Process.GetProcessesByName("RobloxPlayerBeta").Where(x => x.MainModule!.FileName == _playerLocation).Any()) + { + App.Logger.WriteLine(LOG_IDENT, "Roblox is running, aborting mod check"); + return; + } + SetStatus("Applying Roblox modifications..."); // set executable flags for fullscreen optimizations @@ -1355,6 +1319,9 @@ namespace Bloxstrap for (int i = 1; i <= maxTries; i++) { + if (_cancelFired) + return; + int totalBytesRead = 0; try @@ -1393,7 +1360,8 @@ namespace Bloxstrap App.Logger.WriteLine(LOG_IDENT, $"An exception occurred after downloading {totalBytesRead} bytes. ({i}/{maxTries})"); App.Logger.WriteException(LOG_IDENT, ex); - File.Delete(packageLocation); + if (File.Exists(packageLocation)) + File.Delete(packageLocation); if (i >= maxTries) throw; @@ -1438,6 +1406,16 @@ namespace Bloxstrap if (directory is not null) Directory.CreateDirectory(directory); + if (File.Exists(extractPath)) + { + var fileManifest = _versionFileManifest.FirstOrDefault(x => x.Name == Path.Combine(PackageDirectories[package.Name], entry.FullName)); + + if (fileManifest is not null && MD5Hash.FromFile(extractPath) == fileManifest.Signature) + continue; + + File.Delete(extractPath); + } + entry.ExtractToFile(extractPath, true); } diff --git a/Bloxstrap/FastFlagManager.cs b/Bloxstrap/FastFlagManager.cs index f71273f..62eb394 100644 --- a/Bloxstrap/FastFlagManager.cs +++ b/Bloxstrap/FastFlagManager.cs @@ -45,7 +45,12 @@ namespace Bloxstrap { "UI.Menu.GraphicsSlider", "FFlagFixGraphicsQuality" }, { "UI.Menu.Style.DisableV2", "FFlagDisableNewIGMinDUA" }, - { "UI.Menu.Style.EnableV4", "FFlagEnableInGameMenuControls" } + { "UI.Menu.Style.EnableV4", "FFlagEnableInGameMenuControls" }, + + { "UI.Menu.Style.ABTest.1", "FFlagEnableMenuControlsABTest" }, + { "UI.Menu.Style.ABTest.2", "FFlagEnableMenuModernizationABTest" }, + { "UI.Menu.Style.ABTest.3", "FFlagEnableMenuModernizationABTest2" }, + { "UI.Menu.Style.ABTest.4", "FFlagEnableV3MenuABTest3" } }; // only one missing here is Metal because lol @@ -91,7 +96,8 @@ namespace Bloxstrap new Dictionary { { "DisableV2", null }, - { "EnableV4", null } + { "EnableV4", null }, + { "ABTest", null } } }, @@ -100,7 +106,8 @@ namespace Bloxstrap new Dictionary { { "DisableV2", "True" }, - { "EnableV4", "False" } + { "EnableV4", "False" }, + { "ABTest", "False" } } }, @@ -109,7 +116,8 @@ namespace Bloxstrap new Dictionary { { "DisableV2", "False" }, - { "EnableV4", "False" } + { "EnableV4", "False" }, + { "ABTest", "False" } } }, @@ -118,7 +126,8 @@ namespace Bloxstrap new Dictionary { { "DisableV2", "True" }, - { "EnableV4", "True" } + { "EnableV4", "True" }, + { "ABTest", "False" } } } }; diff --git a/Bloxstrap/GlobalUsings.cs b/Bloxstrap/GlobalUsings.cs index 105c8d9..15c873c 100644 --- a/Bloxstrap/GlobalUsings.cs +++ b/Bloxstrap/GlobalUsings.cs @@ -18,8 +18,9 @@ global using Bloxstrap.Enums; global using Bloxstrap.Exceptions; global using Bloxstrap.Extensions; global using Bloxstrap.Models; -global using Bloxstrap.Models.BloxstrapRPC; global using Bloxstrap.Models.Attributes; +global using Bloxstrap.Models.BloxstrapRPC; global using Bloxstrap.Models.RobloxApi; +global using Bloxstrap.Models.Manifest; global using Bloxstrap.UI; global using Bloxstrap.Utility; \ No newline at end of file diff --git a/Bloxstrap/InstallChecker.cs b/Bloxstrap/InstallChecker.cs index a833acb..7e4bb09 100644 --- a/Bloxstrap/InstallChecker.cs +++ b/Bloxstrap/InstallChecker.cs @@ -223,6 +223,10 @@ namespace Bloxstrap else if (existingVersionInfo.ProductVersion == "2.5.0") { App.FastFlags.SetValue("FIntDebugForceMSAASamples", null); + + if (App.FastFlags.GetPreset("UI.Menu.Style.DisableV2") is not null) + App.FastFlags.SetPreset("UI.Menu.Style.ABTest", false); + App.FastFlags.Save(); } } diff --git a/Bloxstrap/Integrations/DiscordRichPresence.cs b/Bloxstrap/Integrations/DiscordRichPresence.cs index 5d74c73..ab920d1 100644 --- a/Bloxstrap/Integrations/DiscordRichPresence.cs +++ b/Bloxstrap/Integrations/DiscordRichPresence.cs @@ -261,6 +261,9 @@ namespace Bloxstrap.Integrations _ => $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""), }; + if (universeDetails.Name.Length < 2) + universeDetails.Name = $"{universeDetails.Name}\x2800\x2800\x2800"; + _currentPresence = new DiscordRPC.RichPresence { Details = $"Playing {universeDetails.Name}", diff --git a/Bloxstrap/Models/Manifest/FileManifest.cs b/Bloxstrap/Models/Manifest/FileManifest.cs new file mode 100644 index 0000000..904f1ec --- /dev/null +++ b/Bloxstrap/Models/Manifest/FileManifest.cs @@ -0,0 +1,33 @@ +namespace Bloxstrap.Models.Manifest +{ + public class FileManifest : List + { + private FileManifest(string data) + { + using StringReader reader = new StringReader(data); + + while (true) + { + string? fileName = reader.ReadLine(); + string? signature = reader.ReadLine(); + + if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(signature)) + break; + + Add(new ManifestFile + { + Name = fileName, + Signature = signature + }); + } + } + + public static async Task Get(string versionGuid) + { + string pkgManifestUrl = RobloxDeployment.GetLocation($"/{versionGuid}-rbxManifest.txt"); + var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl); + + return new FileManifest(pkgManifestData); + } + } +} diff --git a/Bloxstrap/Models/Manifest/ManifestFile.cs b/Bloxstrap/Models/Manifest/ManifestFile.cs new file mode 100644 index 0000000..71b1976 --- /dev/null +++ b/Bloxstrap/Models/Manifest/ManifestFile.cs @@ -0,0 +1,13 @@ +namespace Bloxstrap.Models.Manifest +{ + public class ManifestFile + { + public string Name { get; set; } = ""; + public string Signature { get; set; } = ""; + + public override string ToString() + { + return $"[{Signature}] {Name}"; + } + } +} diff --git a/Bloxstrap/Models/Package.cs b/Bloxstrap/Models/Manifest/Package.cs similarity index 92% rename from Bloxstrap/Models/Package.cs rename to Bloxstrap/Models/Manifest/Package.cs index 8b6be56..6feb162 100644 --- a/Bloxstrap/Models/Package.cs +++ b/Bloxstrap/Models/Manifest/Package.cs @@ -4,7 +4,7 @@ * Copyright (c) 2015-present MaximumADHD */ -namespace Bloxstrap.Models +namespace Bloxstrap.Models.Manifest { public class Package { diff --git a/Bloxstrap/Models/PackageManifest.cs b/Bloxstrap/Models/Manifest/PackageManifest.cs similarity index 98% rename from Bloxstrap/Models/PackageManifest.cs rename to Bloxstrap/Models/Manifest/PackageManifest.cs index 4d7c3a1..13c7172 100644 --- a/Bloxstrap/Models/PackageManifest.cs +++ b/Bloxstrap/Models/Manifest/PackageManifest.cs @@ -4,7 +4,7 @@ * Copyright (c) 2015-present MaximumADHD */ -namespace Bloxstrap.Models +namespace Bloxstrap.Models.Manifest { public class PackageManifest : List { diff --git a/Bloxstrap/RobloxDeployment.cs b/Bloxstrap/RobloxDeployment.cs index 598812c..cdeda88 100644 --- a/Bloxstrap/RobloxDeployment.cs +++ b/Bloxstrap/RobloxDeployment.cs @@ -52,18 +52,6 @@ return _baseUrl; } } - - // most commonly used/interesting channels - public static readonly List SelectableChannels = new() - { - "LIVE", - "ZFlag", - "ZNext", - "ZCanary", - "ZIntegration", - "ZAvatarTeam", - "ZSocialTeam" - }; #endregion public static string GetLocation(string resource, string? channel = null) diff --git a/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs b/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs index d2f6a8a..8354ced 100644 --- a/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs +++ b/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs @@ -96,7 +96,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu private void RichPresenceMenuItem_Click(object sender, RoutedEventArgs e) => _richPresenceHandler?.SetVisibility(((MenuItem)sender).IsChecked); - private void InviteDeeplinkMenuItem_Click(object sender, RoutedEventArgs e) => Clipboard.SetText($"roblox://experiences/start?placeId={_activityWatcher?.ActivityPlaceId}&gameInstanceId={_activityWatcher?.ActivityJobId}"); + private void InviteDeeplinkMenuItem_Click(object sender, RoutedEventArgs e) => Clipboard.SetDataObject($"roblox://experiences/start?placeId={_activityWatcher?.ActivityPlaceId}&gameInstanceId={_activityWatcher?.ActivityJobId}"); private void ServerDetailsMenuItem_Click(object sender, RoutedEventArgs e) => ShowServerInformationWindow(); diff --git a/Bloxstrap/UI/Elements/Dialogs/AddFastFlagDialog.xaml b/Bloxstrap/UI/Elements/Dialogs/AddFastFlagDialog.xaml index 06e7cc0..3a47528 100644 --- a/Bloxstrap/UI/Elements/Dialogs/AddFastFlagDialog.xaml +++ b/Bloxstrap/UI/Elements/Dialogs/AddFastFlagDialog.xaml @@ -33,10 +33,10 @@ - + - + diff --git a/Bloxstrap/UI/Elements/Dialogs/BulkAddFastFlagDialog.xaml b/Bloxstrap/UI/Elements/Dialogs/BulkAddFastFlagDialog.xaml new file mode 100644 index 0000000..4cef49f --- /dev/null +++ b/Bloxstrap/UI/Elements/Dialogs/BulkAddFastFlagDialog.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + Paste in your JSON here... + + + + + + +