diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs index 691d868..dfc11ca 100644 --- a/Bloxstrap/App.xaml.cs +++ b/Bloxstrap/App.xaml.cs @@ -42,7 +42,7 @@ namespace Bloxstrap public static bool IsProductionBuild => IsActionBuild && BuildMetadata.CommitRef.StartsWith("tag", StringComparison.Ordinal); - public static bool IsStudioVisible => !String.IsNullOrEmpty(App.State.Prop.Studio.VersionGuid); + public static bool IsStudioVisible => !String.IsNullOrEmpty(App.RobloxState.Prop.Studio.VersionGuid); public static readonly MD5 MD5Provider = MD5.Create(); @@ -54,6 +54,8 @@ namespace Bloxstrap public static readonly JsonManager State = new(); + public static readonly JsonManager RobloxState = new(); + public static readonly FastFlagManager FastFlags = new(); public static readonly HttpClient HttpClient = new( @@ -336,6 +338,7 @@ namespace Bloxstrap Settings.Load(); State.Load(); + RobloxState.Load(); FastFlags.Load(); if (!Locale.SupportedLocales.ContainsKey(Settings.Prop.Locale)) diff --git a/Bloxstrap/AppData/RobloxPlayerData.cs b/Bloxstrap/AppData/RobloxPlayerData.cs index 3c4f728..bba6cc8 100644 --- a/Bloxstrap/AppData/RobloxPlayerData.cs +++ b/Bloxstrap/AppData/RobloxPlayerData.cs @@ -16,7 +16,7 @@ namespace Bloxstrap.AppData public override string ExecutableName => "RobloxPlayerBeta.exe"; - public override AppState State => App.State.Prop.Player; + public override AppState State => App.RobloxState.Prop.Player; public override IReadOnlyDictionary PackageDirectoryMap { get; set; } = new Dictionary() { diff --git a/Bloxstrap/AppData/RobloxStudioData.cs b/Bloxstrap/AppData/RobloxStudioData.cs index 2ada1c2..18c8e36 100644 --- a/Bloxstrap/AppData/RobloxStudioData.cs +++ b/Bloxstrap/AppData/RobloxStudioData.cs @@ -10,7 +10,7 @@ public override string ExecutableName => "RobloxStudioBeta.exe"; - public override AppState State => App.State.Prop.Studio; + public override AppState State => App.RobloxState.Prop.Studio; public override IReadOnlyDictionary PackageDirectoryMap { get; set; } = new Dictionary() { diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index 7361353..f680e00 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -49,6 +49,7 @@ namespace Bloxstrap private LaunchMode _launchMode; private string _launchCommandLine = App.LaunchSettings.RobloxLaunchArgs; + private Version? _latestVersion = null; private string _latestVersionGuid = null!; private string _latestVersionDirectory = null!; private PackageManifest _versionPackageManifest = null!; @@ -60,7 +61,7 @@ namespace Bloxstrap private long _totalDownloadedBytes = 0; private bool _packageExtractionSuccess = true; - private bool _mustUpgrade => App.LaunchSettings.ForceFlag.Active || String.IsNullOrEmpty(AppData.State.VersionGuid) || !File.Exists(AppData.ExecutablePath); + private bool _mustUpgrade => App.LaunchSettings.ForceFlag.Active || App.State.Prop.ForceReinstall || String.IsNullOrEmpty(AppData.State.VersionGuid) || !File.Exists(AppData.ExecutablePath); private bool _noConnection = false; private AsyncMutex? _mutex; @@ -70,6 +71,9 @@ namespace Bloxstrap public IBootstrapperDialog? Dialog = null; public bool IsStudioLaunch => _launchMode != LaunchMode.Player; + + public string MutexName { get; set; } = "Bloxstrap-Bootstrapper"; + public bool QuitIfMutexExists { get; set; } = false; #endregion #region Core @@ -198,22 +202,24 @@ namespace Bloxstrap // 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; + bool mutexExists = Utilities.DoesMutexExist(MutexName); - try + if (mutexExists) { - 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 + if (!QuitIfMutexExists) + { + App.Logger.WriteLine(LOG_IDENT, $"{MutexName} mutex exists, waiting..."); + SetStatus(Strings.Bootstrapper_Status_WaitingOtherInstances); + } + else + { + App.Logger.WriteLine(LOG_IDENT, $"{MutexName} mutex exists, exiting!"); + return; + } } // wait for mutex to be released if it's not yet - await using var mutex = new AsyncMutex(false, "Bloxstrap-Bootstrapper"); + await using var mutex = new AsyncMutex(false, MutexName); await mutex.AcquireAsync(_cancelTokenSource.Token); _mutex = mutex; @@ -223,6 +229,7 @@ namespace Bloxstrap { App.Settings.Load(); App.State.Load(); + App.RobloxState.Load(); } if (!_noConnection) @@ -237,12 +244,35 @@ namespace Bloxstrap } } + CleanupVersionsFolder(); // cleanup after background updater + bool allModificationsApplied = true; if (!_noConnection) { if (AppData.State.VersionGuid != _latestVersionGuid || _mustUpgrade) - await UpgradeRoblox(); + { + bool backgroundUpdaterMutexOpen = Utilities.DoesMutexExist("Bloxstrap-BackgroundUpdater"); + if (App.LaunchSettings.BackgroundUpdaterFlag.Active) + backgroundUpdaterMutexOpen = false; // we want to actually update lol + + App.Logger.WriteLine(LOG_IDENT, $"Background updater running: {backgroundUpdaterMutexOpen}"); + + if (backgroundUpdaterMutexOpen && _mustUpgrade) + { + // I am Forced Upgrade, killer of Background Updates + Utilities.KillBackgroundUpdater(); + backgroundUpdaterMutexOpen = false; + } + + if (!backgroundUpdaterMutexOpen) + { + if (IsEligibleForBackgroundUpdate()) + StartBackgroundUpdater(); + else + await UpgradeRoblox(); + } + } if (_cancelTokenSource.IsCancellationRequested) return; @@ -339,11 +369,13 @@ namespace Bloxstrap key.SetValueSafe("www.roblox.com", Deployment.IsDefaultChannel ? "" : Deployment.Channel); _latestVersionGuid = clientVersion.VersionGuid; + _latestVersion = Utilities.ParseVersionSafe(clientVersion.Version); } else { App.Logger.WriteLine(LOG_IDENT, $"Version set to {App.LaunchSettings.VersionFlag.Data} from arguments"); _latestVersionGuid = App.LaunchSettings.VersionFlag.Data; + // we can't determine the version } _latestVersionDirectory = Path.Combine(Paths.Versions, _latestVersionGuid); @@ -366,6 +398,79 @@ namespace Bloxstrap } } + private bool IsEligibleForBackgroundUpdate() + { + const string LOG_IDENT = "Bootstrapper::IsEligibleForBackgroundUpdate"; + + if (App.LaunchSettings.BackgroundUpdaterFlag.Active) + { + App.Logger.WriteLine(LOG_IDENT, "Not eligible: Is the background updater process"); + return false; + } + + if (!App.Settings.Prop.BackgroundUpdatesEnabled) + { + App.Logger.WriteLine(LOG_IDENT, "Not eligible: Background updates disabled"); + return false; + } + + if (IsStudioLaunch) + { + App.Logger.WriteLine(LOG_IDENT, "Not eligible: Studio launch"); + return false; + } + + if (_mustUpgrade) + { + App.Logger.WriteLine(LOG_IDENT, "Not eligible: Must upgrade is true"); + return false; + } + + // at least 3GB of free space + const long minimumFreeSpace = 3_000_000_000; + long space = Filesystem.GetFreeDiskSpace(Paths.Base); + if (space < minimumFreeSpace) + { + App.Logger.WriteLine(LOG_IDENT, $"Not eligible: User has {space} free space, at least {minimumFreeSpace} is required"); + return false; + } + + if (_latestVersion == default) + { + App.Logger.WriteLine(LOG_IDENT, "Not eligible: Latest version is undefined"); + return false; + } + + Version? currentVersion = Utilities.GetRobloxVersion(AppData); + if (currentVersion == default) + { + App.Logger.WriteLine(LOG_IDENT, "Not eligible: Current version is undefined"); + return false; + } + + // always normally upgrade for downgrades + if (currentVersion.Minor > _latestVersion.Minor) + { + App.Logger.WriteLine(LOG_IDENT, "Not eligible: Downgrade"); + return false; + } + + // only background update if we're: + // - one major update behind + // - the same major update + int diff = _latestVersion.Minor - currentVersion.Minor; + if (diff == 0 || diff == 1) + { + App.Logger.WriteLine(LOG_IDENT, "Eligible"); + return true; + } + else + { + App.Logger.WriteLine(LOG_IDENT, $"Not eligible: Major version diff is {diff}"); + return false; + } + } + private void StartRoblox() { const string LOG_IDENT = "Bootstrapper::StartRoblox"; @@ -718,13 +823,20 @@ namespace Bloxstrap { const string LOG_IDENT = "Bootstrapper::CleanupVersionsFolder"; + if (App.LaunchSettings.BackgroundUpdaterFlag.Active) + { + App.Logger.WriteLine(LOG_IDENT, "Background updater tried to cleanup, stopping!"); + return; + } + 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) + if (dirName != App.RobloxState.Prop.Player.VersionGuid && dirName != App.RobloxState.Prop.Studio.VersionGuid) { - Filesystem.AssertReadOnlyDirectory(dir); + // TODO: this is too expensive + //Filesystem.AssertReadOnlyDirectory(dir); // check if it's still being used first // we dont want to accidentally delete the files of a running roblox instance @@ -801,11 +913,11 @@ namespace Bloxstrap _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 + if (!App.LaunchSettings.BackgroundUpdaterFlag.Active && !IsStudioLaunch) // TODO: wait for studio processes to close before updating to prevent data loss KillRobloxPlayers(); // get a fully clean install - if (Directory.Exists(_latestVersionDirectory)) + if (!App.LaunchSettings.BackgroundUpdaterFlag.Active && Directory.Exists(_latestVersionDirectory)) { try { @@ -958,8 +1070,8 @@ namespace Bloxstrap var allPackageHashes = new List(); - allPackageHashes.AddRange(App.State.Prop.Player.PackageHashes.Values); - allPackageHashes.AddRange(App.State.Prop.Studio.PackageHashes.Values); + allPackageHashes.AddRange(App.RobloxState.Prop.Player.PackageHashes.Values); + allPackageHashes.AddRange(App.RobloxState.Prop.Studio.PackageHashes.Values); if (!App.Settings.Prop.DebugDisableVersionPackageCleanup) { @@ -988,7 +1100,7 @@ namespace Bloxstrap AppData.State.Size = distributionSize; - int totalSize = App.State.Prop.Player.Size + App.State.Prop.Studio.Size; + int totalSize = App.RobloxState.Prop.Player.Size + App.RobloxState.Prop.Studio.Size; using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey)) { @@ -998,10 +1110,26 @@ namespace Bloxstrap App.Logger.WriteLine(LOG_IDENT, $"Registered as {totalSize} KB"); App.State.Save(); + App.RobloxState.Save(); _isInstalling = false; } + private static void StartBackgroundUpdater() + { + const string LOG_IDENT = "Bootstrapper::StartBackgroundUpdater"; + + if (Utilities.DoesMutexExist("Bloxstrap-BackgroundUpdater")) + { + App.Logger.WriteLine(LOG_IDENT, "Background updater already running"); + return; + } + + App.Logger.WriteLine(LOG_IDENT, "Starting background updater"); + + Process.Start(Paths.Process, "-backgroundupdater"); + } + private async Task ApplyModifications() { const string LOG_IDENT = "Bootstrapper::ApplyModifications"; @@ -1135,7 +1263,7 @@ namespace Bloxstrap var fileRestoreMap = new Dictionary>(); - foreach (string fileLocation in App.State.Prop.ModManifest) + foreach (string fileLocation in App.RobloxState.Prop.ModManifest) { if (modFolderFiles.Contains(fileLocation)) continue; @@ -1180,8 +1308,17 @@ namespace Bloxstrap } } - App.State.Prop.ModManifest = modFolderFiles; - App.State.Save(); + // make sure we're not overwriting a new update + // if we're the background update process, always overwrite + if (App.LaunchSettings.BackgroundUpdaterFlag.Active || !App.RobloxState.HasFileOnDiskChanged()) + { + App.RobloxState.Prop.ModManifest = modFolderFiles; + App.RobloxState.Save(); + } + else + { + App.Logger.WriteLine(LOG_IDENT, "RobloxState disk mismatch, not saving ModManifest"); + } App.Logger.WriteLine(LOG_IDENT, $"Finished checking file mods"); diff --git a/Bloxstrap/Installer.cs b/Bloxstrap/Installer.cs index 93f40c1..d57e985 100644 --- a/Bloxstrap/Installer.cs +++ b/Bloxstrap/Installer.cs @@ -197,7 +197,7 @@ namespace Bloxstrap var processes = new List(); - if (!String.IsNullOrEmpty(App.State.Prop.Player.VersionGuid)) + if (!String.IsNullOrEmpty(App.RobloxState.Prop.Player.VersionGuid)) processes.AddRange(Process.GetProcessesByName(App.RobloxPlayerAppName)); if (App.IsStudioVisible) @@ -587,16 +587,23 @@ namespace Bloxstrap } } - if (Utilities.CompareVersions(existingVer, "2.8.3") == VersionComparison.LessThan) + if (Utilities.CompareVersions(existingVer, "2.9.0") == VersionComparison.LessThan) { - // force reinstallation - App.State.Prop.Player.VersionGuid = ""; - App.State.Prop.Studio.VersionGuid = ""; + // move from App.State to App.RobloxState + if (App.State.Prop.GetDeprecatedPlayer() != null) + App.RobloxState.Prop.Player = App.State.Prop.GetDeprecatedPlayer()!; + + if (App.State.Prop.GetDeprecatedStudio() != null) + App.RobloxState.Prop.Studio = App.State.Prop.GetDeprecatedStudio()!; + + if (App.State.Prop.GetDeprecatedModManifest() != null) + App.RobloxState.Prop.ModManifest = App.State.Prop.GetDeprecatedModManifest()!; } App.Settings.Save(); App.FastFlags.Save(); App.State.Save(); + App.RobloxState.Save(); } if (currentVer is null) diff --git a/Bloxstrap/JsonManager.cs b/Bloxstrap/JsonManager.cs index bb1eb77..6c04a2a 100644 --- a/Bloxstrap/JsonManager.cs +++ b/Bloxstrap/JsonManager.cs @@ -8,6 +8,11 @@ namespace Bloxstrap public T Prop { get; set; } = new(); + /// + /// The file hash when last retrieved from disk + /// + public string? LastFileHash { get; private set; } + public virtual string ClassName => typeof(T).Name; public virtual string FileLocation => Path.Combine(Paths.Base, $"{ClassName}.json"); @@ -22,12 +27,15 @@ namespace Bloxstrap try { - T? settings = JsonSerializer.Deserialize(File.ReadAllText(FileLocation)); + string contents = File.ReadAllText(FileLocation); + + T? settings = JsonSerializer.Deserialize(contents); if (settings is null) throw new ArgumentNullException("Deserialization returned null"); Prop = settings; + LastFileHash = MD5Hash.FromString(contents); App.Logger.WriteLine(LOG_IDENT, "Loaded successfully!"); } @@ -74,7 +82,11 @@ namespace Bloxstrap try { - File.WriteAllText(FileLocation, JsonSerializer.Serialize(Prop, new JsonSerializerOptions { WriteIndented = true })); + string contents = JsonSerializer.Serialize(Prop, new JsonSerializerOptions { WriteIndented = true }); + + File.WriteAllText(FileLocation, contents); + + LastFileHash = MD5Hash.FromString(contents); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { @@ -89,5 +101,13 @@ namespace Bloxstrap App.Logger.WriteLine(LOG_IDENT, "Save complete!"); } + + /// + /// Is the file on disk different to the one deserialised during this session? + /// + public bool HasFileOnDiskChanged() + { + return LastFileHash != MD5Hash.FromFile(FileLocation); + } } } diff --git a/Bloxstrap/LaunchHandler.cs b/Bloxstrap/LaunchHandler.cs index 2dda0c9..1eee8b2 100644 --- a/Bloxstrap/LaunchHandler.cs +++ b/Bloxstrap/LaunchHandler.cs @@ -4,6 +4,7 @@ using Windows.Win32; using Windows.Win32.Foundation; using Bloxstrap.UI.Elements.Dialogs; +using Bloxstrap.Enums; namespace Bloxstrap { @@ -58,6 +59,11 @@ namespace Bloxstrap App.Logger.WriteLine(LOG_IDENT, "Opening watcher"); LaunchWatcher(); } + else if (App.LaunchSettings.BackgroundUpdaterFlag.Active) + { + App.Logger.WriteLine(LOG_IDENT, "Opening background updater"); + LaunchBackgroundUpdater(); + } else if (App.LaunchSettings.RobloxLaunchMode != LaunchMode.None) { App.Logger.WriteLine(LOG_IDENT, $"Opening bootstrapper ({App.LaunchSettings.RobloxLaunchMode})"); @@ -295,5 +301,51 @@ namespace Bloxstrap App.Terminate(); }); } + + public static void LaunchBackgroundUpdater() + { + const string LOG_IDENT = "LaunchHandler::LaunchBackgroundUpdater"; + + // Activate some LaunchFlags we need + App.LaunchSettings.QuietFlag.Active = true; + App.LaunchSettings.NoLaunchFlag.Active = true; + + App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper"); + App.Bootstrapper = new Bootstrapper(LaunchMode.Player) + { + MutexName = "Bloxstrap-BackgroundUpdater", + QuitIfMutexExists = true + }; + + CancellationTokenSource cts = new CancellationTokenSource(); + + Task.Run(() => + { + App.Logger.WriteLine(LOG_IDENT, "Started event waiter"); + using (EventWaitHandle handle = new EventWaitHandle(false, EventResetMode.AutoReset, "Bloxstrap-BackgroundUpdaterKillEvent")) + handle.WaitOne(); + + App.Logger.WriteLine(LOG_IDENT, "Received close event, killing it all!"); + App.Bootstrapper.Cancel(); + }, cts.Token); + + Task.Run(App.Bootstrapper.Run).ContinueWith(t => + { + App.Logger.WriteLine(LOG_IDENT, "Bootstrapper task has finished"); + cts.Cancel(); // stop event waiter + + if (t.IsFaulted) + { + App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper"); + + if (t.Exception is not null) + App.FinalizeExceptionHandling(t.Exception); + } + + App.Terminate(); + }); + + App.Logger.WriteLine(LOG_IDENT, "Exiting"); + } } } diff --git a/Bloxstrap/LaunchSettings.cs b/Bloxstrap/LaunchSettings.cs index bd4c36a..e0a5051 100644 --- a/Bloxstrap/LaunchSettings.cs +++ b/Bloxstrap/LaunchSettings.cs @@ -12,31 +12,33 @@ namespace Bloxstrap { public class LaunchSettings { - public LaunchFlag MenuFlag { get; } = new("preferences,menu,settings"); + public LaunchFlag MenuFlag { get; } = new("preferences,menu,settings"); - public LaunchFlag WatcherFlag { get; } = new("watcher"); + public LaunchFlag WatcherFlag { get; } = new("watcher"); - public LaunchFlag QuietFlag { get; } = new("quiet"); + public LaunchFlag BackgroundUpdaterFlag { get; } = new("backgroundupdater"); - public LaunchFlag UninstallFlag { get; } = new("uninstall"); + public LaunchFlag QuietFlag { get; } = new("quiet"); - public LaunchFlag NoLaunchFlag { get; } = new("nolaunch"); + public LaunchFlag UninstallFlag { get; } = new("uninstall"); + + public LaunchFlag NoLaunchFlag { get; } = new("nolaunch"); - public LaunchFlag TestModeFlag { get; } = new("testmode"); + public LaunchFlag TestModeFlag { get; } = new("testmode"); - public LaunchFlag NoGPUFlag { get; } = new("nogpu"); + public LaunchFlag NoGPUFlag { get; } = new("nogpu"); - public LaunchFlag UpgradeFlag { get; } = new("upgrade"); + public LaunchFlag UpgradeFlag { get; } = new("upgrade"); - public LaunchFlag PlayerFlag { get; } = new("player"); + public LaunchFlag PlayerFlag { get; } = new("player"); - public LaunchFlag StudioFlag { get; } = new("studio"); + public LaunchFlag StudioFlag { get; } = new("studio"); - public LaunchFlag VersionFlag { get; } = new("version"); + public LaunchFlag VersionFlag { get; } = new("version"); - public LaunchFlag ChannelFlag { get; } = new("channel"); + public LaunchFlag ChannelFlag { get; } = new("channel"); - public LaunchFlag ForceFlag { get; } = new("force"); + public LaunchFlag ForceFlag { get; } = new("force"); #if DEBUG public bool BypassUpdateCheck => true; diff --git a/Bloxstrap/Models/Persistable/RobloxState.cs b/Bloxstrap/Models/Persistable/RobloxState.cs new file mode 100644 index 0000000..f4b3098 --- /dev/null +++ b/Bloxstrap/Models/Persistable/RobloxState.cs @@ -0,0 +1,11 @@ +namespace Bloxstrap.Models.Persistable +{ + public class RobloxState + { + public AppState Player { get; set; } = new(); + + public AppState Studio { get; set; } = new(); + + public List ModManifest { get; set; } = new(); + } +} diff --git a/Bloxstrap/Models/Persistable/Settings.cs b/Bloxstrap/Models/Persistable/Settings.cs index 7523cb3..0f6bb86 100644 --- a/Bloxstrap/Models/Persistable/Settings.cs +++ b/Bloxstrap/Models/Persistable/Settings.cs @@ -17,6 +17,7 @@ namespace Bloxstrap.Models.Persistable public bool UseFastFlagManager { get; set; } = true; public bool WPFSoftwareRender { get; set; } = false; public bool EnableAnalytics { get; set; } = true; + public bool BackgroundUpdatesEnabled { get; set; } = true; public bool DebugDisableVersionPackageCleanup { get; set; } = false; public string? SelectedCustomTheme { get; set; } = null; diff --git a/Bloxstrap/Models/Persistable/State.cs b/Bloxstrap/Models/Persistable/State.cs index de05265..070f8e0 100644 --- a/Bloxstrap/Models/Persistable/State.cs +++ b/Bloxstrap/Models/Persistable/State.cs @@ -6,12 +6,28 @@ public bool PromptWebView2Install { get; set; } = true; - public AppState Player { get; set; } = new(); - - public AppState Studio { get; set; } = new(); + public bool ForceReinstall { get; set; } = false; public WindowState SettingsWindow { get; set; } = new(); - public List ModManifest { get; set; } = new(); + #region Deprecated properties + /// + /// Deprecated, use App.RobloxState.Player + /// + public AppState? Player { private get; set; } + public AppState? GetDeprecatedPlayer() => Player; + + /// + /// Deprecated, use App.RobloxState.Studio + /// + public AppState? Studio { private get; set; } + public AppState? GetDeprecatedStudio() => Studio; + + /// + /// Deprecated, use App.RobloxState.ModManifest + /// + public List? ModManifest { private get; set; } + public List? GetDeprecatedModManifest() => ModManifest; + #endregion } } diff --git a/Bloxstrap/Resources/Strings.Designer.cs b/Bloxstrap/Resources/Strings.Designer.cs index e84f7f9..47190a4 100644 --- a/Bloxstrap/Resources/Strings.Designer.cs +++ b/Bloxstrap/Resources/Strings.Designer.cs @@ -2545,6 +2545,24 @@ namespace Bloxstrap.Resources { } } + /// + /// Looks up a localized string similar to Update Roblox in the background instead of waiting. Not recommended for slow networks. At least 3GB of free storage space is required for this feature to work.. + /// + public static string Menu_Behaviour_BackgroundUpdates_Description { + get { + return ResourceManager.GetString("Menu.Behaviour.BackgroundUpdates.Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Background updates. + /// + public static string Menu_Behaviour_BackgroundUpdates_Title { + get { + return ResourceManager.GetString("Menu.Behaviour.BackgroundUpdates.Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Prevent against closures of your existing game from accidentally launching another one.. /// diff --git a/Bloxstrap/Resources/Strings.resx b/Bloxstrap/Resources/Strings.resx index 8ac1611..56964f7 100644 --- a/Bloxstrap/Resources/Strings.resx +++ b/Bloxstrap/Resources/Strings.resx @@ -1445,4 +1445,10 @@ Defaulting to Fluent. Failed to rename custom theme {0}: {1} + + Background updates + + + Update Roblox in the background instead of waiting. Not recommended for slow networks. At least 3GB of free storage space is required for this feature to work. + \ No newline at end of file diff --git a/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml.cs b/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml.cs index 5afcc43..7a89d97 100644 --- a/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml.cs +++ b/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml.cs @@ -104,7 +104,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper public ByfronDialog() { - string version = Utilities.GetRobloxVersion(Bootstrapper?.IsStudioLaunch ?? false); + string version = Utilities.GetRobloxVersionStr(Bootstrapper?.IsStudioLaunch ?? false); _viewModel = new ByfronDialogViewModel(this, version); DataContext = _viewModel; Title = App.Settings.Prop.BootstrapperTitle; diff --git a/Bloxstrap/UI/Elements/Settings/Pages/BootstrapperPage.xaml b/Bloxstrap/UI/Elements/Settings/Pages/BootstrapperPage.xaml index 370b84f..569e526 100644 --- a/Bloxstrap/UI/Elements/Settings/Pages/BootstrapperPage.xaml +++ b/Bloxstrap/UI/Elements/Settings/Pages/BootstrapperPage.xaml @@ -30,13 +30,19 @@ + + + +