From c650c29016d186d9e83b207a8dd03197e008de18 Mon Sep 17 00:00:00 2001 From: pizzaboxer Date: Fri, 14 Apr 2023 21:28:15 +0200 Subject: [PATCH 1/3] Add WebView2 support currently being tested in zintegration, hotfix needs to be released before it makes it way to LIVE --- Bloxstrap/Bootstrapper.cs | 85 +++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 12 deletions(-) diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index b9bc1073..8c0989f7 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -38,6 +38,10 @@ public class Bootstrapper { "shaders.zip", @"shaders\" }, { "ssl.zip", @"ssl\" }, + // the runtime installer is only extracted if it needs installing + { "WebView2.zip", @"" }, + { "WebView2RuntimeInstaller.zip", @"WebView2RuntimeInstaller\" }, + { "content-avatar.zip", @"content\avatar\" }, { "content-configs.zip", @"content\configs\" }, { "content-fonts.zip", @"content\fonts\" }, @@ -68,10 +72,11 @@ public class Bootstrapper private static bool FreshInstall => String.IsNullOrEmpty(App.State.Prop.VersionGuid); private static string DesktopShortcutLocation => Path.Combine(Directories.Desktop, "Play Roblox.lnk"); + private static bool ShouldInstallWebView2 = false; private string? _launchCommandLine; - private string _versionGuid = null!; + private string _latestVersionGuid = null!; private PackageManifest _versionPackageManifest = null!; private string _versionFolder = null!; @@ -88,6 +93,18 @@ public class Bootstrapper public Bootstrapper(string? launchCommandLine = null) { _launchCommandLine = launchCommandLine; + + // check if the webview2 runtime needs to be installed + // webview2 can either be installed be per-user or globally, so we need to check in both hklm and hkcu + // https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment + + string hklmLocation = "SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"; + string hkcuLocation = "Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"; + + if (!Environment.Is64BitOperatingSystem) + hklmLocation = "SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"; + + ShouldInstallWebView2 = Registry.LocalMachine.OpenSubKey(hklmLocation) is null && Registry.CurrentUser.OpenSubKey(hkcuLocation) is null; } private void SetStatus(string message) @@ -145,7 +162,7 @@ public async Task Run() CheckInstallMigration(); // if roblox needs updating but is running and we have multiple instances open, ignore update for now - if (App.IsFirstRun || _versionGuid != App.State.Prop.VersionGuid && !Utilities.CheckIfRobloxRunning()) + if (App.IsFirstRun || _latestVersionGuid != App.State.Prop.VersionGuid && !Utilities.CheckIfRobloxRunning()) await InstallLatestVersion(); // last time the version folder was set, it was set to the latest version guid @@ -158,6 +175,9 @@ public async Task Run() App.FastFlags.Save(); } + if (ShouldInstallWebView2) + await InstallWebView2(); + if (App.Settings.Prop.UseReShade) SetStatus("Configuring/Downloading ReShade..."); @@ -244,9 +264,9 @@ private async Task CheckLatestVersion() SetStatus("Connecting to Roblox..."); ClientVersion clientVersion = await App.DeployManager.GetLastDeploy(); - _versionGuid = clientVersion.VersionGuid; - _versionFolder = Path.Combine(Directories.Versions, _versionGuid); - _versionPackageManifest = await PackageManifest.Get(_versionGuid); + _latestVersionGuid = clientVersion.VersionGuid; + _versionFolder = Path.Combine(Directories.Versions, _latestVersionGuid); + _versionPackageManifest = await PackageManifest.Get(_latestVersionGuid); } private void CheckInstallMigration() @@ -683,8 +703,13 @@ private async Task InstallLatestVersion() // download all the packages synchronously await DownloadPackage(package); + // we'll extract the runtime installer later if we need to + if (package.Name == "WebView2RuntimeInstaller.zip") + continue; + // extract the package immediately after download asynchronously - ExtractPackage(package); + // discard is just used to suppress the warning + Task _ = ExtractPackage(package); } if (_cancelFired) @@ -699,8 +724,8 @@ private async Task InstallLatestVersion() SetStatus("Configuring Roblox..."); } - // wait for all packages to finish extracting - while (_packagesExtracted < _versionPackageManifest.Count) + // wait for all packages to finish extracting, with an exception for the webview2 runtime installer + while (_packagesExtracted < _versionPackageManifest.Where(x => x.Name != "WebView2RuntimeInstaller.zip").Count()) { await Task.Delay(100); } @@ -727,7 +752,7 @@ private async Task InstallLatestVersion() string oldVersionFolder = Path.Combine(Directories.Versions, App.State.Prop.VersionGuid); - if (_versionGuid != App.State.Prop.VersionGuid && Directory.Exists(oldVersionFolder)) + if (_latestVersionGuid != App.State.Prop.VersionGuid && Directory.Exists(oldVersionFolder)) { // and also to delete our old version folder Directory.Delete(oldVersionFolder, true); @@ -752,10 +777,46 @@ private async Task InstallLatestVersion() if (Dialog is not null) Dialog.CancelEnabled = false; - App.State.Prop.VersionGuid = _versionGuid; + App.State.Prop.VersionGuid = _latestVersionGuid; _isInstalling = false; } + + private async Task InstallWebView2() + { + if (!ShouldInstallWebView2) + return; + + App.Logger.WriteLine($"[Bootstrapper::InstallWebView2] Installing runtime..."); + + string baseDirectory = Path.Combine(_versionFolder, "WebView2RuntimeInstaller"); + + if (!Directory.Exists(baseDirectory)) + { + Package? package = _versionPackageManifest.Find(x => x.Name == "WebView2RuntimeInstaller.zip"); + + if (package is null) + { + App.Logger.WriteLine($"[Bootstrapper::InstallWebView2] Aborted runtime install because package does not exist, has WebView2 been added in this Roblox version yet?"); + return; + } + + await ExtractPackage(package); + } + + SetStatus("Installing WebView2, please wait..."); + + ProcessStartInfo startInfo = new() + { + WorkingDirectory = baseDirectory, + FileName = Path.Combine(baseDirectory, "MicrosoftEdgeWebview2Setup.exe"), + Arguments = "/silent /install" + }; + + await Process.Start(startInfo)!.WaitForExitAsync(); + + App.Logger.WriteLine($"[Bootstrapper::InstallWebView2] Finished installing runtime"); + } private async Task ApplyModifications() { @@ -905,7 +966,7 @@ private async Task DownloadPackage(Package package) if (_cancelFired) return; - string packageUrl = $"{App.DeployManager.BaseUrl}/{_versionGuid}-{package.Name}"; + string packageUrl = $"{App.DeployManager.BaseUrl}/{_latestVersionGuid}-{package.Name}"; string packageLocation = Path.Combine(Directories.Downloads, package.Signature); string robloxPackageLocation = Path.Combine(Directories.LocalAppData, "Roblox", "Downloads", package.Signature); @@ -975,7 +1036,7 @@ private async Task DownloadPackage(Package package) } } - private async void ExtractPackage(Package package) + private async Task ExtractPackage(Package package) { if (_cancelFired) return; From ef9a28c8ec75544820221bcee50573d56a648283 Mon Sep 17 00:00:00 2001 From: pizzaboxer Date: Wed, 12 Apr 2023 11:06:27 +0200 Subject: [PATCH 2/3] Check if player executable exists (#128) --- Bloxstrap/Bootstrapper.cs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index 8c0989f7..3e019e74 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -71,14 +71,15 @@ public class Bootstrapper private readonly CancellationTokenSource _cancelTokenSource = new(); private static bool FreshInstall => String.IsNullOrEmpty(App.State.Prop.VersionGuid); - private static string DesktopShortcutLocation => Path.Combine(Directories.Desktop, "Play Roblox.lnk"); private static bool ShouldInstallWebView2 = false; + private static string DesktopShortcutLocation => Path.Combine(Directories.Desktop, "Play Roblox.lnk"); private string? _launchCommandLine; private string _latestVersionGuid = null!; private PackageManifest _versionPackageManifest = null!; private string _versionFolder = null!; + private string _playerLocation => Path.Combine(_versionFolder, "RobloxPlayerBeta.exe"); private bool _isInstalling = false; private double _progressIncrement; @@ -161,8 +162,9 @@ public async Task Run() CheckInstallMigration(); - // if roblox needs updating but is running and we have multiple instances open, ignore update for now - if (App.IsFirstRun || _latestVersionGuid != App.State.Prop.VersionGuid && !Utilities.CheckIfRobloxRunning()) + // only update roblox if we're running for the first time, or if + // roblox isn't running and our version guid is out of date, or the player exe doesn't exist + if (App.IsFirstRun || !Utilities.CheckIfRobloxRunning() && (_latestVersionGuid != App.State.Prop.VersionGuid || !File.Exists(_playerLocation))) await InstallLatestVersion(); // last time the version folder was set, it was set to the latest version guid @@ -356,7 +358,7 @@ private async Task StartRoblox() // whether we should wait for roblox to exit to handle stuff in the background or clean up after roblox closes bool shouldWait = false; - Process gameClient = Process.Start(Path.Combine(_versionFolder, "RobloxPlayerBeta.exe"), _launchCommandLine); + Process gameClient = Process.Start(_playerLocation, _launchCommandLine); List autocloseProcesses = new(); GameActivityWatcher? activityWatcher = null; DiscordRichPresence? richPresence = null; @@ -762,13 +764,12 @@ private async Task InstallLatestVersion() using (RegistryKey appFlagsKey = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers")) { string oldGameClientLocation = Path.Combine(oldVersionFolder, "RobloxPlayerBeta.exe"); - string newGameClientLocation = Path.Combine(_versionFolder, "RobloxPlayerBeta.exe"); string? appFlags = (string?)appFlagsKey.GetValue(oldGameClientLocation); if (appFlags is not null) { - App.Logger.WriteLine($"[Bootstrapper::InstallLatestVersion] Migrating app compatibility flags from {oldGameClientLocation} to {newGameClientLocation}..."); - appFlagsKey.SetValue(newGameClientLocation, appFlags); + App.Logger.WriteLine($"[Bootstrapper::InstallLatestVersion] Migrating app compatibility flags from {oldGameClientLocation} to {_playerLocation}..."); + appFlagsKey.SetValue(_playerLocation, appFlags); appFlagsKey.DeleteValue(oldGameClientLocation); } } @@ -827,23 +828,22 @@ private async Task ApplyModifications() using (RegistryKey appFlagsKey = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers")) { const string flag = " DISABLEDXMAXIMIZEDWINDOWEDMODE"; - string gameClientLocation = Path.Combine(_versionFolder, "RobloxPlayerBeta.exe"); - string? appFlags = (string?)appFlagsKey.GetValue(gameClientLocation); + string? appFlags = (string?)appFlagsKey.GetValue(_playerLocation); if (App.Settings.Prop.DisableFullscreenOptimizations) { if (appFlags is null) - appFlagsKey.SetValue(gameClientLocation, $"~{flag}"); + appFlagsKey.SetValue(_playerLocation, $"~{flag}"); else if (!appFlags.Contains(flag)) - appFlagsKey.SetValue(gameClientLocation, appFlags + flag); + appFlagsKey.SetValue(_playerLocation, appFlags + flag); } else if (appFlags is not null && appFlags.Contains(flag)) { // if there's more than one space, there's more flags set we need to preserve if (appFlags.Split(' ').Length > 2) - appFlagsKey.SetValue(gameClientLocation, appFlags.Remove(appFlags.IndexOf(flag), flag.Length)); + appFlagsKey.SetValue(_playerLocation, appFlags.Remove(appFlags.IndexOf(flag), flag.Length)); else - appFlagsKey.DeleteValue(gameClientLocation); + appFlagsKey.DeleteValue(_playerLocation); } } From be938796aeebecd248a8bd4b9326b98e6f4568d0 Mon Sep 17 00:00:00 2001 From: pizzaboxer Date: Sun, 16 Apr 2023 01:58:11 +0100 Subject: [PATCH 3/3] Add check for working deployment domain (#134) this is gonna suck to merge into 2.2.0 lmao --- Bloxstrap/App.xaml.cs | 2 +- Bloxstrap/Helpers/DeployManager.cs | 76 +++++++++++++++---- Bloxstrap/ViewModels/InstallationViewModel.cs | 2 +- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs index fb76ee9a..83688357 100644 --- a/Bloxstrap/App.xaml.cs +++ b/Bloxstrap/App.xaml.cs @@ -248,7 +248,7 @@ protected override void OnStartup(StartupEventArgs e) if (!IsFirstRun) ShouldSaveConfigs = true; - DeployManager.SetChannel(Settings.Prop.Channel); + DeployManager.Channel = Settings.Prop.Channel; // start bootstrapper and show the bootstrapper modal if we're not running silently Logger.WriteLine($"[App::OnStartup] Initializing bootstrapper"); diff --git a/Bloxstrap/Helpers/DeployManager.cs b/Bloxstrap/Helpers/DeployManager.cs index 40d01656..87030ae6 100644 --- a/Bloxstrap/Helpers/DeployManager.cs +++ b/Bloxstrap/Helpers/DeployManager.cs @@ -13,11 +13,68 @@ namespace Bloxstrap.Helpers public class DeployManager { #region Properties - public const string DefaultBaseUrl = "https://setup.rbxcdn.com"; public const string DefaultChannel = "LIVE"; - - public string BaseUrl { get; private set; } = DefaultBaseUrl; - public string Channel { get; private set; } = DefaultChannel; + + private string _channel = DefaultChannel; + + public string Channel + { + get => _channel; + set + { + if (_channel != value) + App.Logger.WriteLine($"[DeployManager::SetChannel] Changed channel to {value}"); + + _channel = value; + } + } + + // a list of roblox delpoyment locations that we check for, in case one of them don't work + private List BaseUrls = new() + { + "https://setup.rbxcdn.com", + "https://setup-ak.rbxcdn.com", + "https://s3.amazonaws.com/setup.roblox.com" + }; + + private string? _baseUrl = null; + + public string BaseUrl + { + get + { + if (String.IsNullOrEmpty(_baseUrl)) + { + // check for a working accessible deployment domain + foreach (string attemptedUrl in BaseUrls) + { + App.Logger.WriteLine($"[DeployManager::DefaultBaseUrl.Set] Testing connection to '{attemptedUrl}'..."); + + try + { + App.HttpClient.GetAsync($"{attemptedUrl}/version").Wait(); + App.Logger.WriteLine($"[DeployManager::DefaultBaseUrl.Set] Connection successful!"); + _baseUrl = attemptedUrl; + break; + } + catch (Exception ex) + { + App.Logger.WriteLine($"[DeployManager::DefaultBaseUrl.Set] Connection failed!"); + App.Logger.WriteLine($"[DeployManager::DefaultBaseUrl.Set] {ex}"); + continue; + } + } + + if (String.IsNullOrEmpty(_baseUrl)) + throw new Exception("Unable to find an accessible Roblox deploy mirror!"); + } + + if (Channel == DefaultChannel) + return _baseUrl; + else + return $"{_baseUrl}/channel/{Channel.ToLower()}"; + } + } // basically any channel that has had a deploy within the past month with a windowsplayer build public static readonly List ChannelsAbstracted = new() @@ -51,17 +108,6 @@ public class DeployManager }; #endregion - public void SetChannel(string channel) - { - if (Channel == channel) - return; - - App.Logger.WriteLine($"[DeployManager::SetChannel] Set channel to {Channel}"); - - Channel = channel; - BaseUrl = channel == DefaultChannel ? DefaultBaseUrl : $"{DefaultBaseUrl}/channel/{channel.ToLower()}"; - } - public async Task GetLastDeploy(bool timestamp = false) { App.Logger.WriteLine($"[DeployManager::GetLastDeploy] Getting deploy info for channel {Channel} (timestamp={timestamp})"); diff --git a/Bloxstrap/ViewModels/InstallationViewModel.cs b/Bloxstrap/ViewModels/InstallationViewModel.cs index 96078704..9a0d2b47 100644 --- a/Bloxstrap/ViewModels/InstallationViewModel.cs +++ b/Bloxstrap/ViewModels/InstallationViewModel.cs @@ -39,7 +39,7 @@ private async Task LoadChannelDeployInfo(string channel) ChannelDeployInfo = null; OnPropertyChanged(nameof(ChannelDeployInfo)); - App.DeployManager.SetChannel(channel); + App.DeployManager.Channel = channel; ClientVersion info = await App.DeployManager.GetLastDeploy(true); ChannelDeployInfo = new DeployInfo