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.Resources;
using Bloxstrap.AppData;
namespace Bloxstrap
{
@ -24,8 +25,9 @@ namespace Bloxstrap
private bool FreshInstall => String.IsNullOrEmpty(_versionGuid);
private string _playerFileName => _launchMode == LaunchMode.Player ? "RobloxPlayerBeta.exe" : "RobloxStudioBeta.exe";
private string _playerLocation => Path.Combine(_versionFolder, _playerFileName);
private IAppData AppData;
private string _playerLocation => Path.Combine(_versionFolder, AppData.ExecutableName);
private string _launchCommandLine = App.LaunchSettings.RobloxLaunchArgs;
private LaunchMode _launchMode = App.LaunchSettings.RobloxLaunchMode;
@ -73,8 +75,6 @@ namespace Bloxstrap
private int _packagesExtracted = 0;
private bool _cancelFired = false;
private IReadOnlyDictionary<string, string> _packageDirectories;
public IBootstrapperDialog? Dialog = null;
public bool IsStudioLaunch => _launchMode != LaunchMode.Player;
@ -85,19 +85,17 @@ namespace Bloxstrap
{
_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)
{
App.Logger.WriteLine("Bootstrapper::SetStatus", message);
string productName = "Roblox";
if (_launchMode != LaunchMode.Player)
productName = "Roblox Studio";
message = message.Replace("{product}", productName);
message = message.Replace("{product}", AppData.ProductName);
if (Dialog is not null)
Dialog.Message = message;
@ -229,9 +227,7 @@ namespace Bloxstrap
string channel = "production";
string keyPath = _launchMode == LaunchMode.Player ? "RobloxPlayer" : "RobloxStudio";
using var key = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\ROBLOX Corporation\\Environments\\{keyPath}\\Channel");
using var key = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\ROBLOX Corporation\\Environments\\{AppData.RegistryName}\\Channel");
var match = Regex.Match(App.LaunchSettings.RobloxLaunchArgs, "channel:([a-zA-Z0-9-_]+)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
@ -246,11 +242,9 @@ namespace Bloxstrap
ClientVersion clientVersion;
string binaryType = _launchMode == LaunchMode.Player ? "WindowsPlayer" : "WindowsStudio64";
try
{
clientVersion = await RobloxDeployment.GetInfo(channel, binaryType);
clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType);
}
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}");
channel = RobloxDeployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(channel, binaryType);
clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType);
}
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");
channel = RobloxDeployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(channel, binaryType);
clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType);
}
key.SetValue("www.roblox.com", channel);
@ -325,13 +319,13 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid})");
string eventName = _launchMode == LaunchMode.Player ? "www.roblox.com/robloxStartedEvent" : "www.roblox.com/robloxQTStudioStartedEvent";
using (SystemEvent startEvent = new(eventName))
using (var startEvent = new SystemEvent(AppData.StartEvent))
{
bool startEventFired = await startEvent.WaitForEvent();
startEvent.Close();
// TODO: this cannot silently exit like this
if (!startEventFired)
return;
}
@ -695,7 +689,7 @@ namespace Bloxstrap
// 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, _playerFileName);
string oldGameClientLocation = Path.Combine(oldVersionFolder, AppData.ExecutableName);
string? appFlags = (string?)appFlagsKey.GetValue(oldGameClientLocation);
if (appFlags is not null)
@ -812,7 +806,7 @@ namespace Bloxstrap
{
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");
return;
@ -958,7 +952,7 @@ namespace Bloxstrap
if (modFolderFiles.Contains(fileLocation))
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
if (String.IsNullOrEmpty(package.Key))
@ -1128,7 +1122,7 @@ namespace Bloxstrap
return Task.CompletedTask;
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}...");
@ -1158,7 +1152,7 @@ namespace Bloxstrap
if (entry is null)
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);
}
#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;
}
}
}