Abstract differences in handling player and studio

This commit is contained in:
pizzaboxer 2024-08-19 16:27:42 +01:00
parent 765cccf89e
commit d15e910904
No known key found for this signature in database
GPG Key ID: 59D4A1DBAD0F2BA8
6 changed files with 174 additions and 114 deletions

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.AppData
{
public abstract class CommonAppData
{
// in case a new package is added, you can find the corresponding directory
// by opening the stock bootstrapper in a hex editor
private IReadOnlyDictionary<string, string> _commonMap { get; } = new Dictionary<string, string>()
{
{ "Libraries.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\" },
};
public virtual IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; }
public CommonAppData()
{
if (PackageDirectoryMap is null)
{
PackageDirectoryMap = _commonMap;
return;
}
var merged = new Dictionary<string, string>();
foreach (var entry in _commonMap)
merged[entry.Key] = entry.Value;
foreach (var entry in PackageDirectoryMap)
merged[entry.Key] = entry.Value;
PackageDirectoryMap = merged;
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.AppData
{
internal interface IAppData
{
string ProductName { get; }
string BinaryType { get; }
string RegistryName { get; }
string ExecutableName { get; }
string StartEvent { get; }
IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; }
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.AppData
{
public class RobloxPlayerData : CommonAppData, IAppData
{
public string ProductName { get; } = "Roblox";
public string BinaryType { get; } = "WindowsPlayer";
public string RegistryName { get; } = "RobloxPlayer";
public string ExecutableName { get; } = "RobloxPlayerBeta.exe";
public string StartEvent { get; } = "www.roblox.com/robloxStartedEvent";
public override IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
{
{ "RobloxApp.zip", @"" }
};
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.AppData
{
public class RobloxStudioData : CommonAppData, IAppData
{
public string ProductName { get; } = "Roblox Studio";
public string BinaryType { get; } = "WindowsStudio64";
public string RegistryName { get; } = "RobloxStudio";
public string ExecutableName { get; } = "RobloxStudioBeta.exe";
public string StartEvent { get; } = "www.roblox.com/robloxStudioStartedEvent";
public override IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
{
{ "RobloxStudio.zip", @"" },
{ "redist.zip", @"" },
{ "LibrariesQt5.zip", @"" },
{ "content-studio_svg_textures.zip", @"content\studio_svg_textures\"},
{ "content-qt_translations.zip", @"content\qt_translations\" },
{ "content-api-docs.zip", @"content\api_docs\" },
{ "extracontent-scripts.zip", @"ExtraContent\scripts\" },
{ "BuiltInPlugins.zip", @"BuiltInPlugins\" },
{ "BuiltInStandalonePlugins.zip", @"BuiltInStandalonePlugins\" },
{ "ApplicationConfig.zip", @"ApplicationConfig\" },
{ "Plugins.zip", @"Plugins\" },
{ "Qml.zip", @"Qml\" },
{ "StudioFonts.zip", @"StudioFonts\" }
};
}
}

View File

@ -5,6 +5,7 @@ using Microsoft.Win32;
using Bloxstrap.Integrations; using Bloxstrap.Integrations;
using Bloxstrap.Resources; using Bloxstrap.Resources;
using Bloxstrap.AppData;
namespace Bloxstrap namespace Bloxstrap
{ {
@ -24,8 +25,9 @@ namespace Bloxstrap
private bool FreshInstall => String.IsNullOrEmpty(_versionGuid); private bool FreshInstall => String.IsNullOrEmpty(_versionGuid);
private string _playerFileName => _launchMode == LaunchMode.Player ? "RobloxPlayerBeta.exe" : "RobloxStudioBeta.exe"; private IAppData AppData;
private string _playerLocation => Path.Combine(_versionFolder, _playerFileName);
private string _playerLocation => Path.Combine(_versionFolder, AppData.ExecutableName);
private string _launchCommandLine = App.LaunchSettings.RobloxLaunchArgs; private string _launchCommandLine = App.LaunchSettings.RobloxLaunchArgs;
private LaunchMode _launchMode = App.LaunchSettings.RobloxLaunchMode; private LaunchMode _launchMode = App.LaunchSettings.RobloxLaunchMode;
@ -73,8 +75,6 @@ namespace Bloxstrap
private int _packagesExtracted = 0; private int _packagesExtracted = 0;
private bool _cancelFired = false; private bool _cancelFired = false;
private IReadOnlyDictionary<string, string> _packageDirectories;
public IBootstrapperDialog? Dialog = null; public IBootstrapperDialog? Dialog = null;
public bool IsStudioLaunch => _launchMode != LaunchMode.Player; public bool IsStudioLaunch => _launchMode != LaunchMode.Player;
@ -85,19 +85,17 @@ namespace Bloxstrap
{ {
_installWebView2 = installWebView2; _installWebView2 = installWebView2;
_packageDirectories = _launchMode == LaunchMode.Player ? PackageMap.Player : PackageMap.Studio; if (_launchMode == LaunchMode.Player)
AppData = new RobloxPlayerData();
else
AppData = new RobloxStudioData();
} }
private void SetStatus(string message) private void SetStatus(string message)
{ {
App.Logger.WriteLine("Bootstrapper::SetStatus", message); App.Logger.WriteLine("Bootstrapper::SetStatus", message);
string productName = "Roblox"; message = message.Replace("{product}", AppData.ProductName);
if (_launchMode != LaunchMode.Player)
productName = "Roblox Studio";
message = message.Replace("{product}", productName);
if (Dialog is not null) if (Dialog is not null)
Dialog.Message = message; Dialog.Message = message;
@ -229,9 +227,7 @@ namespace Bloxstrap
string channel = "production"; string channel = "production";
string keyPath = _launchMode == LaunchMode.Player ? "RobloxPlayer" : "RobloxStudio"; using var key = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\ROBLOX Corporation\\Environments\\{AppData.RegistryName}\\Channel");
using var key = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\ROBLOX Corporation\\Environments\\{keyPath}\\Channel");
var match = Regex.Match(App.LaunchSettings.RobloxLaunchArgs, "channel:([a-zA-Z0-9-_]+)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); var match = Regex.Match(App.LaunchSettings.RobloxLaunchArgs, "channel:([a-zA-Z0-9-_]+)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
@ -246,11 +242,9 @@ namespace Bloxstrap
ClientVersion clientVersion; ClientVersion clientVersion;
string binaryType = _launchMode == LaunchMode.Player ? "WindowsPlayer" : "WindowsStudio64";
try try
{ {
clientVersion = await RobloxDeployment.GetInfo(channel, binaryType); clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType);
} }
catch (HttpResponseException ex) catch (HttpResponseException ex)
{ {
@ -263,7 +257,7 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, $"Changing channel from {channel} to {RobloxDeployment.DefaultChannel} because HTTP {(int)ex.ResponseMessage.StatusCode}"); App.Logger.WriteLine(LOG_IDENT, $"Changing channel from {channel} to {RobloxDeployment.DefaultChannel} because HTTP {(int)ex.ResponseMessage.StatusCode}");
channel = RobloxDeployment.DefaultChannel; channel = RobloxDeployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(channel, binaryType); clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType);
} }
if (clientVersion.IsBehindDefaultChannel) if (clientVersion.IsBehindDefaultChannel)
@ -271,7 +265,7 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, $"Changing channel from {channel} to {RobloxDeployment.DefaultChannel} because channel is behind production"); App.Logger.WriteLine(LOG_IDENT, $"Changing channel from {channel} to {RobloxDeployment.DefaultChannel} because channel is behind production");
channel = RobloxDeployment.DefaultChannel; channel = RobloxDeployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(channel, binaryType); clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType);
} }
key.SetValue("www.roblox.com", channel); key.SetValue("www.roblox.com", channel);
@ -325,13 +319,13 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid})"); App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid})");
string eventName = _launchMode == LaunchMode.Player ? "www.roblox.com/robloxStartedEvent" : "www.roblox.com/robloxQTStudioStartedEvent"; using (var startEvent = new SystemEvent(AppData.StartEvent))
using (SystemEvent startEvent = new(eventName))
{ {
bool startEventFired = await startEvent.WaitForEvent(); bool startEventFired = await startEvent.WaitForEvent();
startEvent.Close(); startEvent.Close();
// TODO: this cannot silently exit like this
if (!startEventFired) if (!startEventFired)
return; return;
} }
@ -695,7 +689,7 @@ namespace Bloxstrap
// move old compatibility flags for the old location // move old compatibility flags for the old location
using (RegistryKey appFlagsKey = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers")) using (RegistryKey appFlagsKey = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"))
{ {
string oldGameClientLocation = Path.Combine(oldVersionFolder, _playerFileName); string oldGameClientLocation = Path.Combine(oldVersionFolder, AppData.ExecutableName);
string? appFlags = (string?)appFlagsKey.GetValue(oldGameClientLocation); string? appFlags = (string?)appFlagsKey.GetValue(oldGameClientLocation);
if (appFlags is not null) if (appFlags is not null)
@ -812,7 +806,7 @@ namespace Bloxstrap
{ {
const string LOG_IDENT = "Bootstrapper::ApplyModifications"; const string LOG_IDENT = "Bootstrapper::ApplyModifications";
if (Process.GetProcessesByName(_playerFileName[..^4]).Any()) if (Process.GetProcessesByName(AppData.ExecutableName[..^4]).Any())
{ {
App.Logger.WriteLine(LOG_IDENT, "Roblox is running, aborting mod check"); App.Logger.WriteLine(LOG_IDENT, "Roblox is running, aborting mod check");
return; return;
@ -958,7 +952,7 @@ namespace Bloxstrap
if (modFolderFiles.Contains(fileLocation)) if (modFolderFiles.Contains(fileLocation))
continue; continue;
var package = _packageDirectories.SingleOrDefault(x => x.Value != "" && fileLocation.StartsWith(x.Value)); var package = AppData.PackageDirectoryMap.SingleOrDefault(x => x.Value != "" && fileLocation.StartsWith(x.Value));
// package doesn't exist, likely mistakenly placed file // package doesn't exist, likely mistakenly placed file
if (String.IsNullOrEmpty(package.Key)) if (String.IsNullOrEmpty(package.Key))
@ -1128,7 +1122,7 @@ namespace Bloxstrap
return Task.CompletedTask; return Task.CompletedTask;
string packageLocation = Path.Combine(Paths.Downloads, package.Signature); string packageLocation = Path.Combine(Paths.Downloads, package.Signature);
string packageFolder = Path.Combine(_versionFolder, _packageDirectories[package.Name]); string packageFolder = Path.Combine(_versionFolder, AppData.PackageDirectoryMap[package.Name]);
App.Logger.WriteLine(LOG_IDENT, $"Extracting {package.Name}..."); App.Logger.WriteLine(LOG_IDENT, $"Extracting {package.Name}...");
@ -1158,7 +1152,7 @@ namespace Bloxstrap
if (entry is null) if (entry is null)
return; return;
string extractionPath = Path.Combine(_versionFolder, _packageDirectories[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

View File

@ -1,88 +0,0 @@
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>()
{
{ "Libraries.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 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\" },
{ "extracontent-scripts.zip", @"ExtraContent\scripts\" },
{ "BuiltInPlugins.zip", @"BuiltInPlugins\" },
{ "BuiltInStandalonePlugins.zip", @"BuiltInStandalonePlugins\" },
{ "LibrariesQt5.zip", @"" },
{ "Plugins.zip", @"Plugins\" },
{ "Qml.zip", @"Qml\" },
{ "StudioFonts.zip", @"StudioFonts\" },
{ "redist.zip", @"" },
};
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;
}
}
}