Merge branch 'main' into feature/custom-bootstrappers

This commit is contained in:
bluepilledgreat 2025-03-11 11:57:28 +00:00
commit a4a82e1057
10 changed files with 199 additions and 35 deletions

View File

@ -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. 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: validations:
required: true 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 - type: textarea
id: log id: log
attributes: attributes:

View File

@ -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) protected override void OnStartup(StartupEventArgs e)
{ {
const string LOG_IDENT = "App::OnStartup"; const string LOG_IDENT = "App::OnStartup";
@ -213,6 +229,8 @@ namespace Bloxstrap
#endif #endif
} }
Logger.WriteLine(LOG_IDENT, $"OSVersion: {Environment.OSVersion}");
Logger.WriteLine(LOG_IDENT, $"Loaded from {Paths.Process}"); Logger.WriteLine(LOG_IDENT, $"Loaded from {Paths.Process}");
Logger.WriteLine(LOG_IDENT, $"Temp path is {Paths.Temp}"); Logger.WriteLine(LOG_IDENT, $"Temp path is {Paths.Temp}");
Logger.WriteLine(LOG_IDENT, $"WindowsStartMenu path is {Paths.WindowsStartMenu}"); Logger.WriteLine(LOG_IDENT, $"WindowsStartMenu path is {Paths.WindowsStartMenu}");
@ -292,6 +310,7 @@ namespace Bloxstrap
{ {
Logger.Initialize(true); Logger.Initialize(true);
Logger.WriteLine(LOG_IDENT, "Not installed, launching the installer"); Logger.WriteLine(LOG_IDENT, "Not installed, launching the installer");
AssertWindowsOSVersion(); // prevent new installs from unsupported operating systems
LaunchHandler.LaunchInstaller(); LaunchHandler.LaunchInstaller();
} }
else else

View File

@ -7,8 +7,8 @@
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<UseWindowsForms>True</UseWindowsForms> <UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>Bloxstrap.ico</ApplicationIcon> <ApplicationIcon>Bloxstrap.ico</ApplicationIcon>
<Version>2.8.6</Version> <Version>2.9.0</Version>
<FileVersion>2.8.6</FileVersion> <FileVersion>2.9.0</FileVersion>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
@ -55,10 +55,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AvalonEdit" Version="6.3.0.90" /> <PackageReference Include="AvalonEdit" Version="6.3.0.90" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="Markdig" Version="0.37.0" /> <PackageReference Include="Markdig" Version="0.40.0" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106"> <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>--> <!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View File

