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.Forms;
using System.Windows.Shell;
using Microsoft.Win32;
using Bloxstrap.AppData;
using System.Windows.Shell;
using Bloxstrap.RobloxInterfaces;
using Bloxstrap.UI.Elements.Bootstrapper.Base;
using ICSharpCode.SharpZipLib.Zip;
@ -77,6 +78,7 @@ namespace Bloxstrap
_fastZipEvents.ProcessFile += (_, e) => e.ContinueRunning = !_cancelTokenSource.IsCancellationRequested;
AppData = IsStudioLaunch ? new RobloxStudioData() : new RobloxPlayerData();
Deployment.BinaryType = AppData.BinaryType;
}
private void SetStatus(string message)
@ -151,7 +153,7 @@ namespace Bloxstrap
SetStatus(Strings.Bootstrapper_Status_Connecting);
var connectionResult = await RobloxDeployment.InitializeConnectivity();
var connectionResult = await Deployment.InitializeConnectivity();
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
// 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");
var match = Regex.Match(App.LaunchSettings.RobloxLaunchArgs, "channel:([a-zA-Z0-9-_]+)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
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))
{
channel = value;
Deployment.Channel = value.ToLowerInvariant();
}
if (channel != "production")
App.SendStat("robloxChannel", channel);
App.Logger.WriteLine(LOG_IDENT, "Got channel as " + (String.IsNullOrEmpty(Deployment.Channel) ? Deployment.DefaultChannel : Deployment.Channel));
if (Deployment.Channel != "production")
App.SendStat("robloxChannel", Deployment.Channel);
ClientVersion clientVersion;
try
{
clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType);
clientVersion = await Deployment.GetInfo();
}
catch (HttpRequestException ex)
{
@ -275,25 +277,25 @@ namespace Bloxstrap
and not HttpStatusCode.NotFound)
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;
clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType);
Deployment.Channel = Deployment.DefaultChannel;
clientVersion = await Deployment.GetInfo();
}
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;
clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType);
Deployment.Channel = Deployment.DefaultChannel;
clientVersion = await Deployment.GetInfo();
}
key.SetValueSafe("www.roblox.com", channel);
key.SetValueSafe("www.roblox.com", Deployment.IsDefaultChannel ? "" : Deployment.Channel);
_latestVersionGuid = clientVersion.VersionGuid;
string pkgManifestUrl = RobloxDeployment.GetLocation($"/{_latestVersionGuid}-rbxPkgManifest.txt");
string pkgManifestUrl = Deployment.GetLocation($"/{_latestVersionGuid}-rbxPkgManifest.txt");
var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl);
_versionPackageManifest = new(pkgManifestData);
@ -962,7 +964,7 @@ namespace Bloxstrap
if (_cancelTokenSource.IsCancellationRequested)
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);
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>
{
@ -24,7 +26,7 @@
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);
return new FileManifest(pkgManifestData);

View File

@ -1,8 +1,11 @@
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 _channelName;
@ -12,7 +15,7 @@ namespace Bloxstrap
private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
private RobloxFastFlags(string applicationName, string channelName)
private ApplicationSettings(string applicationName, string channelName)
{
_applicationName = applicationName;
_channelName = channelName;
@ -29,11 +32,11 @@ namespace Bloxstrap
if (_initialised)
return;
string logIndent = $"RobloxFastFlags::Fetch.{_applicationName}.{_channelName}";
string logIndent = $"ApplicationSettings::Fetch.{_applicationName}.{_channelName}";
App.Logger.WriteLine(logIndent, "Fetching fast flags");
string path = $"/v2/settings/application/{_applicationName}";
if (_channelName != RobloxDeployment.DefaultChannel.ToLowerInvariant())
if (_channelName != Deployment.DefaultChannel.ToLowerInvariant())
path += $"/bucket/{_channelName}";
HttpResponseMessage response;
@ -100,12 +103,13 @@ namespace Bloxstrap
}
// _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 RobloxFastFlags PCClientBootstrapper { get; } = GetSettings("PCClientBootstrapper");
public static ApplicationSettings PCDesktopClient => GetSettings("PCDesktopClient");
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();
@ -114,7 +118,7 @@ namespace Bloxstrap
if (_cache.ContainsKey(applicationName) && _cache[applicationName].ContainsKey(channelName))
return _cache[applicationName][channelName];
var flags = new RobloxFastFlags(applicationName, channelName);
var flags = new ApplicationSettings(applicationName, channelName);
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";
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!;
private static readonly Dictionary<string, ClientVersion> ClientVersionCache = new();
@ -23,7 +29,7 @@
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);
@ -56,16 +62,14 @@
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()
{
const string LOG_IDENT = "RobloxDeployment::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
const string LOG_IDENT = "Deployment::InitializeConnectivity";
var tokenSource = new CancellationTokenSource();
@ -74,25 +78,22 @@
App.Logger.WriteLine(LOG_IDENT, "Testing connectivity...");
while (tasks.Any())
while (tasks.Any() && String.IsNullOrEmpty(BaseUrl))
{
var finishedTask = await Task.WhenAny(tasks);
if (finishedTask.IsFaulted)
{
tasks.Remove(finishedTask);
exceptions.Add(finishedTask.Exception!.InnerException!);
continue;
}
BaseUrl = await finishedTask;
break;
if (finishedTask.IsFaulted)
exceptions.Add(finishedTask.Exception!.InnerException!);
else
BaseUrl = finishedTask.Result;
}
// stop other running connectivity tests
tokenSource.Cancel();
if (String.IsNullOrEmpty(BaseUrl))
if (string.IsNullOrEmpty(BaseUrl))
return exceptions[0];
App.Logger.WriteLine(LOG_IDENT, $"Got {BaseUrl} as the optimal base URL");
@ -100,18 +101,18 @@
return null;
}
public static string GetLocation(string resource, string channel = DefaultChannel)
public static string GetLocation(string resource)
{
string location = BaseUrl;
if (String.Compare(channel, DefaultChannel, StringComparison.InvariantCultureIgnoreCase) != 0)
if (!IsDefaultChannel)
{
string channelName;
if (RobloxFastFlags.GetSettings(nameof(RobloxFastFlags.PCClientBootstrapper), channel).Get<bool>("FFlagReplaceChannelNameForDownload"))
if (ApplicationSettings.GetSettings(nameof(ApplicationSettings.PCClientBootstrapper), Channel).Get<bool>("FFlagReplaceChannelNameForDownload"))
channelName = "common";
else
channelName = channel.ToLowerInvariant();
channelName = Channel.ToLowerInvariant();
location += $"/channel/{channelName}";
}
@ -121,16 +122,18 @@
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}");
if (String.IsNullOrEmpty(channel))
channel = DefaultChannel;
string cacheKey = $"{channel}-{binaryType}";
string cacheKey = $"{channel}-{BinaryType}";
ClientVersion clientVersion;
@ -141,12 +144,10 @@
}
else
{
bool isDefaultChannel = String.Compare(channel, DefaultChannel, StringComparison.OrdinalIgnoreCase) == 0;
string path = $"/v2/client-version/{binaryType}";
string path = $"/v2/client-version/{BinaryType}";
if (!isDefaultChannel)
path = $"/v2/client-version/{binaryType}/channel/{channel}";
path = $"/v2/client-version/{BinaryType}/channel/{channel}";
try
{