Fix handling of RBX deployment interfacing

This commit is contained in:
pizzaboxer 2024-10-11 18:28:08 +01:00
parent d542efd945
commit ce82c3faa7
No known key found for this signature in database
GPG Key ID: 59D4A1DBAD0F2BA8
4 changed files with 77 additions and 68 deletions

View File

@ -13,11 +13,12 @@
using System.Windows; using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using System.Windows.Shell;
using Microsoft.Win32; using Microsoft.Win32;
using Bloxstrap.AppData; using Bloxstrap.AppData;
using System.Windows.Shell; using Bloxstrap.RobloxInterfaces;
using Bloxstrap.UI.Elements.Bootstrapper.Base; using Bloxstrap.UI.Elements.Bootstrapper.Base;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
@ -77,6 +78,7 @@ namespace Bloxstrap
_fastZipEvents.ProcessFile += (_, e) => e.ContinueRunning = !_cancelTokenSource.IsCancellationRequested; _fastZipEvents.ProcessFile += (_, e) => e.ContinueRunning = !_cancelTokenSource.IsCancellationRequested;
AppData = IsStudioLaunch ? new RobloxStudioData() : new RobloxPlayerData(); AppData = IsStudioLaunch ? new RobloxStudioData() : new RobloxPlayerData();
Deployment.BinaryType = AppData.BinaryType;
} }
private void SetStatus(string message) private void SetStatus(string message)
@ -151,7 +153,7 @@ namespace Bloxstrap
SetStatus(Strings.Bootstrapper_Status_Connecting); SetStatus(Strings.Bootstrapper_Status_Connecting);
var connectionResult = await RobloxDeployment.InitializeConnectivity(); var connectionResult = await Deployment.InitializeConnectivity();
App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished"); App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished");
@ -244,29 +246,29 @@ namespace Bloxstrap
// 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
// else, check if the registry key for it exists, and use it // else, check if the registry key for it exists, and use it
string channel = "production";
using var key = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\ROBLOX Corporation\\Environments\\{AppData.RegistryName}\\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); var match = Regex.Match(App.LaunchSettings.RobloxLaunchArgs, "channel:([a-zA-Z0-9-_]+)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (match.Groups.Count == 2) if (match.Groups.Count == 2)
{ {
channel = match.Groups[1].Value.ToLowerInvariant(); Deployment.Channel = match.Groups[1].Value.ToLowerInvariant();
} }
else if (key.GetValue("www.roblox.com") is string value && !String.IsNullOrEmpty(value)) else if (key.GetValue("www.roblox.com") is string value && !String.IsNullOrEmpty(value))
{ {
channel = value; Deployment.Channel = value.ToLowerInvariant();
} }
if (channel != "production") App.Logger.WriteLine(LOG_IDENT, "Got channel as " + (String.IsNullOrEmpty(Deployment.Channel) ? Deployment.DefaultChannel : Deployment.Channel));
App.SendStat("robloxChannel", channel);
if (Deployment.Channel != "production")
App.SendStat("robloxChannel", Deployment.Channel);
ClientVersion clientVersion; ClientVersion clientVersion;
try try
{ {
clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType); clientVersion = await Deployment.GetInfo();
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
@ -275,25 +277,25 @@ namespace Bloxstrap
and not HttpStatusCode.NotFound) and not HttpStatusCode.NotFound)
throw; throw;
App.Logger.WriteLine(LOG_IDENT, $"Changing channel from {channel} to {RobloxDeployment.DefaultChannel} because HTTP {(int)ex.StatusCode}"); App.Logger.WriteLine(LOG_IDENT, $"Changing channel from {Deployment.Channel} to {Deployment.DefaultChannel} because HTTP {(int)ex.StatusCode}");
channel = RobloxDeployment.DefaultChannel; Deployment.Channel = Deployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType); clientVersion = await Deployment.GetInfo();
} }
if (clientVersion.IsBehindDefaultChannel) if (clientVersion.IsBehindDefaultChannel)
{ {
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 {Deployment.Channel} to {Deployment.DefaultChannel} because channel is behind production");
channel = RobloxDeployment.DefaultChannel; Deployment.Channel = Deployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType); clientVersion = await Deployment.GetInfo();
} }
key.SetValueSafe("www.roblox.com", channel); key.SetValueSafe("www.roblox.com", Deployment.IsDefaultChannel ? "" : Deployment.Channel);
_latestVersionGuid = clientVersion.VersionGuid; _latestVersionGuid = clientVersion.VersionGuid;
string pkgManifestUrl = RobloxDeployment.GetLocation($"/{_latestVersionGuid}-rbxPkgManifest.txt"); string pkgManifestUrl = Deployment.GetLocation($"/{_latestVersionGuid}-rbxPkgManifest.txt");
var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl); var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl);
_versionPackageManifest = new(pkgManifestData); _versionPackageManifest = new(pkgManifestData);
@ -962,7 +964,7 @@ namespace Bloxstrap
if (_cancelTokenSource.IsCancellationRequested) if (_cancelTokenSource.IsCancellationRequested)
return; return;
string packageUrl = RobloxDeployment.GetLocation($"/{_latestVersionGuid}-{package.Name}"); string packageUrl = Deployment.GetLocation($"/{_latestVersionGuid}-{package.Name}");
string robloxPackageLocation = Path.Combine(Paths.LocalAppData, "Roblox", "Downloads", package.Signature); string robloxPackageLocation = Path.Combine(Paths.LocalAppData, "Roblox", "Downloads", package.Signature);
if (File.Exists(package.DownloadPath)) if (File.Exists(package.DownloadPath))

