From 9d357179030b7185e3b51608772b49fa9b906297 Mon Sep 17 00:00:00 2001 From: bluepilledgreat <97983689+bluepilledgreat@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:21:51 +0100 Subject: [PATCH] add replacing channel name for download --- Bloxstrap/Models/ClientFlagSettings.cs | 8 ++ Bloxstrap/RobloxDeployment.cs | 11 +- Bloxstrap/RobloxFastFlags.cs | 144 +++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 Bloxstrap/Models/ClientFlagSettings.cs create mode 100644 Bloxstrap/RobloxFastFlags.cs diff --git a/Bloxstrap/Models/ClientFlagSettings.cs b/Bloxstrap/Models/ClientFlagSettings.cs new file mode 100644 index 0000000..b50932b --- /dev/null +++ b/Bloxstrap/Models/ClientFlagSettings.cs @@ -0,0 +1,8 @@ +namespace Bloxstrap.Models +{ + public class ClientFlagSettings + { + [JsonPropertyName("applicationSettings")] + public Dictionary? ApplicationSettings { get; set; } + } +} diff --git a/Bloxstrap/RobloxDeployment.cs b/Bloxstrap/RobloxDeployment.cs index d064e8c..639fc07 100644 --- a/Bloxstrap/RobloxDeployment.cs +++ b/Bloxstrap/RobloxDeployment.cs @@ -95,7 +95,16 @@ string location = BaseUrl; if (channel.ToLowerInvariant() != DefaultChannel.ToLowerInvariant()) - location += $"/channel/{channel.ToLowerInvariant()}"; + { + string channelName; + + if (RobloxFastFlags.GetSettings(nameof(RobloxFastFlags.PCClientBootstrapper), channel).Get("FFlagReplaceChannelNameForDownload")) + channelName = "common"; + else + channelName = channel.ToLowerInvariant(); + + location += $"/channel/{channelName}"; + } location += resource; diff --git a/Bloxstrap/RobloxFastFlags.cs b/Bloxstrap/RobloxFastFlags.cs new file mode 100644 index 0000000..6121861 --- /dev/null +++ b/Bloxstrap/RobloxFastFlags.cs @@ -0,0 +1,144 @@ +using System.ComponentModel; + +namespace Bloxstrap +{ + public class RobloxFastFlags + { + private string _applicationName; + private string _channelName; + + private bool _initialised = false; + private Dictionary? _flags; + + private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + + private RobloxFastFlags(string applicationName, string channelName) + { + _applicationName = applicationName; + _channelName = channelName; + } + + private async Task Fetch() + { + if (_initialised) + return; + + await semaphoreSlim.WaitAsync(); + try + { + if (_initialised) + return; + + string logIndent = $"RobloxFastFlags::Fetch.{_applicationName}.{_channelName}"; + App.Logger.WriteLine(logIndent, "Fetching fast flags"); + + string path = $"/v2/settings/application/{_applicationName}"; + if (_channelName != RobloxDeployment.DefaultChannel.ToLowerInvariant()) + path += $"/bucket/{_channelName}"; + + HttpResponseMessage response; + + try + { + response = await App.HttpClient.GetAsync("https://clientsettingscdn.roblox.com" + path); + } + catch (Exception ex) + { + App.Logger.WriteLine(logIndent, "Failed to contact clientsettingscdn! Falling back to clientsettings..."); + App.Logger.WriteException(logIndent, ex); + + response = await App.HttpClient.GetAsync("https://clientsettings.roblox.com" + path); + } + + string rawResponse = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + App.Logger.WriteLine(logIndent, + "Failed to fetch client settings!\r\n" + + $"\tStatus code: {response.StatusCode}\r\n" + + $"\tResponse: {rawResponse}" + ); + + throw new HttpResponseException(response); + } + + var clientSettings = JsonSerializer.Deserialize(rawResponse); + + if (clientSettings == null) + throw new Exception("Deserialised client settings is null!"); + + if (clientSettings.ApplicationSettings == null) + throw new Exception("Deserialised application settings is null!"); + + _flags = clientSettings.ApplicationSettings; + _initialised = true; + } + finally + { + semaphoreSlim.Release(); + } + } + + public async Task GetAsync(string name) + { + await Fetch(); + + if (!_flags!.ContainsKey(name)) + return default; + + string value = _flags[name]; + + try + { + var converter = TypeDescriptor.GetConverter(typeof(T)); + if (converter == null) + return default; + + return (T?)converter.ConvertFromString(value); + } + catch (NotSupportedException) // boohoo + { + return default; + } + } + + public T? Get(string name) + { + return GetAsync(name).Result; + } + + // _cache[applicationName][channelName] + private static Dictionary> _cache = new(); + + public static RobloxFastFlags PCDesktopClient { get; } = GetSettings("PCDesktopClient"); + public static RobloxFastFlags PCClientBootstrapper { get; } = GetSettings("PCClientBootstrapper"); + + public static RobloxFastFlags GetSettings(string applicationName, string? channelName = null, bool shouldCache = true) + { + string channelNameLower; + if (!string.IsNullOrEmpty(channelName)) + channelNameLower = channelName.ToLowerInvariant(); + else + channelNameLower = App.Settings.Prop.Channel.ToLowerInvariant(); + + lock (_cache) + { + if (_cache.ContainsKey(applicationName) && _cache[applicationName].ContainsKey(channelNameLower)) + return _cache[applicationName][channelNameLower]; + + var flags = new RobloxFastFlags(applicationName, channelNameLower); + + if (shouldCache) + { + if (!_cache.ContainsKey(applicationName)) + _cache[applicationName] = new(); + + _cache[applicationName][channelNameLower] = flags; + } + + return flags; + } + } + } +}