add studio support

This commit is contained in:
bluepilledgreat 2023-10-04 12:05:53 +01:00
parent bb5b46adf5
commit 1fcd05096a
5 changed files with 140 additions and 73 deletions

View File

@ -30,7 +30,6 @@ namespace Bloxstrap
public static bool IsNoLaunch { get; private set; } = false;
public static bool IsUpgrade { get; private set; } = false;
public static bool IsMenuLaunch { get; private set; } = false;
public static bool IsStudioLaunch { get; private set; } = false;
public static string[] LaunchArgs { get; private set; } = null!;
public static BuildMetadataAttribute BuildMetadata = Assembly.GetExecutingAssembly().GetCustomAttribute<BuildMetadataAttribute>()!;
@ -155,12 +154,6 @@ namespace Bloxstrap
Logger.WriteLine(LOG_IDENT, "Bloxstrap started with IsUpgrade flag");
IsUpgrade = true;
}
if (Array.IndexOf(LaunchArgs, "-studio") != -1)
{
Logger.WriteLine(LOG_IDENT, "Bloxstrap started with IsStudioLaunch flag");
IsStudioLaunch = true;
}
}
using (var checker = new InstallChecker())
@ -196,6 +189,7 @@ namespace Bloxstrap
#endif
string commandLine = "";
bool isStudioLaunch = false;
if (IsMenuLaunch)
{
@ -234,6 +228,10 @@ namespace Bloxstrap
commandLine = $"--app --deeplink {LaunchArgs[0]}";
}
else if (LaunchArgs[0].StartsWith("roblox-studio:") || LaunchArgs[0].StartsWith("roblox-studio-auth:"))
{
commandLine = LaunchArgs[0];
}
else
{
commandLine = "--app";
@ -251,7 +249,7 @@ namespace Bloxstrap
// start bootstrapper and show the bootstrapper modal if we're not running silently
Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
Bootstrapper bootstrapper = new(commandLine);
Bootstrapper bootstrapper = new(commandLine, isStudioLaunch);
IBootstrapperDialog? dialog = null;
if (!IsQuiet)

View File

@ -10,38 +10,6 @@ namespace Bloxstrap
public class Bootstrapper
{
#region Properties
// in case a new package is added, you can find the corresponding directory
// by opening the stock bootstrapper in a hex editor
// TODO - there ideally should be a less static way to do this that's not hardcoded?
private static readonly IReadOnlyDictionary<string, string> PackageDirectories = new Dictionary<string, string>()
{
{ "RobloxApp.zip", @"" },
{ "shaders.zip", @"shaders\" },
{ "ssl.zip", @"ssl\" },
// the runtime installer is only extracted if it needs installing
{ "WebView2.zip", @"" },
{ "WebView2RuntimeInstaller.zip", @"WebView2RuntimeInstaller\" },
{ "content-avatar.zip", @"content\avatar\" },
{ "content-configs.zip", @"content\configs\" },
{ "content-fonts.zip", @"content\fonts\" },
{ "content-sky.zip", @"content\sky\" },
{ "content-sounds.zip", @"content\sounds\" },
{ "content-textures2.zip", @"content\textures\" },
{ "content-models.zip", @"content\models\" },
{ "content-textures3.zip", @"PlatformContent\pc\textures\" },
{ "content-terrain.zip", @"PlatformContent\pc\terrain\" },
{ "content-platform-fonts.zip", @"PlatformContent\pc\fonts\" },
{ "extracontent-luapackages.zip", @"ExtraContent\LuaPackages\" },
{ "extracontent-translations.zip", @"ExtraContent\translations\" },
{ "extracontent-models.zip", @"ExtraContent\models\" },
{ "extracontent-textures.zip", @"ExtraContent\textures\" },
{ "extracontent-places.zip", @"ExtraContent\places\" },
};
private const string AppSettings =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
"<Settings>\r\n" +
@ -51,11 +19,30 @@ namespace Bloxstrap
private readonly CancellationTokenSource _cancelTokenSource = new();
private static bool FreshInstall => String.IsNullOrEmpty(App.State.Prop.PlayerVersionGuid);
private bool FreshInstall => String.IsNullOrEmpty(_versionGuid);
private string _playerLocation => Path.Combine(_versionFolder, "RobloxPlayerBeta.exe");
private string _playerFileName => _studioLaunch ? "RobloxStudioBeta.exe" : "RobloxPlayerBeta.exe";
// TODO: change name
private string _playerLocation => Path.Combine(_versionFolder, _playerFileName);
private string _launchCommandLine;
private bool _studioLaunch;
private string _versionGuid
{
get
{
return _studioLaunch ? App.State.Prop.StudioVersionGuid : App.State.Prop.PlayerVersionGuid;
}
set
{
if (_studioLaunch)
App.State.Prop.StudioVersionGuid = value;
else
App.State.Prop.PlayerVersionGuid = value;
}
}
private string _latestVersionGuid = null!;
private PackageManifest _versionPackageManifest = null!;
@ -68,13 +55,16 @@ namespace Bloxstrap
private int _packagesExtracted = 0;
private bool _cancelFired = false;
private IReadOnlyDictionary<string, string> _packageDirectories => _studioLaunch ? PackageMap.Studio : PackageMap.Player;
public IBootstrapperDialog? Dialog = null;
#endregion
#region Core
public Bootstrapper(string launchCommandLine)
public Bootstrapper(string launchCommandLine, bool studioLaunch)
{
_launchCommandLine = launchCommandLine;
_studioLaunch = studioLaunch;
}
private void SetStatus(string message)
@ -183,7 +173,7 @@ namespace Bloxstrap
await CheckLatestVersion();
// install/update roblox if we're running for the first time, needs updating, or the player location doesn't exist
if (App.IsFirstRun || _latestVersionGuid != App.State.Prop.PlayerVersionGuid || !File.Exists(_playerLocation))
if (App.IsFirstRun || _latestVersionGuid != _versionGuid || !File.Exists(_playerLocation))
await InstallLatestVersion();
if (App.IsFirstRun)
@ -199,7 +189,7 @@ namespace Bloxstrap
if (App.IsFirstRun || FreshInstall)
{
Register();
RegisterProgramSize();
RegisterProgramSize(); // STUDIO TODO
}
CheckInstall();
@ -223,18 +213,20 @@ namespace Bloxstrap
ClientVersion clientVersion;
string binaryType = _studioLaunch ? "WindowsStudio64" : "WindowsPlayer";
try
{
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel);
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel, binaryType: binaryType);
}
catch (HttpResponseException ex)
{
if (ex.ResponseMessage.StatusCode != HttpStatusCode.NotFound)
throw;
App.Logger.WriteLine(LOG_IDENT, $"Reverting enrolled channel to {RobloxDeployment.DefaultChannel} because a WindowsPlayer build does not exist for {App.Settings.Prop.Channel}");
App.Logger.WriteLine(LOG_IDENT, $"Reverting enrolled channel to {RobloxDeployment.DefaultChannel} because a {binaryType} build does not exist for {App.Settings.Prop.Channel}");
App.Settings.Prop.Channel = RobloxDeployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel);
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel, binaryType: binaryType);
}
if (clientVersion.IsBehindDefaultChannel)
@ -257,7 +249,7 @@ namespace Bloxstrap
App.Logger.WriteLine("Bootstrapper::CheckLatestVersion", $"Changed Roblox channel from {App.Settings.Prop.Channel} to {RobloxDeployment.DefaultChannel}");
App.Settings.Prop.Channel = RobloxDeployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel);
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel, binaryType: binaryType);
}
}
@ -502,8 +494,8 @@ namespace Bloxstrap
ProtocolHandler.Register("roblox", "Roblox", Paths.Application);
ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application);
ProtocolHandler.Register("roblox-studio", "Roblox", Paths.Application, "-studio");
ProtocolHandler.Register("roblox-studio-auth", "Roblox", Paths.Application, "-studio");
ProtocolHandler.Register("roblox-studio", "Roblox", Paths.Application);
ProtocolHandler.Register("roblox-studio-auth", "Roblox", Paths.Application);
if (Environment.ProcessPath is not null && Environment.ProcessPath != Paths.Application)
{
@ -798,7 +790,8 @@ namespace Bloxstrap
_isInstalling = true;
SetStatus(FreshInstall ? "Installing Roblox..." : "Upgrading Roblox...");
string extra = _studioLaunch ? " Studio" : "";
SetStatus(FreshInstall ? $"Installing Roblox{extra}..." : $"Upgrading Roblox{extra}...");
Directory.CreateDirectory(Paths.Base);
Directory.CreateDirectory(Paths.Downloads);
@ -892,12 +885,12 @@ namespace Bloxstrap
}
}
string oldVersionFolder = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid);
string oldVersionFolder = Path.Combine(Paths.Versions, _versionGuid);
// move old compatibility flags for the old location
using (RegistryKey appFlagsKey = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"))
{
string oldGameClientLocation = Path.Combine(oldVersionFolder, "RobloxPlayerBeta.exe");
string oldGameClientLocation = Path.Combine(oldVersionFolder, _playerFileName);
string? appFlags = (string?)appFlagsKey.GetValue(oldGameClientLocation);
if (appFlags is not null)
@ -915,7 +908,7 @@ namespace Bloxstrap
{
foreach (DirectoryInfo dir in new DirectoryInfo(Paths.Versions).GetDirectories())
{
if (dir.Name == _latestVersionGuid || !dir.Name.StartsWith("version-"))
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}");
@ -933,7 +926,7 @@ namespace Bloxstrap
}
}
App.State.Prop.PlayerVersionGuid = _latestVersionGuid;
_versionGuid = _latestVersionGuid;
// don't register program size until the program is registered, which will be done after this
if (!App.IsFirstRun && !FreshInstall)
@ -1228,7 +1221,7 @@ namespace Bloxstrap
if (modFolderFiles.Contains(fileLocation))
continue;
var package = PackageDirectories.SingleOrDefault(x => x.Value != "" && fileLocation.StartsWith(x.Value));
var package = _packageDirectories.SingleOrDefault(x => x.Value != "" && fileLocation.StartsWith(x.Value));
// package doesn't exist, likely mistakenly placed file
if (String.IsNullOrEmpty(package.Key))
@ -1436,7 +1429,7 @@ namespace Bloxstrap
return;
string packageLocation = Path.Combine(Paths.Downloads, package.Signature);
string packageFolder = Path.Combine(_versionFolder, PackageDirectories[package.Name]);
string packageFolder = Path.Combine(_versionFolder, _packageDirectories[package.Name]);
App.Logger.WriteLine(LOG_IDENT, $"Reading {package.Name}...");
@ -1462,7 +1455,7 @@ namespace Bloxstrap
if (directory is not null)
Directory.CreateDirectory(directory);
var fileManifest = _versionFileManifest.FirstOrDefault(x => x.Name == Path.Combine(PackageDirectories[package.Name], entry.FullName));
var fileManifest = _versionFileManifest.FirstOrDefault(x => x.Name == Path.Combine(_packageDirectories[package.Name], entry.FullName));
string? signature = fileManifest?.Signature;
if (File.Exists(extractPath))
@ -1515,7 +1508,7 @@ namespace Bloxstrap
if (entry is null)
return;
string extractionPath = Path.Combine(_versionFolder, PackageDirectories[package.Name], entry.FullName);
string extractionPath = Path.Combine(_versionFolder, _packageDirectories[package.Name], entry.FullName);
entry.ExtractToFile(extractionPath, true);
}
#endregion

85
Bloxstrap/PackageMap.cs Normal file
View File

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap
{
internal class PackageMap
{
public static IReadOnlyDictionary<string, string> Player
{
get { return CombineDictionaries(_common, _playerOnly); }
}
public static IReadOnlyDictionary<string, string> Studio
{
get { return CombineDictionaries(_common, _studioOnly); }
}
// in case a new package is added, you can find the corresponding directory
// by opening the stock bootstrapper in a hex editor
// TODO - there ideally should be a less static way to do this that's not hardcoded?
private static IReadOnlyDictionary<string, string> _common = new Dictionary<string, string>()
{
{ "shaders.zip", @"shaders\" },
{ "ssl.zip", @"ssl\" },
// the runtime installer is only extracted if it needs installing
{ "WebView2.zip", @"" },
{ "WebView2RuntimeInstaller.zip", @"WebView2RuntimeInstaller\" },
{ "content-avatar.zip", @"content\avatar\" },
{ "content-configs.zip", @"content\configs\" },
{ "content-fonts.zip", @"content\fonts\" },
{ "content-sky.zip", @"content\sky\" },
{ "content-sounds.zip", @"content\sounds\" },
{ "content-textures2.zip", @"content\textures\" },
{ "content-models.zip", @"content\models\" },
{ "content-textures3.zip", @"PlatformContent\pc\textures\" },
{ "content-terrain.zip", @"PlatformContent\pc\terrain\" },
{ "content-platform-fonts.zip", @"PlatformContent\pc\fonts\" },
{ "extracontent-luapackages.zip", @"ExtraContent\LuaPackages\" },
{ "extracontent-translations.zip", @"ExtraContent\translations\" },
{ "extracontent-models.zip", @"ExtraContent\models\" },
{ "extracontent-textures.zip", @"ExtraContent\textures\" },
{ "extracontent-places.zip", @"ExtraContent\places\" },
};
private static IReadOnlyDictionary<string, string> _playerOnly = new Dictionary<string, string>()
{
{ "RobloxApp.zip", @"" }
};
private static IReadOnlyDictionary<string, string> _studioOnly = new Dictionary<string, string>()
{
{ "RobloxStudio.zip", @"" },
{ "ApplicationConfig.zip", @"ApplicationConfig\" },
{ "content-studio_svg_textures.zip", @"content\studio_svg_textures\"},
{ "content-qt_translations.zip", @"content\qt_translations\" },
{ "content-api-docs.zip", @"content\api_docs\" },
{ "BuiltInPlugins.zip", @"BuiltInPlugins\" },
{ "BuiltInStandalonePlugins.zip", @"BuiltInStandalonePlugins\" },
{ "LibrariesQt5.zip", @"" },
{ "Plugins.zip", @"Plugins\" },
{ "Qml.zip", @"Qml\" },
{ "StudioFonts.zip", @"StudioFonts\" },
};
private static Dictionary<string, string> CombineDictionaries(IReadOnlyDictionary<string, string> d1, IReadOnlyDictionary<string, string> d2)
{
Dictionary<string, string> newD = new Dictionary<string, string>();
foreach (var d in d1)
newD[d.Key] = d.Value;
foreach (var d in d2)
newD[d.Key] = d.Value;
return newD;
}
}
}

View File

@ -105,18 +105,9 @@ namespace Bloxstrap
App.State.Save();
}
private static string ConstructHandlerArgs(string handler, string? extraArgs = null)
public static void Register(string key, string name, string handler)
{
string handlerArgs = $"\"{handler}\"";
if (!string.IsNullOrEmpty(extraArgs))
handlerArgs += $" {extraArgs}";
handlerArgs += " %1";
return handlerArgs;
}
public static void Register(string key, string name, string handler, string? extraArgs = null)
{
string handlerArgs = ConstructHandlerArgs(handler, extraArgs);
string handlerArgs = $"\"{handler}\" %1";
RegistryKey uriKey = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{key}");
RegistryKey uriIconKey = uriKey.CreateSubKey("DefaultIcon");
RegistryKey uriCommandKey = uriKey.CreateSubKey(@"shell\open\command");

View File

@ -69,7 +69,7 @@
return location;
}
public static async Task<ClientVersion> GetInfo(string channel, bool extraInformation = false)
public static async Task<ClientVersion> GetInfo(string channel, bool extraInformation = false, string binaryType = "WindowsPlayer")
{
const string LOG_IDENT = "RobloxDeployment::GetInfo";
@ -84,7 +84,7 @@
}
else
{
string path = $"/v2/client-version/WindowsPlayer/channel/{channel}";
string path = $"/v2/client-version/{binaryType}/channel/{channel}";
HttpResponseMessage deployInfoResponse;
try