View File

@ -1,4 +1,6 @@
namespace Bloxstrap.Models.Manifest using Bloxstrap.RobloxInterfaces;
namespace Bloxstrap.Models.Manifest
{ {
public class FileManifest : List<ManifestFile> public class FileManifest : List<ManifestFile>
{ {
@ -24,7 +26,7 @@
public static async Task<FileManifest> Get(string versionGuid) public static async Task<FileManifest> Get(string versionGuid)
{ {
string pkgManifestUrl = RobloxDeployment.GetLocation($"/{versionGuid}-rbxManifest.txt"); string pkgManifestUrl = Deployment.GetLocation($"/{versionGuid}-rbxManifest.txt");
var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl); var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl);
return new FileManifest(pkgManifestData); return new FileManifest(pkgManifestData);

View File

@ -1,8 +1,11 @@
using System.ComponentModel; using System.ComponentModel;
namespace Bloxstrap namespace Bloxstrap.RobloxInterfaces
{ {
public class RobloxFastFlags // i am 100% sure there is a much, MUCH better way to handle this
// matt wrote this so this is effectively a black box to me right now
// i'll likely refactor this at some point
public class ApplicationSettings
{ {
private string _applicationName; private string _applicationName;
private string _channelName; private string _channelName;
@ -12,7 +15,7 @@ namespace Bloxstrap
private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
private RobloxFastFlags(string applicationName, string channelName) private ApplicationSettings(string applicationName, string channelName)
{ {
_applicationName = applicationName; _applicationName = applicationName;
_channelName = channelName; _channelName = channelName;
@ -29,11 +32,11 @@ namespace Bloxstrap
if (_initialised) if (_initialised)
return; return;
string logIndent = $"RobloxFastFlags::Fetch.{_applicationName}.{_channelName}"; string logIndent = $"ApplicationSettings::Fetch.{_applicationName}.{_channelName}";
App.Logger.WriteLine(logIndent, "Fetching fast flags"); App.Logger.WriteLine(logIndent, "Fetching fast flags");
string path = $"/v2/settings/application/{_applicationName}"; string path = $"/v2/settings/application/{_applicationName}";
if (_channelName != RobloxDeployment.DefaultChannel.ToLowerInvariant()) if (_channelName != Deployment.DefaultChannel.ToLowerInvariant())
path += $"/bucket/{_channelName}"; path += $"/bucket/{_channelName}";
HttpResponseMessage response; HttpResponseMessage response;
@ -100,12 +103,13 @@ namespace Bloxstrap
} }
// _cache[applicationName][channelName] // _cache[applicationName][channelName]
private static Dictionary<string, Dictionary<string, RobloxFastFlags>> _cache = new(); private static Dictionary<string, Dictionary<string, ApplicationSettings>> _cache = new();
public static RobloxFastFlags PCDesktopClient { get; } = GetSettings("PCDesktopClient"); public static ApplicationSettings PCDesktopClient => GetSettings("PCDesktopClient");
public static RobloxFastFlags PCClientBootstrapper { get; } = GetSettings("PCClientBootstrapper");
public static RobloxFastFlags GetSettings(string applicationName, string channelName = RobloxDeployment.DefaultChannel, bool shouldCache = true) public static ApplicationSettings PCClientBootstrapper => GetSettings("PCClientBootstrapper");
public static ApplicationSettings GetSettings(string applicationName, string channelName = Deployment.DefaultChannel, bool shouldCache = true)
{ {
channelName = channelName.ToLowerInvariant(); channelName = channelName.ToLowerInvariant();
@ -114,7 +118,7 @@ namespace Bloxstrap
if (_cache.ContainsKey(applicationName) && _cache[applicationName].ContainsKey(channelName)) if (_cache.ContainsKey(applicationName) && _cache[applicationName].ContainsKey(channelName))
return _cache[applicationName][channelName]; return _cache[applicationName][channelName];
var flags = new RobloxFastFlags(applicationName, channelName); var flags = new ApplicationSettings(applicationName, channelName);
if (shouldCache) if (shouldCache)
{ {

View File

@ -1,11 +1,17 @@
namespace Bloxstrap namespace Bloxstrap.RobloxInterfaces
{ {
public static class RobloxDeployment public static class Deployment
{ {
public const string DefaultChannel = "production"; public const string DefaultChannel = "production";
private const string VersionStudioHash = "version-012732894899482c"; private const string VersionStudioHash = "version-012732894899482c";
public static string Channel = DefaultChannel;
public static string BinaryType = "WindowsPlayer";
public static bool IsDefaultChannel => String.Compare(Channel, DefaultChannel, StringComparison.OrdinalIgnoreCase) == 0;
public static string BaseUrl { get; private set; } = null!; public static string BaseUrl { get; private set; } = null!;
private static readonly Dictionary<string, ClientVersion> ClientVersionCache = new(); private static readonly Dictionary<string, ClientVersion> ClientVersionCache = new();
@ -23,7 +29,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 = $"Deployment::TestConnection<{url}>";
await Task.Delay(priority * 1000, token); await Task.Delay(priority * 1000, token);
@ -56,16 +62,14 @@
return url; return url;
} }
/// <summary>
/// This function serves double duty as the setup mirror enumerator, and as our connectivity check.
/// Returns null for success.
/// </summary>
/// <returns></returns>
public static async Task<Exception?> InitializeConnectivity() public static async Task<Exception?> InitializeConnectivity()
{ {
const string LOG_IDENT = "RobloxDeployment::InitializeConnectivity"; const string LOG_IDENT = "Deployment::InitializeConnectivity";
// this function serves double duty as the setup mirror enumerator, and as our connectivity check
// since we're basically asking four different urls for the exact same thing, if all four fail, then it has to be a user-side problem
// this should be checked for in the installer and in the bootstrapper
// returns null for success
var tokenSource = new CancellationTokenSource(); var tokenSource = new CancellationTokenSource();
@ -74,25 +78,22 @@
App.Logger.WriteLine(LOG_IDENT, "Testing connectivity..."); App.Logger.WriteLine(LOG_IDENT, "Testing connectivity...");
while (tasks.Any()) while (tasks.Any() && String.IsNullOrEmpty(BaseUrl))
{ {
var finishedTask = await Task.WhenAny(tasks); var finishedTask = await Task.WhenAny(tasks);
if (finishedTask.IsFaulted)
{
tasks.Remove(finishedTask); tasks.Remove(finishedTask);
exceptions.Add(finishedTask.Exception!.InnerException!);
continue;
}
BaseUrl = await finishedTask; if (finishedTask.IsFaulted)
break; exceptions.Add(finishedTask.Exception!.InnerException!);
else
BaseUrl = finishedTask.Result;
} }
// stop other running connectivity tests // stop other running connectivity tests
tokenSource.Cancel(); tokenSource.Cancel();
if (String.IsNullOrEmpty(BaseUrl)) if (string.IsNullOrEmpty(BaseUrl))
return exceptions[0]; return exceptions[0];
App.Logger.WriteLine(LOG_IDENT, $"Got {BaseUrl} as the optimal base URL"); App.Logger.WriteLine(LOG_IDENT, $"Got {BaseUrl} as the optimal base URL");
@ -100,18 +101,18 @@
return null; return null;
} }
public static string GetLocation(string resource, string channel = DefaultChannel) public static string GetLocation(string resource)
{ {
string location = BaseUrl; string location = BaseUrl;
if (String.Compare(channel, DefaultChannel, StringComparison.InvariantCultureIgnoreCase) != 0) if (!IsDefaultChannel)
{ {
string channelName; string channelName;
if (RobloxFastFlags.GetSettings(nameof(RobloxFastFlags.PCClientBootstrapper), channel).Get<bool>("FFlagReplaceChannelNameForDownload")) if (ApplicationSettings.GetSettings(nameof(ApplicationSettings.PCClientBootstrapper), Channel).Get<bool>("FFlagReplaceChannelNameForDownload"))
channelName = "common"; channelName = "common";
else else
channelName = channel.ToLowerInvariant(); channelName = Channel.ToLowerInvariant();
location += $"/channel/{channelName}"; location += $"/channel/{channelName}";
} }
@ -121,16 +122,18 @@
return location; return location;
} }
public static async Task<ClientVersion> GetInfo(string channel, string binaryType = "WindowsPlayer") public static async Task<ClientVersion> GetInfo(string? channel = null)
{ {
const string LOG_IDENT = "RobloxDeployment::GetInfo"; const string LOG_IDENT = "Deployment::GetInfo";
if (String.IsNullOrEmpty(channel))
channel = Channel;
bool isDefaultChannel = String.Compare(channel, DefaultChannel, StringComparison.OrdinalIgnoreCase) == 0;
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)) string cacheKey = $"{channel}-{BinaryType}";
channel = DefaultChannel;
string cacheKey = $"{channel}-{binaryType}";
ClientVersion clientVersion; ClientVersion clientVersion;
@ -141,12 +144,10 @@
} }
else else
{ {
bool isDefaultChannel = String.Compare(channel, DefaultChannel, StringComparison.OrdinalIgnoreCase) == 0; string path = $"/v2/client-version/{BinaryType}";
string path = $"/v2/client-version/{binaryType}";
if (!isDefaultChannel) if (!isDefaultChannel)
path = $"/v2/client-version/{binaryType}/channel/{channel}"; path = $"/v2/client-version/{BinaryType}/channel/{channel}";
try try
{ {