diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml new file mode 100644 index 0000000..d11fd2a --- /dev/null +++ b/.github/workflows/winget.yml @@ -0,0 +1,15 @@ +name: Publish to Winget + +on: + release: + types: [released] + +jobs: + publish: + # Action can only be run on windows + runs-on: windows-latest + steps: + - uses: vedantmgoyal2009/winget-releaser@v1 + with: + identifier: pizzaboxer.Bloxstrap + token: ${{ secrets.WINGET_TOKEN }} diff --git a/Bloxstrap/Bloxstrap.csproj b/Bloxstrap/Bloxstrap.csproj index aaeec16..e8c835a 100644 --- a/Bloxstrap/Bloxstrap.csproj +++ b/Bloxstrap/Bloxstrap.csproj @@ -10,14 +10,20 @@ AnyCPU AnyCPU;x86 Bloxstrap.ico - 1.5.6 - 1.5.6.0 + 1.6.2 + 1.6.2.0 + + + + + + diff --git a/Bloxstrap/Bloxstrap.ico b/Bloxstrap/Bloxstrap.ico index 35ffd33..93904fb 100644 Binary files a/Bloxstrap/Bloxstrap.ico and b/Bloxstrap/Bloxstrap.ico differ diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index 18cf310..af19d50 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -12,6 +12,7 @@ using Bloxstrap.Helpers.RSMM; using Bloxstrap.Models; using System.Net; +using Bloxstrap.Properties; namespace Bloxstrap { @@ -81,7 +82,10 @@ public partial class Bootstrapper private readonly bool FreshInstall; - private int ProgressIncrement; + private double ProgressIncrement; + private long TotalBytes = 0; + private long TotalDownloadedBytes = 0; + private int PackagesExtracted = 0; private bool CancelFired = false; public IBootstrapperDialog Dialog = null!; @@ -106,6 +110,11 @@ public async Task Run() return; } +#if !DEBUG + if (!Program.IsFirstRun && Program.Settings.CheckForUpdates) + await CheckForUpdates(); +#endif + await CheckLatestVersion(); // if bloxstrap is installing for the first time but is running, prompt to close roblox @@ -113,7 +122,7 @@ public async Task Run() if (!Directory.Exists(VersionFolder) && CheckIfRunning(true) || Program.Settings.VersionGuid != VersionGuid && !CheckIfRunning(false)) await InstallLatestVersion(); - ApplyModifications(); + await ApplyModifications(); if (Program.IsFirstRun) Program.SettingsManager.ShouldSave = true; @@ -136,6 +145,50 @@ public async Task Run() Program.Exit(); } + private async Task CheckForUpdates() + { + string currentVersion = $"Bloxstrap v{Program.Version}"; + + var releaseInfo = await Utilities.GetJson($"https://api.github.com/repos/{Program.ProjectRepository}/releases/latest"); + + if (releaseInfo is null || releaseInfo.Name is null || releaseInfo.Assets is null || currentVersion == releaseInfo.Name) + return; + + Dialog.Message = "Getting the latest Bloxstrap..."; + + // 64-bit is always the first option + GithubReleaseAsset asset = releaseInfo.Assets[Environment.Is64BitOperatingSystem ? 0 : 1]; + string downloadLocation = Path.Combine(Directories.Updates, asset.Name); + + Directory.CreateDirectory(Directories.Updates); + + Debug.WriteLine($"Downloading {releaseInfo.Name}..."); + + if (!File.Exists(downloadLocation)) + { + var response = await Program.HttpClient.GetAsync(asset.BrowserDownloadUrl); + + using (var fileStream = new FileStream(Path.Combine(Directories.Updates, asset.Name), FileMode.CreateNew)) + { + await response.Content.CopyToAsync(fileStream); + } + } + + Debug.WriteLine($"Starting {releaseInfo.Name}..."); + + ProcessStartInfo startInfo = new() + { + FileName = downloadLocation, + }; + + foreach (string arg in Program.LaunchArgs) + startInfo.ArgumentList.Add(arg); + + Process.Start(startInfo); + + Program.Exit(); + } + private async Task CheckLatestVersion() { Dialog.Message = "Connecting to Roblox..."; @@ -179,6 +232,12 @@ private async Task StartRoblox() Dialog.Message = "Starting Roblox..."; + if (LaunchCommandLine == "--app" && Program.Settings.UseDisableAppPatch) + { + Utilities.OpenWebsite("https://www.roblox.com/games"); + return; + } + // launch time isn't really required for all launches, but it's usually just safest to do this LaunchCommandLine += " --launchtime=" + DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -204,9 +263,11 @@ private async Task StartRoblox() if (Program.Settings.RFUEnabled && Process.GetProcessesByName("rbxfpsunlocker").Length == 0) { - ProcessStartInfo startInfo = new(); - startInfo.FileName = Path.Combine(Directories.Integrations, @"rbxfpsunlocker\rbxfpsunlocker.exe"); - startInfo.WorkingDirectory = Path.Combine(Directories.Integrations, "rbxfpsunlocker"); + ProcessStartInfo startInfo = new() + { + FileName = Path.Combine(Directories.Integrations, @"rbxfpsunlocker\rbxfpsunlocker.exe"), + WorkingDirectory = Path.Combine(Directories.Integrations, "rbxfpsunlocker") + }; rbxFpsUnlocker = Process.Start(startInfo); @@ -218,10 +279,6 @@ private async Task StartRoblox() await Task.Delay(3000); // now we move onto handling rich presence - // this is going to be an issue for desktop app launches as this gets the place id from the command line, - // but the desktop app launch can switch games without having to close the client - // i might be able to experiment with reading from the latest log file in realtime to circumvent this, - // but i have no idea how reliable it will be. todo? if (Program.Settings.UseDiscordRichPresence) { richPresence = new DiscordRichPresence(); @@ -266,9 +323,9 @@ public void CancelButtonClicked() Program.Exit(ERROR_INSTALL_USEREXIT); } - #endregion +#endregion - #region App Install +#region App Install public static void Register() { RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{Program.ProjectName}"); @@ -384,13 +441,19 @@ private void Uninstall() } catch (Exception) { } - Dialog.ShowSuccess($"{Program.ProjectName} has been uninstalled"); + Dialog.ShowSuccess($"{Program.ProjectName} has succesfully uninstalled"); - Environment.Exit(ERROR_PRODUCT_UNINSTALLED); + Program.Exit(); + } +#endregion + +#region Roblox Install + private void UpdateProgressbar() + { + int newProgress = (int)Math.Floor(ProgressIncrement * TotalDownloadedBytes); + Dialog.ProgressValue = newProgress; } - #endregion - #region Roblox Install private async Task InstallLatestVersion() { if (FreshInstall) @@ -402,50 +465,40 @@ private async Task InstallLatestVersion() Dialog.CancelEnabled = true; - // i believe the bootstrapper bases the progress bar off - // bytes downloaded / bytes total according to rbxPkgManifest? - // i'm too lazy for that, so here it's just based off how many packages - // have finished downloading - Dialog.ProgressStyle = ProgressBarStyle.Continuous; - ProgressIncrement = (int)Math.Floor((decimal)1 / VersionPackageManifest.Count * 100); + // compute total bytes to download + + foreach (Package package in VersionPackageManifest) + TotalBytes += package.PackedSize; + + ProgressIncrement = (double)1 / TotalBytes * 100; Directory.CreateDirectory(Directories.Downloads); + Directory.CreateDirectory(Directories.Versions); foreach (Package package in VersionPackageManifest) { - // download all the packages at once - DownloadPackage(package); - } - - do - { - // wait for download to finish (and also round off the progress bar if needed) - - if (Dialog.ProgressValue == ProgressIncrement * VersionPackageManifest.Count) - Dialog.ProgressValue = 100; + // download all the packages synchronously + await DownloadPackage(package); - await Task.Delay(1000); + // extract the package immediately after download + ExtractPackage(package); } - while (Dialog.ProgressValue != 100); - Dialog.ProgressStyle = ProgressBarStyle.Marquee; + // allow progress bar to 100% before continuing (purely ux reasons lol) + await Task.Delay(1000); - Debug.WriteLine("Finished downloading"); + Dialog.ProgressStyle = ProgressBarStyle.Marquee; Dialog.Message = "Configuring Roblox..."; - Directory.CreateDirectory(Directories.Versions); - - foreach (Package package in VersionPackageManifest) + // wait for all packages to finish extracting + while (PackagesExtracted < VersionPackageManifest.Count) { - // extract all the packages at once (shouldn't be too heavy on cpu?) - ExtractPackage(package); + await Task.Delay(100); } - Debug.WriteLine("Finished extracting packages"); - string appSettingsLocation = Path.Combine(VersionFolder, "AppSettings.xml"); await File.WriteAllTextAsync(appSettingsLocation, AppSettings); @@ -472,7 +525,7 @@ private async Task InstallLatestVersion() Program.Settings.VersionGuid = VersionGuid; } - private void ApplyModifications() + private async Task ApplyModifications() { Dialog.Message = "Applying Roblox modifications..."; @@ -485,13 +538,13 @@ private void ApplyModifications() if (!Directory.Exists(modFolder)) { Directory.CreateDirectory(modFolder); - File.WriteAllText(Path.Combine(modFolder, "README.txt"), ModReadme); + await File.WriteAllTextAsync(Path.Combine(modFolder, "README.txt"), ModReadme); } - CheckModPreset(Program.Settings.UseOldDeathSound, @"content\sounds\ouch.ogg", Program.Base64OldDeathSound); - CheckModPreset(Program.Settings.UseOldMouseCursor, @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", Program.Base64OldArrowCursor); - CheckModPreset(Program.Settings.UseOldMouseCursor, @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", Program.Base64OldArrowFarCursor); - CheckModPreset(Program.Settings.UseDisableAppPatch, @"ExtraContent\places\Mobile.rbxl", ""); + await CheckModPreset(Program.Settings.UseOldDeathSound, @"content\sounds\ouch.ogg", "OldDeath.ogg"); + await CheckModPreset(Program.Settings.UseOldMouseCursor, @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", "OldCursor.png"); + await CheckModPreset(Program.Settings.UseOldMouseCursor, @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", "OldFarCursor.png"); + await CheckModPreset(Program.Settings.UseDisableAppPatch, @"ExtraContent\places\Mobile.rbxl", ""); foreach (string file in Directory.GetFiles(modFolder, "*.*", SearchOption.AllDirectories)) { @@ -510,7 +563,7 @@ private void ApplyModifications() // original files from the downloaded packages if (File.Exists(manifestFile)) - manifestFiles = File.ReadAllLines(manifestFile).ToList(); + manifestFiles = (await File.ReadAllLinesAsync(manifestFile)).ToList(); else manifestFiles = modFolderFiles; @@ -537,8 +590,7 @@ private void ApplyModifications() File.SetAttributes(fileVersionFolder, File.GetAttributes(fileModFolder) & ~FileAttributes.ReadOnly); } - // now check for files that have been deleted from the mod folder - // according to the manifest + // now check for files that have been deleted from the mod folder according to the manifest foreach (string fileLocation in manifestFiles) { if (modFolderFiles.Contains(fileLocation)) @@ -564,10 +616,10 @@ private void ApplyModifications() File.WriteAllLines(manifestFile, modFolderFiles); } - private static void CheckModPreset(bool condition, string location, string base64Contents) + private static async Task CheckModPreset(bool condition, string location, string name) { string modFolderLocation = Path.Combine(Directories.Modifications, location); - byte[] binaryData = Convert.FromBase64String(base64Contents); + byte[] binaryData = string.IsNullOrEmpty(name) ? Array.Empty() : await ResourceHelper.Get(name); if (condition) { @@ -580,7 +632,7 @@ private static void CheckModPreset(bool condition, string location, string base6 Directory.CreateDirectory(directory); - File.WriteAllBytes(modFolderLocation, binaryData); + await File.WriteAllBytesAsync(modFolderLocation, binaryData); } } else if (File.Exists(modFolderLocation) && Utilities.MD5File(modFolderLocation) == Utilities.MD5Data(binaryData)) @@ -589,7 +641,7 @@ private static void CheckModPreset(bool condition, string location, string base6 } } - private async void DownloadPackage(Package package) + private async Task DownloadPackage(Package package) { string packageUrl = $"{DeployManager.BaseUrl}/{VersionGuid}-{package.Name}"; string packageLocation = Path.Combine(Directories.Downloads, package.Signature); @@ -603,12 +655,13 @@ private async void DownloadPackage(Package package) if (calculatedMD5 != package.Signature) { Debug.WriteLine($"{package.Name} is corrupted ({calculatedMD5} != {package.Signature})! Deleting and re-downloading..."); - file.Delete(); + file.Delete(); } else { Debug.WriteLine($"{package.Name} is already downloaded, skipping..."); - Dialog.ProgressValue += ProgressIncrement; + TotalDownloadedBytes += package.PackedSize; + UpdateProgressbar(); return; } } @@ -619,7 +672,8 @@ private async void DownloadPackage(Package package) Debug.WriteLine($"Found existing version of {package.Name} ({robloxPackageLocation})! Copying to Downloads folder..."); File.Copy(robloxPackageLocation, packageLocation); - Dialog.ProgressValue += ProgressIncrement; + TotalDownloadedBytes += package.PackedSize; + UpdateProgressbar(); return; } @@ -627,22 +681,34 @@ private async void DownloadPackage(Package package) { Debug.WriteLine($"Downloading {package.Name}..."); - var response = await Program.HttpClient.GetAsync(packageUrl); - if (CancelFired) return; + var response = await Program.HttpClient.GetAsync(packageUrl, HttpCompletionOption.ResponseHeadersRead); + + var buffer = new byte[8192]; + + using (var stream = await response.Content.ReadAsStreamAsync()) using (var fileStream = new FileStream(packageLocation, FileMode.CreateNew)) { - await response.Content.CopyToAsync(fileStream); + while (true) + { + var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + if (bytesRead == 0) + break; // we're done + + await fileStream.WriteAsync(buffer, 0, bytesRead); + + TotalDownloadedBytes += bytesRead; + UpdateProgressbar(); + } } Debug.WriteLine($"Finished downloading {package.Name}!"); - Dialog.ProgressValue += ProgressIncrement; } } - private void ExtractPackage(Package package) + private async void ExtractPackage(Package package) { if (CancelFired) return; @@ -654,7 +720,7 @@ private void ExtractPackage(Package package) Debug.WriteLine($"Extracting {package.Name} to {packageFolder}..."); - using (ZipArchive archive = ZipFile.OpenRead(packageLocation)) + using (ZipArchive archive = await Task.Run(() => ZipFile.OpenRead(packageLocation))) { foreach (ZipArchiveEntry entry in archive.Entries) { @@ -666,7 +732,7 @@ private void ExtractPackage(Package package) extractPath = Path.Combine(packageFolder, entry.FullName); - Debug.WriteLine($"[{package.Name}] Writing {extractPath}..."); + //Debug.WriteLine($"[{package.Name}] Writing {extractPath}..."); directory = Path.GetDirectoryName(extractPath); @@ -678,9 +744,13 @@ private void ExtractPackage(Package package) if (File.Exists(extractPath)) File.Delete(extractPath); - entry.ExtractToFile(extractPath); + await Task.Run(() => entry.ExtractToFile(extractPath)); } } + + Debug.WriteLine($"Finished extracting {package.Name}"); + + PackagesExtracted += 1; } private void ExtractFileFromPackage(string packageName, string fileName) @@ -690,7 +760,7 @@ private void ExtractFileFromPackage(string packageName, string fileName) if (package is null) return; - DownloadPackage(package); + DownloadPackage(package).GetAwaiter().GetResult(); string packageLocation = Path.Combine(Directories.Downloads, package.Signature); string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]); @@ -710,6 +780,6 @@ private void ExtractFileFromPackage(string packageName, string fileName) entry.ExtractToFile(fileLocation); } } - #endregion +#endregion } } diff --git a/Bloxstrap/Dialogs/BootstrapperDialogs/BootstrapperDialogForm.cs b/Bloxstrap/Dialogs/BootstrapperDialogs/BootstrapperDialogForm.cs index adac9b6..425df1b 100644 --- a/Bloxstrap/Dialogs/BootstrapperDialogs/BootstrapperDialogForm.cs +++ b/Bloxstrap/Dialogs/BootstrapperDialogs/BootstrapperDialogForm.cs @@ -1,4 +1,5 @@ using Bloxstrap.Enums; +using Bloxstrap.Helpers; namespace Bloxstrap.Dialogs.BootstrapperDialogs { @@ -59,6 +60,18 @@ public bool CancelEnabled } } + public void ScaleWindow() + { + this.Size = this.MinimumSize = this.MaximumSize = WindowScaling.GetScaledSize(this.Size); + + foreach (Control control in this.Controls) + { + control.Size = WindowScaling.GetScaledSize(control.Size); + control.Location = WindowScaling.GetScaledPoint(control.Location); + control.Padding = WindowScaling.GetScaledPadding(control.Padding); + } + } + public void SetupDialog() { if (Program.IsQuiet) diff --git a/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.cs b/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.cs index b09109b..24224d8 100644 --- a/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.cs +++ b/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.cs @@ -35,6 +35,7 @@ public LegacyDialog2009(Bootstrapper? bootstrapper = null) Bootstrapper = bootstrapper; + ScaleWindow(); SetupDialog(); } diff --git a/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.cs b/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.cs index c9dcfdd..8b5e8d5 100644 --- a/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.cs +++ b/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.cs @@ -37,8 +37,9 @@ public LegacyDialog2011(Bootstrapper? bootstrapper = null) Bootstrapper = bootstrapper; // have to convert icon -> bitmap since winforms scaling is poop - this.IconBox.Image = Program.Settings.BootstrapperIcon.GetIcon().ToBitmap(); + this.IconBox.BackgroundImage = Program.Settings.BootstrapperIcon.GetIcon().ToBitmap(); + ScaleWindow(); SetupDialog(); } diff --git a/Bloxstrap/Dialogs/Preferences.xaml b/Bloxstrap/Dialogs/Preferences.xaml index e956848..d0dcc11 100644 --- a/Bloxstrap/Dialogs/Preferences.xaml +++ b/Bloxstrap/Dialogs/Preferences.xaml @@ -34,7 +34,7 @@ - + @@ -107,6 +107,7 @@ + @@ -117,18 +118,22 @@ - - + + +