diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index d73f1ea..b1a25c2 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -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)) diff --git a/Bloxstrap/Models/Manifest/FileManifest.cs b/Bloxstrap/Models/Manifest/FileManifest.cs index 904f1ec..8936a70 100644 --- a/Bloxstrap/Models/Manifest/FileManifest.cs +++ b/Bloxstrap/Models/Manifest/FileManifest.cs @@ -1,4 +1,6 @@ -namespace Bloxstrap.Models.Manifest +using Bloxstrap.RobloxInterfaces; + +namespace Bloxstrap.Models.Manifest { public class FileManifest : List { @@ -24,7 +26,7 @@ public static async Task 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); diff --git a/Bloxstrap/RobloxFastFlags.cs b/Bloxstrap/RobloxInterfaces/ApplicationSettings.cs similarity index 76% rename from Bloxstrap/RobloxFastFlags.cs rename to Bloxstrap/RobloxInterfaces/ApplicationSettings.cs index 38799c1..f3eec9e 100644 --- a/Bloxstrap/RobloxFastFlags.cs +++ b/Bloxstrap/RobloxInterfaces/ApplicationSettings.cs @@ -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> _cache = new(); + private static Dictionary> _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) { diff --git a/Bloxstrap/RobloxDeployment.cs b/Bloxstrap/RobloxInterfaces/Deployment.cs similarity index 73% rename from Bloxstrap/RobloxDeployment.cs rename to Bloxstrap/RobloxInterfaces/Deployment.cs index e2022c1..383cab4 100644 --- a/Bloxstrap/RobloxDeployment.cs +++ b/Bloxstrap/RobloxInterfaces/Deployment.cs @@ -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 ClientVersionCache = new(); // a list of roblox deployment locations that we check for, in case one of them don't work @@ -23,7 +29,7 @@ private static async Task 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); @@ -32,7 +38,7 @@ 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. @@ -56,16 +62,14 @@ return url; } + /// + /// This function serves double duty as the setup mirror enumerator, and as our connectivity check. + /// Returns null for success. + /// + /// public static async Task 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; - } + tasks.Remove(finishedTask); - 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("FFlagReplaceChannelNameForDownload")) + if (ApplicationSettings.GetSettings(nameof(ApplicationSettings.PCClientBootstrapper), Channel).Get("FFlagReplaceChannelNameForDownload")) channelName = "common"; else - channelName = channel.ToLowerInvariant(); + channelName = Channel.ToLowerInvariant(); location += $"/channel/{channelName}"; } @@ -121,16 +122,18 @@ return location; } - public static async Task GetInfo(string channel, string binaryType = "WindowsPlayer") + public static async Task 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 {