mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-21 10:01:27 -07:00
Draft: bootstrapper refactoring
Roblox now installs to /Roblox/Player/ instead of /Versions/<version guid> This is a checkpoint commit. No mod manager, no error checking, no fullscreen optimizations configuration. Only installing and launching Roblox. THIS WORKED FIRST TRY BY THE WAY
This commit is contained in:
parent
15dc2dfbfe
commit
3eeebc7a8b
@ -39,8 +39,17 @@ namespace Bloxstrap.AppData
|
|||||||
{ "extracontent-places.zip", @"ExtraContent\places\" },
|
{ "extracontent-places.zip", @"ExtraContent\places\" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public virtual string FinalDirectory { get; } = null!;
|
||||||
|
|
||||||
|
public string StagingDirectory => $"{FinalDirectory}.staging";
|
||||||
|
|
||||||
|
public virtual string ExecutableName { get; } = null!;
|
||||||
|
|
||||||
|
public string ExecutablePath => Path.Combine(FinalDirectory, ExecutableName);
|
||||||
|
|
||||||
public virtual IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; }
|
public virtual IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public CommonAppData()
|
public CommonAppData()
|
||||||
{
|
{
|
||||||
if (PackageDirectoryMap is null)
|
if (PackageDirectoryMap is null)
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
using System;
|
namespace Bloxstrap.AppData
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bloxstrap.AppData
|
|
||||||
{
|
{
|
||||||
internal interface IAppData
|
internal interface IAppData
|
||||||
{
|
{
|
||||||
@ -18,6 +12,14 @@ namespace Bloxstrap.AppData
|
|||||||
|
|
||||||
string StartEvent { get; }
|
string StartEvent { get; }
|
||||||
|
|
||||||
|
string FinalDirectory { get; }
|
||||||
|
|
||||||
|
string StagingDirectory { get; }
|
||||||
|
|
||||||
|
string ExecutablePath { get; }
|
||||||
|
|
||||||
|
AppState State { get; }
|
||||||
|
|
||||||
IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; }
|
IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,15 +8,19 @@ namespace Bloxstrap.AppData
|
|||||||
{
|
{
|
||||||
public class RobloxPlayerData : CommonAppData, IAppData
|
public class RobloxPlayerData : CommonAppData, IAppData
|
||||||
{
|
{
|
||||||
public string ProductName { get; } = "Roblox";
|
public string ProductName => "Roblox";
|
||||||
|
|
||||||
public string BinaryType { get; } = "WindowsPlayer";
|
public string BinaryType => "WindowsPlayer";
|
||||||
|
|
||||||
public string RegistryName { get; } = "RobloxPlayer";
|
public string RegistryName => "RobloxPlayer";
|
||||||
|
|
||||||
public string ExecutableName { get; } = "RobloxPlayerBeta.exe";
|
public override string ExecutableName => "RobloxPlayerBeta.exe";
|
||||||
|
|
||||||
public string StartEvent { get; } = "www.roblox.com/robloxStartedEvent";
|
public string StartEvent => "www.roblox.com/robloxStartedEvent";
|
||||||
|
|
||||||
|
public override string FinalDirectory => Path.Combine(Paths.Roblox, "Player");
|
||||||
|
|
||||||
|
public AppState State => App.State.Prop.Player;
|
||||||
|
|
||||||
public override IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
|
public override IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
|
||||||
{
|
{
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
using System;
|
namespace Bloxstrap.AppData
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bloxstrap.AppData
|
|
||||||
{
|
{
|
||||||
public class RobloxStudioData : CommonAppData, IAppData
|
public class RobloxStudioData : CommonAppData, IAppData
|
||||||
{
|
{
|
||||||
public string ProductName { get; } = "Roblox Studio";
|
public string ProductName => "Roblox Studio";
|
||||||
|
|
||||||
public string BinaryType { get; } = "WindowsStudio64";
|
public string BinaryType => "WindowsStudio64";
|
||||||
|
|
||||||
public string RegistryName { get; } = "RobloxStudio";
|
public string RegistryName => "RobloxStudio";
|
||||||
|
|
||||||
public string ExecutableName { get; } = "RobloxStudioBeta.exe";
|
public override string ExecutableName => "RobloxStudioBeta.exe";
|
||||||
|
|
||||||
public string StartEvent { get; } = "www.roblox.com/robloxStudioStartedEvent";
|
public string StartEvent => "www.roblox.com/robloxStudioStartedEvent";
|
||||||
|
|
||||||
|
public override string FinalDirectory => Path.Combine(Paths.Roblox, "Studio");
|
||||||
|
|
||||||
|
public AppState State => App.State.Prop.Studio;
|
||||||
|
|
||||||
public override IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
|
public override IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
|
||||||
{
|
{
|
||||||
|
@ -34,57 +34,18 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
private readonly CancellationTokenSource _cancelTokenSource = new();
|
private readonly CancellationTokenSource _cancelTokenSource = new();
|
||||||
|
|
||||||
private bool FreshInstall => String.IsNullOrEmpty(_versionGuid);
|
|
||||||
|
|
||||||
private IAppData AppData;
|
private IAppData AppData;
|
||||||
|
|
||||||
private string _playerLocation => Path.Combine(_versionFolder, AppData.ExecutableName);
|
private bool FreshInstall => String.IsNullOrEmpty(AppData.State.VersionGuid);
|
||||||
|
|
||||||
private string _launchCommandLine = App.LaunchSettings.RobloxLaunchArgs;
|
private string _launchCommandLine = App.LaunchSettings.RobloxLaunchArgs;
|
||||||
private LaunchMode _launchMode = App.LaunchSettings.RobloxLaunchMode;
|
private LaunchMode _launchMode = App.LaunchSettings.RobloxLaunchMode;
|
||||||
private bool _installWebView2;
|
|
||||||
|
|
||||||
private string _versionGuid
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _launchMode == LaunchMode.Player ? App.State.Prop.PlayerVersionGuid : App.State.Prop.StudioVersionGuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_launchMode == LaunchMode.Player)
|
|
||||||
App.State.Prop.PlayerVersionGuid = value;
|
|
||||||
else
|
|
||||||
App.State.Prop.StudioVersionGuid = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int _distributionSize
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _launchMode == LaunchMode.Player ? App.State.Prop.PlayerSize : App.State.Prop.StudioSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_launchMode == LaunchMode.Player)
|
|
||||||
App.State.Prop.PlayerSize = value;
|
|
||||||
else
|
|
||||||
App.State.Prop.StudioSize = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _latestVersionGuid = null!;
|
private string _latestVersionGuid = null!;
|
||||||
private PackageManifest _versionPackageManifest = null!;
|
private PackageManifest _versionPackageManifest = null!;
|
||||||
private string _versionFolder = null!;
|
|
||||||
|
|
||||||
private bool _isInstalling = false;
|
private bool _isInstalling = false;
|
||||||
private double _progressIncrement;
|
private double _progressIncrement;
|
||||||
private long _totalDownloadedBytes = 0;
|
private long _totalDownloadedBytes = 0;
|
||||||
private int _packagesExtracted = 0;
|
|
||||||
private bool _cancelFired = false;
|
|
||||||
|
|
||||||
public IBootstrapperDialog? Dialog = null;
|
public IBootstrapperDialog? Dialog = null;
|
||||||
|
|
||||||
@ -92,14 +53,9 @@ namespace Bloxstrap
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Core
|
#region Core
|
||||||
public Bootstrapper(bool installWebView2)
|
public Bootstrapper()
|
||||||
{
|
{
|
||||||
_installWebView2 = installWebView2;
|
AppData = IsStudioLaunch ? new RobloxStudioData() : new RobloxPlayerData();
|
||||||
|
|
||||||
if (_launchMode == LaunchMode.Player)
|
|
||||||
AppData = new RobloxPlayerData();
|
|
||||||
else
|
|
||||||
AppData = new RobloxStudioData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetStatus(string message)
|
private void SetStatus(string message)
|
||||||
@ -140,6 +96,8 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
var connectionResult = await RobloxDeployment.InitializeConnectivity();
|
var connectionResult = await RobloxDeployment.InitializeConnectivity();
|
||||||
|
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished");
|
||||||
|
|
||||||
if (connectionResult is not null)
|
if (connectionResult is not null)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Connectivity check failed!");
|
App.Logger.WriteLine(LOG_IDENT, "Connectivity check failed!");
|
||||||
@ -154,6 +112,7 @@ namespace Bloxstrap
|
|||||||
else if (connectionResult.GetType() == typeof(AggregateException))
|
else if (connectionResult.GetType() == typeof(AggregateException))
|
||||||
connectionResult = connectionResult.InnerException!;
|
connectionResult = connectionResult.InnerException!;
|
||||||
|
|
||||||
|
// TODO: handle update skip
|
||||||
Frontend.ShowConnectivityDialog(Strings.Dialog_Connectivity_UnableToConnect, message, connectionResult);
|
Frontend.ShowConnectivityDialog(Strings.Dialog_Connectivity_UnableToConnect, message, connectionResult);
|
||||||
|
|
||||||
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
||||||
@ -161,10 +120,6 @@ namespace Bloxstrap
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished");
|
|
||||||
|
|
||||||
await RobloxDeployment.GetInfo(RobloxDeployment.DefaultChannel);
|
|
||||||
|
|
||||||
#if !DEBUG || DEBUG_UPDATER
|
#if !DEBUG || DEBUG_UPDATER
|
||||||
if (App.Settings.Prop.CheckForUpdates && !App.LaunchSettings.UpgradeFlag.Active)
|
if (App.Settings.Prop.CheckForUpdates && !App.LaunchSettings.UpgradeFlag.Active)
|
||||||
{
|
{
|
||||||
@ -182,8 +137,8 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Mutex.OpenExisting("Bloxstrap_SingletonMutex").Close();
|
Mutex.OpenExisting("Bloxstrap-Bootstrapper").Close();
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Bloxstrap_SingletonMutex mutex exists, waiting...");
|
App.Logger.WriteLine(LOG_IDENT, "Bloxstrap-Bootstrapper mutex exists, waiting...");
|
||||||
SetStatus(Strings.Bootstrapper_Status_WaitingOtherInstances);
|
SetStatus(Strings.Bootstrapper_Status_WaitingOtherInstances);
|
||||||
mutexExists = true;
|
mutexExists = true;
|
||||||
}
|
}
|
||||||
@ -193,7 +148,7 @@ namespace Bloxstrap
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wait for mutex to be released if it's not yet
|
// wait for mutex to be released if it's not yet
|
||||||
await using var mutex = new AsyncMutex(true, "Bloxstrap_SingletonMutex");
|
await using var mutex = new AsyncMutex(false, "Bloxstrap-Bootstrapper");
|
||||||
await mutex.AcquireAsync(_cancelTokenSource.Token);
|
await mutex.AcquireAsync(_cancelTokenSource.Token);
|
||||||
|
|
||||||
// reload our configs since they've likely changed by now
|
// reload our configs since they've likely changed by now
|
||||||
@ -203,37 +158,48 @@ namespace Bloxstrap
|
|||||||
App.State.Load();
|
App.State.Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
await CheckLatestVersion();
|
// TODO: handle exception and update skip
|
||||||
|
await GetLatestVersionInfo();
|
||||||
|
|
||||||
// install/update roblox if we're running for the first time, needs updating, or the player location doesn't exist
|
// install/update roblox if we're running for the first time, needs updating, or the player location doesn't exist
|
||||||
if (_latestVersionGuid != _versionGuid || !File.Exists(_playerLocation))
|
if (!File.Exists(AppData.ExecutablePath) || AppData.State.VersionGuid != _latestVersionGuid)
|
||||||
await InstallLatestVersion();
|
await InstallLatestVersion();
|
||||||
|
|
||||||
if (_installWebView2)
|
//await ApplyModifications();
|
||||||
await InstallWebView2();
|
|
||||||
|
|
||||||
await ApplyModifications();
|
// check if launch uri is set to our bootstrapper
|
||||||
|
// this doesn't go under register, so we check every launch
|
||||||
|
// just in case the stock bootstrapper changes it back
|
||||||
|
|
||||||
// TODO: move this to install/upgrade flow
|
if (IsStudioLaunch)
|
||||||
if (FreshInstall)
|
{
|
||||||
RegisterProgramSize();
|
#if STUDIO_FEATURES
|
||||||
|
ProtocolHandler.Register("roblox-studio", "Roblox", Paths.Application);
|
||||||
|
ProtocolHandler.Register("roblox-studio-auth", "Roblox", Paths.Application);
|
||||||
|
|
||||||
CheckInstall();
|
ProtocolHandler.RegisterRobloxPlace(Paths.Application);
|
||||||
|
ProtocolHandler.RegisterExtension(".rbxl");
|
||||||
// at this point we've finished updating our configs
|
ProtocolHandler.RegisterExtension(".rbxlx");
|
||||||
App.State.Save();
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: there needs to be better helper functions for these
|
||||||
|
ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\"");
|
||||||
|
ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\"");
|
||||||
|
}
|
||||||
|
|
||||||
await mutex.ReleaseAsync();
|
await mutex.ReleaseAsync();
|
||||||
|
|
||||||
if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelFired)
|
if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelTokenSource.IsCancellationRequested)
|
||||||
StartRoblox();
|
StartRoblox();
|
||||||
|
|
||||||
Dialog?.CloseBootstrapper();
|
Dialog?.CloseBootstrapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckLatestVersion()
|
private async Task GetLatestVersionInfo()
|
||||||
{
|
{
|
||||||
const string LOG_IDENT = "Bootstrapper::CheckLatestVersion";
|
const string LOG_IDENT = "Bootstrapper::GetLatestVersionInfo";
|
||||||
|
|
||||||
// before we do anything, we need to query our channel
|
// before we do anything, we need to query our channel
|
||||||
// if it's set in the launch uri, we need to use it and set the registry key for it
|
// if it's set in the launch uri, we need to use it and set the registry key for it
|
||||||
@ -249,7 +215,7 @@ namespace Bloxstrap
|
|||||||
{
|
{
|
||||||
channel = match.Groups[1].Value.ToLowerInvariant();
|
channel = match.Groups[1].Value.ToLowerInvariant();
|
||||||
}
|
}
|
||||||
else if (key.GetValue("www.roblox.com") is string value)
|
else if (key.GetValue("www.roblox.com") is string value && !String.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
channel = value;
|
channel = value;
|
||||||
}
|
}
|
||||||
@ -285,8 +251,11 @@ namespace Bloxstrap
|
|||||||
key.SetValue("www.roblox.com", channel);
|
key.SetValue("www.roblox.com", channel);
|
||||||
|
|
||||||
_latestVersionGuid = clientVersion.VersionGuid;
|
_latestVersionGuid = clientVersion.VersionGuid;
|
||||||
_versionFolder = Path.Combine(Paths.Versions, _latestVersionGuid);
|
|
||||||
_versionPackageManifest = await PackageManifest.Get(_latestVersionGuid);
|
string pkgManifestUrl = RobloxDeployment.GetLocation($"/{_latestVersionGuid}-rbxPkgManifest.txt");
|
||||||
|
var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl);
|
||||||
|
|
||||||
|
_versionPackageManifest = new(pkgManifestData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartRoblox()
|
private void StartRoblox()
|
||||||
@ -313,9 +282,9 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
var startInfo = new ProcessStartInfo()
|
var startInfo = new ProcessStartInfo()
|
||||||
{
|
{
|
||||||
FileName = _playerLocation,
|
FileName = AppData.ExecutablePath,
|
||||||
Arguments = _launchCommandLine,
|
Arguments = _launchCommandLine,
|
||||||
WorkingDirectory = _versionFolder
|
WorkingDirectory = AppData.FinalDirectory
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_launchMode == LaunchMode.StudioAuth)
|
if (_launchMode == LaunchMode.StudioAuth)
|
||||||
@ -340,7 +309,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid}), waiting for start event");
|
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid}), waiting for start event");
|
||||||
|
|
||||||
startEventSignalled = startEvent.WaitOne(TimeSpan.FromSeconds(10));
|
startEventSignalled = startEvent.WaitOne(TimeSpan.FromSeconds(30));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!startEventSignalled)
|
if (!startEventSignalled)
|
||||||
@ -351,6 +320,9 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Start event signalled");
|
App.Logger.WriteLine(LOG_IDENT, "Start event signalled");
|
||||||
|
|
||||||
|
if (IsStudioLaunch)
|
||||||
|
return;
|
||||||
|
|
||||||
var autoclosePids = new List<int>();
|
var autoclosePids = new List<int>();
|
||||||
|
|
||||||
// launch custom integrations now
|
// launch custom integrations now
|
||||||
@ -401,23 +373,23 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
if (!_isInstalling)
|
if (!_isInstalling)
|
||||||
{
|
{
|
||||||
|
// TODO: this sucks and needs to be done better
|
||||||
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_cancelFired)
|
if (_cancelTokenSource.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Cancelling install...");
|
App.Logger.WriteLine(LOG_IDENT, "Cancelling install...");
|
||||||
|
|
||||||
_cancelTokenSource.Cancel();
|
_cancelTokenSource.Cancel();
|
||||||
_cancelFired = true;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// clean up install
|
// clean up install
|
||||||
if (Directory.Exists(_versionFolder))
|
if (Directory.Exists(AppData.StagingDirectory))
|
||||||
Directory.Delete(_versionFolder, true);
|
Directory.Delete(AppData.StagingDirectory, true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -432,47 +404,6 @@ namespace Bloxstrap
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region App Install
|
#region App Install
|
||||||
public void RegisterProgramSize()
|
|
||||||
{
|
|
||||||
const string LOG_IDENT = "Bootstrapper::RegisterProgramSize";
|
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Registering approximate program size...");
|
|
||||||
|
|
||||||
using RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{App.ProjectName}");
|
|
||||||
|
|
||||||
// sum compressed and uncompressed package sizes and convert to kilobytes
|
|
||||||
int distributionSize = (_versionPackageManifest.Sum(x => x.Size) + _versionPackageManifest.Sum(x => x.PackedSize)) / 1000;
|
|
||||||
_distributionSize = distributionSize;
|
|
||||||
|
|
||||||
int totalSize = App.State.Prop.PlayerSize + App.State.Prop.StudioSize;
|
|
||||||
|
|
||||||
uninstallKey.SetValue("EstimatedSize", totalSize);
|
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Registered as {totalSize} KB");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void CheckInstall()
|
|
||||||
{
|
|
||||||
const string LOG_IDENT = "Bootstrapper::CheckInstall";
|
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Checking install");
|
|
||||||
|
|
||||||
// check if launch uri is set to our bootstrapper
|
|
||||||
// this doesn't go under register, so we check every launch
|
|
||||||
// just in case the stock bootstrapper changes it back
|
|
||||||
|
|
||||||
ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\"");
|
|
||||||
ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\"");
|
|
||||||
#if STUDIO_FEATURES
|
|
||||||
ProtocolHandler.Register("roblox-studio", "Roblox", Paths.Application);
|
|
||||||
ProtocolHandler.Register("roblox-studio-auth", "Roblox", Paths.Application);
|
|
||||||
|
|
||||||
ProtocolHandler.RegisterRobloxPlace(Paths.Application);
|
|
||||||
ProtocolHandler.RegisterExtension(".rbxl");
|
|
||||||
ProtocolHandler.RegisterExtension(".rbxlx");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> CheckForUpdates()
|
private async Task<bool> CheckForUpdates()
|
||||||
{
|
{
|
||||||
const string LOG_IDENT = "Bootstrapper::CheckForUpdates";
|
const string LOG_IDENT = "Bootstrapper::CheckForUpdates";
|
||||||
@ -588,26 +519,28 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
Directory.CreateDirectory(Paths.Base);
|
Directory.CreateDirectory(Paths.Base);
|
||||||
Directory.CreateDirectory(Paths.Downloads);
|
Directory.CreateDirectory(Paths.Downloads);
|
||||||
Directory.CreateDirectory(Paths.Versions);
|
Directory.CreateDirectory(Paths.Roblox);
|
||||||
|
|
||||||
|
if (Directory.Exists(AppData.StagingDirectory))
|
||||||
|
Directory.Delete(AppData.StagingDirectory, true);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(AppData.StagingDirectory);
|
||||||
|
|
||||||
// package manifest states packed size and uncompressed size in exact bytes
|
// package manifest states packed size and uncompressed size in exact bytes
|
||||||
// packed size only matters if we don't already have the package cached on disk
|
// packed size only matters if we don't already have the package cached on disk
|
||||||
string[] cachedPackages = Directory.GetFiles(Paths.Downloads);
|
var cachedPackages = Directory.GetFiles(Paths.Downloads);
|
||||||
int totalSizeRequired = _versionPackageManifest.Where(x => !cachedPackages.Contains(x.Signature)).Sum(x => x.PackedSize) + _versionPackageManifest.Sum(x => x.Size);
|
int totalSizeRequired = _versionPackageManifest.Where(x => !cachedPackages.Contains(x.Signature)).Sum(x => x.PackedSize) + _versionPackageManifest.Sum(x => x.Size);
|
||||||
|
|
||||||
if (Filesystem.GetFreeDiskSpace(Paths.Base) < totalSizeRequired)
|
if (Filesystem.GetFreeDiskSpace(Paths.Base) < totalSizeRequired)
|
||||||
{
|
{
|
||||||
Frontend.ShowMessageBox(
|
Frontend.ShowMessageBox(Strings.Bootstrapper_NotEnoughSpace, MessageBoxImage.Error);
|
||||||
Strings.Bootstrapper_NotEnoughSpace,
|
|
||||||
MessageBoxImage.Error
|
|
||||||
);
|
|
||||||
|
|
||||||
App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
|
App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Dialog is not null)
|
if (Dialog is not null)
|
||||||
{
|
{
|
||||||
|
// TODO: cancelling needs to always be enabled
|
||||||
Dialog.CancelEnabled = true;
|
Dialog.CancelEnabled = true;
|
||||||
Dialog.ProgressStyle = ProgressBarStyle.Continuous;
|
Dialog.ProgressStyle = ProgressBarStyle.Continuous;
|
||||||
|
|
||||||
@ -617,9 +550,11 @@ namespace Bloxstrap
|
|||||||
_progressIncrement = (double)ProgressBarMaximum / _versionPackageManifest.Sum(package => package.PackedSize);
|
_progressIncrement = (double)ProgressBarMaximum / _versionPackageManifest.Sum(package => package.PackedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Package package in _versionPackageManifest)
|
var extractionTasks = new List<Task>();
|
||||||
|
|
||||||
|
foreach (var package in _versionPackageManifest)
|
||||||
{
|
{
|
||||||
if (_cancelFired)
|
if (_cancelTokenSource.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// download all the packages synchronously
|
// download all the packages synchronously
|
||||||
@ -631,40 +566,91 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
// extract the package immediately after download asynchronously
|
// extract the package immediately after download asynchronously
|
||||||
// discard is just used to suppress the warning
|
// discard is just used to suppress the warning
|
||||||
_ = Task.Run(() => ExtractPackage(package).ContinueWith(AsyncHelpers.ExceptionHandler, $"extracting {package.Name}"));
|
extractionTasks.Add(Task.Run(() => ExtractPackage(package), _cancelTokenSource.Token));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_cancelFired)
|
if (_cancelTokenSource.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// allow progress bar to 100% before continuing (purely ux reasons lol)
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
if (Dialog is not null)
|
if (Dialog is not null)
|
||||||
{
|
{
|
||||||
|
// allow progress bar to 100% before continuing (purely ux reasons lol)
|
||||||
|
// TODO: come up with a better way of handling this that is non-blocking
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
Dialog.ProgressStyle = ProgressBarStyle.Marquee;
|
Dialog.ProgressStyle = ProgressBarStyle.Marquee;
|
||||||
SetStatus(Strings.Bootstrapper_Status_Configuring);
|
SetStatus(Strings.Bootstrapper_Status_Configuring);
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for all packages to finish extracting, with an exception for the webview2 runtime installer
|
// TODO: handle faulted tasks
|
||||||
while (_packagesExtracted < _versionPackageManifest.Where(x => x.Name != "WebView2RuntimeInstaller.zip").Count())
|
await Task.WhenAll(extractionTasks);
|
||||||
{
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Writing AppSettings.xml...");
|
App.Logger.WriteLine(LOG_IDENT, "Writing AppSettings.xml...");
|
||||||
string appSettingsLocation = Path.Combine(_versionFolder, "AppSettings.xml");
|
await File.WriteAllTextAsync(Path.Combine(AppData.StagingDirectory, "AppSettings.xml"), AppSettings);
|
||||||
await File.WriteAllTextAsync(appSettingsLocation, AppSettings);
|
|
||||||
|
|
||||||
if (_cancelFired)
|
if (_cancelTokenSource.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!FreshInstall)
|
if (FreshInstall)
|
||||||
{
|
{
|
||||||
// let's take this opportunity to delete any packages we don't need anymore
|
using var hklmKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
|
||||||
|
using var hkcuKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
|
||||||
|
|
||||||
|
if (hklmKey is null && hkcuKey is null)
|
||||||
|
{
|
||||||
|
var result = Frontend.ShowMessageBox(Strings.Bootstrapper_WebView2NotFound, MessageBoxImage.Warning, MessageBoxButton.YesNo, MessageBoxResult.Yes);
|
||||||
|
|
||||||
|
if (result == MessageBoxResult.Yes)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Installing WebView2 runtime...");
|
||||||
|
|
||||||
|
var package = _versionPackageManifest.Find(x => x.Name == "WebView2RuntimeInstaller.zip");
|
||||||
|
|
||||||
|
if (package is null)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Aborted runtime install because package does not exist, has WebView2 been added in this Roblox version yet?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string baseDirectory = Path.Combine(AppData.StagingDirectory, AppData.PackageDirectoryMap[package.Name]);
|
||||||
|
|
||||||
|
ExtractPackage(package);
|
||||||
|
|
||||||
|
SetStatus(Strings.Bootstrapper_Status_InstallingWebView2);
|
||||||
|
|
||||||
|
var startInfo = new ProcessStartInfo()
|
||||||
|
{
|
||||||
|
WorkingDirectory = baseDirectory,
|
||||||
|
FileName = Path.Combine(baseDirectory, "MicrosoftEdgeWebview2Setup.exe"),
|
||||||
|
Arguments = "/silent /install"
|
||||||
|
};
|
||||||
|
|
||||||
|
await Process.Start(startInfo)!.WaitForExitAsync();
|
||||||
|
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Finished installing runtime");
|
||||||
|
|
||||||
|
Directory.Delete(baseDirectory, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finishing and cleanup
|
||||||
|
|
||||||
|
AppData.State.VersionGuid = _latestVersionGuid;
|
||||||
|
|
||||||
|
AppData.State.PackageHashes.Clear();
|
||||||
|
|
||||||
|
foreach (var package in _versionPackageManifest)
|
||||||
|
AppData.State.PackageHashes.Add(package.Name, package.Signature);
|
||||||
|
|
||||||
|
var allPackageHashes = new List<string>();
|
||||||
|
|
||||||
|
allPackageHashes.AddRange(App.State.Prop.Player.PackageHashes.Values);
|
||||||
|
allPackageHashes.AddRange(App.State.Prop.Studio.PackageHashes.Values);
|
||||||
|
|
||||||
foreach (string filename in cachedPackages)
|
foreach (string filename in cachedPackages)
|
||||||
{
|
{
|
||||||
if (!_versionPackageManifest.Exists(package => filename.Contains(package.Signature)))
|
if (!allPackageHashes.Contains(filename))
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Deleting unused package {filename}");
|
App.Logger.WriteLine(LOG_IDENT, $"Deleting unused package {filename}");
|
||||||
|
|
||||||
@ -680,284 +666,199 @@ namespace Bloxstrap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string oldVersionFolder = Path.Combine(Paths.Versions, _versionGuid);
|
App.Logger.WriteLine(LOG_IDENT, "Registering approximate program size...");
|
||||||
|
|
||||||
// move old compatibility flags for the old location
|
int distributionSize = _versionPackageManifest.Sum(x => x.Size + x.PackedSize) / 1000;
|
||||||
using (RegistryKey appFlagsKey = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"))
|
|
||||||
|
AppData.State.Size = distributionSize;
|
||||||
|
|
||||||
|
int totalSize = App.State.Prop.Player.Size + App.State.Prop.Studio.Size;
|
||||||
|
|
||||||
|
using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey))
|
||||||
{
|
{
|
||||||
string oldGameClientLocation = Path.Combine(oldVersionFolder, AppData.ExecutableName);
|
uninstallKey.SetValue("EstimatedSize", totalSize);
|
||||||
string? appFlags = (string?)appFlagsKey.GetValue(oldGameClientLocation);
|
|
||||||
|
|
||||||
if (appFlags is not null)
|
|
||||||
{
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Migrating app compatibility flags from {oldGameClientLocation} to {_playerLocation}...");
|
|
||||||
appFlagsKey.SetValue(_playerLocation, appFlags);
|
|
||||||
appFlagsKey.DeleteValue(oldGameClientLocation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_versionGuid = _latestVersionGuid;
|
App.Logger.WriteLine(LOG_IDENT, $"Registered as {totalSize} KB");
|
||||||
|
|
||||||
// delete any old version folders
|
|
||||||
// we only do this if roblox isnt running just in case an update happened
|
|
||||||
// while they were launching a second instance or something idk
|
|
||||||
#if STUDIO_FEATURES
|
|
||||||
if (!Process.GetProcessesByName(App.RobloxPlayerAppName).Any() && !Process.GetProcessesByName(App.RobloxStudioAppName).Any())
|
|
||||||
#else
|
|
||||||
if (!Process.GetProcessesByName(App.RobloxPlayerAppName).Any())
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
foreach (DirectoryInfo dir in new DirectoryInfo(Paths.Versions).GetDirectories())
|
|
||||||
{
|
|
||||||
if (dir.Name == App.State.Prop.PlayerVersionGuid || dir.Name == App.State.Prop.StudioVersionGuid || !dir.Name.StartsWith("version-"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Removing old version folder for {dir.Name}");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
dir.Delete(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Failed to delete version folder!");
|
|
||||||
App.Logger.WriteException(LOG_IDENT, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't register program size until the program is registered, which will be done after this
|
|
||||||
if (!FreshInstall)
|
|
||||||
RegisterProgramSize();
|
|
||||||
|
|
||||||
if (Dialog is not null)
|
if (Dialog is not null)
|
||||||
Dialog.CancelEnabled = false;
|
Dialog.CancelEnabled = false;
|
||||||
|
|
||||||
|
if (Directory.Exists(AppData.FinalDirectory))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// gross hack to see if roblox is still running
|
||||||
|
// i don't want to rely on mutexes because they can change, and will false flag for
|
||||||
|
// running installations that are not by bloxstrap
|
||||||
|
File.Delete(AppData.ExecutablePath);
|
||||||
|
|
||||||
|
Directory.Delete(AppData.FinalDirectory, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Could not delete executable/folder, Roblox may still be running. Aborting update.");
|
||||||
|
App.Logger.WriteException(LOG_IDENT, ex);
|
||||||
|
|
||||||
|
Directory.Delete(AppData.StagingDirectory);
|
||||||
|
|
||||||
|
_isInstalling = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.Move(AppData.StagingDirectory, AppData.FinalDirectory);
|
||||||
|
|
||||||
|
App.State.Save();
|
||||||
|
|
||||||
_isInstalling = false;
|
_isInstalling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InstallWebView2()
|
//private async Task ApplyModifications()
|
||||||
{
|
//{
|
||||||
const string LOG_IDENT = "Bootstrapper::InstallWebView2";
|
// const string LOG_IDENT = "Bootstrapper::ApplyModifications";
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Installing runtime...");
|
// if (Process.GetProcessesByName(AppData.ExecutableName[..^4]).Any())
|
||||||
|
// {
|
||||||
|
// App.Logger.WriteLine(LOG_IDENT, "Roblox is running, aborting mod check");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
string baseDirectory = Path.Combine(_versionFolder, "WebView2RuntimeInstaller");
|
// SetStatus(Strings.Bootstrapper_Status_ApplyingModifications);
|
||||||
|
|
||||||
if (!Directory.Exists(baseDirectory))
|
// // handle file mods
|
||||||
{
|
// App.Logger.WriteLine(LOG_IDENT, "Checking file mods...");
|
||||||
Package? package = _versionPackageManifest.Find(x => x.Name == "WebView2RuntimeInstaller.zip");
|
|
||||||
|
|
||||||
if (package is null)
|
// // manifest has been moved to State.json
|
||||||
{
|
// File.Delete(Path.Combine(Paths.Base, "ModManifest.txt"));
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Aborted runtime install because package does not exist, has WebView2 been added in this Roblox version yet?");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ExtractPackage(package);
|
// List<string> modFolderFiles = new();
|
||||||
}
|
|
||||||
|
|
||||||
SetStatus(Strings.Bootstrapper_Status_InstallingWebView2);
|
// if (!Directory.Exists(Paths.Modifications))
|
||||||
|
// Directory.CreateDirectory(Paths.Modifications);
|
||||||
|
|
||||||
ProcessStartInfo startInfo = new()
|
// // check custom font mod
|
||||||
{
|
// // instead of replacing the fonts themselves, we'll just alter the font family manifests
|
||||||
WorkingDirectory = baseDirectory,
|
|
||||||
FileName = Path.Combine(baseDirectory, "MicrosoftEdgeWebview2Setup.exe"),
|
|
||||||
Arguments = "/silent /install"
|
|
||||||
};
|
|
||||||
|
|
||||||
await Process.Start(startInfo)!.WaitForExitAsync();
|
// string modFontFamiliesFolder = Path.Combine(Paths.Modifications, "content\\fonts\\families");
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Finished installing runtime");
|
// if (File.Exists(Paths.CustomFont))
|
||||||
}
|
// {
|
||||||
|
// App.Logger.WriteLine(LOG_IDENT, "Begin font check");
|
||||||
|
|
||||||
private async Task ApplyModifications()
|
// Directory.CreateDirectory(modFontFamiliesFolder);
|
||||||
{
|
|
||||||
const string LOG_IDENT = "Bootstrapper::ApplyModifications";
|
|
||||||
|
|
||||||
if (Process.GetProcessesByName(AppData.ExecutableName[..^4]).Any())
|
// foreach (string jsonFilePath in Directory.GetFiles(Path.Combine(_versionFolder, "content\\fonts\\families")))
|
||||||
{
|
// {
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Roblox is running, aborting mod check");
|
// string jsonFilename = Path.GetFileName(jsonFilePath);
|
||||||
return;
|
// string modFilepath = Path.Combine(modFontFamiliesFolder, jsonFilename);
|
||||||
}
|
|
||||||
|
|
||||||
SetStatus(Strings.Bootstrapper_Status_ApplyingModifications);
|
// if (File.Exists(modFilepath))
|
||||||
|
// continue;
|
||||||
|
|
||||||
// set executable flags for fullscreen optimizations
|
// App.Logger.WriteLine(LOG_IDENT, $"Setting font for {jsonFilename}");
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Checking executable flags...");
|
|
||||||
using (RegistryKey appFlagsKey = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"))
|
|
||||||
{
|
|
||||||
string flag = " DISABLEDXMAXIMIZEDWINDOWEDMODE";
|
|
||||||
string? appFlags = (string?)appFlagsKey.GetValue(_playerLocation);
|
|
||||||
|
|
||||||
if (App.Settings.Prop.DisableFullscreenOptimizations)
|
// FontFamily? fontFamilyData = JsonSerializer.Deserialize<FontFamily>(File.ReadAllText(jsonFilePath));
|
||||||
{
|
|
||||||
if (appFlags is null)
|
|
||||||
appFlagsKey.SetValue(_playerLocation, $"~{flag}");
|
|
||||||
else if (!appFlags.Contains(flag))
|
|
||||||
appFlagsKey.SetValue(_playerLocation, appFlags + flag);
|
|
||||||
}
|
|
||||||
else if (appFlags is not null && appFlags.Contains(flag))
|
|
||||||
{
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Deleting flag '{flag.Trim()}'");
|
|
||||||
|
|
||||||
// if there's more than one space, there's more flags set we need to preserve
|
// if (fontFamilyData is null)
|
||||||
if (appFlags.Split(' ').Length > 2)
|
// continue;
|
||||||
appFlagsKey.SetValue(_playerLocation, appFlags.Remove(appFlags.IndexOf(flag), flag.Length));
|
|
||||||
else
|
|
||||||
appFlagsKey.DeleteValue(_playerLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hmm, maybe make a unified handler for this? this is just lazily copy pasted from above
|
// foreach (FontFace fontFace in fontFamilyData.Faces)
|
||||||
|
// fontFace.AssetId = "rbxasset://fonts/CustomFont.ttf";
|
||||||
|
|
||||||
flag = " RUNASADMIN";
|
// // TODO: writing on every launch is not necessary
|
||||||
appFlags = (string?)appFlagsKey.GetValue(_playerLocation);
|
// File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true }));
|
||||||
|
// }
|
||||||
|
|
||||||
if (appFlags is not null && appFlags.Contains(flag))
|
// App.Logger.WriteLine(LOG_IDENT, "End font check");
|
||||||
{
|
// }
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Deleting flag '{flag.Trim()}'");
|
// else if (Directory.Exists(modFontFamiliesFolder))
|
||||||
|
// {
|
||||||
|
// Directory.Delete(modFontFamiliesFolder, true);
|
||||||
|
// }
|
||||||
|
|
||||||
// if there's more than one space, there's more flags set we need to preserve
|
// foreach (string file in Directory.GetFiles(Paths.Modifications, "*.*", SearchOption.AllDirectories))
|
||||||
if (appFlags.Split(' ').Length > 2)
|
// {
|
||||||
appFlagsKey.SetValue(_playerLocation, appFlags.Remove(appFlags.IndexOf(flag), flag.Length));
|
// // get relative directory path
|
||||||
else
|
// string relativeFile = file.Substring(Paths.Modifications.Length + 1);
|
||||||
appFlagsKey.DeleteValue(_playerLocation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle file mods
|
// // v1.7.0 - README has been moved to the preferences menu now
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Checking file mods...");
|
// if (relativeFile == "README.txt")
|
||||||
|
// {
|
||||||
|
// File.Delete(file);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
// manifest has been moved to State.json
|
// if (!App.Settings.Prop.UseFastFlagManager && String.Equals(relativeFile, "ClientSettings\\ClientAppSettings.json", StringComparison.OrdinalIgnoreCase))
|
||||||
File.Delete(Path.Combine(Paths.Base, "ModManifest.txt"));
|
// continue;
|
||||||
|
|
||||||
List<string> modFolderFiles = new();
|
// if (relativeFile.EndsWith(".lock"))
|
||||||
|
// continue;
|
||||||
|
|
||||||
if (!Directory.Exists(Paths.Modifications))
|
// modFolderFiles.Add(relativeFile);
|
||||||
Directory.CreateDirectory(Paths.Modifications);
|
|
||||||
|
|
||||||
// check custom font mod
|
// string fileModFolder = Path.Combine(Paths.Modifications, relativeFile);
|
||||||
// instead of replacing the fonts themselves, we'll just alter the font family manifests
|
// string fileVersionFolder = Path.Combine(_versionFolder, relativeFile);
|
||||||
|
|
||||||
string modFontFamiliesFolder = Path.Combine(Paths.Modifications, "content\\fonts\\families");
|
// if (File.Exists(fileVersionFolder) && MD5Hash.FromFile(fileModFolder) == MD5Hash.FromFile(fileVersionFolder))
|
||||||
|
// {
|
||||||
|
// App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} already exists in the version folder, and is a match");
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
if (File.Exists(Paths.CustomFont))
|
// Directory.CreateDirectory(Path.GetDirectoryName(fileVersionFolder)!);
|
||||||
{
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Begin font check");
|
|
||||||
|
|
||||||
Directory.CreateDirectory(modFontFamiliesFolder);
|
// Filesystem.AssertReadOnly(fileVersionFolder);
|
||||||
|
// File.Copy(fileModFolder, fileVersionFolder, true);
|
||||||
|
// Filesystem.AssertReadOnly(fileVersionFolder);
|
||||||
|
|
||||||
foreach (string jsonFilePath in Directory.GetFiles(Path.Combine(_versionFolder, "content\\fonts\\families")))
|
// App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} has been copied to the version folder");
|
||||||
{
|
// }
|
||||||
string jsonFilename = Path.GetFileName(jsonFilePath);
|
|
||||||
string modFilepath = Path.Combine(modFontFamiliesFolder, jsonFilename);
|
|
||||||
|
|
||||||
if (File.Exists(modFilepath))
|
// // the manifest is primarily here to keep track of what files have been
|
||||||
continue;
|
// // deleted from the modifications folder, so that we know when to restore the original files from the downloaded packages
|
||||||
|
// // now check for files that have been deleted from the mod folder according to the manifest
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Setting font for {jsonFilename}");
|
// // TODO: this needs to extract the files from packages in bulk, this is way too slow
|
||||||
|
// foreach (string fileLocation in App.State.Prop.ModManifest)
|
||||||
|
// {
|
||||||
|
// if (modFolderFiles.Contains(fileLocation))
|
||||||
|
// continue;
|
||||||
|
|
||||||
FontFamily? fontFamilyData = JsonSerializer.Deserialize<FontFamily>(File.ReadAllText(jsonFilePath));
|
// var package = AppData.PackageDirectoryMap.SingleOrDefault(x => x.Value != "" && fileLocation.StartsWith(x.Value));
|
||||||
|
|
||||||
if (fontFamilyData is null)
|
// // package doesn't exist, likely mistakenly placed file
|
||||||
continue;
|
// if (String.IsNullOrEmpty(package.Key))
|
||||||
|
// {
|
||||||
|
// App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod but does not belong to a package");
|
||||||
|
|
||||||
foreach (FontFace fontFace in fontFamilyData.Faces)
|
// string versionFileLocation = Path.Combine(_versionFolder, fileLocation);
|
||||||
fontFace.AssetId = "rbxasset://fonts/CustomFont.ttf";
|
|
||||||
|
|
||||||
// TODO: writing on every launch is not necessary
|
// if (File.Exists(versionFileLocation))
|
||||||
File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true }));
|
// File.Delete(versionFileLocation);
|
||||||
}
|
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "End font check");
|
// continue;
|
||||||
}
|
// }
|
||||||
else if (Directory.Exists(modFontFamiliesFolder))
|
|
||||||
{
|
|
||||||
Directory.Delete(modFontFamiliesFolder, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (string file in Directory.GetFiles(Paths.Modifications, "*.*", SearchOption.AllDirectories))
|
// // restore original file
|
||||||
{
|
// string fileName = fileLocation.Substring(package.Value.Length);
|
||||||
// get relative directory path
|
// await ExtractFileFromPackage(package.Key, fileName);
|
||||||
string relativeFile = file.Substring(Paths.Modifications.Length + 1);
|
|
||||||
|
|
||||||
// v1.7.0 - README has been moved to the preferences menu now
|
// App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod, restored from {package.Key}");
|
||||||
if (relativeFile == "README.txt")
|
// }
|
||||||
{
|
|
||||||
File.Delete(file);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!App.Settings.Prop.UseFastFlagManager && String.Equals(relativeFile, "ClientSettings\\ClientAppSettings.json", StringComparison.OrdinalIgnoreCase))
|
// App.State.Prop.ModManifest = modFolderFiles;
|
||||||
continue;
|
// App.State.Save();
|
||||||
|
|
||||||
if (relativeFile.EndsWith(".lock"))
|
// App.Logger.WriteLine(LOG_IDENT, $"Finished checking file mods");
|
||||||
continue;
|
//}
|
||||||
|
|
||||||
modFolderFiles.Add(relativeFile);
|
|
||||||
|
|
||||||
string fileModFolder = Path.Combine(Paths.Modifications, relativeFile);
|
|
||||||
string fileVersionFolder = Path.Combine(_versionFolder, relativeFile);
|
|
||||||
|
|
||||||
if (File.Exists(fileVersionFolder) && MD5Hash.FromFile(fileModFolder) == MD5Hash.FromFile(fileVersionFolder))
|
|
||||||
{
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} already exists in the version folder, and is a match");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
// the manifest is primarily here to keep track of what files have been
|
|
||||||
// deleted from the modifications folder, so that we know when to restore the original files from the downloaded packages
|
|
||||||
// now check for files that have been deleted from the mod folder according to the manifest
|
|
||||||
|
|
||||||
// TODO: this needs to extract the files from packages in bulk, this is way too slow
|
|
||||||
foreach (string fileLocation in App.State.Prop.ModManifest)
|
|
||||||
{
|
|
||||||
if (modFolderFiles.Contains(fileLocation))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var package = AppData.PackageDirectoryMap.SingleOrDefault(x => x.Value != "" && fileLocation.StartsWith(x.Value));
|
|
||||||
|
|
||||||
// package doesn't exist, likely mistakenly placed file
|
|
||||||
if (String.IsNullOrEmpty(package.Key))
|
|
||||||
{
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod but does not belong to a package");
|
|
||||||
|
|
||||||
string versionFileLocation = Path.Combine(_versionFolder, fileLocation);
|
|
||||||
|
|
||||||
if (File.Exists(versionFileLocation))
|
|
||||||
File.Delete(versionFileLocation);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// restore original file
|
|
||||||
string fileName = fileLocation.Substring(package.Value.Length);
|
|
||||||
await ExtractFileFromPackage(package.Key, fileName);
|
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod, restored from {package.Key}");
|
|
||||||
}
|
|
||||||
|
|
||||||
App.State.Prop.ModManifest = modFolderFiles;
|
|
||||||
App.State.Save();
|
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Finished checking file mods");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DownloadPackage(Package package)
|
private async Task DownloadPackage(Package package)
|
||||||
{
|
{
|
||||||
string LOG_IDENT = $"Bootstrapper::DownloadPackage.{package.Name}";
|
string LOG_IDENT = $"Bootstrapper::DownloadPackage.{package.Name}";
|
||||||
|
|
||||||
if (_cancelFired)
|
if (_cancelTokenSource.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string packageUrl = RobloxDeployment.GetLocation($"/{_latestVersionGuid}-{package.Name}");
|
string packageUrl = RobloxDeployment.GetLocation($"/{_latestVersionGuid}-{package.Name}");
|
||||||
@ -966,7 +867,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
if (File.Exists(packageLocation))
|
if (File.Exists(packageLocation))
|
||||||
{
|
{
|
||||||
FileInfo file = new(packageLocation);
|
var file = new FileInfo(packageLocation);
|
||||||
|
|
||||||
string calculatedMD5 = MD5Hash.FromFile(packageLocation);
|
string calculatedMD5 = MD5Hash.FromFile(packageLocation);
|
||||||
|
|
||||||
@ -1002,6 +903,9 @@ namespace Bloxstrap
|
|||||||
if (File.Exists(packageLocation))
|
if (File.Exists(packageLocation))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// TODO: telemetry for this. chances are that this is completely unnecessary and that it can be removed.
|
||||||
|
// but, we need to ensure this doesn't work before we can do that.
|
||||||
|
|
||||||
const int maxTries = 5;
|
const int maxTries = 5;
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Downloading...");
|
App.Logger.WriteLine(LOG_IDENT, "Downloading...");
|
||||||
@ -1010,7 +914,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
for (int i = 1; i <= maxTries; i++)
|
for (int i = 1; i <= maxTries; i++)
|
||||||
{
|
{
|
||||||
if (_cancelFired)
|
if (_cancelTokenSource.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int totalBytesRead = 0;
|
int totalBytesRead = 0;
|
||||||
@ -1023,7 +927,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (_cancelFired)
|
if (_cancelTokenSource.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
stream.Close();
|
stream.Close();
|
||||||
fileStream.Close();
|
fileStream.Close();
|
||||||
@ -1087,15 +991,12 @@ namespace Bloxstrap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task ExtractPackage(Package package)
|
private void ExtractPackage(Package package)
|
||||||
{
|
{
|
||||||
const string LOG_IDENT = "Bootstrapper::ExtractPackage";
|
const string LOG_IDENT = "Bootstrapper::ExtractPackage";
|
||||||
|
|
||||||
if (_cancelFired)
|
|
||||||
return Task.CompletedTask;
|
|
||||||
|
|
||||||
string packageLocation = Path.Combine(Paths.Downloads, package.Signature);
|
string packageLocation = Path.Combine(Paths.Downloads, package.Signature);
|
||||||
string packageFolder = Path.Combine(_versionFolder, AppData.PackageDirectoryMap[package.Name]);
|
string packageFolder = Path.Combine(AppData.StagingDirectory, AppData.PackageDirectoryMap[package.Name]);
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Extracting {package.Name}...");
|
App.Logger.WriteLine(LOG_IDENT, $"Extracting {package.Name}...");
|
||||||
|
|
||||||
@ -1103,31 +1004,27 @@ namespace Bloxstrap
|
|||||||
fastZip.ExtractZip(packageLocation, packageFolder, null);
|
fastZip.ExtractZip(packageLocation, packageFolder, null);
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Finished extracting {package.Name}");
|
App.Logger.WriteLine(LOG_IDENT, $"Finished extracting {package.Name}");
|
||||||
|
|
||||||
_packagesExtracted += 1;
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExtractFileFromPackage(string packageName, string fileName)
|
//private async Task ExtractFileFromPackage(string packageName, string fileName)
|
||||||
{
|
//{
|
||||||
Package? package = _versionPackageManifest.Find(x => x.Name == packageName);
|
// Package? package = _versionPackageManifest.Find(x => x.Name == packageName);
|
||||||
|
|
||||||
if (package is null)
|
// if (package is null)
|
||||||
return;
|
// return;
|
||||||
|
|
||||||
await DownloadPackage(package);
|
// await DownloadPackage(package);
|
||||||
|
|
||||||
using ZipArchive archive = ZipFile.OpenRead(Path.Combine(Paths.Downloads, package.Signature));
|
// using ZipArchive archive = ZipFile.OpenRead(Path.Combine(Paths.Downloads, package.Signature));
|
||||||
|
|
||||||
ZipArchiveEntry? entry = archive.Entries.FirstOrDefault(x => x.FullName == fileName);
|
// ZipArchiveEntry? entry = archive.Entries.FirstOrDefault(x => x.FullName == fileName);
|
||||||
|
|
||||||
if (entry is null)
|
// if (entry is null)
|
||||||
return;
|
// return;
|
||||||
|
|
||||||
string extractionPath = Path.Combine(_versionFolder, AppData.PackageDirectoryMap[package.Name], entry.FullName);
|
// string extractionPath = Path.Combine(_versionFolder, AppData.PackageDirectoryMap[package.Name], entry.FullName);
|
||||||
entry.ExtractToFile(extractionPath, true);
|
// entry.ExtractToFile(extractionPath, true);
|
||||||
}
|
//}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
using System.Windows;
|
namespace Bloxstrap.Integrations
|
||||||
|
|
||||||
namespace Bloxstrap.Integrations
|
|
||||||
{
|
{
|
||||||
public class ActivityWatcher : IDisposable
|
public class ActivityWatcher : IDisposable
|
||||||
{
|
{
|
||||||
|
@ -169,15 +169,6 @@ namespace Bloxstrap
|
|||||||
App.Terminate(ErrorCode.ERROR_FILE_NOT_FOUND);
|
App.Terminate(ErrorCode.ERROR_FILE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool installWebView2 = false;
|
|
||||||
{
|
|
||||||
using var hklmKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
|
|
||||||
using var hkcuKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
|
|
||||||
|
|
||||||
if (hklmKey is null && hkcuKey is null)
|
|
||||||
installWebView2 = Frontend.ShowMessageBox(Strings.Bootstrapper_WebView2NotFound, MessageBoxImage.Warning, MessageBoxButton.YesNo, MessageBoxResult.Yes) == MessageBoxResult.Yes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (App.Settings.Prop.ConfirmLaunches && Mutex.TryOpenExisting("ROBLOX_singletonMutex", out var _))
|
if (App.Settings.Prop.ConfirmLaunches && Mutex.TryOpenExisting("ROBLOX_singletonMutex", out var _))
|
||||||
{
|
{
|
||||||
// this currently doesn't work very well since it relies on checking the existence of the singleton mutex
|
// this currently doesn't work very well since it relies on checking the existence of the singleton mutex
|
||||||
@ -195,7 +186,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
// start bootstrapper and show the bootstrapper modal if we're not running silently
|
// start bootstrapper and show the bootstrapper modal if we're not running silently
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
|
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
|
||||||
var bootstrapper = new Bootstrapper(installWebView2);
|
var bootstrapper = new Bootstrapper();
|
||||||
IBootstrapperDialog? dialog = null;
|
IBootstrapperDialog? dialog = null;
|
||||||
|
|
||||||
if (!App.LaunchSettings.QuietFlag.Active)
|
if (!App.LaunchSettings.QuietFlag.Active)
|
||||||
|
@ -137,7 +137,7 @@ namespace Bloxstrap.Models
|
|||||||
|
|
||||||
private void RejoinServer()
|
private void RejoinServer()
|
||||||
{
|
{
|
||||||
string playerPath = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid, "RobloxPlayerBeta.exe");
|
string playerPath = Path.Combine(Paths.Roblox, "Player", "RobloxPlayerBeta.exe");
|
||||||
|
|
||||||
Process.Start(playerPath, GetInviteDeeplink(false));
|
Process.Start(playerPath, GetInviteDeeplink(false));
|
||||||
}
|
}
|
||||||
|
11
Bloxstrap/Models/AppState.cs
Normal file
11
Bloxstrap/Models/AppState.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace Bloxstrap.Models
|
||||||
|
{
|
||||||
|
public class AppState
|
||||||
|
{
|
||||||
|
public string VersionGuid { get; set; } = String.Empty;
|
||||||
|
|
||||||
|
public Dictionary<string, string> PackageHashes { get; set; } = new();
|
||||||
|
|
||||||
|
public int Size { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -8,9 +8,9 @@ namespace Bloxstrap.Models.Manifest
|
|||||||
{
|
{
|
||||||
public class PackageManifest : List<Package>
|
public class PackageManifest : List<Package>
|
||||||
{
|
{
|
||||||
private PackageManifest(string data)
|
public PackageManifest(string data)
|
||||||
{
|
{
|
||||||
using StringReader reader = new StringReader(data);
|
using var reader = new StringReader(data);
|
||||||
string? version = reader.ReadLine();
|
string? version = reader.ReadLine();
|
||||||
|
|
||||||
if (version != "v0")
|
if (version != "v0")
|
||||||
@ -46,13 +46,5 @@ namespace Bloxstrap.Models.Manifest
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<PackageManifest> Get(string versionGuid)
|
|
||||||
{
|
|
||||||
string pkgManifestUrl = RobloxDeployment.GetLocation($"/{versionGuid}-rbxPkgManifest.txt");
|
|
||||||
var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl);
|
|
||||||
|
|
||||||
return new PackageManifest(pkgManifestData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,9 @@
|
|||||||
{
|
{
|
||||||
public bool ShowFFlagEditorWarning { get; set; } = true;
|
public bool ShowFFlagEditorWarning { get; set; } = true;
|
||||||
|
|
||||||
[Obsolete("Use PlayerVersionGuid instead", true)]
|
public AppState Player { get; set; } = new();
|
||||||
public string VersionGuid { set { PlayerVersionGuid = value; } }
|
|
||||||
public string PlayerVersionGuid { get; set; } = "";
|
|
||||||
public string StudioVersionGuid { get; set; } = "";
|
|
||||||
|
|
||||||
public int PlayerSize { get; set; } = 0;
|
public AppState Studio { get; set; } = new();
|
||||||
public int StudioSize { get; set; } = 0;
|
|
||||||
|
|
||||||
public List<string> ModManifest { get; set; } = new();
|
public List<string> ModManifest { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
public static string Integrations { get; private set; } = "";
|
public static string Integrations { get; private set; } = "";
|
||||||
public static string Versions { get; private set; } = "";
|
public static string Versions { get; private set; } = "";
|
||||||
public static string Modifications { get; private set; } = "";
|
public static string Modifications { get; private set; } = "";
|
||||||
|
public static string Roblox { get; private set; } = "";
|
||||||
|
|
||||||
public static string Application { get; private set; } = "";
|
public static string Application { get; private set; } = "";
|
||||||
|
|
||||||
@ -37,6 +38,7 @@
|
|||||||
Integrations = Path.Combine(Base, "Integrations");
|
Integrations = Path.Combine(Base, "Integrations");
|
||||||
Versions = Path.Combine(Base, "Versions");
|
Versions = Path.Combine(Base, "Versions");
|
||||||
Modifications = Path.Combine(Base, "Modifications");
|
Modifications = Path.Combine(Base, "Modifications");
|
||||||
|
Roblox = Path.Combine(Base, "Roblox");
|
||||||
|
|
||||||
Application = Path.Combine(Base, $"{App.ProjectName}.exe");
|
Application = Path.Combine(Base, $"{App.ProjectName}.exe");
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
private static readonly Dictionary<string, int> BaseUrls = new()
|
private static readonly Dictionary<string, int> BaseUrls = new()
|
||||||
{
|
{
|
||||||
{ "https://setup.rbxcdn.com", 0 },
|
{ "https://setup.rbxcdn.com", 0 },
|
||||||
|
{ "https://setup-aws.rbxcdn.com", 2 },
|
||||||
{ "https://setup-ak.rbxcdn.com", 2 },
|
{ "https://setup-ak.rbxcdn.com", 2 },
|
||||||
{ "https://roblox-setup.cachefly.net", 2 },
|
{ "https://roblox-setup.cachefly.net", 2 },
|
||||||
{ "https://s3.amazonaws.com/setup.roblox.com", 4 }
|
{ "https://s3.amazonaws.com/setup.roblox.com", 4 }
|
||||||
@ -22,7 +23,7 @@
|
|||||||
|
|
||||||
private static async Task<string?> TestConnection(string url, int priority, CancellationToken token)
|
private static async Task<string?> TestConnection(string url, int priority, CancellationToken token)
|
||||||
{
|
{
|
||||||
string LOG_IDENT = $"RobloxDeployment::TestConnection.{url}";
|
string LOG_IDENT = $"RobloxDeployment::TestConnection<{url}>";
|
||||||
|
|
||||||
await Task.Delay(priority * 1000, token);
|
await Task.Delay(priority * 1000, token);
|
||||||
|
|
||||||
@ -38,8 +39,9 @@
|
|||||||
// versionStudio is the version hash for the last MFC studio to be deployed.
|
// versionStudio is the version hash for the last MFC studio to be deployed.
|
||||||
// the response body should always be "version-012732894899482c".
|
// the response body should always be "version-012732894899482c".
|
||||||
string content = await response.Content.ReadAsStringAsync(token);
|
string content = await response.Content.ReadAsStringAsync(token);
|
||||||
|
|
||||||
if (content != VersionStudioHash)
|
if (content != VersionStudioHash)
|
||||||
throw new Exception($"versionStudio response does not match (expected \"{VersionStudioHash}\", got \"{content}\")");
|
throw new InvalidHTTPResponseException($"versionStudio response does not match (expected \"{VersionStudioHash}\", got \"{content}\")");
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
@ -66,11 +68,10 @@
|
|||||||
|
|
||||||
// returns null for success
|
// returns null for success
|
||||||
|
|
||||||
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
var tokenSource = new CancellationTokenSource();
|
||||||
CancellationToken token = tokenSource.Token;
|
|
||||||
|
|
||||||
var exceptions = new List<Exception>();
|
var exceptions = new List<Exception>();
|
||||||
var tasks = (from entry in BaseUrls select TestConnection(entry.Key, entry.Value, token)).ToList();
|
var tasks = (from entry in BaseUrls select TestConnection(entry.Key, entry.Value, tokenSource.Token)).ToList();
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "Testing connectivity...");
|
App.Logger.WriteLine(LOG_IDENT, "Testing connectivity...");
|
||||||
|
|
||||||
@ -127,7 +128,11 @@
|
|||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"Getting deploy info for channel {channel}");
|
App.Logger.WriteLine(LOG_IDENT, $"Getting deploy info for channel {channel}");
|
||||||
|
|
||||||
|
if (String.IsNullOrEmpty(channel))
|
||||||
|
channel = DefaultChannel;
|
||||||
|
|
||||||
string cacheKey = $"{channel}-{binaryType}";
|
string cacheKey = $"{channel}-{binaryType}";
|
||||||
|
|
||||||
ClientVersion clientVersion;
|
ClientVersion clientVersion;
|
||||||
|
|
||||||
if (ClientVersionCache.ContainsKey(cacheKey))
|
if (ClientVersionCache.ContainsKey(cacheKey))
|
||||||
@ -137,48 +142,27 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
bool isDefaultChannel = String.Compare(channel, DefaultChannel, StringComparison.OrdinalIgnoreCase) == 0;
|
||||||
|
|
||||||
string path = $"/v2/client-version/{binaryType}";
|
string path = $"/v2/client-version/{binaryType}";
|
||||||
|
|
||||||
if (String.Compare(channel, DefaultChannel, StringComparison.InvariantCultureIgnoreCase) != 0)
|
if (!isDefaultChannel)
|
||||||
path = $"/v2/client-version/{binaryType}/channel/{channel}";
|
path = $"/v2/client-version/{binaryType}/channel/{channel}";
|
||||||
|
|
||||||
HttpResponseMessage deployInfoResponse;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
deployInfoResponse = await App.HttpClient.GetAsync("https://clientsettingscdn.roblox.com" + path);
|
clientVersion = await Http.GetJson<ClientVersion>($"https://clientsettingscdn.roblox.com/{path}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
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);
|
||||||
|
|
||||||
deployInfoResponse = await App.HttpClient.GetAsync("https://clientsettings.roblox.com" + path);
|
clientVersion = await Http.GetJson<ClientVersion>($"https://clientsettings.roblox.com/{path}");
|
||||||
}
|
|
||||||
|
|
||||||
string rawResponse = await deployInfoResponse.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
if (!deployInfoResponse.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
// 400 = Invalid binaryType.
|
|
||||||
// 404 = Could not find version details for binaryType.
|
|
||||||
// 500 = Error while fetching version information.
|
|
||||||
// either way, we throw
|
|
||||||
|
|
||||||
App.Logger.WriteLine(LOG_IDENT,
|
|
||||||
"Failed to fetch deploy info!\r\n" +
|
|
||||||
$"\tStatus code: {deployInfoResponse.StatusCode}\r\n" +
|
|
||||||
$"\tResponse: {rawResponse}"
|
|
||||||
);
|
|
||||||
|
|
||||||
throw new HttpResponseException(deployInfoResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
clientVersion = JsonSerializer.Deserialize<ClientVersion>(rawResponse)!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if channel is behind LIVE
|
// check if channel is behind LIVE
|
||||||
if (channel != DefaultChannel)
|
if (!isDefaultChannel)
|
||||||
{
|
{
|
||||||
var defaultClientVersion = await GetInfo(DefaultChannel);
|
var defaultClientVersion = await GetInfo(DefaultChannel);
|
||||||
|
|
||||||
@ -187,6 +171,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClientVersionCache[cacheKey] = clientVersion;
|
ClientVersionCache[cacheKey] = clientVersion;
|
||||||
|
}
|
||||||
|
|
||||||
return clientVersion;
|
return clientVersion;
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,6 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
|
|||||||
|
|
||||||
public ICommand CopyInstanceIdCommand => new RelayCommand(CopyInstanceId);
|
public ICommand CopyInstanceIdCommand => new RelayCommand(CopyInstanceId);
|
||||||
|
|
||||||
public EventHandler? RequestCloseEvent;
|
|
||||||
|
|
||||||
public ServerInformationViewModel(Watcher watcher)
|
public ServerInformationViewModel(Watcher watcher)
|
||||||
{
|
{
|
||||||
_activityWatcher = watcher.ActivityWatcher!;
|
_activityWatcher = watcher.ActivityWatcher!;
|
||||||
|
@ -27,20 +27,20 @@
|
|||||||
{
|
{
|
||||||
// wouldnt it be better to check old version guids?
|
// wouldnt it be better to check old version guids?
|
||||||
// what about fresh installs?
|
// what about fresh installs?
|
||||||
get => String.IsNullOrEmpty(App.State.Prop.PlayerVersionGuid) && String.IsNullOrEmpty(App.State.Prop.StudioVersionGuid);
|
get => String.IsNullOrEmpty(App.State.Prop.Player.VersionGuid) && String.IsNullOrEmpty(App.State.Prop.Studio.VersionGuid);
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value)
|
if (value)
|
||||||
{
|
{
|
||||||
_oldPlayerVersionGuid = App.State.Prop.PlayerVersionGuid;
|
_oldPlayerVersionGuid = App.State.Prop.Player.VersionGuid;
|
||||||
_oldStudioVersionGuid = App.State.Prop.StudioVersionGuid;
|
_oldStudioVersionGuid = App.State.Prop.Studio.VersionGuid;
|
||||||
App.State.Prop.PlayerVersionGuid = "";
|
App.State.Prop.Player.VersionGuid = "";
|
||||||
App.State.Prop.StudioVersionGuid = "";
|
App.State.Prop.Studio.VersionGuid = "";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
App.State.Prop.PlayerVersionGuid = _oldPlayerVersionGuid;
|
App.State.Prop.Player.VersionGuid = _oldPlayerVersionGuid;
|
||||||
App.State.Prop.StudioVersionGuid = _oldStudioVersionGuid;
|
App.State.Prop.Studio.VersionGuid = _oldStudioVersionGuid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
public static string GetRobloxVersion(bool studio)
|
public static string GetRobloxVersion(bool studio)
|
||||||
{
|
{
|
||||||
string versionGuid = studio ? App.State.Prop.StudioVersionGuid : App.State.Prop.PlayerVersionGuid;
|
string versionGuid = studio ? App.State.Prop.Studio.VersionGuid : App.State.Prop.Player.VersionGuid;
|
||||||
string fileName = studio ? "RobloxStudioBeta.exe" : "RobloxPlayerBeta.exe";
|
string fileName = studio ? "RobloxStudioBeta.exe" : "RobloxPlayerBeta.exe";
|
||||||
|
|
||||||
string playerLocation = Path.Combine(Paths.Versions, versionGuid, fileName);
|
string playerLocation = Path.Combine(Paths.Versions, versionGuid, fileName);
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
namespace Bloxstrap.Utility
|
|
||||||
{
|
|
||||||
public static class AsyncHelpers
|
|
||||||
{
|
|
||||||
public static void ExceptionHandler(Task task, object? state)
|
|
||||||
{
|
|
||||||
const string LOG_IDENT = "AsyncHelpers::ExceptionHandler";
|
|
||||||
|
|
||||||
if (task.Exception is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (state is null)
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, "An exception occurred while running the task");
|
|
||||||
else
|
|
||||||
App.Logger.WriteLine(LOG_IDENT, $"An exception occurred while running the task '{state}'");
|
|
||||||
|
|
||||||
App.FinalizeExceptionHandling(task.Exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -31,7 +31,7 @@ namespace Bloxstrap
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (String.IsNullOrEmpty(watcherData))
|
if (String.IsNullOrEmpty(watcherData))
|
||||||
{
|
{
|
||||||
string path = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid, "RobloxPlayerBeta.exe");
|
string path = Path.Combine(Paths.Roblox, "Player", "RobloxPlayerBeta.exe");
|
||||||
using var gameClientProcess = Process.Start(path);
|
using var gameClientProcess = Process.Start(path);
|
||||||
_gameClientPid = gameClientProcess.Id;
|
_gameClientPid = gameClientProcess.Id;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user