diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 00cd191..a52add6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -45,6 +45,16 @@ body: description: Provide a comprehensive description of the problem you're facing. Don't forget to attach any additional resources you may have, such as log files and screenshots. validations: required: true + - type: textarea + id: repro-steps + attributes: + label: How do you reproduce the problem? + description: Include the steps to reproduce the problem from start to finish. Include details such as FastFlags you added and settings you changed. + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error - type: textarea id: log attributes: diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs index 5083121..691d868 100644 --- a/Bloxstrap/App.xaml.cs +++ b/Bloxstrap/App.xaml.cs @@ -181,6 +181,22 @@ namespace Bloxstrap } } + 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"; @@ -213,6 +229,8 @@ namespace Bloxstrap #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}"); @@ -292,6 +310,7 @@ namespace Bloxstrap { Logger.Initialize(true); Logger.WriteLine(LOG_IDENT, "Not installed, launching the installer"); + AssertWindowsOSVersion(); // prevent new installs from unsupported operating systems LaunchHandler.LaunchInstaller(); } else diff --git a/Bloxstrap/Bloxstrap.csproj b/Bloxstrap/Bloxstrap.csproj index 9426fa2..bb30ff6 100644 --- a/Bloxstrap/Bloxstrap.csproj +++ b/Bloxstrap/Bloxstrap.csproj @@ -7,8 +7,8 @@ true True Bloxstrap.ico - 2.8.6 - 2.8.6 + 2.9.0 + 2.9.0 app.manifest true false @@ -55,10 +55,10 @@ - + - - + + all diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index ea0cd74..3bf0cff 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -58,6 +58,7 @@ namespace Bloxstrap private double _taskbarProgressIncrement; private double _taskbarProgressMaximum; private long _totalDownloadedBytes = 0; + private bool _packageExtractionSuccess = true; private bool _mustUpgrade => String.IsNullOrEmpty(AppData.State.VersionGuid) || !File.Exists(AppData.ExecutablePath); private bool _noConnection = false; @@ -78,7 +79,15 @@ namespace Bloxstrap // 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.FileFailure += (_, e) => + { + // only give a pass to font files (no idea whats wrong with them) + if (!e.Name.EndsWith(".ttf")) + throw e.Exception; + + App.Logger.WriteLine("FastZipEvents::OnFileFailure", $"Failed to extract {e.Name}"); + _packageExtractionSuccess = false; + }; _fastZipEvents.DirectoryFailure += (_, e) => throw e.Exception; _fastZipEvents.ProcessFile += (_, e) => e.ContinueRunning = !_cancelTokenSource.IsCancellationRequested; @@ -179,6 +188,8 @@ namespace Bloxstrap } #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 @@ -221,6 +232,8 @@ namespace Bloxstrap } } + bool allModificationsApplied = true; + if (!_noConnection) { if (AppData.State.VersionGuid != _latestVersionGuid || _mustUpgrade) @@ -231,7 +244,7 @@ namespace Bloxstrap // 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(); + allModificationsApplied = await ApplyModifications(); } // check registry entries for every launch, just in case the stock bootstrapper changes it back @@ -245,7 +258,15 @@ namespace Bloxstrap await mutex.ReleaseAsync(); if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelTokenSource.IsCancellationRequested) + { + // show some balloon tips + if (!_packageExtractionSuccess) + Frontend.ShowBalloonTip(Strings.Bootstrapper_ExtractionFailed_Title, Strings.Bootstrapper_ExtractionFailed_Message, ToolTipIcon.Warning); + else if (!allModificationsApplied) + Frontend.ShowBalloonTip(Strings.Bootstrapper_ModificationsFailed_Title, Strings.Bootstrapper_ModificationsFailed_Message, ToolTipIcon.Warning); + StartRoblox(); + } await mutex.ReleaseAsync(); @@ -303,14 +324,6 @@ namespace Bloxstrap clientVersion = await Deployment.GetInfo(); } - if (clientVersion.IsBehindDefaultChannel) - { - App.Logger.WriteLine(LOG_IDENT, $"Resetting channel from {Deployment.Channel} because it's behind production"); - - Deployment.Channel = Deployment.DefaultChannel; - clientVersion = await Deployment.GetInfo(); - } - key.SetValueSafe("www.roblox.com", Deployment.IsDefaultChannel ? "" : Deployment.Channel); _latestVersionGuid = clientVersion.VersionGuid; @@ -649,7 +662,28 @@ namespace Bloxstrap #endregion #region Roblox Install - private void CleanupVersionsFolder() + private static bool TryDeleteRobloxInDirectory(string dir) + { + string clientPath = Path.Combine(dir, "RobloxPlayerBeta.exe"); + if (!File.Exists(dir)) + { + clientPath = Path.Combine(dir, "RobloxStudioBeta.exe"); + if (!File.Exists(dir)) + return true; // ok??? + } + + try + { + File.Delete(clientPath); + return true; + } + catch (Exception) + { + return false; + } + } + + public static void CleanupVersionsFolder() { const string LOG_IDENT = "Bootstrapper::CleanupVersionsFolder"; @@ -659,6 +693,13 @@ namespace Bloxstrap if (dirName != App.State.Prop.Player.VersionGuid && dirName != App.State.Prop.Studio.VersionGuid) { + Filesystem.AssertReadOnlyDirectory(dir); + + // check if it's still being used first + // we dont want to accidentally delete the files of a running roblox instance + if (!TryDeleteRobloxInDirectory(dir)) + continue; + try { Directory.Delete(dir, true); @@ -927,10 +968,12 @@ namespace Bloxstrap _isInstalling = false; } - private async Task ApplyModifications() + private async Task ApplyModifications() { const string LOG_IDENT = "Bootstrapper::ApplyModifications"; + bool success = true; + SetStatus(Strings.Bootstrapper_Status_ApplyingModifications); // handle file mods @@ -1006,7 +1049,7 @@ namespace Bloxstrap foreach (string file in Directory.GetFiles(Paths.Modifications, "*.*", SearchOption.AllDirectories)) { if (_cancelTokenSource.IsCancellationRequested) - return; + return true; // get relative directory path string relativeFile = file.Substring(Paths.Modifications.Length + 1); @@ -1038,10 +1081,18 @@ namespace Bloxstrap 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"); + try + { + File.Copy(fileModFolder, fileVersionFolder, true); + Filesystem.AssertReadOnly(fileVersionFolder); + App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} has been copied to the version folder"); + } + catch (Exception ex) + { + App.Logger.WriteLine(LOG_IDENT, $"Failed to apply modification ({relativeFile})"); + App.Logger.WriteException(LOG_IDENT, ex); + success = false; + } } // the manifest is primarily here to keep track of what files have been @@ -1088,7 +1139,7 @@ namespace Bloxstrap if (package is not null) { if (_cancelTokenSource.IsCancellationRequested) - return; + return true; await DownloadPackage(package); ExtractPackage(package, entry.Value); @@ -1099,6 +1150,11 @@ namespace Bloxstrap App.State.Save(); App.Logger.WriteLine(LOG_IDENT, $"Finished checking file mods"); + + if (!success) + App.Logger.WriteLine(LOG_IDENT, "Failed to apply all modifications"); + + return success; } private async Task DownloadPackage(Package package) diff --git a/Bloxstrap/Models/APIs/Roblox/ClientVersion.cs b/Bloxstrap/Models/APIs/Roblox/ClientVersion.cs index 9fa405e..5e584f4 100644 --- a/Bloxstrap/Models/APIs/Roblox/ClientVersion.cs +++ b/Bloxstrap/Models/APIs/Roblox/ClientVersion.cs @@ -12,7 +12,5 @@ public string BootstrapperVersion { get; set; } = null!; public DateTime? Timestamp { get; set; } - - public bool IsBehindDefaultChannel { get; set; } } } diff --git a/Bloxstrap/Resources/Strings.Designer.cs b/Bloxstrap/Resources/Strings.Designer.cs index 93bbed5..8b4247b 100644 --- a/Bloxstrap/Resources/Strings.Designer.cs +++ b/Bloxstrap/Resources/Strings.Designer.cs @@ -151,6 +151,15 @@ namespace Bloxstrap.Resources { } } + /// + /// Looks up a localized string similar to Roblox no longer supports Windows 7 or 8.1. To continue playing Roblox, please upgrade to Windows 10 or newer.. + /// + public static string App_OSDeprecation_Win7_81 { + get { + return ResourceManager.GetString("App.OSDeprecation.Win7_81", resourceCulture); + } + } + /// /// Looks up a localized string similar to Bloxstrap was unable to automatically update to version {0}. Please update it manually by downloading and running it from the website.. /// @@ -169,6 +178,24 @@ namespace Bloxstrap.Resources { } } + /// + /// Looks up a localized string similar to Some content may be missing. Force a Roblox reinstallation in settings to fix this.. + /// + public static string Bootstrapper_ExtractionFailed_Message { + get { + return ResourceManager.GetString("Bootstrapper.ExtractionFailed.Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to extract all files. + /// + public static string Bootstrapper_ExtractionFailed_Title { + get { + return ResourceManager.GetString("Bootstrapper.ExtractionFailed.Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Bloxstrap tried to upgrade Roblox but can't because Roblox's files are still in use. /// @@ -198,6 +225,24 @@ namespace Bloxstrap.Resources { } } + /// + /// Looks up a localized string similar to Not all modifications will be present in the current launch.. + /// + public static string Bootstrapper_ModificationsFailed_Message { + get { + return ResourceManager.GetString("Bootstrapper.ModificationsFailed.Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to apply all modifications. + /// + public static string Bootstrapper_ModificationsFailed_Title { + get { + return ResourceManager.GetString("Bootstrapper.ModificationsFailed.Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Bloxstrap does not have enough disk space to download and install Roblox. Please free up some disk space and try again.. /// diff --git a/Bloxstrap/Resources/Strings.resx b/Bloxstrap/Resources/Strings.resx index 2261088..2b1fb0f 100644 --- a/Bloxstrap/Resources/Strings.resx +++ b/Bloxstrap/Resources/Strings.resx @@ -1270,6 +1270,21 @@ Please close any applications that may be using Roblox's files, and relaunch.All Bloxstrap logs Label that appears next to a checkbox + + Roblox no longer supports Windows 7 or 8.1. To continue playing Roblox, please upgrade to Windows 10 or newer. + + + Failed to extract all files + + + Some content may be missing. Force a Roblox reinstallation in settings to fix this. + + + Failed to apply all modifications + + + Not all modifications will be present in the current launch. + Apache License 2.0 diff --git a/Bloxstrap/RobloxInterfaces/Deployment.cs b/Bloxstrap/RobloxInterfaces/Deployment.cs index da88c14..10fae6c 100644 --- a/Bloxstrap/RobloxInterfaces/Deployment.cs +++ b/Bloxstrap/RobloxInterfaces/Deployment.cs @@ -176,16 +176,15 @@ App.Logger.WriteLine(LOG_IDENT, "Failed to contact clientsettingscdn! Falling back to clientsettings..."); App.Logger.WriteException(LOG_IDENT, ex); - clientVersion = await Http.GetJson("https://clientsettings.roblox.com" + path); - } - - // check if channel is behind LIVE - if (!isDefaultChannel) - { - var defaultClientVersion = await GetInfo(DefaultChannel); - - if (Utilities.CompareVersions(clientVersion.Version, defaultClientVersion.Version) == VersionComparison.LessThan) - clientVersion.IsBehindDefaultChannel = true; + try + { + clientVersion = await Http.GetJson("https://clientsettings.roblox.com" + path); + } + catch (HttpRequestException httpEx) + when (!isDefaultChannel && BadChannelCodes.Contains(httpEx.StatusCode)) + { + throw new InvalidChannelException(httpEx.StatusCode); + } } ClientVersionCache[cacheKey] = clientVersion; diff --git a/Bloxstrap/UI/Frontend.cs b/Bloxstrap/UI/Frontend.cs index cf5537d..8be7e1e 100644 --- a/Bloxstrap/UI/Frontend.cs +++ b/Bloxstrap/UI/Frontend.cs @@ -110,5 +110,17 @@ namespace Bloxstrap.UI return messagebox.Result; })); } + + public static void ShowBalloonTip(string title, string message, System.Windows.Forms.ToolTipIcon icon = System.Windows.Forms.ToolTipIcon.None, int timeout = 5) + { + var notifyIcon = new System.Windows.Forms.NotifyIcon + { + Icon = Properties.Resources.IconBloxstrap, + Text = App.ProjectName, + Visible = true + }; + + notifyIcon.ShowBalloonTip(timeout, title, message, icon); + } } } diff --git a/Bloxstrap/Utility/Filesystem.cs b/Bloxstrap/Utility/Filesystem.cs index 77bd284..13a9d7f 100644 --- a/Bloxstrap/Utility/Filesystem.cs +++ b/Bloxstrap/Utility/Filesystem.cs @@ -31,5 +31,15 @@ namespace Bloxstrap.Utility fileInfo.IsReadOnly = false; App.Logger.WriteLine("Filesystem::AssertReadOnly", $"The following file was set as read-only: {filePath}"); } + + internal static void AssertReadOnlyDirectory(string directoryPath) + { + var directory = new DirectoryInfo(directoryPath) { Attributes = FileAttributes.Normal }; + + foreach (var info in directory.GetFileSystemInfos("*", SearchOption.AllDirectories)) + info.Attributes = FileAttributes.Normal; + + App.Logger.WriteLine("Filesystem::AssertReadOnlyDirectory", $"The following directory was set as read-only: {directoryPath}"); + } } }