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}");
+ }
}
}