@ -58,6 +58,7 @@ namespace Bloxstrap
private double _taskbarProgressIncrement; private double _taskbarProgressIncrement;
private double _taskbarProgressMaximum; private double _taskbarProgressMaximum;
private long _totalDownloadedBytes = 0; private long _totalDownloadedBytes = 0;
private bool _packageExtractionSuccess = true;
private bool _mustUpgrade => String.IsNullOrEmpty(AppData.State.VersionGuid) || !File.Exists(AppData.ExecutablePath); private bool _mustUpgrade => String.IsNullOrEmpty(AppData.State.VersionGuid) || !File.Exists(AppData.ExecutablePath);
private bool _noConnection = false; 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 // 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. ¯\_(ツ)_/¯ // 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.DirectoryFailure += (_, e) => throw e.Exception;
_fastZipEvents.ProcessFile += (_, e) => e.ContinueRunning = !_cancelTokenSource.IsCancellationRequested; _fastZipEvents.ProcessFile += (_, e) => e.ContinueRunning = !_cancelTokenSource.IsCancellationRequested;
@ -179,6 +188,8 @@ namespace Bloxstrap
} }
#endif #endif
App.AssertWindowsOSVersion();
// ensure only one instance of the bootstrapper is running at the time // ensure only one instance of the bootstrapper is running at the time
// so that we don't have stuff like two updates happening simultaneously // so that we don't have stuff like two updates happening simultaneously
@ -221,6 +232,8 @@ namespace Bloxstrap
} }
} }
bool allModificationsApplied = true;
if (!_noConnection) if (!_noConnection)
{ {
if (AppData.State.VersionGuid != _latestVersionGuid || _mustUpgrade) if (AppData.State.VersionGuid != _latestVersionGuid || _mustUpgrade)
@ -231,7 +244,7 @@ namespace Bloxstrap
// we require deployment details for applying modifications for a worst case scenario, // 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 // 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 // check registry entries for every launch, just in case the stock bootstrapper changes it back
@ -245,7 +258,15 @@ namespace Bloxstrap
await mutex.ReleaseAsync(); await mutex.ReleaseAsync();
if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelTokenSource.IsCancellationRequested) 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(); StartRoblox();
}
await mutex.ReleaseAsync(); await mutex.ReleaseAsync();
@ -303,14 +324,6 @@ namespace Bloxstrap
clientVersion = await Deployment.GetInfo(); 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); key.SetValueSafe("www.roblox.com", Deployment.IsDefaultChannel ? "" : Deployment.Channel);
_latestVersionGuid = clientVersion.VersionGuid; _latestVersionGuid = clientVersion.VersionGuid;
@ -649,7 +662,28 @@ namespace Bloxstrap
#endregion #endregion
#region Roblox Install #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"; 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) 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 try
{ {
Directory.Delete(dir, true); Directory.Delete(dir, true);
@ -927,10 +968,12 @@ namespace Bloxstrap
_isInstalling = false; _isInstalling = false;
} }
private async Task ApplyModifications() private async Task<bool> ApplyModifications()
{ {
const string LOG_IDENT = "Bootstrapper::ApplyModifications"; const string LOG_IDENT = "Bootstrapper::ApplyModifications";
bool success = true;
SetStatus(Strings.Bootstrapper_Status_ApplyingModifications); SetStatus(Strings.Bootstrapper_Status_ApplyingModifications);
// handle file mods // handle file mods
@ -1006,7 +1049,7 @@ namespace Bloxstrap
foreach (string file in Directory.GetFiles(Paths.Modifications, "*.*", SearchOption.AllDirectories)) foreach (string file in Directory.GetFiles(Paths.Modifications, "*.*", SearchOption.AllDirectories))
{ {
if (_cancelTokenSource.IsCancellationRequested) if (_cancelTokenSource.IsCancellationRequested)
return; return true;
// get relative directory path // get relative directory path
string relativeFile = file.Substring(Paths.Modifications.Length + 1); string relativeFile = file.Substring(Paths.Modifications.Length + 1);
@ -1038,10 +1081,18 @@ namespace Bloxstrap
Directory.CreateDirectory(Path.GetDirectoryName(fileVersionFolder)!); Directory.CreateDirectory(Path.GetDirectoryName(fileVersionFolder)!);
Filesystem.AssertReadOnly(fileVersionFolder); Filesystem.AssertReadOnly(fileVersionFolder);
File.Copy(fileModFolder, fileVersionFolder, true); try
Filesystem.AssertReadOnly(fileVersionFolder); {
File.Copy(fileModFolder, fileVersionFolder, true);
App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} has been copied to the version folder"); 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 // 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 (package is not null)
{ {
if (_cancelTokenSource.IsCancellationRequested) if (_cancelTokenSource.IsCancellationRequested)
return; return true;
await DownloadPackage(package); await DownloadPackage(package);
ExtractPackage(package, entry.Value); ExtractPackage(package, entry.Value);
@ -1099,6 +1150,11 @@ namespace Bloxstrap
App.State.Save(); App.State.Save();
App.Logger.WriteLine(LOG_IDENT, $"Finished checking file mods"); 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) private async Task DownloadPackage(Package package)

View File

@ -12,7 +12,5 @@
public string BootstrapperVersion { get; set; } = null!; public string BootstrapperVersion { get; set; } = null!;
public DateTime? Timestamp { get; set; } public DateTime? Timestamp { get; set; }
public bool IsBehindDefaultChannel { get; set; }
} }
} }

View File

@ -151,6 +151,15 @@ namespace Bloxstrap.Resources {
} }
} }
/// <summary>
/// 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..
/// </summary>
public static string App_OSDeprecation_Win7_81 {
get {
return ResourceManager.GetString("App.OSDeprecation.Win7_81", resourceCulture);
}
}
/// <summary> /// <summary>
/// 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.. /// 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..
/// </summary> /// </summary>
@ -169,6 +178,24 @@ namespace Bloxstrap.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Some content may be missing. Force a Roblox reinstallation in settings to fix this..
/// </summary>
public static string Bootstrapper_ExtractionFailed_Message {
get {
return ResourceManager.GetString("Bootstrapper.ExtractionFailed.Message", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed to extract all files.
/// </summary>
public static string Bootstrapper_ExtractionFailed_Title {
get {
return ResourceManager.GetString("Bootstrapper.ExtractionFailed.Title", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Bloxstrap tried to upgrade Roblox but can&apos;t because Roblox&apos;s files are still in use. /// Looks up a localized string similar to Bloxstrap tried to upgrade Roblox but can&apos;t because Roblox&apos;s files are still in use.
/// ///
@ -198,6 +225,24 @@ namespace Bloxstrap.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Not all modifications will be present in the current launch..
/// </summary>
public static string Bootstrapper_ModificationsFailed_Message {
get {
return ResourceManager.GetString("Bootstrapper.ModificationsFailed.Message", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed to apply all modifications.
/// </summary>
public static string Bootstrapper_ModificationsFailed_Title {
get {
return ResourceManager.GetString("Bootstrapper.ModificationsFailed.Title", resourceCulture);
}
}
/// <summary> /// <summary>
/// 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.. /// 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..
/// </summary> /// </summary>

View File

@ -1270,6 +1270,21 @@ Please close any applications that may be using Roblox's files, and relaunch.</v
<value>All Bloxstrap logs</value> <value>All Bloxstrap logs</value>
<comment>Label that appears next to a checkbox</comment> <comment>Label that appears next to a checkbox</comment>
</data> </data>
<data name="App.OSDeprecation.Win7_81" xml:space="preserve">
<value>Roblox no longer supports Windows 7 or 8.1. To continue playing Roblox, please upgrade to Windows 10 or newer.</value>
</data>
<data name="Bootstrapper.ExtractionFailed.Title" xml:space="preserve">
<value>Failed to extract all files</value>
</data>
<data name="Bootstrapper.ExtractionFailed.Message" xml:space="preserve">
<value>Some content may be missing. Force a Roblox reinstallation in settings to fix this.</value>
</data>
<data name="Bootstrapper.ModificationsFailed.Title" xml:space="preserve">
<value>Failed to apply all modifications</value>
</data>
<data name="Bootstrapper.ModificationsFailed.Message" xml:space="preserve">
<value>Not all modifications will be present in the current launch.</value>
</data>
<data name="Menu.About.Licenses.Apache" xml:space="preserve"> <data name="Menu.About.Licenses.Apache" xml:space="preserve">
<value>Apache License 2.0</value> <value>Apache License 2.0</value>
</data> </data>

View File

@ -176,16 +176,15 @@
App.Logger.WriteLine(LOG_IDENT, "Failed to contact clientsettingscdn! Falling back to clientsettings..."); App.Logger.WriteLine(LOG_IDENT, "Failed to contact clientsettingscdn! Falling back to clientsettings...");
App.Logger.WriteException(LOG_IDENT, ex); App.Logger.WriteException(LOG_IDENT, ex);
clientVersion = await Http.GetJson<ClientVersion>("https://clientsettings.roblox.com" + path); try
} {
clientVersion = await Http.GetJson<ClientVersion>("https://clientsettings.roblox.com" + path);
// check if channel is behind LIVE }
if (!isDefaultChannel) catch (HttpRequestException httpEx)
{ when (!isDefaultChannel && BadChannelCodes.Contains(httpEx.StatusCode))
var defaultClientVersion = await GetInfo(DefaultChannel); {
throw new InvalidChannelException(httpEx.StatusCode);
if (Utilities.CompareVersions(clientVersion.Version, defaultClientVersion.Version) == VersionComparison.LessThan) }
clientVersion.IsBehindDefaultChannel = true;
} }
ClientVersionCache[cacheKey] = clientVersion; ClientVersionCache[cacheKey] = clientVersion;

View File

@ -110,5 +110,17 @@ namespace Bloxstrap.UI
return messagebox.Result; 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);
}
} }
} }

View File

@ -31,5 +31,15 @@ namespace Bloxstrap.Utility
fileInfo.IsReadOnly = false; fileInfo.IsReadOnly = false;
App.Logger.WriteLine("Filesystem::AssertReadOnly", $"The following file was set as read-only: {filePath}"); 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}");
}
} }
} }