Improve bootstrapper cancelling (+ error handling)

This commit is contained in:
pizzaboxer 2024-10-02 01:21:40 +01:00
parent 9a21c47ef0
commit 8ed89bb65e
No known key found for this signature in database
GPG Key ID: 59D4A1DBAD0F2BA8

View File

@ -18,6 +18,8 @@ using Microsoft.Win32;
using Bloxstrap.AppData; using Bloxstrap.AppData;
using ICSharpCode.SharpZipLib.Zip;
namespace Bloxstrap namespace Bloxstrap
{ {
public class Bootstrapper public class Bootstrapper
@ -32,6 +34,7 @@ namespace Bloxstrap
" <BaseUrl>http://www.roblox.com</BaseUrl>\r\n" + " <BaseUrl>http://www.roblox.com</BaseUrl>\r\n" +
"</Settings>\r\n"; "</Settings>\r\n";
private readonly FastZipEvents _fastZipEvents = new();
private readonly CancellationTokenSource _cancelTokenSource = new(); private readonly CancellationTokenSource _cancelTokenSource = new();
private readonly IAppData AppData; private readonly IAppData AppData;
@ -48,6 +51,8 @@ namespace Bloxstrap
private bool _mustUpgrade => String.IsNullOrEmpty(AppData.State.VersionGuid) || File.Exists(AppData.LockFilePath) || !File.Exists(AppData.ExecutablePath); private bool _mustUpgrade => String.IsNullOrEmpty(AppData.State.VersionGuid) || File.Exists(AppData.LockFilePath) || !File.Exists(AppData.ExecutablePath);
private bool _noConnection = false; private bool _noConnection = false;
private int _appPid = 0;
public IBootstrapperDialog? Dialog = null; public IBootstrapperDialog? Dialog = null;
public bool IsStudioLaunch => _launchMode != LaunchMode.Player; public bool IsStudioLaunch => _launchMode != LaunchMode.Player;
@ -56,6 +61,16 @@ namespace Bloxstrap
#region Core #region Core
public Bootstrapper() public Bootstrapper()
{ {
// this is now always enabled as of v2.8.0
if (Dialog is not null)
Dialog.CancelEnabled = true;
// 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.DirectoryFailure += (_, e) => throw e.Exception;
_fastZipEvents.ProcessFile += (_, e) => e.ContinueRunning = !_cancelTokenSource.IsCancellationRequested;
AppData = IsStudioLaunch ? new RobloxStudioData() : new RobloxPlayerData(); AppData = IsStudioLaunch ? new RobloxStudioData() : new RobloxPlayerData();
} }
@ -182,6 +197,9 @@ namespace Bloxstrap
if (AppData.State.VersionGuid != _latestVersionGuid || _mustUpgrade) if (AppData.State.VersionGuid != _latestVersionGuid || _mustUpgrade)
await UpgradeRoblox(); await UpgradeRoblox();
if (_cancelTokenSource.IsCancellationRequested)
return;
// 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(); await ApplyModifications();
@ -297,7 +315,6 @@ namespace Bloxstrap
return; return;
} }
int gameClientPid;
bool startEventSignalled; bool startEventSignalled;
// TODO: figure out why this is causing roblox to block for some users // TODO: figure out why this is causing roblox to block for some users
@ -306,12 +323,19 @@ namespace Bloxstrap
startEvent.Reset(); startEvent.Reset();
// v2.2.0 - byfron will trip if we keep a process handle open for over a minute, so we're doing this now // v2.2.0 - byfron will trip if we keep a process handle open for over a minute, so we're doing this now
using (var process = Process.Start(startInfo)!) try
{ {
gameClientPid = process.Id; using var process = Process.Start(startInfo)!;
_appPid = process.Id;
}
catch (Exception)
{
// attempt a reinstall on next launch
File.Delete(AppData.ExecutablePath);
throw;
} }
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid}), waiting for start event"); App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {_appPid}), waiting for start event");
startEventSignalled = startEvent.WaitOne(TimeSpan.FromSeconds(30)); startEventSignalled = startEvent.WaitOne(TimeSpan.FromSeconds(30));
} }
@ -335,6 +359,7 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, $"Launching custom integration '{integration.Name}' ({integration.Location} {integration.LaunchArgs} - autoclose is {integration.AutoClose})"); App.Logger.WriteLine(LOG_IDENT, $"Launching custom integration '{integration.Name}' ({integration.Location} {integration.LaunchArgs} - autoclose is {integration.AutoClose})");
int pid = 0; int pid = 0;
try try
{ {
var process = Process.Start(new ProcessStartInfo var process = Process.Start(new ProcessStartInfo
@ -357,7 +382,7 @@ namespace Bloxstrap
autoclosePids.Add(pid); autoclosePids.Add(pid);
} }
string args = gameClientPid.ToString(); string args = _appPid.ToString();
if (autoclosePids.Any()) if (autoclosePids.Any())
args += $";{String.Join(',', autoclosePids)}"; args += $";{String.Join(',', autoclosePids)}";
@ -378,13 +403,6 @@ namespace Bloxstrap
{ {
const string LOG_IDENT = "Bootstrapper::Cancel"; const string LOG_IDENT = "Bootstrapper::Cancel";
if (!_isInstalling)
{
// TODO: this sucks and needs to be done better
App.Terminate(ErrorCode.ERROR_CANCELLED);
return;
}
if (_cancelTokenSource.IsCancellationRequested) if (_cancelTokenSource.IsCancellationRequested)
return; return;
@ -392,6 +410,9 @@ namespace Bloxstrap
_cancelTokenSource.Cancel(); _cancelTokenSource.Cancel();
if (Dialog is not null)
Dialog.CancelEnabled = false;
if (_isInstalling) if (_isInstalling)
{ {
try try
@ -404,12 +425,26 @@ namespace Bloxstrap
{ {
App.Logger.WriteLine(LOG_IDENT, "Could not fully clean up installation!"); App.Logger.WriteLine(LOG_IDENT, "Could not fully clean up installation!");
App.Logger.WriteException(LOG_IDENT, ex); App.Logger.WriteException(LOG_IDENT, ex);
// assurance to make sure the next launch does a fresh install
// we probably shouldn't be using the lockfile to do this, but meh
var lockFile = new FileInfo(AppData.LockFilePath);
lockFile.Create().Dispose();
} }
} }
else if (_appPid != 0)
{
try
{
using var process = Process.GetProcessById(_appPid);
process.Kill();
}
catch (Exception) { }
}
Dialog?.CloseBootstrapper(); Dialog?.CloseBootstrapper();
App.Terminate(ErrorCode.ERROR_CANCELLED); App.SoftTerminate(ErrorCode.ERROR_CANCELLED);
} }
#endregion #endregion
@ -443,6 +478,9 @@ namespace Bloxstrap
return false; return false;
} }
if (Dialog is not null)
Dialog.CancelEnabled = false;
string version = releaseInfo.TagName; string version = releaseInfo.TagName;
#else #else
string version = App.Version; string version = App.Version;
@ -581,8 +619,6 @@ namespace Bloxstrap
if (Dialog is not null) if (Dialog is not null)
{ {
// TODO: cancelling needs to always be enabled
Dialog.CancelEnabled = true;
Dialog.ProgressStyle = ProgressBarStyle.Continuous; Dialog.ProgressStyle = ProgressBarStyle.Continuous;
Dialog.ProgressMaximum = ProgressBarMaximum; Dialog.ProgressMaximum = ProgressBarMaximum;
@ -733,9 +769,6 @@ namespace Bloxstrap
lockFile.Delete(); lockFile.Delete();
if (Dialog is not null)
Dialog.CancelEnabled = false;
_isInstalling = false; _isInstalling = false;
} }
@ -807,6 +840,9 @@ 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)
return;
// get relative directory path // get relative directory path
string relativeFile = file.Substring(Paths.Modifications.Length + 1); string relativeFile = file.Substring(Paths.Modifications.Length + 1);
@ -886,6 +922,9 @@ namespace Bloxstrap
if (package is not null) if (package is not null)
{ {
if (_cancelTokenSource.IsCancellationRequested)
return;
await DownloadPackage(package); await DownloadPackage(package);
ExtractPackage(package, entry.Value); ExtractPackage(package, entry.Value);
} }
@ -1056,7 +1095,7 @@ namespace Bloxstrap
string packageFolder = Path.Combine(AppData.Directory, AppData.PackageDirectoryMap[package.Name]); string packageFolder = Path.Combine(AppData.Directory, AppData.PackageDirectoryMap[package.Name]);
string? fileFilter = null; string? fileFilter = null;
// for sharpziplib, each file in the filter // for sharpziplib, each file in the filter needs to be a regex
if (files is not null) if (files is not null)
{ {
var regexList = new List<string>(); var regexList = new List<string>();
@ -1069,7 +1108,8 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, $"Extracting {package.Name}..."); App.Logger.WriteLine(LOG_IDENT, $"Extracting {package.Name}...");
var fastZip = new ICSharpCode.SharpZipLib.Zip.FastZip(); var fastZip = new FastZip(_fastZipEvents);
fastZip.ExtractZip(package.DownloadPath, packageFolder, fileFilter); fastZip.ExtractZip(package.DownloadPath, packageFolder, fileFilter);
App.Logger.WriteLine(LOG_IDENT, $"Finished extracting {package.Name}"); App.Logger.WriteLine(LOG_IDENT, $"Finished extracting {package.Name}");