add background updating

This commit is contained in:
bluepilledgreat 2025-03-12 18:29:16 +00:00
parent 893aecbdd1
commit 3f666333ee
5 changed files with 207 additions and 30 deletions

View File

@ -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!;
@ -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;
@ -237,12 +243,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 +368,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 +397,31 @@ namespace Bloxstrap
}
}
private bool IsEligibleForBackgroundUpdate()
{
// TODO: storage check
if (App.LaunchSettings.BackgroundUpdaterFlag.Active || _mustUpgrade || IsStudioLaunch)
return false;
if (_latestVersion == default) // todo: check if this even works
return false;
Version? currentVersion = Utilities.GetRobloxVersion(AppData);
if (currentVersion == default)
return false;
// always normally upgrade for downgrades
if (currentVersion.Minor > _latestVersion.Minor)
return false;
// only background update if we're:
// - one major update behind
// - the same major update
int diff = _latestVersion.Minor - currentVersion.Minor;
return diff == 0 || diff == 1;
}
private void StartRoblox()
{
const string LOG_IDENT = "Bootstrapper::StartRoblox";
@ -718,6 +774,12 @@ 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);
@ -801,11 +863,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
{
@ -1002,6 +1064,21 @@ namespace Bloxstrap
_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<bool> ApplyModifications()
{
const string LOG_IDENT = "Bootstrapper::ApplyModifications";

View File

@ -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-BackgroundUpdaterKillEvent",
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-BackgroundUpdater"))
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");
}
}
}

View File

@ -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 TestModeFlag { get; } = new("testmode");
public LaunchFlag NoLaunchFlag { get; } = new("nolaunch");
public LaunchFlag NoGPUFlag { get; } = new("nogpu");
public LaunchFlag TestModeFlag { get; } = new("testmode");
public LaunchFlag UpgradeFlag { get; } = new("upgrade");
public LaunchFlag NoGPUFlag { get; } = new("nogpu");
public LaunchFlag PlayerFlag { get; } = new("player");
public LaunchFlag UpgradeFlag { get; } = new("upgrade");
public LaunchFlag StudioFlag { get; } = new("studio");
public LaunchFlag PlayerFlag { get; } = new("player");
public LaunchFlag VersionFlag { get; } = new("version");
public LaunchFlag StudioFlag { get; } = new("studio");
public LaunchFlag ChannelFlag { get; } = new("channel");
public LaunchFlag VersionFlag { get; } = new("version");
public LaunchFlag ForceFlag { get; } = new("force");
public LaunchFlag ChannelFlag { get; } = new("channel");
public LaunchFlag ForceFlag { get; } = new("force");
#if DEBUG
public bool BypassUpdateCheck => true;

View File

@ -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;

View File

@ -75,10 +75,24 @@ namespace Bloxstrap
}
}
public static string GetRobloxVersion(bool studio)
/// <summary>
/// Parses the input version string and prints if fails
/// </summary>
public static Version? ParseVersionSafe(string versionStr)
{
IAppData data = studio ? new RobloxStudioData() : new RobloxPlayerData();
const string LOG_IDENT = "Utilities::ParseVersionSafe";
if (!Version.TryParse(versionStr, out Version? version))
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to convert {versionStr} to a valid Version type.");
return version;
}
return version;
}
public static string GetRobloxVersionStr(IAppData data)
{
string playerLocation = data.ExecutablePath;
if (!File.Exists(playerLocation))
@ -92,6 +106,19 @@ namespace Bloxstrap
return versionInfo.ProductVersion.Replace(", ", ".");
}
public static string GetRobloxVersionStr(bool studio)
{
IAppData data = studio ? new RobloxStudioData() : new RobloxPlayerData();
return GetRobloxVersionStr(data);
}
public static Version? GetRobloxVersion(IAppData data)
{
string str = GetRobloxVersionStr(data);
return ParseVersionSafe(str);
}
public static Process[] GetProcessesSafe()
{
const string LOG_IDENT = "Utilities::GetProcessesSafe";
@ -107,5 +134,24 @@ namespace Bloxstrap
return Array.Empty<Process>(); // can we retry?
}
}
public static bool DoesMutexExist(string name)
{
try
{
Mutex.OpenExisting(name).Close();
return true;
}
catch
{
return false;
}
}
public static void KillBackgroundUpdater()
{
using EventWaitHandle handle = new EventWaitHandle(false, EventResetMode.AutoReset, "Bloxstrap-BackgroundUpdaterKillEvent");
handle.Set();
}
}
}