From bacb650ddc035d4f2b954d65a7bd559ec4338c9e Mon Sep 17 00:00:00 2001
From: pizzaboxer <41478239+pizzaboxer@users.noreply.github.com>
Date: Thu, 11 Aug 2022 08:26:28 +0100
Subject: [PATCH] Features and bugfixes for v1.1.0
- Features
- Add Discord Rich Presence support (the nuget package is like a year and a half out of date so submodule it is lol)
- Add update checker
- Add start menu folder creation
- Bugfixes
- Fix "Directory is not empty" error when updating Roblox
- Fix uninstalling sometimes not working properly
- Quality of Life
- Split Bootstrapper class into partial files
- Renamed TaskDialogStyle to VistaDialog for name simplification
---
.gitmodules | 3 +
Bloxstrap.sln | 6 +
Bloxstrap/Bloxstrap.csproj | 13 +-
Bloxstrap/Bootstrapper.cs | 655 ------------------
.../Bootstrapper/Bootstrapper.AppInstall.cs | 128 ++++
.../Bootstrapper/Bootstrapper.Properties.cs | 130 ++++
.../Bootstrapper.RobloxInstall.cs | 208 ++++++
.../Bootstrapper.RobloxModifications.cs | 62 ++
Bloxstrap/Bootstrapper/Bootstrapper.cs | 178 +++++
...e.Designer.cs => LegacyDialog.Designer.cs} | 30 +-
.../{LegacyDialogStyle.cs => LegacyDialog.cs} | 50 +-
...gacyDialogStyle.resx => LegacyDialog.resx} | 0
...Designer.cs => ProgressDialog.Designer.cs} | 36 +-
...ogressDialogStyle.cs => ProgressDialog.cs} | 41 +-
...ssDialogStyle.resx => ProgressDialog.resx} | 0
.../{TaskDialogStyle.cs => VistaDialog.cs} | 10 +-
Bloxstrap/Dialogs/Preferences.Designer.cs | 130 ++--
Bloxstrap/Dialogs/Preferences.cs | 92 ++-
Bloxstrap/Dialogs/Preferences.resx | 3 +
Bloxstrap/Enums/BootstrapperStyle.cs | 2 +-
Bloxstrap/Helpers/DiscordRichPresence.cs | 73 ++
Bloxstrap/Helpers/UpdateChecker.cs | 76 ++
Bloxstrap/Helpers/Utilities.cs | 48 ++
Bloxstrap/Program.cs | 27 +-
Bloxstrap/Settings.cs | 4 +-
DiscordRPC | 1 +
26 files changed, 1177 insertions(+), 829 deletions(-)
create mode 100644 .gitmodules
delete mode 100644 Bloxstrap/Bootstrapper.cs
create mode 100644 Bloxstrap/Bootstrapper/Bootstrapper.AppInstall.cs
create mode 100644 Bloxstrap/Bootstrapper/Bootstrapper.Properties.cs
create mode 100644 Bloxstrap/Bootstrapper/Bootstrapper.RobloxInstall.cs
create mode 100644 Bloxstrap/Bootstrapper/Bootstrapper.RobloxModifications.cs
create mode 100644 Bloxstrap/Bootstrapper/Bootstrapper.cs
rename Bloxstrap/Dialogs/BootstrapperStyles/{LegacyDialogStyle.Designer.cs => LegacyDialog.Designer.cs} (82%)
rename Bloxstrap/Dialogs/BootstrapperStyles/{LegacyDialogStyle.cs => LegacyDialog.cs} (84%)
rename Bloxstrap/Dialogs/BootstrapperStyles/{LegacyDialogStyle.resx => LegacyDialog.resx} (100%)
rename Bloxstrap/Dialogs/BootstrapperStyles/{ProgressDialogStyle.Designer.cs => ProgressDialog.Designer.cs} (80%)
rename Bloxstrap/Dialogs/BootstrapperStyles/{ProgressDialogStyle.cs => ProgressDialog.cs} (80%)
rename Bloxstrap/Dialogs/BootstrapperStyles/{ProgressDialogStyle.resx => ProgressDialog.resx} (100%)
rename Bloxstrap/Dialogs/BootstrapperStyles/{TaskDialogStyle.cs => VistaDialog.cs} (91%)
create mode 100644 Bloxstrap/Helpers/DiscordRichPresence.cs
create mode 100644 Bloxstrap/Helpers/UpdateChecker.cs
create mode 100644 Bloxstrap/Helpers/Utilities.cs
create mode 160000 DiscordRPC
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..dc74b79
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "DiscordRPC"]
+ path = DiscordRPC
+ url = https://github.com/Lachee/discord-rpc-csharp.git
diff --git a/Bloxstrap.sln b/Bloxstrap.sln
index 62d830d..ec42cc7 100644
--- a/Bloxstrap.sln
+++ b/Bloxstrap.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bloxstrap", "Bloxstrap\Bloxstrap.csproj", "{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordRPC", "DiscordRPC\DiscordRPC\DiscordRPC.csproj", "{BDB66971-35FA-45BD-ABD6-70B814D2E55C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BDB66971-35FA-45BD-ABD6-70B814D2E55C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BDB66971-35FA-45BD-ABD6-70B814D2E55C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BDB66971-35FA-45BD-ABD6-70B814D2E55C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BDB66971-35FA-45BD-ABD6-70B814D2E55C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Bloxstrap/Bloxstrap.csproj b/Bloxstrap/Bloxstrap.csproj
index cfe3a0c..34e8cfb 100644
--- a/Bloxstrap/Bloxstrap.csproj
+++ b/Bloxstrap/Bloxstrap.csproj
@@ -9,14 +9,23 @@
AnyCPU
AnyCPU;x86
Bloxstrap.ico
- 1.0.0
- 1.0.0.0
+ 1.1.0
+ 1.1.0.0
+
+
+
+
+
+
+
+
+
True
diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs
deleted file mode 100644
index a14dda0..0000000
--- a/Bloxstrap/Bootstrapper.cs
+++ /dev/null
@@ -1,655 +0,0 @@
-using System.Diagnostics;
-using System.IO.Compression;
-using System.Security.Cryptography;
-
-using Microsoft.Win32;
-
-using Bloxstrap.Enums;
-using Bloxstrap.Dialogs.BootstrapperStyles;
-using Bloxstrap.Helpers;
-using Bloxstrap.Helpers.RSMM;
-
-namespace Bloxstrap
-{
- public class Bootstrapper
- {
- private string? LaunchCommandLine;
-
- private string VersionGuid;
- private PackageManifest VersionPackageManifest;
- private FileManifest VersionFileManifest;
- private string VersionFolder;
-
- private readonly string DownloadsFolder;
- private readonly bool FreshInstall;
-
- private int ProgressIncrement;
- private bool CancelFired = false;
-
- private static readonly HttpClient Client = new();
-
- // in case a new package is added, you can find the corresponding directory
- // by opening the stock bootstrapper in a hex editor
- // TODO - there ideally should be a less static way to do this that's not hardcoded?
- private static readonly IReadOnlyDictionary PackageDirectories = new Dictionary()
- {
- { "RobloxApp.zip", @"" },
- { "shaders.zip", @"shaders\" },
- { "ssl.zip", @"ssl\" },
-
- { "content-avatar.zip", @"content\avatar\" },
- { "content-configs.zip", @"content\configs\" },
- { "content-fonts.zip", @"content\fonts\" },
- { "content-sky.zip", @"content\sky\" },
- { "content-sounds.zip", @"content\sounds\" },
- { "content-textures2.zip", @"content\textures\" },
- { "content-models.zip", @"content\models\" },
-
- { "content-textures3.zip", @"PlatformContent\pc\textures\" },
- { "content-terrain.zip", @"PlatformContent\pc\terrain\" },
- { "content-platform-fonts.zip", @"PlatformContent\pc\fonts\" },
-
- { "extracontent-luapackages.zip", @"ExtraContent\LuaPackages\" },
- { "extracontent-translations.zip", @"ExtraContent\translations\" },
- { "extracontent-models.zip", @"ExtraContent\models\" },
- { "extracontent-textures.zip", @"ExtraContent\textures\" },
- { "extracontent-places.zip", @"ExtraContent\places\" },
- };
-
- private static readonly string AppSettings =
- "\n" +
- "\n" +
- " content\n" +
- " http://www.roblox.com\n" +
- "\n";
-
- public event EventHandler PromptShutdownEvent;
- public event ChangeEventHandler ShowSuccessEvent;
- public event ChangeEventHandler MessageChanged;
- public event ChangeEventHandler ProgressBarValueChanged;
- public event ChangeEventHandler ProgressBarStyleChanged;
- public event ChangeEventHandler CancelEnabledChanged;
-
- private string _message;
- private int _progress = 0;
- private ProgressBarStyle _progressStyle = ProgressBarStyle.Marquee;
- private bool _cancelEnabled = false;
-
- public string Message
- {
- get => _message;
-
- private set
- {
- if (_message == value)
- return;
-
- MessageChanged.Invoke(this, new ChangeEventArgs(value));
-
- _message = value;
- }
- }
-
- public int Progress
- {
- get => _progress;
-
- private set
- {
- if (_progress == value)
- return;
-
- ProgressBarValueChanged.Invoke(this, new ChangeEventArgs(value));
-
- _progress = value;
- }
- }
-
- public ProgressBarStyle ProgressStyle
- {
- get => _progressStyle;
-
- private set
- {
- if (_progressStyle == value)
- return;
-
- ProgressBarStyleChanged.Invoke(this, new ChangeEventArgs(value));
-
- _progressStyle = value;
- }
- }
-
- public bool CancelEnabled
- {
- get => _cancelEnabled;
-
- private set
- {
- if (_cancelEnabled == value)
- return;
-
- CancelEnabledChanged.Invoke(this, new ChangeEventArgs(value));
-
- _cancelEnabled = value;
- }
- }
-
- public Bootstrapper(BootstrapperStyle bootstrapperStyle, string? launchCommandLine = null)
- {
- Debug.WriteLine("Initializing bootstrapper");
-
- FreshInstall = String.IsNullOrEmpty(Program.Settings.VersionGuid);
- LaunchCommandLine = launchCommandLine;
- DownloadsFolder = Path.Combine(Program.BaseDirectory, "Downloads");
- Client.Timeout = TimeSpan.FromMinutes(10);
-
- switch (bootstrapperStyle)
- {
- case BootstrapperStyle.TaskDialog:
- new TaskDialogStyle(this);
- break;
-
- case BootstrapperStyle.LegacyDialog:
- Application.Run(new LegacyDialogStyle(this));
- break;
-
- case BootstrapperStyle.ProgressDialog:
- Application.Run(new ProgressDialogStyle(this));
- break;
- }
- }
-
- public async Task Run()
- {
- if (LaunchCommandLine == "-uninstall")
- {
- Uninstall();
- return;
- }
-
- await CheckLatestVersion();
-
- if (!Directory.Exists(VersionFolder) || Program.Settings.VersionGuid != VersionGuid)
- {
- Debug.WriteLineIf(!Directory.Exists(VersionFolder), $"Installing latest version (!Directory.Exists({VersionFolder}))");
- Debug.WriteLineIf(Program.Settings.VersionGuid != VersionGuid, $"Installing latest version ({Program.Settings.VersionGuid} != {VersionGuid})");
-
- await InstallLatestVersion();
- }
-
- // yes, doing this for every start is stupid, but the death sound mod is dynamically toggleable after all
- ApplyModifications();
-
- if (Program.IsFirstRun)
- Program.SettingsManager.ShouldSave = true;
-
- if (Program.IsFirstRun || FreshInstall)
- Register();
-
- CheckInstall();
-
- await StartRoblox();
- }
-
- private void CheckIfRunning()
- {
- Process[] processes = Process.GetProcessesByName("RobloxPlayerBeta");
-
- if (processes.Length > 0)
- PromptShutdown();
-
- try
- {
- // try/catch just in case process was closed before prompt was answered
-
- foreach (Process process in processes)
- {
- process.CloseMainWindow();
- process.Close();
- }
- }
- catch (Exception) { }
- }
-
- private async Task StartRoblox()
- {
- string startEventName = Program.ProjectName.Replace(" ", "") + "StartEvent";
-
- Message = "Starting Roblox...";
-
- // launch time isn't really required for all launches, but it's usually just safest to do this
- LaunchCommandLine += " --launchtime=" + DateTimeOffset.Now.ToUnixTimeSeconds() + " -startEvent " + startEventName;
- Debug.WriteLine($"Starting game client with command line '{LaunchCommandLine}'");
-
- using (SystemEvent startEvent = new(startEventName))
- {
- Process.Start(Path.Combine(VersionFolder, "RobloxPlayerBeta.exe"), LaunchCommandLine);
-
- Debug.WriteLine($"Waiting for {startEventName} event to be fired...");
- bool startEventFired = await startEvent.WaitForEvent();
-
- startEvent.Close();
-
- if (startEventFired)
- {
- Debug.WriteLine($"{startEventName} event fired! Exiting in 5 seconds...");
- await Task.Delay(5000);
-
- Program.Exit();
- }
- }
- }
-
- // Bootstrapper Installing
-
- public static void Register()
- {
- RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{Program.ProjectName}");
-
- // new install location selected, delete old one
- string? oldInstallLocation = (string?)applicationKey.GetValue("OldInstallLocation");
- if (!String.IsNullOrEmpty(oldInstallLocation) && oldInstallLocation != Program.BaseDirectory)
- {
- try
- {
- if (Directory.Exists(oldInstallLocation))
- Directory.Delete(oldInstallLocation, true);
- }
- catch (Exception) { }
-
- applicationKey.DeleteValue("OldInstallLocation");
- }
-
- applicationKey.SetValue("InstallLocation", Program.BaseDirectory);
- applicationKey.Close();
-
- // set uninstall key
- RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Program.ProjectName}");
- uninstallKey.SetValue("DisplayIcon", $"{Program.FilePath},0");
- uninstallKey.SetValue("DisplayName", Program.ProjectName);
- uninstallKey.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
- uninstallKey.SetValue("InstallLocation", Program.BaseDirectory);
- // uninstallKey.SetValue("NoModify", 1);
- uninstallKey.SetValue("NoRepair", 1);
- uninstallKey.SetValue("Publisher", Program.ProjectName);
- uninstallKey.SetValue("ModifyPath", $"\"{Program.FilePath}\" -preferences");
- uninstallKey.SetValue("UninstallString", $"\"{Program.FilePath}\" -uninstall");
- uninstallKey.Close();
- }
-
- public static void CheckInstall()
- {
- // check if launch uri is set to our bootstrapper
- // this doesn't go under register, so we check every launch
- // just in case the stock bootstrapper changes it back
-
- Protocol.Register("roblox", "Roblox", Program.FilePath);
- Protocol.Register("roblox-player", "Roblox", Program.FilePath);
-
- // in case the user is reinstalling
- if (File.Exists(Program.FilePath) && Program.IsFirstRun)
- File.Delete(Program.FilePath);
-
- // check to make sure bootstrapper is in the install folder
- if (!File.Exists(Program.FilePath) && Environment.ProcessPath is not null)
- File.Copy(Environment.ProcessPath, Program.FilePath);
- }
-
- private void Uninstall()
- {
- CheckIfRunning();
-
- // lots of try/catches here... lol
-
- Message = $"Uninstalling {Program.ProjectName}...";
-
- Program.SettingsManager.ShouldSave = false;
-
- // check if stock bootstrapper is still installed
- RegistryKey? bootstrapperKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-player");
- if (bootstrapperKey is null)
- {
- Protocol.Unregister("roblox");
- Protocol.Unregister("roblox-player");
- }
- else
- {
- // revert launch uri handler to stock bootstrapper
-
- string bootstrapperLocation = (string?)bootstrapperKey.GetValue("InstallLocation") + "RobloxPlayerLauncher.exe";
-
- Protocol.Register("roblox", "Roblox", bootstrapperLocation);
- Protocol.Register("roblox-player", "Roblox", bootstrapperLocation);
- }
-
- try
- {
- // delete application key
- Registry.CurrentUser.DeleteSubKey($@"Software\{Program.ProjectName}");
- }
- catch (Exception) { }
-
- try
- {
- // delete installation folder
- // (should delete everything except bloxstrap itself)
- Directory.Delete(Program.BaseDirectory, true);
- }
- catch (Exception) { }
-
- try
- {
- // delete uninstall key
- Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Program.ProjectName}");
- }
- catch (Exception) { }
-
- ShowSuccess($"{Program.ProjectName} has been uninstalled");
- Program.Exit();
- }
-
- // Roblox Installing
-
- private async Task CheckLatestVersion()
- {
- Message = "Connecting to Roblox...";
-
- Debug.WriteLine($"Checking latest version...");
- VersionGuid = await Client.GetStringAsync($"{Program.BaseUrlSetup}/version");
- VersionFolder = Path.Combine(Program.BaseDirectory, "Versions", VersionGuid);
- Debug.WriteLine($"Latest version is {VersionGuid}");
-
- Debug.WriteLine("Getting package manifest...");
- VersionPackageManifest = await PackageManifest.Get(VersionGuid);
-
- Debug.WriteLine("Getting file manifest...");
- VersionFileManifest = await FileManifest.Get(VersionGuid);
- }
-
- private async Task InstallLatestVersion()
- {
- CheckIfRunning();
-
- if (FreshInstall)
- Message = "Installing Roblox...";
- else
- Message = "Upgrading Roblox...";
-
- Directory.CreateDirectory(Program.BaseDirectory);
-
- CancelEnabled = true;
-
- // i believe the original bootstrapper bases the progress bar off zip
- // extraction progress, but here i'm doing package download progress
-
- ProgressStyle = ProgressBarStyle.Continuous;
-
- ProgressIncrement = (int)Math.Floor((decimal) 1 / VersionPackageManifest.Count * 100);
- Debug.WriteLine($"Progress Increment is {ProgressIncrement}");
-
- Directory.CreateDirectory(Path.Combine(Program.BaseDirectory, "Downloads"));
-
- foreach (Package package in VersionPackageManifest)
- {
- // no await, download all the packages at once
- DownloadPackage(package);
- }
-
- do
- {
- // wait for download to finish (and also round off the progress bar if needed)
-
- if (Progress == ProgressIncrement * VersionPackageManifest.Count)
- Progress = 100;
-
- await Task.Delay(1000);
- }
- while (Progress != 100);
-
- ProgressStyle = ProgressBarStyle.Marquee;
-
- Debug.WriteLine("Finished downloading");
-
- Directory.CreateDirectory(Path.Combine(Program.BaseDirectory, "Versions"));
-
- foreach (Package package in VersionPackageManifest)
- {
- // extract all the packages at once (shouldn't be too heavy on cpu?)
- ExtractPackage(package);
- }
-
- Debug.WriteLine("Finished extracting packages");
-
- Message = "Configuring Roblox...";
-
- string appSettingsLocation = Path.Combine(VersionFolder, "AppSettings.xml");
- await File.WriteAllTextAsync(appSettingsLocation, AppSettings);
-
- if (!FreshInstall)
- {
- // let's take this opportunity to delete any packages we don't need anymore
- foreach (string filename in Directory.GetFiles(DownloadsFolder))
- {
- if (!VersionPackageManifest.Exists(package => filename.Contains(package.Signature)))
- File.Delete(filename);
- }
-
- // and also to delete our old version folder
- Directory.Delete(Path.Combine(Program.BaseDirectory, "Versions", Program.Settings.VersionGuid));
- }
-
- CancelEnabled = false;
-
- Program.Settings.VersionGuid = VersionGuid;
- }
-
- private async void ApplyModifications()
- {
- // i guess we can just assume that if the hash does not match the manifest, then it's a mod
- // probably not the best way to do this? don't think file corruption is that much of a worry here
-
- // TODO - i'm thinking i could have a manifest on my website like rbxManifest.txt
- // for integrity checking and to quickly fix/alter stuff (like ouch.ogg being renamed)
- // but that probably wouldn't be great to check on every run in case my webserver ever goes down
- // interesting idea nonetheless, might add it sometime
-
- // TODO - i'm hoping i can take this idea of content mods much further
- // for stuff like easily installing (community-created?) texture/shader/audio mods
- // but for now, let's just keep it at this
-
- string fileContentName = "ouch.ogg";
- string fileContentLocation = "content\\sounds\\ouch.ogg";
- string fileLocation = Path.Combine(VersionFolder, fileContentLocation);
-
- string officialDeathSoundHash = VersionFileManifest[fileContentLocation];
- string currentDeathSoundHash = CalculateMD5(fileLocation);
-
- if (Program.Settings.UseOldDeathSound && currentDeathSoundHash == officialDeathSoundHash)
- {
- // let's get the old one!
-
- Debug.WriteLine($"Fetching old death sound...");
-
- var response = await Client.GetAsync($"{Program.BaseUrlApplication}/mods/{fileContentLocation}");
-
- if (File.Exists(fileLocation))
- File.Delete(fileLocation);
-
- using (var fileStream = new FileStream(fileLocation, FileMode.CreateNew))
- {
- await response.Content.CopyToAsync(fileStream);
- }
- }
- else if (!Program.Settings.UseOldDeathSound && currentDeathSoundHash != officialDeathSoundHash)
- {
- // who's lame enough to ever do this?
- // well, we need to re-extract the one that's in the content-sounds.zip package
-
- Debug.WriteLine("Fetching current death sound...");
-
- var package = VersionPackageManifest.Find(x => x.Name == "content-sounds.zip");
-
- if (package is null)
- {
- Debug.WriteLine("Failed to find content-sounds.zip package! Aborting...");
- return;
- }
-
- DownloadPackage(package);
-
- string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
- string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
-
- using (ZipArchive archive = ZipFile.OpenRead(packageLocation))
- {
- ZipArchiveEntry? entry = archive.Entries.Where(x => x.FullName == fileContentName).FirstOrDefault();
-
- if (entry is null)
- {
- Debug.WriteLine("Failed to find file entry in content-sounds.zip! Aborting...");
- return;
- }
-
- if (File.Exists(fileLocation))
- File.Delete(fileLocation);
-
- entry.ExtractToFile(fileLocation);
- }
- }
- }
-
- private async void DownloadPackage(Package package)
- {
- string packageUrl = $"{Program.BaseUrlSetup}/{VersionGuid}-{package.Name}";
- string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
- string robloxPackageLocation = Path.Combine(Program.LocalAppData, "Roblox", "Downloads", package.Signature);
-
- if (File.Exists(packageLocation))
- {
- FileInfo file = new(packageLocation);
-
- string calculatedMD5 = CalculateMD5(packageLocation);
- if (calculatedMD5 != package.Signature)
- {
- Debug.WriteLine($"{package.Name} is corrupted ({calculatedMD5} != {package.Signature})! Deleting and re-downloading...");
- file.Delete();
- }
- else
- {
- Debug.WriteLine($"{package.Name} is already downloaded, skipping...");
- Progress += ProgressIncrement;
- return;
- }
- }
- else if (File.Exists(robloxPackageLocation))
- {
- // let's cheat! if the stock bootstrapper already previously downloaded the file,
- // then we can just copy the one from there
-
- Debug.WriteLine($"Found existing version of {package.Name} ({robloxPackageLocation})! Copying to Downloads folder...");
- File.Copy(robloxPackageLocation, packageLocation);
- Progress += ProgressIncrement;
- return;
- }
-
- if (!File.Exists(packageLocation))
- {
- Debug.WriteLine($"Downloading {package.Name}...");
-
- var response = await Client.GetAsync(packageUrl);
-
- if (CancelFired)
- return;
-
- using (var fileStream = new FileStream(packageLocation, FileMode.CreateNew))
- {
- await response.Content.CopyToAsync(fileStream);
- }
-
- Debug.WriteLine($"Finished downloading {package.Name}!");
- Progress += ProgressIncrement;
- }
- }
-
- private void ExtractPackage(Package package)
- {
- if (CancelFired)
- return;
-
- string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
- string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
- string extractPath;
-
- Debug.WriteLine($"Extracting {package.Name} to {packageFolder}...");
-
- using (ZipArchive archive = ZipFile.OpenRead(packageLocation))
- {
- foreach (ZipArchiveEntry entry in archive.Entries)
- {
- if (CancelFired)
- return;
-
- if (entry.FullName.EndsWith(@"\"))
- continue;
-
- extractPath = Path.Combine(packageFolder, entry.FullName);
-
- Debug.WriteLine($"[{package.Name}] Writing {extractPath}...");
-
- Directory.CreateDirectory(Path.GetDirectoryName(extractPath));
-
- if (File.Exists(extractPath))
- File.Delete(extractPath);
-
- entry.ExtractToFile(extractPath);
- }
- }
- }
-
- // Dialog Events
-
- public void CancelButtonClicked()
- {
- CancelFired = true;
-
- try
- {
- if (Program.IsFirstRun)
- Directory.Delete(Program.BaseDirectory, true);
- else if (Directory.Exists(VersionFolder))
- Directory.Delete(VersionFolder, true);
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"Failed to cleanup install!\n\n{ex}");
- }
-
- Program.Exit();
- }
-
- private void ShowSuccess(string message)
- {
- ShowSuccessEvent.Invoke(this, new ChangeEventArgs(message));
- }
-
- private void PromptShutdown()
- {
- PromptShutdownEvent.Invoke(this, new EventArgs());
- }
-
- // Utilities
-
- private static string CalculateMD5(string filename)
- {
- using (MD5 md5 = MD5.Create())
- {
- using (FileStream stream = File.OpenRead(filename))
- {
- byte[] hash = md5.ComputeHash(stream);
- return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
- }
- }
- }
- }
-}
diff --git a/Bloxstrap/Bootstrapper/Bootstrapper.AppInstall.cs b/Bloxstrap/Bootstrapper/Bootstrapper.AppInstall.cs
new file mode 100644
index 0000000..f45aece
--- /dev/null
+++ b/Bloxstrap/Bootstrapper/Bootstrapper.AppInstall.cs
@@ -0,0 +1,128 @@
+using Microsoft.Win32;
+using Bloxstrap.Helpers;
+
+namespace Bloxstrap
+{
+ partial class Bootstrapper
+ {
+ public static void Register()
+ {
+ if (Program.BaseDirectory is null)
+ return;
+
+ RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{Program.ProjectName}");
+
+ // new install location selected, delete old one
+ string? oldInstallLocation = (string?)applicationKey.GetValue("OldInstallLocation");
+ if (!String.IsNullOrEmpty(oldInstallLocation) && oldInstallLocation != Program.BaseDirectory)
+ {
+ try
+ {
+ if (Directory.Exists(oldInstallLocation))
+ Directory.Delete(oldInstallLocation, true);
+ }
+ catch (Exception) { }
+
+ applicationKey.DeleteValue("OldInstallLocation");
+ }
+
+ applicationKey.SetValue("InstallLocation", Program.BaseDirectory);
+ applicationKey.Close();
+
+ // set uninstall key
+ RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Program.ProjectName}");
+ uninstallKey.SetValue("DisplayIcon", $"{Program.FilePath},0");
+ uninstallKey.SetValue("DisplayName", Program.ProjectName);
+ uninstallKey.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
+ uninstallKey.SetValue("InstallLocation", Program.BaseDirectory);
+ uninstallKey.SetValue("NoRepair", 1);
+ uninstallKey.SetValue("Publisher", Program.ProjectName);
+ uninstallKey.SetValue("ModifyPath", $"\"{Program.FilePath}\" -preferences");
+ uninstallKey.SetValue("UninstallString", $"\"{Program.FilePath}\" -uninstall");
+ uninstallKey.Close();
+ }
+
+ public static void CheckInstall()
+ {
+ // check if launch uri is set to our bootstrapper
+ // this doesn't go under register, so we check every launch
+ // just in case the stock bootstrapper changes it back
+
+ Protocol.Register("roblox", "Roblox", Program.FilePath);
+ Protocol.Register("roblox-player", "Roblox", Program.FilePath);
+
+ // in case the user is reinstalling
+ if (File.Exists(Program.FilePath) && Program.IsFirstRun)
+ File.Delete(Program.FilePath);
+
+ // check to make sure bootstrapper is in the install folder
+ if (!File.Exists(Program.FilePath) && Environment.ProcessPath is not null)
+ File.Copy(Environment.ProcessPath, Program.FilePath);
+
+ // this SHOULD go under Register(),
+ // but then people who have Bloxstrap v1.0.0 installed won't have this without a reinstall
+ // maybe in a later version?
+ if (!Directory.Exists(Program.StartMenuDirectory))
+ {
+ Directory.CreateDirectory(Program.StartMenuDirectory);
+
+ ShellLink.Shortcut.CreateShortcut(Program.FilePath, "", Program.FilePath, 0)
+ .WriteToFile(Path.Combine(Program.StartMenuDirectory, "Play Roblox.lnk"));
+
+ ShellLink.Shortcut.CreateShortcut(Program.FilePath, "-preferences", Program.FilePath, 0)
+ .WriteToFile(Path.Combine(Program.StartMenuDirectory, "Configure Bloxstrap.lnk"));
+ }
+ }
+
+ private void Uninstall()
+ {
+ if (Program.BaseDirectory is null)
+ return;
+
+ CheckIfRunning();
+
+ // lots of try/catches here... lol
+
+ Message = $"Uninstalling {Program.ProjectName}...";
+
+ Program.SettingsManager.ShouldSave = false;
+
+ // check if stock bootstrapper is still installed
+ RegistryKey? bootstrapperKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-player");
+ if (bootstrapperKey is null)
+ {
+ Protocol.Unregister("roblox");
+ Protocol.Unregister("roblox-player");
+ }
+ else
+ {
+ // revert launch uri handler to stock bootstrapper
+
+ string bootstrapperLocation = (string?)bootstrapperKey.GetValue("InstallLocation") + "RobloxPlayerLauncher.exe";
+
+ Protocol.Register("roblox", "Roblox", bootstrapperLocation);
+ Protocol.Register("roblox-player", "Roblox", bootstrapperLocation);
+ }
+
+ try
+ {
+ // delete application key
+ Registry.CurrentUser.DeleteSubKey($@"Software\{Program.ProjectName}");
+
+ // delete start menu folder
+ Directory.Delete(Program.StartMenuDirectory, true);
+
+ // delete uninstall key
+ Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Program.ProjectName}");
+
+ // delete installation folder
+ // (should delete everything except bloxstrap itself)
+ Directory.Delete(Program.BaseDirectory, true);
+ }
+ catch (Exception) { }
+
+ ShowSuccess($"{Program.ProjectName} has been uninstalled");
+ Program.Exit();
+ }
+ }
+}
diff --git a/Bloxstrap/Bootstrapper/Bootstrapper.Properties.cs b/Bloxstrap/Bootstrapper/Bootstrapper.Properties.cs
new file mode 100644
index 0000000..bd36f05
--- /dev/null
+++ b/Bloxstrap/Bootstrapper/Bootstrapper.Properties.cs
@@ -0,0 +1,130 @@
+using Bloxstrap.Helpers.RSMM;
+
+namespace Bloxstrap
+{
+ partial class Bootstrapper
+ {
+ private string? LaunchCommandLine;
+
+ private string VersionGuid;
+ private PackageManifest VersionPackageManifest;
+ private FileManifest VersionFileManifest;
+ private string VersionFolder;
+
+ private readonly string DownloadsFolder;
+ private readonly bool FreshInstall;
+
+ private int ProgressIncrement;
+ private bool CancelFired = false;
+
+ private static readonly HttpClient Client = new();
+
+ // in case a new package is added, you can find the corresponding directory
+ // by opening the stock bootstrapper in a hex editor
+ // TODO - there ideally should be a less static way to do this that's not hardcoded?
+ private static readonly IReadOnlyDictionary PackageDirectories = new Dictionary()
+ {
+ { "RobloxApp.zip", @"" },
+ { "shaders.zip", @"shaders\" },
+ { "ssl.zip", @"ssl\" },
+
+ { "content-avatar.zip", @"content\avatar\" },
+ { "content-configs.zip", @"content\configs\" },
+ { "content-fonts.zip", @"content\fonts\" },
+ { "content-sky.zip", @"content\sky\" },
+ { "content-sounds.zip", @"content\sounds\" },
+ { "content-textures2.zip", @"content\textures\" },
+ { "content-models.zip", @"content\models\" },
+
+ { "content-textures3.zip", @"PlatformContent\pc\textures\" },
+ { "content-terrain.zip", @"PlatformContent\pc\terrain\" },
+ { "content-platform-fonts.zip", @"PlatformContent\pc\fonts\" },
+
+ { "extracontent-luapackages.zip", @"ExtraContent\LuaPackages\" },
+ { "extracontent-translations.zip", @"ExtraContent\translations\" },
+ { "extracontent-models.zip", @"ExtraContent\models\" },
+ { "extracontent-textures.zip", @"ExtraContent\textures\" },
+ { "extracontent-places.zip", @"ExtraContent\places\" },
+ };
+
+ private static readonly string AppSettings =
+ "\n" +
+ "\n" +
+ " content\n" +
+ " http://www.roblox.com\n" +
+ "\n";
+
+ public event EventHandler CloseDialogEvent;
+ public event EventHandler PromptShutdownEvent;
+ public event ChangeEventHandler ShowSuccessEvent;
+ public event ChangeEventHandler MessageChanged;
+ public event ChangeEventHandler ProgressBarValueChanged;
+ public event ChangeEventHandler ProgressBarStyleChanged;
+ public event ChangeEventHandler CancelEnabledChanged;
+
+ private string _message;
+ private int _progress = 0;
+ private ProgressBarStyle _progressStyle = ProgressBarStyle.Marquee;
+ private bool _cancelEnabled = false;
+
+ public string Message
+ {
+ get => _message;
+
+ private set
+ {
+ if (_message == value)
+ return;
+
+ MessageChanged.Invoke(this, new ChangeEventArgs(value));
+
+ _message = value;
+ }
+ }
+
+ public int Progress
+ {
+ get => _progress;
+
+ private set
+ {
+ if (_progress == value)
+ return;
+
+ ProgressBarValueChanged.Invoke(this, new ChangeEventArgs(value));
+
+ _progress = value;
+ }
+ }
+
+ public ProgressBarStyle ProgressStyle
+ {
+ get => _progressStyle;
+
+ private set
+ {
+ if (_progressStyle == value)
+ return;
+
+ ProgressBarStyleChanged.Invoke(this, new ChangeEventArgs(value));
+
+ _progressStyle = value;
+ }
+ }
+
+ public bool CancelEnabled
+ {
+ get => _cancelEnabled;
+
+ private set
+ {
+ if (_cancelEnabled == value)
+ return;
+
+ CancelEnabledChanged.Invoke(this, new ChangeEventArgs(value));
+
+ _cancelEnabled = value;
+ }
+ }
+ }
+}
diff --git a/Bloxstrap/Bootstrapper/Bootstrapper.RobloxInstall.cs b/Bloxstrap/Bootstrapper/Bootstrapper.RobloxInstall.cs
new file mode 100644
index 0000000..915272c
--- /dev/null
+++ b/Bloxstrap/Bootstrapper/Bootstrapper.RobloxInstall.cs
@@ -0,0 +1,208 @@
+using System.Diagnostics;
+using System.IO.Compression;
+
+using Bloxstrap.Helpers;
+using Bloxstrap.Helpers.RSMM;
+
+namespace Bloxstrap
+{
+ partial class Bootstrapper
+ {
+ private async Task CheckLatestVersion()
+ {
+ if (Program.BaseDirectory is null)
+ return;
+
+ Message = "Connecting to Roblox...";
+
+ VersionGuid = await Client.GetStringAsync($"{Program.BaseUrlSetup}/version");
+ VersionFolder = Path.Combine(Program.BaseDirectory, "Versions", VersionGuid);
+ VersionPackageManifest = await PackageManifest.Get(VersionGuid);
+ VersionFileManifest = await FileManifest.Get(VersionGuid);
+ }
+
+ private async Task InstallLatestVersion()
+ {
+ if (Program.BaseDirectory is null)
+ return;
+
+ CheckIfRunning();
+
+ if (FreshInstall)
+ Message = "Installing Roblox...";
+ else
+ Message = "Upgrading Roblox...";
+
+ Directory.CreateDirectory(Program.BaseDirectory);
+
+ CancelEnabled = true;
+
+ // i believe the original bootstrapper bases the progress bar off zip
+ // extraction progress, but here i'm doing package download progress
+
+ ProgressStyle = ProgressBarStyle.Continuous;
+
+ ProgressIncrement = (int)Math.Floor((decimal)1 / VersionPackageManifest.Count * 100);
+
+ Directory.CreateDirectory(Path.Combine(Program.BaseDirectory, "Downloads"));
+
+ foreach (Package package in VersionPackageManifest)
+ {
+ // no await, download all the packages at once
+ DownloadPackage(package);
+ }
+
+ do
+ {
+ // wait for download to finish (and also round off the progress bar if needed)
+
+ if (Progress == ProgressIncrement * VersionPackageManifest.Count)
+ Progress = 100;
+
+ await Task.Delay(1000);
+ }
+ while (Progress != 100);
+
+ ProgressStyle = ProgressBarStyle.Marquee;
+
+ Debug.WriteLine("Finished downloading");
+
+ Directory.CreateDirectory(Path.Combine(Program.BaseDirectory, "Versions"));
+
+ foreach (Package package in VersionPackageManifest)
+ {
+ // extract all the packages at once (shouldn't be too heavy on cpu?)
+ ExtractPackage(package);
+ }
+
+ Debug.WriteLine("Finished extracting packages");
+
+ Message = "Configuring Roblox...";
+
+ string appSettingsLocation = Path.Combine(VersionFolder, "AppSettings.xml");
+ await File.WriteAllTextAsync(appSettingsLocation, AppSettings);
+
+ if (!FreshInstall)
+ {
+ // let's take this opportunity to delete any packages we don't need anymore
+ foreach (string filename in Directory.GetFiles(DownloadsFolder))
+ {
+ if (!VersionPackageManifest.Exists(package => filename.Contains(package.Signature)))
+ File.Delete(filename);
+ }
+
+ // and also to delete our old version folder
+ Directory.Delete(Path.Combine(Program.BaseDirectory, "Versions", Program.Settings.VersionGuid), true);
+ }
+
+ CancelEnabled = false;
+
+ Program.Settings.VersionGuid = VersionGuid;
+ }
+
+ private async void ApplyModifications()
+ {
+ // i guess we can just assume that if the hash does not match the manifest, then it's a mod
+ // probably not the best way to do this? don't think file corruption is that much of a worry here
+
+ // TODO - i'm thinking i could have a manifest on my website like rbxManifest.txt
+ // for integrity checking and to quickly fix/alter stuff (like ouch.ogg being renamed)
+ // but that probably wouldn't be great to check on every run in case my webserver ever goes down
+ // interesting idea nonetheless, might add it sometime
+
+ // TODO - i'm hoping i can take this idea of content mods much further
+ // for stuff like easily installing (community-created?) texture/shader/audio mods
+ // but for now, let's just keep it at this
+
+ await ModifyDeathSound();
+ }
+
+ private async void DownloadPackage(Package package)
+ {
+ string packageUrl = $"{Program.BaseUrlSetup}/{VersionGuid}-{package.Name}";
+ string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
+ string robloxPackageLocation = Path.Combine(Program.LocalAppData, "Roblox", "Downloads", package.Signature);
+
+ if (File.Exists(packageLocation))
+ {
+ FileInfo file = new(packageLocation);
+
+ string calculatedMD5 = Utilities.CalculateMD5(packageLocation);
+ if (calculatedMD5 != package.Signature)
+ {
+ Debug.WriteLine($"{package.Name} is corrupted ({calculatedMD5} != {package.Signature})! Deleting and re-downloading...");
+ file.Delete();
+ }
+ else
+ {
+ Debug.WriteLine($"{package.Name} is already downloaded, skipping...");
+ Progress += ProgressIncrement;
+ return;
+ }
+ }
+ else if (File.Exists(robloxPackageLocation))
+ {
+ // let's cheat! if the stock bootstrapper already previously downloaded the file,
+ // then we can just copy the one from there
+
+ Debug.WriteLine($"Found existing version of {package.Name} ({robloxPackageLocation})! Copying to Downloads folder...");
+ File.Copy(robloxPackageLocation, packageLocation);
+ Progress += ProgressIncrement;
+ return;
+ }
+
+ if (!File.Exists(packageLocation))
+ {
+ Debug.WriteLine($"Downloading {package.Name}...");
+
+ var response = await Client.GetAsync(packageUrl);
+
+ if (CancelFired)
+ return;
+
+ using (var fileStream = new FileStream(packageLocation, FileMode.CreateNew))
+ {
+ await response.Content.CopyToAsync(fileStream);
+ }
+
+ Debug.WriteLine($"Finished downloading {package.Name}!");
+ Progress += ProgressIncrement;
+ }
+ }
+
+ private void ExtractPackage(Package package)
+ {
+ if (CancelFired)
+ return;
+
+ string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
+ string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
+ string extractPath;
+
+ Debug.WriteLine($"Extracting {package.Name} to {packageFolder}...");
+
+ using (ZipArchive archive = ZipFile.OpenRead(packageLocation))
+ {
+ foreach (ZipArchiveEntry entry in archive.Entries)
+ {
+ if (CancelFired)
+ return;
+
+ if (entry.FullName.EndsWith(@"\"))
+ continue;
+
+ extractPath = Path.Combine(packageFolder, entry.FullName);
+
+ Debug.WriteLine($"[{package.Name}] Writing {extractPath}...");
+
+ Directory.CreateDirectory(Path.GetDirectoryName(extractPath));
+
+ if (File.Exists(extractPath))
+ File.Delete(extractPath);
+
+ entry.ExtractToFile(extractPath);
+ }
+ }
+ }
+ }
+}
diff --git a/Bloxstrap/Bootstrapper/Bootstrapper.RobloxModifications.cs b/Bloxstrap/Bootstrapper/Bootstrapper.RobloxModifications.cs
new file mode 100644
index 0000000..955f82e
--- /dev/null
+++ b/Bloxstrap/Bootstrapper/Bootstrapper.RobloxModifications.cs
@@ -0,0 +1,62 @@
+using System.IO.Compression;
+
+using Bloxstrap.Helpers;
+
+namespace Bloxstrap
+{
+ partial class Bootstrapper
+ {
+ private async Task ModifyDeathSound()
+ {
+ string fileContentName = "ouch.ogg";
+ string fileContentLocation = "content\\sounds\\ouch.ogg";
+ string fileLocation = Path.Combine(VersionFolder, fileContentLocation);
+
+ string officialDeathSoundHash = VersionFileManifest[fileContentLocation];
+ string currentDeathSoundHash = Utilities.CalculateMD5(fileLocation);
+
+ if (Program.Settings.UseOldDeathSound && currentDeathSoundHash == officialDeathSoundHash)
+ {
+ // let's get the old one!
+
+ var response = await Client.GetAsync($"{Program.BaseUrlApplication}/mods/{fileContentLocation}");
+
+ if (File.Exists(fileLocation))
+ File.Delete(fileLocation);
+
+ using (var fileStream = new FileStream(fileLocation, FileMode.CreateNew))
+ {
+ await response.Content.CopyToAsync(fileStream);
+ }
+ }
+ else if (!Program.Settings.UseOldDeathSound && currentDeathSoundHash != officialDeathSoundHash)
+ {
+ // who's lame enough to ever do this?
+ // well, we need to re-extract the one that's in the content-sounds.zip package
+
+ var package = VersionPackageManifest.Find(x => x.Name == "content-sounds.zip");
+
+ if (package is null)
+ return;
+
+ DownloadPackage(package);
+
+ string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
+ string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
+
+ using (ZipArchive archive = ZipFile.OpenRead(packageLocation))
+ {
+ ZipArchiveEntry? entry = archive.Entries.Where(x => x.FullName == fileContentName).FirstOrDefault();
+
+ if (entry is null)
+ return;
+
+ if (File.Exists(fileLocation))
+ File.Delete(fileLocation);
+
+ entry.ExtractToFile(fileLocation);
+ }
+ }
+ }
+ }
+}
diff --git a/Bloxstrap/Bootstrapper/Bootstrapper.cs b/Bloxstrap/Bootstrapper/Bootstrapper.cs
new file mode 100644
index 0000000..30727dc
--- /dev/null
+++ b/Bloxstrap/Bootstrapper/Bootstrapper.cs
@@ -0,0 +1,178 @@
+using System.Diagnostics;
+
+using Bloxstrap.Enums;
+using Bloxstrap.Dialogs.BootstrapperStyles;
+using Bloxstrap.Helpers;
+using Bloxstrap.Helpers.RSMM;
+
+namespace Bloxstrap
+{
+ public partial class Bootstrapper
+ {
+ public Bootstrapper()
+ {
+ if (Program.BaseDirectory is null)
+ return;
+
+ FreshInstall = String.IsNullOrEmpty(Program.Settings.VersionGuid);
+ DownloadsFolder = Path.Combine(Program.BaseDirectory, "Downloads");
+ Client.Timeout = TimeSpan.FromMinutes(10);
+ }
+
+ public void Initialize(BootstrapperStyle bootstrapperStyle, string? launchCommandLine = null)
+ {
+ LaunchCommandLine = launchCommandLine;
+
+ switch (bootstrapperStyle)
+ {
+ case BootstrapperStyle.VistaDialog:
+ new VistaDialog(this);
+ break;
+
+ case BootstrapperStyle.LegacyDialog:
+ Application.Run(new LegacyDialog(this));
+ break;
+
+ case BootstrapperStyle.ProgressDialog:
+ Application.Run(new ProgressDialog(this));
+ break;
+ }
+ }
+
+ public async Task Run()
+ {
+ if (LaunchCommandLine == "-uninstall")
+ {
+ Uninstall();
+ return;
+ }
+
+ await CheckLatestVersion();
+
+ if (!Directory.Exists(VersionFolder) || Program.Settings.VersionGuid != VersionGuid)
+ {
+ Debug.WriteLineIf(!Directory.Exists(VersionFolder), $"Installing latest version (!Directory.Exists({VersionFolder}))");
+ Debug.WriteLineIf(Program.Settings.VersionGuid != VersionGuid, $"Installing latest version ({Program.Settings.VersionGuid} != {VersionGuid})");
+
+ await InstallLatestVersion();
+ }
+
+ // yes, doing this for every start is stupid, but the death sound mod is dynamically toggleable after all
+ ApplyModifications();
+
+ if (Program.IsFirstRun)
+ Program.SettingsManager.ShouldSave = true;
+
+ if (Program.IsFirstRun || FreshInstall)
+ Register();
+
+ CheckInstall();
+
+ await StartRoblox();
+
+ Program.Exit();
+ }
+
+ private void CheckIfRunning()
+ {
+ Process[] processes = Process.GetProcessesByName("RobloxPlayerBeta");
+
+ if (processes.Length > 0)
+ PromptShutdown();
+
+ try
+ {
+ // try/catch just in case process was closed before prompt was answered
+
+ foreach (Process process in processes)
+ {
+ process.CloseMainWindow();
+ process.Close();
+ }
+ }
+ catch (Exception) { }
+ }
+
+ private async Task StartRoblox()
+ {
+ string startEventName = Program.ProjectName.Replace(" ", "") + "StartEvent";
+
+ Message = "Starting Roblox...";
+
+ // launch time isn't really required for all launches, but it's usually just safest to do this
+ LaunchCommandLine += " --launchtime=" + DateTimeOffset.Now.ToUnixTimeSeconds() + " -startEvent " + startEventName;
+
+ using (SystemEvent startEvent = new(startEventName))
+ {
+ Process gameClient = Process.Start(Path.Combine(VersionFolder, "RobloxPlayerBeta.exe"), LaunchCommandLine);
+
+ bool startEventFired = await startEvent.WaitForEvent();
+
+ startEvent.Close();
+
+ if (!startEventFired)
+ return;
+
+ // event fired, wait for 6 seconds then close
+ await Task.Delay(6000);
+
+ // now we move onto handling rich presence
+ // except beta app launch since we have to rely strictly on website launch
+ if (!Program.Settings.UseDiscordRichPresence || LaunchCommandLine.Contains("--app"))
+ return;
+
+ // probably not the most ideal way to do this
+ string? placeId = Utilities.GetKeyValue(LaunchCommandLine, "placeId=", '&');
+
+ if (placeId is null)
+ return;
+
+ // keep bloxstrap open to handle rich presence
+ using (DiscordRichPresence richPresence = new())
+ {
+ bool presenceSet = await richPresence.SetPresence(placeId);
+
+ if (!presenceSet)
+ return;
+
+ CloseDialog();
+ await gameClient.WaitForExitAsync();
+ }
+ }
+ }
+
+ public void CancelButtonClicked()
+ {
+ if (Program.BaseDirectory is null)
+ return;
+
+ CancelFired = true;
+
+ try
+ {
+ if (Program.IsFirstRun)
+ Directory.Delete(Program.BaseDirectory, true);
+ else if (Directory.Exists(VersionFolder))
+ Directory.Delete(VersionFolder, true);
+ }
+ catch (Exception) { }
+
+ Program.Exit();
+ }
+
+ private void ShowSuccess(string message)
+ {
+ ShowSuccessEvent.Invoke(this, new ChangeEventArgs(message));
+ }
+
+ private void PromptShutdown()
+ {
+ PromptShutdownEvent.Invoke(this, new EventArgs());
+ }
+
+ private void CloseDialog()
+ {
+ CloseDialogEvent.Invoke(this, new EventArgs());
+ }
+ }
+}
diff --git a/Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialogStyle.Designer.cs b/Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialog.Designer.cs
similarity index 82%
rename from Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialogStyle.Designer.cs
rename to Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialog.Designer.cs
index 2da9d57..270c499 100644
--- a/Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialogStyle.Designer.cs
+++ b/Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialog.Designer.cs
@@ -1,6 +1,6 @@
namespace Bloxstrap.Dialogs.BootstrapperStyles
{
- partial class LegacyDialogStyle
+ partial class LegacyDialog
{
///
/// Required designer variable.
@@ -31,7 +31,7 @@
this.Message = new System.Windows.Forms.Label();
this.ProgressBar = new System.Windows.Forms.ProgressBar();
this.IconBox = new System.Windows.Forms.PictureBox();
- this.CancelButton = new System.Windows.Forms.Button();
+ this.ButtonCancel = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.IconBox)).BeginInit();
this.SuspendLayout();
//
@@ -62,25 +62,25 @@
this.IconBox.TabIndex = 2;
this.IconBox.TabStop = false;
//
- // CancelButton
+ // ButtonCancel
//
- this.CancelButton.Enabled = false;
- this.CancelButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
- this.CancelButton.Location = new System.Drawing.Point(271, 83);
- this.CancelButton.Name = "CancelButton";
- this.CancelButton.Size = new System.Drawing.Size(75, 23);
- this.CancelButton.TabIndex = 3;
- this.CancelButton.Text = "Cancel";
- this.CancelButton.UseVisualStyleBackColor = true;
- this.CancelButton.Visible = false;
- this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click);
+ this.ButtonCancel.Enabled = false;
+ this.ButtonCancel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.ButtonCancel.Location = new System.Drawing.Point(271, 83);
+ this.ButtonCancel.Name = "ButtonCancel";
+ this.ButtonCancel.Size = new System.Drawing.Size(75, 23);
+ this.ButtonCancel.TabIndex = 3;
+ this.ButtonCancel.Text = "Cancel";
+ this.ButtonCancel.UseVisualStyleBackColor = true;
+ this.ButtonCancel.Visible = false;
+ this.ButtonCancel.Click += new System.EventHandler(this.ButtonCancel_Click);
//
// LegacyDialogStyle
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(362, 131);
- this.Controls.Add(this.CancelButton);
+ this.Controls.Add(this.ButtonCancel);
this.Controls.Add(this.IconBox);
this.Controls.Add(this.ProgressBar);
this.Controls.Add(this.Message);
@@ -103,6 +103,6 @@
private Label Message;
private ProgressBar ProgressBar;
private PictureBox IconBox;
- private Button CancelButton;
+ private Button ButtonCancel;
}
}
\ No newline at end of file
diff --git a/Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialogStyle.cs b/Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialog.cs
similarity index 84%
rename from Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialogStyle.cs
rename to Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialog.cs
index 0a44dea..da6c3b6 100644
--- a/Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialogStyle.cs
+++ b/Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialog.cs
@@ -11,27 +11,17 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
// but once winforms code is cleaned up we could also do the 2009 version too
// example: https://youtu.be/VpduiruysuM?t=18
- public partial class LegacyDialogStyle : Form
+ public partial class LegacyDialog : Form
{
- private Bootstrapper? Bootstrapper;
+ private readonly Bootstrapper? Bootstrapper;
- public LegacyDialogStyle(Bootstrapper? bootstrapper = null)
+ public LegacyDialog(Bootstrapper? bootstrapper = null)
{
InitializeComponent();
- if (bootstrapper is not null)
- {
- Bootstrapper = bootstrapper;
- Bootstrapper.PromptShutdownEvent += new EventHandler(PromptShutdown);
- Bootstrapper.ShowSuccessEvent += new ChangeEventHandler(ShowSuccess);
- Bootstrapper.MessageChanged += new ChangeEventHandler(MessageChanged);
- Bootstrapper.ProgressBarValueChanged += new ChangeEventHandler(ProgressBarValueChanged);
- Bootstrapper.ProgressBarStyleChanged += new ChangeEventHandler(ProgressBarStyleChanged);
- Bootstrapper.CancelEnabledChanged += new ChangeEventHandler(CancelEnabledChanged);
- }
-
+ Bootstrapper = bootstrapper;
+
Icon icon = IconManager.GetIconResource();
-
this.Text = Program.ProjectName;
this.Icon = icon;
this.IconBox.Image = icon.ToBitmap();
@@ -39,17 +29,28 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
if (Bootstrapper is null)
{
this.Message.Text = "Click the Cancel button to return to preferences";
- this.CancelButton.Enabled = true;
- this.CancelButton.Visible = true;
+ this.ButtonCancel.Enabled = true;
+ this.ButtonCancel.Visible = true;
}
else
{
+ Bootstrapper.CloseDialogEvent += new EventHandler(CloseDialog);
+ Bootstrapper.PromptShutdownEvent += new EventHandler(PromptShutdown);
+ Bootstrapper.ShowSuccessEvent += new ChangeEventHandler(ShowSuccess);
+ Bootstrapper.MessageChanged += new ChangeEventHandler(MessageChanged);
+ Bootstrapper.ProgressBarValueChanged += new ChangeEventHandler(ProgressBarValueChanged);
+ Bootstrapper.ProgressBarStyleChanged += new ChangeEventHandler(ProgressBarStyleChanged);
+ Bootstrapper.CancelEnabledChanged += new ChangeEventHandler(CancelEnabledChanged);
+
Task.Run(() => RunBootstrapper());
}
}
public async void RunBootstrapper()
{
+ if (Bootstrapper is null)
+ return;
+
try
{
await Bootstrapper.Run();
@@ -84,6 +85,11 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
);
}
+ private void CloseDialog(object? sender, EventArgs e)
+ {
+ this.Close();
+ }
+
private void PromptShutdown(object? sender, EventArgs e)
{
DialogResult result = MessageBox.Show(
@@ -138,19 +144,19 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
private void CancelEnabledChanged(object sender, ChangeEventArgs e)
{
- if (this.CancelButton.InvokeRequired)
+ if (this.ButtonCancel.InvokeRequired)
{
ChangeEventHandler handler = new(CancelEnabledChanged);
- this.CancelButton.Invoke(handler, sender, e);
+ this.ButtonCancel.Invoke(handler, sender, e);
}
else
{
- this.CancelButton.Enabled = e.Value;
- this.CancelButton.Visible = e.Value;
+ this.ButtonCancel.Enabled = e.Value;
+ this.ButtonCancel.Visible = e.Value;
}
}
- private void CancelButton_Click(object sender, EventArgs e)
+ private void ButtonCancel_Click(object sender, EventArgs e)
{
if (Bootstrapper is null)
this.Close();
diff --git a/Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialogStyle.resx b/Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialog.resx
similarity index 100%
rename from Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialogStyle.resx
rename to Bloxstrap/Dialogs/BootstrapperStyles/LegacyDialog.resx
diff --git a/Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialogStyle.Designer.cs b/Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialog.Designer.cs
similarity index 80%
rename from Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialogStyle.Designer.cs
rename to Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialog.Designer.cs
index 6f8de52..91bd453 100644
--- a/Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialogStyle.Designer.cs
+++ b/Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialog.Designer.cs
@@ -1,6 +1,6 @@
namespace Bloxstrap.Dialogs.BootstrapperStyles
{
- partial class ProgressDialogStyle
+ partial class ProgressDialog
{
///
/// Required designer variable.
@@ -31,10 +31,10 @@
this.ProgressBar = new System.Windows.Forms.ProgressBar();
this.Message = new System.Windows.Forms.Label();
this.IconBox = new System.Windows.Forms.PictureBox();
- this.CancelButton = new System.Windows.Forms.PictureBox();
+ this.ButtonCancel = new System.Windows.Forms.PictureBox();
this.panel1 = new System.Windows.Forms.Panel();
((System.ComponentModel.ISupportInitialize)(this.IconBox)).BeginInit();
- ((System.ComponentModel.ISupportInitialize)(this.CancelButton)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.ButtonCancel)).BeginInit();
this.panel1.SuspendLayout();
this.SuspendLayout();
//
@@ -69,26 +69,26 @@
this.IconBox.TabIndex = 2;
this.IconBox.TabStop = false;
//
- // CancelButton
+ // ButtonCancel
//
- this.CancelButton.Enabled = false;
- this.CancelButton.Image = global::Bloxstrap.Properties.Resources.CancelButton;
- this.CancelButton.Location = new System.Drawing.Point(194, 264);
- this.CancelButton.Name = "CancelButton";
- this.CancelButton.Size = new System.Drawing.Size(130, 44);
- this.CancelButton.TabIndex = 3;
- this.CancelButton.TabStop = false;
- this.CancelButton.Visible = false;
- this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click);
- this.CancelButton.MouseEnter += new System.EventHandler(this.CancelButton_MouseEnter);
- this.CancelButton.MouseLeave += new System.EventHandler(this.CancelButton_MouseLeave);
+ this.ButtonCancel.Enabled = false;
+ this.ButtonCancel.Image = global::Bloxstrap.Properties.Resources.CancelButton;
+ this.ButtonCancel.Location = new System.Drawing.Point(194, 264);
+ this.ButtonCancel.Name = "ButtonCancel";
+ this.ButtonCancel.Size = new System.Drawing.Size(130, 44);
+ this.ButtonCancel.TabIndex = 3;
+ this.ButtonCancel.TabStop = false;
+ this.ButtonCancel.Visible = false;
+ this.ButtonCancel.Click += new System.EventHandler(this.ButtonCancel_Click);
+ this.ButtonCancel.MouseEnter += new System.EventHandler(this.ButtonCancel_MouseEnter);
+ this.ButtonCancel.MouseLeave += new System.EventHandler(this.ButtonCancel_MouseLeave);
//
// panel1
//
this.panel1.BackColor = System.Drawing.SystemColors.Window;
this.panel1.Controls.Add(this.Message);
this.panel1.Controls.Add(this.IconBox);
- this.panel1.Controls.Add(this.CancelButton);
+ this.panel1.Controls.Add(this.ButtonCancel);
this.panel1.Controls.Add(this.ProgressBar);
this.panel1.Location = new System.Drawing.Point(1, 1);
this.panel1.Name = "panel1";
@@ -109,7 +109,7 @@
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "ProgressDialogStyle";
((System.ComponentModel.ISupportInitialize)(this.IconBox)).EndInit();
- ((System.ComponentModel.ISupportInitialize)(this.CancelButton)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.ButtonCancel)).EndInit();
this.panel1.ResumeLayout(false);
this.ResumeLayout(false);
@@ -120,7 +120,7 @@
private ProgressBar ProgressBar;
private Label Message;
private PictureBox IconBox;
- private PictureBox CancelButton;
+ private PictureBox ButtonCancel;
private Panel panel1;
}
}
\ No newline at end of file
diff --git a/Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialogStyle.cs b/Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialog.cs
similarity index 80%
rename from Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialogStyle.cs
rename to Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialog.cs
index 739b26c..70f21a7 100644
--- a/Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialogStyle.cs
+++ b/Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialog.cs
@@ -1,15 +1,17 @@
-using Bloxstrap.Helpers;
+using System.Diagnostics;
+
+using Bloxstrap.Helpers;
using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap.Dialogs.BootstrapperStyles
{
// TODO - universal implementation for winforms-based styles? (to reduce duplicate code)
- public partial class ProgressDialogStyle : Form
+ public partial class ProgressDialog : Form
{
- private Bootstrapper? Bootstrapper;
+ private readonly Bootstrapper? Bootstrapper;
- public ProgressDialogStyle(Bootstrapper? bootstrapper = null)
+ public ProgressDialog(Bootstrapper? bootstrapper = null)
{
InitializeComponent();
@@ -22,11 +24,12 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
if (Bootstrapper is null)
{
this.Message.Text = "Click the Cancel button to return to preferences";
- this.CancelButton.Enabled = true;
- this.CancelButton.Visible = true;
+ this.ButtonCancel.Enabled = true;
+ this.ButtonCancel.Visible = true;
}
else
{
+ Bootstrapper.CloseDialogEvent += new EventHandler(CloseDialog);
Bootstrapper.PromptShutdownEvent += new EventHandler(PromptShutdown);
Bootstrapper.ShowSuccessEvent += new ChangeEventHandler(ShowSuccess);
Bootstrapper.MessageChanged += new ChangeEventHandler(MessageChanged);
@@ -40,6 +43,9 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
public async void RunBootstrapper()
{
+ if (Bootstrapper is null)
+ return;
+
try
{
await Bootstrapper.Run();
@@ -74,6 +80,11 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
);
}
+ private void CloseDialog(object? sender, EventArgs e)
+ {
+ this.Hide();
+ }
+
private void PromptShutdown(object? sender, EventArgs e)
{
DialogResult result = MessageBox.Show(
@@ -128,19 +139,19 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
private void CancelEnabledChanged(object sender, ChangeEventArgs e)
{
- if (this.CancelButton.InvokeRequired)
+ if (this.ButtonCancel.InvokeRequired)
{
ChangeEventHandler handler = new(CancelEnabledChanged);
- this.CancelButton.Invoke(handler, sender, e);
+ this.ButtonCancel.Invoke(handler, sender, e);
}
else
{
- this.CancelButton.Enabled = e.Value;
- this.CancelButton.Visible = e.Value;
+ this.ButtonCancel.Enabled = e.Value;
+ this.ButtonCancel.Visible = e.Value;
}
}
- private void CancelButton_Click(object sender, EventArgs e)
+ private void ButtonCancel_Click(object sender, EventArgs e)
{
if (Bootstrapper is null)
this.Close();
@@ -148,14 +159,14 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
Task.Run(() => Bootstrapper.CancelButtonClicked());
}
- private void CancelButton_MouseEnter(object sender, EventArgs e)
+ private void ButtonCancel_MouseEnter(object sender, EventArgs e)
{
- this.CancelButton.Image = Properties.Resources.CancelButtonHover;
+ this.ButtonCancel.Image = Properties.Resources.CancelButtonHover;
}
- private void CancelButton_MouseLeave(object sender, EventArgs e)
+ private void ButtonCancel_MouseLeave(object sender, EventArgs e)
{
- this.CancelButton.Image = Properties.Resources.CancelButton;
+ this.ButtonCancel.Image = Properties.Resources.CancelButton;
}
}
}
diff --git a/Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialogStyle.resx b/Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialog.resx
similarity index 100%
rename from Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialogStyle.resx
rename to Bloxstrap/Dialogs/BootstrapperStyles/ProgressDialog.resx
diff --git a/Bloxstrap/Dialogs/BootstrapperStyles/TaskDialogStyle.cs b/Bloxstrap/Dialogs/BootstrapperStyles/VistaDialog.cs
similarity index 91%
rename from Bloxstrap/Dialogs/BootstrapperStyles/TaskDialogStyle.cs
rename to Bloxstrap/Dialogs/BootstrapperStyles/VistaDialog.cs
index 019325f..e65a3fe 100644
--- a/Bloxstrap/Dialogs/BootstrapperStyles/TaskDialogStyle.cs
+++ b/Bloxstrap/Dialogs/BootstrapperStyles/VistaDialog.cs
@@ -5,10 +5,6 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
{
// example: https://youtu.be/h0_AL95Sc3o?t=48
- // i suppose a better name for this here would be "VistaDialog" rather than "TaskDialog"?
- // having this named as BootstrapperStyles.TaskDialog would conflict with Forms.TaskDialog
- // so naming it VistaDialog would let us drop the ~Style suffix on every style name
-
// this currently doesn't work because c# is stupid
// technically, task dialogs are treated as winforms controls, but they don't classify as winforms controls at all
// all winforms controls have the ability to be invoked from another thread, but task dialogs don't
@@ -17,12 +13,12 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
// for now, just stick to legacydialog and progressdialog
- public class TaskDialogStyle
+ public class VistaDialog
{
- private Bootstrapper Bootstrapper;
+ private readonly Bootstrapper Bootstrapper;
private TaskDialogPage Dialog;
- public TaskDialogStyle(Bootstrapper bootstrapper)
+ public VistaDialog(Bootstrapper bootstrapper)
{
Bootstrapper = bootstrapper;
Bootstrapper.ShowSuccessEvent += new ChangeEventHandler(ShowSuccess);
diff --git a/Bloxstrap/Dialogs/Preferences.Designer.cs b/Bloxstrap/Dialogs/Preferences.Designer.cs
index c7f0902..fe111a6 100644
--- a/Bloxstrap/Dialogs/Preferences.Designer.cs
+++ b/Bloxstrap/Dialogs/Preferences.Designer.cs
@@ -28,9 +28,12 @@
///
private void InitializeComponent()
{
+ this.components = new System.ComponentModel.Container();
this.label1 = new System.Windows.Forms.Label();
this.Tabs = new System.Windows.Forms.TabControl();
this.DialogTab = new System.Windows.Forms.TabPage();
+ this.groupBox5 = new System.Windows.Forms.GroupBox();
+ this.ToggleDiscordRichPresence = new System.Windows.Forms.CheckBox();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.IconPreview = new System.Windows.Forms.PictureBox();
this.IconSelection = new System.Windows.Forms.ListBox();
@@ -38,23 +41,24 @@
this.StyleSelection = new System.Windows.Forms.ListBox();
this.InstallationTab = new System.Windows.Forms.TabPage();
this.groupBox4 = new System.Windows.Forms.GroupBox();
- this.ModifyDeathSoundToggle = new System.Windows.Forms.CheckBox();
- this.groupBox1 = new System.Windows.Forms.GroupBox();
+ this.ToggleDeathSound = new System.Windows.Forms.CheckBox();
+ this.GroupBoxInstallLocation = new System.Windows.Forms.GroupBox();
this.InstallLocationBrowseButton = new System.Windows.Forms.Button();
this.InstallLocation = new System.Windows.Forms.TextBox();
this.SaveButton = new System.Windows.Forms.Button();
this.panel1 = new System.Windows.Forms.Panel();
- this.label2 = new System.Windows.Forms.Label();
this.PreviewButton = new System.Windows.Forms.Button();
this.InstallLocationBrowseDialog = new System.Windows.Forms.FolderBrowserDialog();
+ this.InfoTooltip = new System.Windows.Forms.ToolTip(this.components);
this.Tabs.SuspendLayout();
this.DialogTab.SuspendLayout();
+ this.groupBox5.SuspendLayout();
this.groupBox3.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.IconPreview)).BeginInit();
this.groupBox2.SuspendLayout();
this.InstallationTab.SuspendLayout();
this.groupBox4.SuspendLayout();
- this.groupBox1.SuspendLayout();
+ this.GroupBoxInstallLocation.SuspendLayout();
this.panel1.SuspendLayout();
this.SuspendLayout();
//
@@ -66,7 +70,7 @@
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(237, 23);
this.label1.TabIndex = 1;
- this.label1.Text = "Configure Preferences";
+ this.label1.Text = "Configure Bloxstrap";
//
// Tabs
//
@@ -75,21 +79,45 @@
this.Tabs.Location = new System.Drawing.Point(12, 40);
this.Tabs.Name = "Tabs";
this.Tabs.SelectedIndex = 0;
- this.Tabs.Size = new System.Drawing.Size(442, 176);
+ this.Tabs.Size = new System.Drawing.Size(442, 226);
this.Tabs.TabIndex = 2;
//
// DialogTab
//
+ this.DialogTab.Controls.Add(this.groupBox5);
this.DialogTab.Controls.Add(this.groupBox3);
this.DialogTab.Controls.Add(this.groupBox2);
this.DialogTab.Location = new System.Drawing.Point(4, 24);
this.DialogTab.Name = "DialogTab";
this.DialogTab.Padding = new System.Windows.Forms.Padding(3);
- this.DialogTab.Size = new System.Drawing.Size(434, 148);
+ this.DialogTab.Size = new System.Drawing.Size(434, 198);
this.DialogTab.TabIndex = 0;
this.DialogTab.Text = "Bootstrapper";
this.DialogTab.UseVisualStyleBackColor = true;
//
+ // groupBox5
+ //
+ this.groupBox5.Controls.Add(this.ToggleDiscordRichPresence);
+ this.groupBox5.Location = new System.Drawing.Point(5, 146);
+ this.groupBox5.Name = "groupBox5";
+ this.groupBox5.Size = new System.Drawing.Size(422, 46);
+ this.groupBox5.TabIndex = 7;
+ this.groupBox5.TabStop = false;
+ this.groupBox5.Text = "Launch";
+ //
+ // ToggleDiscordRichPresence
+ //
+ this.ToggleDiscordRichPresence.AutoSize = true;
+ this.ToggleDiscordRichPresence.Checked = true;
+ this.ToggleDiscordRichPresence.CheckState = System.Windows.Forms.CheckState.Checked;
+ this.ToggleDiscordRichPresence.Location = new System.Drawing.Point(9, 19);
+ this.ToggleDiscordRichPresence.Name = "ToggleDiscordRichPresence";
+ this.ToggleDiscordRichPresence.Size = new System.Drawing.Size(274, 19);
+ this.ToggleDiscordRichPresence.TabIndex = 0;
+ this.ToggleDiscordRichPresence.Text = "Show game activity with Discord Rich Presence";
+ this.ToggleDiscordRichPresence.UseVisualStyleBackColor = true;
+ this.ToggleDiscordRichPresence.CheckedChanged += new System.EventHandler(this.ToggleDiscordRichPresence_CheckedChanged);
+ //
// groupBox3
//
this.groupBox3.Controls.Add(this.IconPreview);
@@ -144,49 +172,49 @@
// InstallationTab
//
this.InstallationTab.Controls.Add(this.groupBox4);
- this.InstallationTab.Controls.Add(this.groupBox1);
+ this.InstallationTab.Controls.Add(this.GroupBoxInstallLocation);
this.InstallationTab.Location = new System.Drawing.Point(4, 24);
this.InstallationTab.Name = "InstallationTab";
this.InstallationTab.Padding = new System.Windows.Forms.Padding(3);
- this.InstallationTab.Size = new System.Drawing.Size(434, 148);
+ this.InstallationTab.Size = new System.Drawing.Size(434, 198);
this.InstallationTab.TabIndex = 2;
this.InstallationTab.Text = "Installation";
this.InstallationTab.UseVisualStyleBackColor = true;
//
// groupBox4
//
- this.groupBox4.Controls.Add(this.ModifyDeathSoundToggle);
- this.groupBox4.Location = new System.Drawing.Point(5, 59);
+ this.groupBox4.Controls.Add(this.ToggleDeathSound);
+ this.groupBox4.Location = new System.Drawing.Point(5, 60);
this.groupBox4.Name = "groupBox4";
- this.groupBox4.Size = new System.Drawing.Size(422, 84);
+ this.groupBox4.Size = new System.Drawing.Size(422, 46);
this.groupBox4.TabIndex = 2;
this.groupBox4.TabStop = false;
this.groupBox4.Text = "Modifications";
//
- // ModifyDeathSoundToggle
+ // ToggleDeathSound
//
- this.ModifyDeathSoundToggle.AutoSize = true;
- this.ModifyDeathSoundToggle.Checked = true;
- this.ModifyDeathSoundToggle.CheckState = System.Windows.Forms.CheckState.Checked;
- this.ModifyDeathSoundToggle.Location = new System.Drawing.Point(9, 21);
- this.ModifyDeathSoundToggle.Margin = new System.Windows.Forms.Padding(2);
- this.ModifyDeathSoundToggle.Name = "ModifyDeathSoundToggle";
- this.ModifyDeathSoundToggle.Size = new System.Drawing.Size(138, 19);
- this.ModifyDeathSoundToggle.TabIndex = 1;
- this.ModifyDeathSoundToggle.Text = "Use Old Death Sound";
- this.ModifyDeathSoundToggle.UseVisualStyleBackColor = true;
- this.ModifyDeathSoundToggle.CheckedChanged += new System.EventHandler(this.ModifyDeathSoundToggle_CheckedChanged);
+ this.ToggleDeathSound.AutoSize = true;
+ this.ToggleDeathSound.Checked = true;
+ this.ToggleDeathSound.CheckState = System.Windows.Forms.CheckState.Checked;
+ this.ToggleDeathSound.Location = new System.Drawing.Point(9, 19);
+ this.ToggleDeathSound.Margin = new System.Windows.Forms.Padding(2);
+ this.ToggleDeathSound.Name = "ToggleDeathSound";
+ this.ToggleDeathSound.Size = new System.Drawing.Size(134, 19);
+ this.ToggleDeathSound.TabIndex = 1;
+ this.ToggleDeathSound.Text = "Use old death sound";
+ this.ToggleDeathSound.UseVisualStyleBackColor = true;
+ this.ToggleDeathSound.CheckedChanged += new System.EventHandler(this.ToggleDeathSound_CheckedChanged);
//
- // groupBox1
+ // GroupBoxInstallLocation
//
- this.groupBox1.Controls.Add(this.InstallLocationBrowseButton);
- this.groupBox1.Controls.Add(this.InstallLocation);
- this.groupBox1.Location = new System.Drawing.Point(5, 3);
- this.groupBox1.Name = "groupBox1";
- this.groupBox1.Size = new System.Drawing.Size(422, 53);
- this.groupBox1.TabIndex = 0;
- this.groupBox1.TabStop = false;
- this.groupBox1.Text = "Install Location";
+ this.GroupBoxInstallLocation.Controls.Add(this.InstallLocationBrowseButton);
+ this.GroupBoxInstallLocation.Controls.Add(this.InstallLocation);
+ this.GroupBoxInstallLocation.Location = new System.Drawing.Point(5, 3);
+ this.GroupBoxInstallLocation.Name = "GroupBoxInstallLocation";
+ this.GroupBoxInstallLocation.Size = new System.Drawing.Size(422, 54);
+ this.GroupBoxInstallLocation.TabIndex = 0;
+ this.GroupBoxInstallLocation.TabStop = false;
+ this.GroupBoxInstallLocation.Text = "Install Location";
//
// InstallLocationBrowseButton
//
@@ -225,24 +253,13 @@
| System.Windows.Forms.AnchorStyles.Right)));
this.panel1.BackColor = System.Drawing.SystemColors.Control;
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
- this.panel1.Controls.Add(this.label2);
this.panel1.Controls.Add(this.PreviewButton);
this.panel1.Controls.Add(this.SaveButton);
- this.panel1.Location = new System.Drawing.Point(-1, 227);
+ this.panel1.Location = new System.Drawing.Point(-1, 277);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(466, 42);
this.panel1.TabIndex = 6;
//
- // label2
- //
- this.label2.AutoSize = true;
- this.label2.Location = new System.Drawing.Point(12, 13);
- this.label2.Margin = new System.Windows.Forms.Padding(0);
- this.label2.Name = "label2";
- this.label2.Size = new System.Drawing.Size(221, 15);
- this.label2.TabIndex = 6;
- this.label2.Text = "made by pizzaboxer - i think this works...";
- //
// PreviewButton
//
this.PreviewButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
@@ -254,12 +271,18 @@
this.PreviewButton.UseVisualStyleBackColor = true;
this.PreviewButton.Click += new System.EventHandler(this.PreviewButton_Click);
//
+ // InfoTooltip
+ //
+ this.InfoTooltip.ShowAlways = true;
+ this.InfoTooltip.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info;
+ this.InfoTooltip.ToolTipTitle = "Information";
+ //
// Preferences
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.SystemColors.Window;
- this.ClientSize = new System.Drawing.Size(464, 268);
+ this.ClientSize = new System.Drawing.Size(464, 318);
this.Controls.Add(this.panel1);
this.Controls.Add(this.Tabs);
this.Controls.Add(this.label1);
@@ -271,16 +294,17 @@
this.Text = "Preferences";
this.Tabs.ResumeLayout(false);
this.DialogTab.ResumeLayout(false);
+ this.groupBox5.ResumeLayout(false);
+ this.groupBox5.PerformLayout();
this.groupBox3.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.IconPreview)).EndInit();
this.groupBox2.ResumeLayout(false);
this.InstallationTab.ResumeLayout(false);
this.groupBox4.ResumeLayout(false);
this.groupBox4.PerformLayout();
- this.groupBox1.ResumeLayout(false);
- this.groupBox1.PerformLayout();
+ this.GroupBoxInstallLocation.ResumeLayout(false);
+ this.GroupBoxInstallLocation.PerformLayout();
this.panel1.ResumeLayout(false);
- this.panel1.PerformLayout();
this.ResumeLayout(false);
}
@@ -294,7 +318,7 @@
private Button SaveButton;
private Panel panel1;
private ListBox StyleSelection;
- private GroupBox groupBox1;
+ private GroupBox GroupBoxInstallLocation;
private Button InstallLocationBrowseButton;
private TextBox InstallLocation;
private FolderBrowserDialog InstallLocationBrowseDialog;
@@ -303,8 +327,10 @@
private PictureBox IconPreview;
private ListBox IconSelection;
private Button PreviewButton;
- private Label label2;
- private CheckBox ModifyDeathSoundToggle;
+ private CheckBox ToggleDeathSound;
private GroupBox groupBox4;
+ private GroupBox groupBox5;
+ private CheckBox ToggleDiscordRichPresence;
+ private ToolTip InfoTooltip;
}
}
\ No newline at end of file
diff --git a/Bloxstrap/Dialogs/Preferences.cs b/Bloxstrap/Dialogs/Preferences.cs
index 5756dff..5c35e2c 100644
--- a/Bloxstrap/Dialogs/Preferences.cs
+++ b/Bloxstrap/Dialogs/Preferences.cs
@@ -25,24 +25,10 @@ namespace Bloxstrap.Dialogs
{ "2019", BootstrapperIcon.Icon2019 },
};
- private bool _useOldDeathSound = true;
private BootstrapperStyle? _selectedStyle;
private BootstrapperIcon? _selectedIcon;
-
- private bool UseOldDeathSound
- {
- get => _useOldDeathSound;
-
- set
- {
- if (_useOldDeathSound == value)
- return;
-
- _useOldDeathSound = value;
-
- this.ModifyDeathSoundToggle.Checked = value;
- }
- }
+ private bool _useDiscordRichPresence = true;
+ private bool _useOldDeathSound = true;
private BootstrapperStyle SelectedStyle
{
@@ -77,16 +63,48 @@ namespace Bloxstrap.Dialogs
}
}
+ private bool UseDiscordRichPresence
+ {
+ get => _useDiscordRichPresence;
+
+ set
+ {
+ if (_useDiscordRichPresence == value)
+ return;
+
+ _useDiscordRichPresence = value;
+
+ this.ToggleDiscordRichPresence.Checked = value;
+ }
+ }
+
+ private bool UseOldDeathSound
+ {
+ get => _useOldDeathSound;
+
+ set
+ {
+ if (_useOldDeathSound == value)
+ return;
+
+ _useOldDeathSound = value;
+
+ this.ToggleDeathSound.Checked = value;
+ }
+ }
+
public Preferences()
{
InitializeComponent();
+ Program.SettingsManager.ShouldSave = false;
+
this.Icon = Properties.Resources.IconBloxstrap_ico;
this.Text = Program.ProjectName;
if (Program.IsFirstRun)
{
- this.SaveButton.Text = "Continue";
+ this.SaveButton.Text = "Install";
this.InstallLocation.Text = Path.Combine(Program.LocalAppData, Program.ProjectName);
}
else
@@ -104,14 +122,15 @@ namespace Bloxstrap.Dialogs
this.IconSelection.Items.Add(icon.Key);
}
- UseOldDeathSound = Program.Settings.UseOldDeathSound;
+ this.InfoTooltip.SetToolTip(this.StyleSelection, "Choose how the bootstrapper dialog should look.");
+ this.InfoTooltip.SetToolTip(this.IconSelection, "Choose what icon the bootstrapper should use.");
+ this.InfoTooltip.SetToolTip(this.GroupBoxInstallLocation, "Choose where Bloxstrap should install to.\nThis is useful if you typically install all your games to a separate storage drive.");
+ this.InfoTooltip.SetToolTip(this.ToggleDiscordRichPresence, "Choose whether to show what game you're playing on your Discord profile.\nThis will ONLY work when you launch a game from the website, and is not supported in the Beta App.");
+
SelectedStyle = Program.Settings.BootstrapperStyle;
SelectedIcon = Program.Settings.BootstrapperIcon;
- }
-
- private void ShowDialog(MessageBoxIcon icon, string message)
- {
- MessageBox.Show(message, Program.ProjectName, MessageBoxButtons.OK, icon);
+ UseDiscordRichPresence = Program.Settings.UseDiscordRichPresence;
+ UseOldDeathSound = Program.Settings.UseOldDeathSound;
}
private void InstallLocationBrowseButton_Click(object sender, EventArgs e)
@@ -140,7 +159,7 @@ namespace Bloxstrap.Dialogs
if (String.IsNullOrEmpty(installLocation))
{
- ShowDialog(MessageBoxIcon.Error, "You must set an install location");
+ Program.ShowMessageBox(MessageBoxIcon.Error, "You must set an install location");
return;
}
@@ -163,12 +182,12 @@ namespace Bloxstrap.Dialogs
}
catch (UnauthorizedAccessException)
{
- ShowDialog(MessageBoxIcon.Error, $"{Program.ProjectName} does not have write access to the install location you selected. Please choose another install location.");
+ Program.ShowMessageBox(MessageBoxIcon.Error, $"{Program.ProjectName} does not have write access to the install location you selected. Please choose another install location.");
return;
}
catch (Exception ex)
{
- ShowDialog(MessageBoxIcon.Error, ex.Message);
+ Program.ShowMessageBox(MessageBoxIcon.Error, ex.Message);
return;
}
@@ -179,7 +198,7 @@ namespace Bloxstrap.Dialogs
}
else if (Program.BaseDirectory != installLocation)
{
- ShowDialog(MessageBoxIcon.Information, $"{Program.ProjectName} will install to the new location you've set the next time it runs.");
+ Program.ShowMessageBox(MessageBoxIcon.Information, $"{Program.ProjectName} will install to the new location you've set the next time it runs.");
Program.Settings.VersionGuid = "";
@@ -196,9 +215,13 @@ namespace Bloxstrap.Dialogs
File.Copy(Path.Combine(Program.BaseDirectory, "Settings.json"), Path.Combine(installLocation, "Settings.json"));
}
- Program.Settings.UseOldDeathSound = UseOldDeathSound;
+ if (!Program.IsFirstRun)
+ Program.SettingsManager.ShouldSave = true;
+
Program.Settings.BootstrapperStyle = SelectedStyle;
Program.Settings.BootstrapperIcon = SelectedIcon;
+ Program.Settings.UseDiscordRichPresence = UseDiscordRichPresence;
+ Program.Settings.UseOldDeathSound = UseOldDeathSound;
this.Close();
}
@@ -214,11 +237,11 @@ namespace Bloxstrap.Dialogs
switch (SelectedStyle)
{
case BootstrapperStyle.LegacyDialog:
- new LegacyDialogStyle().ShowDialog();
+ new LegacyDialog().ShowDialog();
break;
case BootstrapperStyle.ProgressDialog:
- new ProgressDialogStyle().ShowDialog();
+ new ProgressDialog().ShowDialog();
break;
}
@@ -227,9 +250,14 @@ namespace Bloxstrap.Dialogs
this.Visible = true;
}
- private void ModifyDeathSoundToggle_CheckedChanged(object sender, EventArgs e)
+ private void ToggleDiscordRichPresence_CheckedChanged(object sender, EventArgs e)
{
- UseOldDeathSound = this.ModifyDeathSoundToggle.Checked;
+ UseDiscordRichPresence = this.ToggleDiscordRichPresence.Checked;
+ }
+
+ private void ToggleDeathSound_CheckedChanged(object sender, EventArgs e)
+ {
+ UseOldDeathSound = this.ToggleDeathSound.Checked;
}
}
}
diff --git a/Bloxstrap/Dialogs/Preferences.resx b/Bloxstrap/Dialogs/Preferences.resx
index 8e732d6..453d499 100644
--- a/Bloxstrap/Dialogs/Preferences.resx
+++ b/Bloxstrap/Dialogs/Preferences.resx
@@ -60,4 +60,7 @@
17, 17
+
+ 222, 17
+
\ No newline at end of file
diff --git a/Bloxstrap/Enums/BootstrapperStyle.cs b/Bloxstrap/Enums/BootstrapperStyle.cs
index fa3e00a..fd0a9fd 100644
--- a/Bloxstrap/Enums/BootstrapperStyle.cs
+++ b/Bloxstrap/Enums/BootstrapperStyle.cs
@@ -2,7 +2,7 @@
{
public enum BootstrapperStyle
{
- TaskDialog,
+ VistaDialog,
LegacyDialog,
ProgressDialog
}
diff --git a/Bloxstrap/Helpers/DiscordRichPresence.cs b/Bloxstrap/Helpers/DiscordRichPresence.cs
new file mode 100644
index 0000000..8df615d
--- /dev/null
+++ b/Bloxstrap/Helpers/DiscordRichPresence.cs
@@ -0,0 +1,73 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using DiscordRPC;
+
+namespace Bloxstrap.Helpers
+{
+ internal class DiscordRichPresence : IDisposable
+ {
+ readonly DiscordRpcClient RichPresence = new("1005469189907173486");
+
+ public async Task SetPresence(string placeId)
+ {
+ string placeName;
+ string placeThumbnail;
+ string creatorName;
+
+ // null checking could probably be a lot more concrete here
+ using (HttpClient client = new())
+ {
+ JObject placeInfo = await Utilities.GetJson($"https://economy.roblox.com/v2/assets/{placeId}/details");
+
+ placeName = placeInfo["Name"].Value();
+ creatorName = placeInfo["Creator"]["Name"].Value();
+
+ JObject thumbnailInfo = await Utilities.GetJson($"https://thumbnails.roblox.com/v1/places/gameicons?placeIds={placeId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false");
+
+ if (thumbnailInfo["data"] is null)
+ return false;
+
+ placeThumbnail = thumbnailInfo["data"][0]["imageUrl"].Value();
+ }
+
+ RichPresence.Initialize();
+
+ RichPresence.SetPresence(new RichPresence()
+ {
+ Details = placeName,
+ State = $"by {creatorName}",
+ Timestamps = new Timestamps() { Start = DateTime.UtcNow },
+
+ Assets = new Assets()
+ {
+ LargeImageKey = placeThumbnail,
+ LargeImageText = placeName,
+ SmallImageKey = "bloxstrap",
+ SmallImageText = "Rich Presence provided by Bloxstrap"
+ },
+
+ Buttons = new DiscordRPC.Button[]
+ {
+ new DiscordRPC.Button()
+ {
+ Label = "Play",
+ Url = $"https://www.roblox.com/games/start?placeId={placeId}&launchData=%7B%7D"
+ },
+
+ new DiscordRPC.Button()
+ {
+ Label = "View Details",
+ Url = $"https://www.roblox.com/games/{placeId}"
+ }
+ }
+ });
+
+ return true;
+ }
+
+ public void Dispose()
+ {
+ RichPresence.Dispose();
+ }
+ }
+}
diff --git a/Bloxstrap/Helpers/UpdateChecker.cs b/Bloxstrap/Helpers/UpdateChecker.cs
new file mode 100644
index 0000000..b335ec8
--- /dev/null
+++ b/Bloxstrap/Helpers/UpdateChecker.cs
@@ -0,0 +1,76 @@
+using System.Diagnostics;
+using Newtonsoft.Json.Linq;
+
+namespace Bloxstrap.Helpers
+{
+ public class UpdateChecker
+ {
+ public static void CheckInstalledVersion()
+ {
+ if (Environment.ProcessPath is null || !File.Exists(Program.FilePath))
+ return;
+
+ // if downloaded version doesn't match, replace installed version with downloaded version
+ FileVersionInfo currentVersionInfo = FileVersionInfo.GetVersionInfo(Environment.ProcessPath);
+ FileVersionInfo installedVersionInfo = FileVersionInfo.GetVersionInfo(Program.FilePath);
+
+ if (installedVersionInfo != currentVersionInfo)
+ {
+ DialogResult result = MessageBox.Show(
+ $"The version of {Program.ProjectName} you've launched is newer than the version you currently have installed.\nWould you like to update your installed version of {Program.ProjectName}?",
+ Program.ProjectName,
+ MessageBoxButtons.YesNo,
+ MessageBoxIcon.Question
+ );
+
+ if (result == DialogResult.Yes)
+ {
+ File.Delete(Program.FilePath);
+ File.Copy(Environment.ProcessPath, Program.FilePath);
+ }
+ }
+ }
+
+ public static async Task Check()
+ {
+ if (Environment.ProcessPath is null)
+ return;
+
+ FileVersionInfo currentVersionInfo = FileVersionInfo.GetVersionInfo(Environment.ProcessPath);
+ string currentVersion = $"Bloxstrap v{currentVersionInfo.ProductVersion}";
+ string latestVersion;
+ string releaseNotes;
+
+ // get the latest version according to the latest github release info
+ // it should contain the latest product version, which we can check against
+ try
+ {
+ JObject releaseInfo = await Utilities.GetJson($"https://api.github.com/repos/{Program.ProjectRepository}/releases/latest");
+
+ latestVersion = releaseInfo["name"].Value();
+ releaseNotes = releaseInfo["body"].Value();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Failed to fetch latest version info! ({ex.Message})");
+ return;
+ }
+
+ if (currentVersion != latestVersion)
+ {
+ DialogResult result = MessageBox.Show(
+ $"A new version of {Program.ProjectName} is available\n\nRelease notes:\n{releaseNotes}\n\nDo you want to download {latestVersion}?",
+ Program.ProjectName,
+ MessageBoxButtons.YesNo,
+ MessageBoxIcon.Question
+ );
+
+ if (result == DialogResult.Yes)
+ {
+ Process.Start(new ProcessStartInfo { FileName = $"https://github.com/{Program.ProjectRepository}/releases/latest", UseShellExecute = true });
+ Program.Exit();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Helpers/Utilities.cs b/Bloxstrap/Helpers/Utilities.cs
new file mode 100644
index 0000000..894cc71
--- /dev/null
+++ b/Bloxstrap/Helpers/Utilities.cs
@@ -0,0 +1,48 @@
+using System.Security.Cryptography;
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Bloxstrap.Helpers
+{
+ public class Utilities
+ {
+ public static async Task GetJson(string url)
+ {
+ using (HttpClient client = new())
+ {
+ client.DefaultRequestHeaders.Add("User-Agent", Program.ProjectRepository);
+
+ string jsonString = await client.GetStringAsync(url);
+ return (JObject)JsonConvert.DeserializeObject(jsonString);
+ }
+ }
+
+ public static string CalculateMD5(string filename)
+ {
+ using (MD5 md5 = MD5.Create())
+ {
+ using (FileStream stream = File.OpenRead(filename))
+ {
+ byte[] hash = md5.ComputeHash(stream);
+ return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
+ }
+ }
+ }
+
+ // quick and hacky way of getting a value from any key/value pair formatted list
+ // (command line args, uri params, etc)
+ public static string? GetKeyValue(string subject, string key, char delimiter)
+ {
+ if (subject.LastIndexOf(key) == -1)
+ return null;
+
+ string substr = subject.Substring(subject.LastIndexOf(key) + key.Length);
+
+ if (substr.IndexOf(delimiter) == -1)
+ return null;
+
+ return substr.Split(delimiter)[0];
+ }
+ }
+}
diff --git a/Bloxstrap/Program.cs b/Bloxstrap/Program.cs
index b293e03..1ea0dca 100644
--- a/Bloxstrap/Program.cs
+++ b/Bloxstrap/Program.cs
@@ -1,5 +1,5 @@
-using Microsoft.Win32;
using System.Diagnostics;
+using Microsoft.Win32;
using Bloxstrap.Helpers;
namespace Bloxstrap
@@ -19,11 +19,17 @@ namespace Bloxstrap
public static string? BaseDirectory;
public static string LocalAppData { get; private set; }
public static string FilePath { get; private set; }
+ public static string StartMenuDirectory { get; private set; }
public static bool IsFirstRun { get; private set; } = false;
public static SettingsFormat Settings;
public static SettingsManager SettingsManager = new();
+ public static void ShowMessageBox(MessageBoxIcon icon, string message)
+ {
+ MessageBox.Show(message, Program.ProjectName, MessageBoxButtons.OK, icon);
+ }
+
public static void Exit()
{
SettingsManager.Save();
@@ -40,16 +46,17 @@ namespace Bloxstrap
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
- // ensure only one is running
- Process[] processes = Process.GetProcessesByName(ProjectName);
- if (processes.Length > 1)
+ if (Process.GetProcessesByName(ProjectName).Length > 1)
+ {
+ ShowMessageBox(MessageBoxIcon.Error, $"{ProjectName} is already running. Please close any currently open {ProjectName} window.\nIf you have Discord Rich Presence enabled, then close Roblox if it's running.");
return;
+ }
- // Task.Run(() => Updater.CheckForUpdates()).Wait();
- // return;
+ UpdateChecker.Check().Wait();
- LocalAppData = Environment.GetEnvironmentVariable("localappdata");
+ LocalAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ // check if installed
RegistryKey? registryKey = Registry.CurrentUser.OpenSubKey($@"Software\{ProjectName}");
if (registryKey is null)
@@ -64,18 +71,20 @@ namespace Bloxstrap
registryKey.Close();
}
- // selection dialog was closed
+ // preferences dialog was closed, and so base directory was never set
// (this doesnt account for the registry value not existing but thats basically never gonna happen)
if (BaseDirectory is null)
return;
SettingsManager.SaveLocation = Path.Combine(BaseDirectory, "Settings.json");
FilePath = Path.Combine(BaseDirectory, $"{ProjectName}.exe");
+ StartMenuDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs", ProjectName);
// we shouldn't save settings on the first run until the first installation is finished,
// just in case the user decides to cancel the install
if (!IsFirstRun)
{
+ UpdateChecker.CheckInstalledVersion();
Settings = SettingsManager.Settings;
SettingsManager.ShouldSave = true;
}
@@ -107,7 +116,7 @@ namespace Bloxstrap
}
if (!String.IsNullOrEmpty(commandLine))
- new Bootstrapper(Settings.BootstrapperStyle, commandLine);
+ new Bootstrapper().Initialize(Settings.BootstrapperStyle, commandLine);
SettingsManager.Save();
}
diff --git a/Bloxstrap/Settings.cs b/Bloxstrap/Settings.cs
index ede8d38..83064fc 100644
--- a/Bloxstrap/Settings.cs
+++ b/Bloxstrap/Settings.cs
@@ -7,9 +7,11 @@ namespace Bloxstrap
public class SettingsFormat
{
public string VersionGuid { get; set; }
- public bool UseOldDeathSound { get; set; } = true;
+
public BootstrapperStyle BootstrapperStyle { get; set; } = BootstrapperStyle.ProgressDialog;
public BootstrapperIcon BootstrapperIcon { get; set; } = BootstrapperIcon.IconBloxstrap;
+ public bool UseDiscordRichPresence { get; set; } = true;
+ public bool UseOldDeathSound { get; set; } = true;
}
public class SettingsManager
diff --git a/DiscordRPC b/DiscordRPC
new file mode 160000
index 0000000..a9fcc8d
--- /dev/null
+++ b/DiscordRPC
@@ -0,0 +1 @@
+Subproject commit a9fcc8d1e85738bc6493474a62a961842fa8dbc3