Skip to content

Commit

Permalink
Fix handling of RBX deployment interfacing
Browse files Browse the repository at this point in the history
  • Loading branch information
pizzaboxer committed Oct 11, 2024
1 parent d542efd commit ce82c3f
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 67 deletions.
38 changes: 20 additions & 18 deletions Bloxstrap/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -77,6 +78,7 @@ public Bootstrapper(LaunchMode launchMode)
_fastZipEvents.ProcessFile += (_, e) => e.ContinueRunning = !_cancelTokenSource.IsCancellationRequested;

AppData = IsStudioLaunch ? new RobloxStudioData() : new RobloxPlayerData();
Deployment.BinaryType = AppData.BinaryType;
}

private void SetStatus(string message)
Expand Down Expand Up @@ -151,7 +153,7 @@ public async Task Run()

SetStatus(Strings.Bootstrapper_Status_Connecting);

var connectionResult = await RobloxDeployment.InitializeConnectivity();
var connectionResult = await Deployment.InitializeConnectivity();

App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished");

Expand Down Expand Up @@ -244,29 +246,29 @@ private async Task GetLatestVersionInfo()
// 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)
{
Expand All @@ -275,25 +277,25 @@ and not HttpStatusCode.Forbidden
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);
Expand Down Expand Up @@ -962,7 +964,7 @@ private async Task DownloadPackage(Package package)
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))
Expand Down
6 changes: 4 additions & 2 deletions Bloxstrap/Models/Manifest/FileManifest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Bloxstrap.Models.Manifest
using Bloxstrap.RobloxInterfaces;

namespace Bloxstrap.Models.Manifest
{
public class FileManifest : List<ManifestFile>
{
Expand All @@ -24,7 +26,7 @@ private FileManifest(string data)

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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -12,7 +15,7 @@ public class RobloxFastFlags

private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);

private RobloxFastFlags(string applicationName, string channelName)
private ApplicationSettings(string applicationName, string channelName)
{
_applicationName = applicationName;
_channelName = channelName;
Expand All @@ -29,11 +32,11 @@ private async Task Fetch()
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;
Expand Down Expand Up @@ -100,12 +103,13 @@ private async Task Fetch()
}

// _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();

Expand All @@ -114,7 +118,7 @@ public static RobloxFastFlags GetSettings(string applicationName, string channel
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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
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 BaseUrl { get; private set; } = null!;
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();

// a list of roblox deployment locations that we check for, in case one of them don't work
Expand All @@ -23,7 +29,7 @@ public static class RobloxDeployment

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);

Expand All @@ -32,7 +38,7 @@ public static class RobloxDeployment
try
{
var response = await App.HttpClient.GetAsync($"{url}/versionStudio", token);

response.EnsureSuccessStatusCode();

// versionStudio is the version hash for the last MFC studio to be deployed.
Expand All @@ -56,16 +62,14 @@ public static class RobloxDeployment
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();

Expand All @@ -74,44 +78,41 @@ public static class RobloxDeployment

App.Logger.WriteLine(LOG_IDENT, "Testing connectivity...");

while (tasks.Any())
while (tasks.Any() && String.IsNullOrEmpty(BaseUrl))
{
var finishedTask = await Task.WhenAny(tasks);

tasks.Remove(finishedTask);

if (finishedTask.IsFaulted)
{
tasks.Remove(finishedTask);
exceptions.Add(finishedTask.Exception!.InnerException!);
continue;
}

BaseUrl = await finishedTask;
break;
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");

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}";
}
Expand All @@ -121,16 +122,18 @@ public static string GetLocation(string resource, string channel = DefaultChanne
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";

App.Logger.WriteLine(LOG_IDENT, $"Getting deploy info for channel {channel}");
const string LOG_IDENT = "Deployment::GetInfo";

if (String.IsNullOrEmpty(channel))
channel = DefaultChannel;
channel = Channel;

string cacheKey = $"{channel}-{binaryType}";
bool isDefaultChannel = String.Compare(channel, DefaultChannel, StringComparison.OrdinalIgnoreCase) == 0;

App.Logger.WriteLine(LOG_IDENT, $"Getting deploy info for channel {channel}");

string cacheKey = $"{channel}-{BinaryType}";

ClientVersion clientVersion;

Expand All @@ -141,12 +144,10 @@ public static async Task<ClientVersion> GetInfo(string channel, string binaryTyp
}
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
{
Expand Down

0 comments on commit ce82c3f

Please sign in to comment.