diff --git a/Bloxstrap/Bloxstrap/App.xaml.cs b/Bloxstrap/Bloxstrap/App.xaml.cs
index c27cc75..84ab14a 100644
--- a/Bloxstrap/Bloxstrap/App.xaml.cs
+++ b/Bloxstrap/Bloxstrap/App.xaml.cs
@@ -1,10 +1,21 @@
-using System;
+using Bloxstrap.Models;
+using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
+using System.Globalization;
using System.Linq;
+using System.Net.Http;
+using System.Net;
+using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
+using System.Diagnostics;
+using Bloxstrap.Dialogs.Menu;
+using Bloxstrap.Enums;
+using Bloxstrap.Helpers;
+using Microsoft.Win32;
+using System.IO;
namespace Bloxstrap
{
@@ -13,5 +24,154 @@ namespace Bloxstrap
///
public partial class App : Application
{
+ public const StringComparison StringFormat = StringComparison.InvariantCulture;
+ public static readonly CultureInfo CultureFormat = CultureInfo.InvariantCulture;
+
+ public const string ProjectName = "Bloxstrap";
+ public const string ProjectRepository = "pizzaboxer/bloxstrap";
+
+ public static string BaseDirectory = null!;
+ public static bool IsFirstRun { get; private set; } = false;
+ public static bool IsQuiet { get; private set; } = false;
+ public static bool IsUninstall { get; private set; } = false;
+ public static bool IsNoLaunch { get; private set; } = false;
+ public static bool IsUpgrade { get; private set; } = false;
+ public static string[] LaunchArgs { get; private set; } = null!;
+
+
+ public static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2];
+
+ public static SettingsManager SettingsManager = new();
+ public static SettingsFormat Settings = SettingsManager.Settings;
+ public static readonly HttpClient HttpClient = new(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All });
+
+ // shorthand
+ public static MessageBoxResult ShowMessageBox(string message, MessageBoxImage icon = MessageBoxImage.None, MessageBoxButton buttons = MessageBoxButton.OK)
+ {
+ if (IsQuiet)
+ return MessageBoxResult.None;
+
+ return MessageBox.Show(message, ProjectName, buttons, icon);
+ }
+
+ public static void Terminate(int code = Bootstrapper.ERROR_SUCCESS)
+ {
+ SettingsManager.Save();
+ Environment.Exit(code);
+ }
+
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ base.OnStartup(e);
+
+ // To customize application configuration such as set high DPI settings or default font,
+ // see https://aka.ms/applicationconfiguration.
+ ApplicationConfiguration.Initialize();
+
+ LaunchArgs = e.Args;
+
+ HttpClient.Timeout = TimeSpan.FromMinutes(5);
+ HttpClient.DefaultRequestHeaders.Add("User-Agent", ProjectRepository);
+
+ if (LaunchArgs.Length > 0)
+ {
+ if (Array.IndexOf(LaunchArgs, "-quiet") != -1)
+ IsQuiet = true;
+
+ if (Array.IndexOf(LaunchArgs, "-uninstall") != -1)
+ IsUninstall = true;
+
+ if (Array.IndexOf(LaunchArgs, "-nolaunch") != -1)
+ IsNoLaunch = true;
+
+ if (Array.IndexOf(LaunchArgs, "-upgrade") != -1)
+ IsUpgrade = true;
+ }
+
+ // check if installed
+ RegistryKey? registryKey = Registry.CurrentUser.OpenSubKey($@"Software\{ProjectName}");
+
+ if (registryKey is null)
+ {
+ IsFirstRun = true;
+ Settings = SettingsManager.Settings;
+
+ if (IsQuiet)
+ BaseDirectory = Path.Combine(Directories.LocalAppData, ProjectName);
+ else
+ new Preferences().ShowDialog();
+ }
+ else
+ {
+ BaseDirectory = (string)registryKey.GetValue("InstallLocation")!;
+ registryKey.Close();
+ }
+
+ // 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 (String.IsNullOrEmpty(BaseDirectory))
+ return;
+
+ Directories.Initialize(BaseDirectory);
+
+ SettingsManager.SaveLocation = Path.Combine(Directories.Base, "Settings.json");
+
+ // 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)
+ {
+ Settings = SettingsManager.Settings;
+ SettingsManager.ShouldSave = true;
+ }
+
+#if !DEBUG
+ if (!IsUninstall && !IsFirstRun)
+ Updater.CheckInstalledVersion();
+#endif
+
+ string commandLine = "";
+
+#if false//DEBUG
+ new Preferences().ShowDialog();
+#else
+ if (LaunchArgs.Length > 0)
+ {
+ if (LaunchArgs[0] == "-preferences")
+ {
+ if (Process.GetProcessesByName(ProjectName).Length > 1)
+ {
+ ShowMessageBox($"{ProjectName} is already running. Please close any currently open Bloxstrap or Roblox window before opening the configuration menu.", MessageBoxImage.Error);
+ return;
+ }
+
+ new Preferences().ShowDialog();
+ }
+ else if (LaunchArgs[0].StartsWith("roblox-player:"))
+ {
+ commandLine = Protocol.ParseUri(LaunchArgs[0]);
+ }
+ else if (LaunchArgs[0].StartsWith("roblox:"))
+ {
+ commandLine = $"--app --deeplink {LaunchArgs[0]}";
+ }
+ else
+ {
+ commandLine = "--app";
+ }
+ }
+ else
+ {
+ commandLine = "--app";
+ }
+#endif
+
+ if (!String.IsNullOrEmpty(commandLine))
+ {
+ DeployManager.Channel = Settings.Channel;
+ Settings.BootstrapperStyle.Show(new Bootstrapper(commandLine));
+ }
+
+ SettingsManager.Save();
+ }
}
}
diff --git a/Bloxstrap/Bloxstrap/Bloxstrap.csproj b/Bloxstrap/Bloxstrap/Bloxstrap.csproj
index 4106cb0..7ea46c8 100644
--- a/Bloxstrap/Bloxstrap/Bloxstrap.csproj
+++ b/Bloxstrap/Bloxstrap/Bloxstrap.csproj
@@ -5,6 +5,26 @@
net6.0-windows
enable
true
+ True
+ Bloxstrap.ico
+ 2.0.0
+ 2.0.0.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Bloxstrap/Bloxstrap/Bloxstrap.ico b/Bloxstrap/Bloxstrap/Bloxstrap.ico
new file mode 100644
index 0000000..93904fb
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Bloxstrap.ico differ
diff --git a/Bloxstrap/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bloxstrap/Bootstrapper.cs
new file mode 100644
index 0000000..1d37f9e
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Bootstrapper.cs
@@ -0,0 +1,800 @@
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Net.Http;
+
+using Microsoft.Win32;
+
+using Bloxstrap.Enums;
+using Bloxstrap.Dialogs.BootstrapperDialogs;
+using Bloxstrap.Helpers;
+using Bloxstrap.Helpers.Integrations;
+using Bloxstrap.Helpers.RSMM;
+using Bloxstrap.Models;
+using System.Net;
+using Bloxstrap.Properties;
+using System.Threading.Tasks;
+using System.Linq;
+using System.Collections.Generic;
+using System;
+using System.Windows.Forms;
+using System.Windows;
+
+namespace Bloxstrap
+{
+ public partial class Bootstrapper
+ {
+ #region Properties
+
+ // https://learn.microsoft.com/en-us/windows/win32/msi/error-codes
+ public const int ERROR_SUCCESS = 0;
+ public const int ERROR_INSTALL_USEREXIT = 1602;
+ public const int ERROR_INSTALL_FAILURE = 1603;
+ public const int ERROR_PRODUCT_UNINSTALLED = 1614;
+
+ // 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";
+
+ private string? LaunchCommandLine;
+
+ private string VersionGuid = null!;
+ private PackageManifest VersionPackageManifest = null!;
+ private string VersionFolder = null!;
+
+ private readonly bool FreshInstall;
+
+ private double ProgressIncrement;
+ private long TotalBytes = 0;
+ private long TotalDownloadedBytes = 0;
+ private int PackagesExtracted = 0;
+ private bool CancelFired = false;
+
+ public IBootstrapperDialog Dialog = null!;
+ #endregion
+
+ #region Core
+ public Bootstrapper(string? launchCommandLine = null)
+ {
+ LaunchCommandLine = launchCommandLine;
+ FreshInstall = String.IsNullOrEmpty(App.Settings.VersionGuid);
+ }
+
+ // this is called from BootstrapperStyleForm.SetupDialog()
+ public async Task Run()
+ {
+ if (App.IsQuiet)
+ Dialog.CloseDialog();
+
+ if (App.IsUninstall)
+ {
+ Uninstall();
+ return;
+ }
+
+#if !DEBUG
+ if (!App.IsFirstRun && App.Settings.CheckForUpdates)
+ await CheckForUpdates();
+#endif
+
+ await CheckLatestVersion();
+
+ // if bloxstrap is installing for the first time but is running, prompt to close roblox
+ // if roblox needs updating but is running, ignore update for now
+ if (!Directory.Exists(VersionFolder) && CheckIfRunning(true) || App.Settings.VersionGuid != VersionGuid && !CheckIfRunning(false))
+ await InstallLatestVersion();
+
+ await ApplyModifications();
+
+ if (App.IsFirstRun)
+ App.SettingsManager.ShouldSave = true;
+
+ if (App.IsFirstRun || FreshInstall)
+ Register();
+
+ CheckInstall();
+
+ await RbxFpsUnlocker.CheckInstall();
+
+ App.SettingsManager.Save();
+
+ if (App.IsFirstRun && App.IsNoLaunch)
+ Dialog.ShowSuccess($"{App.ProjectName} has successfully installed");
+ else if (!App.IsNoLaunch)
+ await StartRoblox();
+ }
+
+ private async Task CheckForUpdates()
+ {
+ string currentVersion = $"Bloxstrap v{App.Version}";
+
+ var releaseInfo = await Utilities.GetJson($"https://api.github.com/repos/{App.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 App.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 App.LaunchArgs)
+ startInfo.ArgumentList.Add(arg);
+
+ App.SettingsManager.Save();
+
+ Process.Start(startInfo);
+
+ Environment.Exit(0);
+ }
+
+ private async Task CheckLatestVersion()
+ {
+ Dialog.Message = "Connecting to Roblox...";
+
+ ClientVersion clientVersion = await DeployManager.GetLastDeploy(App.Settings.Channel);
+ VersionGuid = clientVersion.VersionGuid;
+ VersionFolder = Path.Combine(Directories.Versions, VersionGuid);
+ VersionPackageManifest = await PackageManifest.Get(VersionGuid);
+ }
+
+ private bool CheckIfRunning(bool shutdown)
+ {
+ Process[] processes = Process.GetProcessesByName("RobloxPlayerBeta");
+
+ if (processes.Length == 0)
+ return false;
+
+ if (shutdown)
+ {
+ Dialog.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) { }
+ }
+
+ return true;
+ }
+
+ private async Task StartRoblox()
+ {
+ string startEventName = App.ProjectName.Replace(" ", "") + "StartEvent";
+
+ Dialog.Message = "Starting Roblox...";
+
+ if (LaunchCommandLine == "--app" && App.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();
+
+ if (App.Settings.Channel.ToLower() != DeployManager.DefaultChannel.ToLower())
+ LaunchCommandLine += " -channel " + App.Settings.Channel.ToLower();
+
+ LaunchCommandLine += " -startEvent " + startEventName;
+
+ using (SystemEvent startEvent = new(startEventName))
+ {
+ bool shouldWait = false;
+
+ Process gameClient = Process.Start(Path.Combine(VersionFolder, "RobloxPlayerBeta.exe"), LaunchCommandLine);
+ Process? rbxFpsUnlocker = null;
+ DiscordRichPresence? richPresence = null;
+
+ bool startEventFired = await startEvent.WaitForEvent();
+
+ startEvent.Close();
+
+ if (!startEventFired)
+ return;
+
+ if (App.Settings.RFUEnabled && Process.GetProcessesByName("rbxfpsunlocker").Length == 0)
+ {
+ ProcessStartInfo startInfo = new()
+ {
+ FileName = Path.Combine(Directories.Integrations, @"rbxfpsunlocker\rbxfpsunlocker.exe"),
+ WorkingDirectory = Path.Combine(Directories.Integrations, "rbxfpsunlocker")
+ };
+
+ rbxFpsUnlocker = Process.Start(startInfo);
+
+ if (App.Settings.RFUAutoclose)
+ shouldWait = true;
+ }
+
+ // event fired, wait for 3 seconds then close
+ await Task.Delay(3000);
+
+ // now we move onto handling rich presence
+ if (App.Settings.UseDiscordRichPresence)
+ {
+ richPresence = new DiscordRichPresence();
+ richPresence.MonitorGameActivity();
+
+ shouldWait = true;
+ }
+
+ if (!shouldWait)
+ return;
+
+ // keep bloxstrap open in the background
+ Dialog.CloseDialog();
+ await gameClient.WaitForExitAsync();
+
+ if (richPresence is not null)
+ richPresence.Dispose();
+
+ if (App.Settings.RFUAutoclose && rbxFpsUnlocker is not null)
+ rbxFpsUnlocker.Kill();
+ }
+ }
+
+ public void CancelButtonClicked()
+ {
+ if (!Dialog.CancelEnabled)
+ {
+ App.Terminate(ERROR_INSTALL_USEREXIT);
+ return;
+ }
+
+ CancelFired = true;
+
+ try
+ {
+ if (App.IsFirstRun)
+ Directory.Delete(Directories.Base, true);
+ else if (Directory.Exists(VersionFolder))
+ Directory.Delete(VersionFolder, true);
+ }
+ catch (Exception) { }
+
+ App.Terminate(ERROR_INSTALL_USEREXIT);
+ }
+#endregion
+
+#region App Install
+ public static void Register()
+ {
+ RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{App.ProjectName}");
+
+ // new install location selected, delete old one
+ string? oldInstallLocation = (string?)applicationKey.GetValue("OldInstallLocation");
+ if (!String.IsNullOrEmpty(oldInstallLocation) && oldInstallLocation != Directories.Base)
+ {
+ try
+ {
+ if (Directory.Exists(oldInstallLocation))
+ Directory.Delete(oldInstallLocation, true);
+ }
+ catch (Exception) { }
+
+ applicationKey.DeleteValue("OldInstallLocation");
+ }
+
+ applicationKey.SetValue("InstallLocation", Directories.Base);
+ applicationKey.Close();
+
+ // set uninstall key
+ RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{App.ProjectName}");
+ uninstallKey.SetValue("DisplayIcon", $"{Directories.App},0");
+ uninstallKey.SetValue("DisplayName", App.ProjectName);
+ uninstallKey.SetValue("DisplayVersion", App.Version);
+
+ if (uninstallKey.GetValue("InstallDate") is null)
+ uninstallKey.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
+
+ uninstallKey.SetValue("InstallLocation", Directories.Base);
+ uninstallKey.SetValue("NoRepair", 1);
+ uninstallKey.SetValue("Publisher", "pizzaboxer");
+ uninstallKey.SetValue("ModifyPath", $"\"{Directories.App}\" -preferences");
+ uninstallKey.SetValue("QuietUninstallString", $"\"{Directories.App}\" -uninstall -quiet");
+ uninstallKey.SetValue("UninstallString", $"\"{Directories.App}\" -uninstall");
+ uninstallKey.SetValue("URLInfoAbout", $"https://github.com/{App.ProjectRepository}");
+ uninstallKey.SetValue("URLUpdateInfo", $"https://github.com/{App.ProjectRepository}/releases/latest");
+ 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", Directories.App);
+ Protocol.Register("roblox-player", "Roblox", Directories.App);
+
+ // in case the user is reinstalling
+ if (File.Exists(Directories.App) && App.IsFirstRun)
+ File.Delete(Directories.App);
+
+ // check to make sure bootstrapper is in the install folder
+ if (!File.Exists(Directories.App) && Environment.ProcessPath is not null)
+ File.Copy(Environment.ProcessPath, Directories.App);
+
+ // 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(Directories.StartMenu))
+ {
+ Directory.CreateDirectory(Directories.StartMenu);
+
+ ShellLink.Shortcut.CreateShortcut(Directories.App, "", Directories.App, 0)
+ .WriteToFile(Path.Combine(Directories.StartMenu, "Play Roblox.lnk"));
+
+ ShellLink.Shortcut.CreateShortcut(Directories.App, "-preferences", Directories.App, 0)
+ .WriteToFile(Path.Combine(Directories.StartMenu, $"Configure {App.ProjectName}.lnk"));
+ }
+
+ if (App.Settings.CreateDesktopIcon && !File.Exists(Path.Combine(Directories.Desktop, "Play Roblox.lnk")))
+ {
+ ShellLink.Shortcut.CreateShortcut(Directories.App, "", Directories.App, 0)
+ .WriteToFile(Path.Combine(Directories.Desktop, "Play Roblox.lnk"));
+ }
+ }
+
+ private void Uninstall()
+ {
+ CheckIfRunning(true);
+
+ Dialog.Message = $"Uninstalling {App.ProjectName}...";
+
+ App.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\{App.ProjectName}");
+
+ // delete start menu folder
+ Directory.Delete(Directories.StartMenu, true);
+
+ // delete desktop shortcut
+ File.Delete(Path.Combine(Directories.Desktop, "Play Roblox.lnk"));
+
+ // delete uninstall key
+ Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{App.ProjectName}");
+
+ // delete installation folder
+ // (should delete everything except bloxstrap itself)
+ Directory.Delete(Directories.Base, true);
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine($"Could not fully uninstall! ({e})");
+ }
+
+ Dialog.ShowSuccess($"{App.ProjectName} has succesfully uninstalled");
+
+ App.Terminate();
+ }
+#endregion
+
+#region Roblox Install
+ private void UpdateProgressbar()
+ {
+ int newProgress = (int)Math.Floor(ProgressIncrement * TotalDownloadedBytes);
+ Dialog.ProgressValue = newProgress;
+ }
+
+ private async Task InstallLatestVersion()
+ {
+ if (FreshInstall)
+ Dialog.Message = "Installing Roblox...";
+ else
+ Dialog.Message = "Upgrading Roblox...";
+
+ // check if we have at least 300 megabytes of free disk space
+ if (Utilities.GetFreeDiskSpace(Directories.Base) < 1024*1024*300)
+ {
+ App.ShowMessageBox($"{App.ProjectName} requires at least 300 MB of disk space to install Roblox. Please free up some disk space and try again.", MessageBoxImage.Error);
+ App.Terminate(ERROR_INSTALL_FAILURE);
+ return;
+ }
+
+ Directory.CreateDirectory(Directories.Base);
+
+ Dialog.CancelEnabled = true;
+ Dialog.ProgressStyle = ProgressBarStyle.Continuous;
+
+ // 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 synchronously
+ await DownloadPackage(package);
+
+ // extract the package immediately after download
+ ExtractPackage(package);
+ }
+
+ // allow progress bar to 100% before continuing (purely ux reasons lol)
+ await Task.Delay(1000);
+
+ Dialog.ProgressStyle = ProgressBarStyle.Marquee;
+
+ Dialog.Message = "Configuring Roblox...";
+
+ // wait for all packages to finish extracting
+ while (PackagesExtracted < VersionPackageManifest.Count)
+ {
+ await Task.Delay(100);
+ }
+
+ string appSettingsLocation = Path.Combine(VersionFolder, "AppSettings.xml");
+ await File.WriteAllTextAsync(appSettingsLocation, AppSettings);
+
+ if (!FreshInstall)
+ {
+ ReShade.SynchronizeConfigFile();
+
+ // let's take this opportunity to delete any packages we don't need anymore
+ foreach (string filename in Directory.GetFiles(Directories.Downloads))
+ {
+ if (!VersionPackageManifest.Exists(package => filename.Contains(package.Signature)))
+ File.Delete(filename);
+ }
+
+ string oldVersionFolder = Path.Combine(Directories.Versions, App.Settings.VersionGuid);
+
+ if (VersionGuid != App.Settings.VersionGuid && Directory.Exists(oldVersionFolder))
+ {
+ // and also to delete our old version folder
+ Directory.Delete(oldVersionFolder, true);
+ }
+ }
+
+ Dialog.CancelEnabled = false;
+
+ App.Settings.VersionGuid = VersionGuid;
+ }
+
+ private async Task ApplyModifications()
+ {
+ Dialog.Message = "Applying Roblox modifications...";
+
+ string modFolder = Path.Combine(Directories.Modifications);
+ string manifestFile = Path.Combine(Directories.Base, "ModManifest.txt");
+
+ List manifestFiles = new();
+ List modFolderFiles = new();
+
+ if (!Directory.Exists(modFolder))
+ Directory.CreateDirectory(modFolder);
+
+ await CheckModPreset(App.Settings.UseOldDeathSound, @"content\sounds\ouch.ogg", "OldDeath.ogg");
+ await CheckModPreset(App.Settings.UseOldMouseCursor, @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", "OldCursor.png");
+ await CheckModPreset(App.Settings.UseOldMouseCursor, @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", "OldFarCursor.png");
+ await CheckModPreset(App.Settings.UseDisableAppPatch, @"ExtraContent\places\Mobile.rbxl", "");
+
+ await ReShade.CheckModifications();
+
+ foreach (string file in Directory.GetFiles(modFolder, "*.*", SearchOption.AllDirectories))
+ {
+ // get relative directory path
+ string relativeFile = file.Substring(modFolder.Length + 1);
+
+ // v1.7.0 - README has been moved to the preferences menu now
+ if (relativeFile == "README.txt")
+ {
+ File.Delete(file);
+ continue;
+ }
+
+ modFolderFiles.Add(relativeFile);
+ }
+
+ // the manifest is primarily here to keep track of what files have been
+ // deleted from the modifications folder, so that we know when to restore the
+ // original files from the downloaded packages
+
+ if (File.Exists(manifestFile))
+ manifestFiles = (await File.ReadAllLinesAsync(manifestFile)).ToList();
+ else
+ manifestFiles = modFolderFiles;
+
+ // copy and overwrite
+ foreach (string file in modFolderFiles)
+ {
+ string fileModFolder = Path.Combine(modFolder, file);
+ string fileVersionFolder = Path.Combine(VersionFolder, file);
+
+ if (File.Exists(fileVersionFolder))
+ {
+ if (Utilities.MD5File(fileModFolder) == Utilities.MD5File(fileVersionFolder))
+ continue;
+ }
+
+ string? directory = Path.GetDirectoryName(fileVersionFolder);
+
+ if (directory is null)
+ continue;
+
+ Directory.CreateDirectory(directory);
+
+ File.Copy(fileModFolder, fileVersionFolder, true);
+ File.SetAttributes(fileVersionFolder, File.GetAttributes(fileModFolder) & ~FileAttributes.ReadOnly);
+ }
+
+ // 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))
+ continue;
+
+ KeyValuePair packageDirectory;
+
+ try
+ {
+ packageDirectory = PackageDirectories.First(x => x.Key != "RobloxApp.zip" && fileLocation.StartsWith(x.Value));
+ }
+ catch (InvalidOperationException)
+ {
+ // package doesn't exist, likely mistakenly placed file
+ string versionFileLocation = Path.Combine(VersionFolder, fileLocation);
+
+ File.Delete(versionFileLocation);
+
+ continue;
+ }
+
+ // restore original file
+ string fileName = fileLocation.Substring(packageDirectory.Value.Length);
+ ExtractFileFromPackage(packageDirectory.Key, fileName);
+ }
+
+ File.WriteAllLines(manifestFile, modFolderFiles);
+ }
+
+ private static async Task CheckModPreset(bool condition, string location, string name)
+ {
+ string modFolderLocation = Path.Combine(Directories.Modifications, location);
+ byte[] binaryData = string.IsNullOrEmpty(name) ? Array.Empty() : await ResourceHelper.Get(name);
+
+ if (condition)
+ {
+ if (!File.Exists(modFolderLocation))
+ {
+ string? directory = Path.GetDirectoryName(modFolderLocation);
+
+ if (directory is null)
+ return;
+
+ Directory.CreateDirectory(directory);
+
+ await File.WriteAllBytesAsync(modFolderLocation, binaryData);
+ }
+ }
+ else if (File.Exists(modFolderLocation) && Utilities.MD5File(modFolderLocation) == Utilities.MD5Data(binaryData))
+ {
+ File.Delete(modFolderLocation);
+ }
+ }
+
+ private async Task DownloadPackage(Package package)
+ {
+ string packageUrl = $"{DeployManager.BaseUrl}/{VersionGuid}-{package.Name}";
+ string packageLocation = Path.Combine(Directories.Downloads, package.Signature);
+ string robloxPackageLocation = Path.Combine(Directories.LocalAppData, "Roblox", "Downloads", package.Signature);
+
+ if (File.Exists(packageLocation))
+ {
+ FileInfo file = new(packageLocation);
+
+ string calculatedMD5 = Utilities.MD5File(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...");
+ TotalDownloadedBytes += package.PackedSize;
+ UpdateProgressbar();
+ 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);
+ TotalDownloadedBytes += package.PackedSize;
+ UpdateProgressbar();
+ return;
+ }
+
+ if (!File.Exists(packageLocation))
+ {
+ Debug.WriteLine($"Downloading {package.Name}...");
+
+ if (CancelFired)
+ return;
+
+ var response = await App.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))
+ {
+ 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}!");
+ }
+ }
+
+ private async void ExtractPackage(Package package)
+ {
+ if (CancelFired)
+ return;
+
+ string packageLocation = Path.Combine(Directories.Downloads, package.Signature);
+ string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
+ string extractPath;
+ string? directory;
+
+ Debug.WriteLine($"Extracting {package.Name} to {packageFolder}...");
+
+ using (ZipArchive archive = await Task.Run(() => 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 = Path.GetDirectoryName(extractPath);
+
+ if (directory is null)
+ continue;
+
+ Directory.CreateDirectory(directory);
+
+ await Task.Run(() => entry.ExtractToFile(extractPath, true));
+ }
+ }
+
+ Debug.WriteLine($"Finished extracting {package.Name}");
+
+ PackagesExtracted += 1;
+ }
+
+ private void ExtractFileFromPackage(string packageName, string fileName)
+ {
+ Package? package = VersionPackageManifest.Find(x => x.Name == packageName);
+
+ if (package is null)
+ return;
+
+ DownloadPackage(package).GetAwaiter().GetResult();
+
+ string packageLocation = Path.Combine(Directories.Downloads, package.Signature);
+ string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
+
+ using (ZipArchive archive = ZipFile.OpenRead(packageLocation))
+ {
+ ZipArchiveEntry? entry = archive.Entries.Where(x => x.FullName == fileName).FirstOrDefault();
+
+ if (entry is null)
+ return;
+
+ string fileLocation = Path.Combine(packageFolder, entry.FullName);
+
+ File.Delete(fileLocation);
+
+ entry.ExtractToFile(fileLocation);
+ }
+ }
+#endregion
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/BootstrapperDialogForm.cs b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/BootstrapperDialogForm.cs
new file mode 100644
index 0000000..94bcc2e
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/BootstrapperDialogForm.cs
@@ -0,0 +1,163 @@
+using Bloxstrap.Enums;
+using Bloxstrap.Helpers;
+using System.Windows.Forms;
+using System.Threading.Tasks;
+using System.Windows;
+using System;
+
+namespace Bloxstrap.Dialogs.BootstrapperDialogs
+{
+ public class BootstrapperDialogForm : Form, IBootstrapperDialog
+ {
+ public Bootstrapper? Bootstrapper { get; set; }
+
+ protected virtual string _message { get; set; } = "Please wait...";
+ protected virtual ProgressBarStyle _progressStyle { get; set; }
+ protected virtual int _progressValue { get; set; }
+ protected virtual bool _cancelEnabled { get; set; }
+
+ public string Message
+ {
+ get => _message;
+ set
+ {
+ if (this.InvokeRequired)
+ this.Invoke(() => _message = value);
+ else
+ _message = value;
+ }
+ }
+
+ public ProgressBarStyle ProgressStyle
+ {
+ get => _progressStyle;
+ set
+ {
+ if (this.InvokeRequired)
+ this.Invoke(() => _progressStyle = value);
+ else
+ _progressStyle = value;
+ }
+ }
+
+ public int ProgressValue
+ {
+ get => _progressValue;
+ set
+ {
+ if (this.InvokeRequired)
+ this.Invoke(() => _progressValue = value);
+ else
+ _progressValue = value;
+ }
+ }
+
+ public bool CancelEnabled
+ {
+ get => _cancelEnabled;
+ set
+ {
+ if (this.InvokeRequired)
+ this.Invoke(() => _cancelEnabled = value);
+ else
+ _cancelEnabled = value;
+ }
+ }
+
+ 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 (App.IsQuiet)
+ this.Hide();
+
+ this.Text = App.ProjectName;
+ this.Icon = App.Settings.BootstrapperIcon.GetIcon();
+
+ if (Bootstrapper is null)
+ {
+ Message = "Style Preview - Click Cancel to return";
+ CancelEnabled = true;
+ }
+ else
+ {
+ Bootstrapper.Dialog = this;
+ Task.Run(() => RunBootstrapper());
+ }
+ }
+
+
+ public async void RunBootstrapper()
+ {
+ if (Bootstrapper is null)
+ return;
+
+#if DEBUG
+ await Bootstrapper.Run();
+#else
+ try
+ {
+ await Bootstrapper.Run();
+ }
+ catch (Exception ex)
+ {
+ // string message = String.Format("{0}: {1}", ex.GetType(), ex.Message);
+ string message = ex.ToString();
+ ShowError(message);
+ }
+#endif
+
+ App.Terminate();
+ }
+
+ public virtual void ShowSuccess(string message)
+ {
+ App.ShowMessageBox(message, MessageBoxImage.Information);
+ App.Terminate();
+ }
+
+ public virtual void ShowError(string message)
+ {
+ App.ShowMessageBox($"An error occurred while starting Roblox\n\nDetails: {message}", MessageBoxImage.Error);
+ App.Terminate(Bootstrapper.ERROR_INSTALL_FAILURE);
+ }
+
+ public virtual void CloseDialog()
+ {
+ if (this.InvokeRequired)
+ this.Invoke(CloseDialog);
+ else
+ this.Hide();
+ }
+
+ public void PromptShutdown()
+ {
+ MessageBoxResult result = App.ShowMessageBox(
+ "Roblox is currently running, but needs to close. Would you like close Roblox now?",
+ MessageBoxImage.Information,
+ MessageBoxButton.OKCancel
+ );
+
+ if (result != MessageBoxResult.OK)
+ Environment.Exit(Bootstrapper.ERROR_INSTALL_USEREXIT);
+ }
+
+ public void ButtonCancel_Click(object? sender, EventArgs e)
+ {
+ if (Bootstrapper is null)
+ this.Close();
+ else
+ Task.Run(() => Bootstrapper.CancelButtonClicked());
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/IBootstrapperDialog.cs b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/IBootstrapperDialog.cs
new file mode 100644
index 0000000..772ecee
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/IBootstrapperDialog.cs
@@ -0,0 +1,20 @@
+using System.Windows.Forms;
+
+namespace Bloxstrap.Dialogs.BootstrapperDialogs
+{
+ public interface IBootstrapperDialog
+ {
+ Bootstrapper? Bootstrapper { get; set; }
+
+ string Message { get; set; }
+ ProgressBarStyle ProgressStyle { get; set; }
+ int ProgressValue { get; set; }
+ bool CancelEnabled { get; set; }
+
+ void RunBootstrapper();
+ void ShowSuccess(string message);
+ void ShowError(string message);
+ void CloseDialog();
+ void PromptShutdown();
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.Designer.cs b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.Designer.cs
new file mode 100644
index 0000000..af757c5
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.Designer.cs
@@ -0,0 +1,96 @@
+using System.Windows.Forms;
+
+namespace Bloxstrap.Dialogs.BootstrapperDialogs
+{
+ partial class LegacyDialog2009
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.labelMessage = new System.Windows.Forms.Label();
+ this.ProgressBar = new System.Windows.Forms.ProgressBar();
+ this.buttonCancel = new System.Windows.Forms.Button();
+ this.SuspendLayout();
+ //
+ // labelMessage
+ //
+ this.labelMessage.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.labelMessage.Location = new System.Drawing.Point(12, 16);
+ this.labelMessage.Name = "labelMessage";
+ this.labelMessage.Size = new System.Drawing.Size(287, 17);
+ this.labelMessage.TabIndex = 0;
+ this.labelMessage.Text = "Please wait...";
+ //
+ // ProgressBar
+ //
+ this.ProgressBar.Location = new System.Drawing.Point(15, 47);
+ this.ProgressBar.MarqueeAnimationSpeed = 33;
+ this.ProgressBar.Name = "ProgressBar";
+ this.ProgressBar.Size = new System.Drawing.Size(281, 20);
+ this.ProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
+ this.ProgressBar.TabIndex = 1;
+ //
+ // buttonCancel
+ //
+ this.buttonCancel.Enabled = false;
+ this.buttonCancel.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.buttonCancel.Location = new System.Drawing.Point(221, 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.Click += new System.EventHandler(this.ButtonCancel_Click);
+ //
+ // LegacyDialog2009
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(311, 122);
+ this.Controls.Add(this.buttonCancel);
+ this.Controls.Add(this.ProgressBar);
+ this.Controls.Add(this.labelMessage);
+ this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+ this.MaximizeBox = false;
+ this.MaximumSize = new System.Drawing.Size(327, 161);
+ this.MinimizeBox = false;
+ this.MinimumSize = new System.Drawing.Size(327, 161);
+ this.Name = "LegacyDialog2009";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "LegacyDialog2009";
+ this.Load += new System.EventHandler(this.LegacyDialog2009_Load);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private Label labelMessage;
+ private ProgressBar ProgressBar;
+ private Button buttonCancel;
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.cs b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.cs
new file mode 100644
index 0000000..0592603
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Windows.Forms;
+
+namespace Bloxstrap.Dialogs.BootstrapperDialogs
+{
+ // windows: https://youtu.be/VpduiruysuM?t=18
+ // mac: https://youtu.be/ncHhbcVDRgQ?t=63
+
+ public partial class LegacyDialog2009 : BootstrapperDialogForm
+ {
+ protected override string _message
+ {
+ get => labelMessage.Text;
+ set => labelMessage.Text = value;
+ }
+
+ protected override ProgressBarStyle _progressStyle
+ {
+ get => ProgressBar.Style;
+ set => ProgressBar.Style = value;
+ }
+
+ protected override int _progressValue
+ {
+ get => ProgressBar.Value;
+ set => ProgressBar.Value = value;
+ }
+
+ protected override bool _cancelEnabled
+ {
+ get => this.buttonCancel.Enabled;
+ set => this.buttonCancel.Enabled = value;
+ }
+
+ public LegacyDialog2009(Bootstrapper? bootstrapper = null)
+ {
+ InitializeComponent();
+
+ Bootstrapper = bootstrapper;
+
+ ScaleWindow();
+ SetupDialog();
+ }
+
+ private void LegacyDialog2009_Load(object sender, EventArgs e)
+ {
+ this.Activate();
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.resx b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.resx
new file mode 100644
index 0000000..f298a7b
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2009.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.Designer.cs b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.Designer.cs
new file mode 100644
index 0000000..2c490ce
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.Designer.cs
@@ -0,0 +1,111 @@
+using System.Windows.Forms;
+
+namespace Bloxstrap.Dialogs.BootstrapperDialogs
+{
+ partial class LegacyDialog2011
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.labelMessage = new System.Windows.Forms.Label();
+ this.ProgressBar = new System.Windows.Forms.ProgressBar();
+ this.IconBox = new System.Windows.Forms.PictureBox();
+ this.buttonCancel = new System.Windows.Forms.Button();
+ ((System.ComponentModel.ISupportInitialize)(this.IconBox)).BeginInit();
+ this.SuspendLayout();
+ //
+ // labelMessage
+ //
+ this.labelMessage.Location = new System.Drawing.Point(55, 23);
+ this.labelMessage.Name = "labelMessage";
+ this.labelMessage.Size = new System.Drawing.Size(287, 17);
+ this.labelMessage.TabIndex = 0;
+ this.labelMessage.Text = "Please wait...";
+ //
+ // ProgressBar
+ //
+ this.ProgressBar.Location = new System.Drawing.Point(58, 51);
+ this.ProgressBar.MarqueeAnimationSpeed = 33;
+ this.ProgressBar.Name = "ProgressBar";
+ this.ProgressBar.Size = new System.Drawing.Size(287, 26);
+ this.ProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
+ this.ProgressBar.TabIndex = 1;
+ //
+ // IconBox
+ //
+ this.IconBox.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
+ this.IconBox.ImageLocation = "";
+ this.IconBox.Location = new System.Drawing.Point(19, 16);
+ this.IconBox.Name = "IconBox";
+ this.IconBox.Size = new System.Drawing.Size(32, 32);
+ this.IconBox.TabIndex = 2;
+ this.IconBox.TabStop = false;
+ //
+ // buttonCancel
+ //
+ 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);
+ //
+ // LegacyDialog2011
+ //
+ 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.buttonCancel);
+ this.Controls.Add(this.IconBox);
+ this.Controls.Add(this.ProgressBar);
+ this.Controls.Add(this.labelMessage);
+ this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+ this.MaximizeBox = false;
+ this.MaximumSize = new System.Drawing.Size(378, 170);
+ this.MinimizeBox = false;
+ this.MinimumSize = new System.Drawing.Size(378, 170);
+ this.Name = "LegacyDialog2011";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "LegacyDialog2011";
+ this.Load += new System.EventHandler(this.LegacyDialog2011_Load);
+ ((System.ComponentModel.ISupportInitialize)(this.IconBox)).EndInit();
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private Label labelMessage;
+ private ProgressBar ProgressBar;
+ private PictureBox IconBox;
+ private Button buttonCancel;
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.cs b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.cs
new file mode 100644
index 0000000..03f34db
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.cs
@@ -0,0 +1,53 @@
+using Bloxstrap.Enums;
+using System;
+using System.Windows.Forms;
+
+namespace Bloxstrap.Dialogs.BootstrapperDialogs
+{
+ // https://youtu.be/3K9oCEMHj2s?t=35
+
+ public partial class LegacyDialog2011 : BootstrapperDialogForm
+ {
+ protected override string _message
+ {
+ get => labelMessage.Text;
+ set => labelMessage.Text = value;
+ }
+
+ protected override ProgressBarStyle _progressStyle
+ {
+ get => ProgressBar.Style;
+ set => ProgressBar.Style = value;
+ }
+
+ protected override int _progressValue
+ {
+ get => ProgressBar.Value;
+ set => ProgressBar.Value = value;
+ }
+
+ protected override bool _cancelEnabled
+ {
+ get => this.buttonCancel.Enabled;
+ set => this.buttonCancel.Enabled = this.buttonCancel.Visible = value;
+ }
+
+ public LegacyDialog2011(Bootstrapper? bootstrapper = null)
+ {
+ InitializeComponent();
+
+ Bootstrapper = bootstrapper;
+
+ // have to convert icon -> bitmap since winforms scaling is poop
+ this.IconBox.BackgroundImage = App.Settings.BootstrapperIcon.GetIcon().ToBitmap();
+
+ ScaleWindow();
+ SetupDialog();
+ }
+
+ private void LegacyDialog2011_Load(object sender, EventArgs e)
+ {
+ this.Activate();
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.resx b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.resx
new file mode 100644
index 0000000..f298a7b
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/LegacyDialog2011.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/ProgressDialog.Designer.cs b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/ProgressDialog.Designer.cs
new file mode 100644
index 0000000..3beb221
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/ProgressDialog.Designer.cs
@@ -0,0 +1,130 @@
+using System.Windows.Forms;
+
+namespace Bloxstrap.Dialogs.BootstrapperDialogs
+{
+ partial class ProgressDialog
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.ProgressBar = new System.Windows.Forms.ProgressBar();
+ this.labelMessage = new System.Windows.Forms.Label();
+ this.IconBox = 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.buttonCancel)).BeginInit();
+ this.panel1.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // ProgressBar
+ //
+ this.ProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
+ this.ProgressBar.Location = new System.Drawing.Point(29, 241);
+ this.ProgressBar.MarqueeAnimationSpeed = 20;
+ this.ProgressBar.Name = "ProgressBar";
+ this.ProgressBar.Size = new System.Drawing.Size(460, 20);
+ this.ProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
+ this.ProgressBar.TabIndex = 0;
+ //
+ // labelMessage
+ //
+ this.labelMessage.Font = new System.Drawing.Font("Tahoma", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.labelMessage.Location = new System.Drawing.Point(29, 199);
+ this.labelMessage.Name = "labelMessage";
+ this.labelMessage.Size = new System.Drawing.Size(460, 18);
+ this.labelMessage.TabIndex = 1;
+ this.labelMessage.Text = "Please wait...";
+ this.labelMessage.TextAlign = System.Drawing.ContentAlignment.TopCenter;
+ this.labelMessage.UseMnemonic = false;
+ //
+ // IconBox
+ //
+ this.IconBox.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
+ this.IconBox.ImageLocation = "";
+ this.IconBox.Location = new System.Drawing.Point(212, 66);
+ this.IconBox.Name = "IconBox";
+ this.IconBox.Size = new System.Drawing.Size(92, 92);
+ this.IconBox.TabIndex = 2;
+ this.IconBox.TabStop = false;
+ //
+ // buttonCancel
+ //
+ 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.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
+ 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.labelMessage);
+ this.panel1.Controls.Add(this.IconBox);
+ 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";
+ this.panel1.Size = new System.Drawing.Size(518, 318);
+ this.panel1.TabIndex = 4;
+ //
+ // ProgressDialog
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.BackColor = System.Drawing.SystemColors.ActiveBorder;
+ this.ClientSize = new System.Drawing.Size(520, 320);
+ this.Controls.Add(this.panel1);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
+ this.MaximumSize = new System.Drawing.Size(520, 320);
+ this.MinimumSize = new System.Drawing.Size(520, 320);
+ this.Name = "ProgressDialog";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "ProgressDialog";
+ this.Load += new System.EventHandler(this.ProgressDialog_Load);
+ ((System.ComponentModel.ISupportInitialize)(this.IconBox)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.buttonCancel)).EndInit();
+ this.panel1.ResumeLayout(false);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private ProgressBar ProgressBar;
+ private Label labelMessage;
+ private PictureBox IconBox;
+ private PictureBox buttonCancel;
+ private Panel panel1;
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/ProgressDialog.cs b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/ProgressDialog.cs
new file mode 100644
index 0000000..74ba41d
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/ProgressDialog.cs
@@ -0,0 +1,84 @@
+using Bloxstrap.Enums;
+using System;
+using System.Windows.Forms;
+using System.Drawing;
+
+namespace Bloxstrap.Dialogs.BootstrapperDialogs
+{
+ // basically just the modern dialog
+
+ public partial class ProgressDialog : BootstrapperDialogForm
+ {
+ protected override string _message
+ {
+ get => labelMessage.Text;
+ set => labelMessage.Text = value;
+ }
+
+ protected override ProgressBarStyle _progressStyle
+ {
+ get => ProgressBar.Style;
+ set => ProgressBar.Style = value;
+ }
+
+ protected override int _progressValue
+ {
+ get => ProgressBar.Value;
+ set => ProgressBar.Value = value;
+ }
+
+ protected override bool _cancelEnabled
+ {
+ get => this.buttonCancel.Enabled;
+ set => this.buttonCancel.Enabled = this.buttonCancel.Visible = value;
+ }
+
+ public ProgressDialog(Bootstrapper? bootstrapper = null)
+ {
+ InitializeComponent();
+
+ if (App.Settings.Theme.GetFinal() == Theme.Dark)
+ {
+ this.labelMessage.ForeColor = SystemColors.Window;
+ this.buttonCancel.Image = Properties.Resources.DarkCancelButton;
+ this.panel1.BackColor = Color.FromArgb(35, 37, 39);
+ this.BackColor = Color.FromArgb(25, 27, 29);
+ }
+
+ Bootstrapper = bootstrapper;
+
+ this.IconBox.BackgroundImage = App.Settings.BootstrapperIcon.GetBitmap();
+
+ SetupDialog();
+ }
+
+ private void ButtonCancel_MouseEnter(object sender, EventArgs e)
+ {
+ if (App.Settings.Theme.GetFinal() == Theme.Dark)
+ {
+ this.buttonCancel.Image = Properties.Resources.DarkCancelButtonHover;
+ }
+ else
+ {
+ this.buttonCancel.Image = Properties.Resources.CancelButtonHover;
+ }
+ }
+
+ private void ButtonCancel_MouseLeave(object sender, EventArgs e)
+ {
+ if (App.Settings.Theme.GetFinal() == Theme.Dark)
+ {
+ this.buttonCancel.Image = Properties.Resources.DarkCancelButton;
+ }
+ else
+ {
+ this.buttonCancel.Image = Properties.Resources.CancelButton;
+ }
+ }
+
+ private void ProgressDialog_Load(object sender, EventArgs e)
+ {
+ this.Activate();
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/ProgressDialog.resx b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/ProgressDialog.resx
new file mode 100644
index 0000000..f298a7b
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/ProgressDialog.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/VistaDialog.Designer.cs b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/VistaDialog.Designer.cs
new file mode 100644
index 0000000..e2d27d5
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/VistaDialog.Designer.cs
@@ -0,0 +1,51 @@
+namespace Bloxstrap.Dialogs.BootstrapperDialogs
+{
+ partial class VistaDialog
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.SuspendLayout();
+ //
+ // VistaDialog
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(0, 0);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
+ this.Name = "VistaDialog";
+ this.Opacity = 0D;
+ this.ShowInTaskbar = false;
+ this.Text = "VistaDialog";
+ this.WindowState = System.Windows.Forms.FormWindowState.Minimized;
+ this.Load += new System.EventHandler(this.VistaDialog_Load);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/VistaDialog.cs b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/VistaDialog.cs
new file mode 100644
index 0000000..35ee378
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/VistaDialog.cs
@@ -0,0 +1,167 @@
+using Bloxstrap.Enums;
+using System.Windows.Forms;
+using System;
+
+namespace Bloxstrap.Dialogs.BootstrapperDialogs
+{
+ // https://youtu.be/h0_AL95Sc3o?t=48
+
+ // a bit hacky, but this is actually a hidden form
+ // since taskdialog is part of winforms, it can't really be properly used without a form
+ // for example, cross-threaded calls to ui controls can't really be done outside of a form
+
+ public partial class VistaDialog : BootstrapperDialogForm
+ {
+ private TaskDialogPage Dialog;
+
+ protected override string _message
+ {
+ get => Dialog.Heading ?? "";
+ set => Dialog.Heading = value;
+ }
+
+ protected override ProgressBarStyle _progressStyle
+ {
+ set
+ {
+ if (Dialog.ProgressBar is null)
+ return;
+
+ switch (value)
+ {
+ case ProgressBarStyle.Continuous:
+ case ProgressBarStyle.Blocks:
+ Dialog.ProgressBar.State = TaskDialogProgressBarState.Normal;
+ break;
+
+ case ProgressBarStyle.Marquee:
+ Dialog.ProgressBar.State = TaskDialogProgressBarState.Marquee;
+ break;
+ }
+ }
+ }
+
+ protected override int _progressValue
+ {
+ get => Dialog.ProgressBar is null ? 0 : Dialog.ProgressBar.Value;
+ set
+ {
+ if (Dialog.ProgressBar is null)
+ return;
+
+ Dialog.ProgressBar.Value = value;
+ }
+ }
+
+ protected override bool _cancelEnabled
+ {
+ get => Dialog.Buttons[0].Enabled;
+ set => Dialog.Buttons[0].Enabled = value;
+ }
+
+ public VistaDialog(Bootstrapper? bootstrapper = null)
+ {
+ InitializeComponent();
+
+ Bootstrapper = bootstrapper;
+
+ Dialog = new TaskDialogPage()
+ {
+ Icon = new TaskDialogIcon(App.Settings.BootstrapperIcon.GetIcon()),
+ Caption = App.ProjectName,
+
+ Buttons = { TaskDialogButton.Cancel },
+ ProgressBar = new TaskDialogProgressBar()
+ {
+ State = TaskDialogProgressBarState.Marquee
+ }
+ };
+
+ _message = "Please wait...";
+ _cancelEnabled = false;
+
+ Dialog.Buttons[0].Click += (sender, e) => ButtonCancel_Click(sender, e);
+
+ SetupDialog();
+ }
+
+ public override void ShowSuccess(string message)
+ {
+ if (this.InvokeRequired)
+ {
+ this.Invoke(ShowSuccess, message);
+ }
+ else
+ {
+ TaskDialogPage successDialog = new()
+ {
+ Icon = TaskDialogIcon.ShieldSuccessGreenBar,
+ Caption = App.ProjectName,
+ Heading = message,
+ Buttons = { TaskDialogButton.OK }
+ };
+
+ successDialog.Buttons[0].Click += (sender, e) => App.Terminate();
+
+ if (!App.IsQuiet)
+ Dialog.Navigate(successDialog);
+
+ Dialog = successDialog;
+ }
+ }
+
+ public override void ShowError(string message)
+ {
+ if (this.InvokeRequired)
+ {
+ this.Invoke(ShowError, message);
+ }
+ else
+ {
+ TaskDialogPage errorDialog = new()
+ {
+ Icon = TaskDialogIcon.Error,
+ Caption = App.ProjectName,
+ Heading = "An error occurred while starting Roblox",
+ Buttons = { TaskDialogButton.Close },
+ Expander = new TaskDialogExpander()
+ {
+ Text = message,
+ CollapsedButtonText = "See details",
+ ExpandedButtonText = "Hide details",
+ Position = TaskDialogExpanderPosition.AfterText
+ }
+ };
+
+ errorDialog.Buttons[0].Click += (sender, e) => App.Terminate(Bootstrapper.ERROR_INSTALL_FAILURE);
+
+ if (!App.IsQuiet)
+ Dialog.Navigate(errorDialog);
+
+ Dialog = errorDialog;
+ }
+ }
+
+ public override void CloseDialog()
+ {
+ if (this.InvokeRequired)
+ {
+ this.Invoke(CloseDialog);
+ }
+ else
+ {
+ if (Dialog.BoundDialog is null)
+ return;
+
+ Dialog.BoundDialog.Close();
+ }
+ }
+
+
+ private void VistaDialog_Load(object sender, EventArgs e)
+ {
+ if (!App.IsQuiet)
+ TaskDialog.ShowDialog(Dialog);
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/VistaDialog.resx b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/VistaDialog.resx
new file mode 100644
index 0000000..f298a7b
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/BootstrapperDialogs/VistaDialog.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/ModHelp.xaml b/Bloxstrap/Bloxstrap/Dialogs/Menu/ModHelp.xaml
new file mode 100644
index 0000000..4775db9
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/ModHelp.xaml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/ModHelp.xaml.cs b/Bloxstrap/Bloxstrap/Dialogs/Menu/ModHelp.xaml.cs
new file mode 100644
index 0000000..62524d6
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/ModHelp.xaml.cs
@@ -0,0 +1,34 @@
+using System.Windows;
+
+using Bloxstrap.Enums;
+using System;
+
+namespace Bloxstrap.Dialogs.Menu
+{
+ ///
+ /// Interaction logic for ModHelp.xaml
+ ///
+ public partial class ModHelp : Window
+ {
+ public ModHelp()
+ {
+ InitializeComponent();
+ SetTheme();
+ }
+
+ public void SetTheme()
+ {
+ string theme = "Light";
+
+ if (App.Settings.Theme.GetFinal() == Theme.Dark)
+ theme = "ColourfulDark";
+
+ this.Resources.MergedDictionaries[0] = new ResourceDictionary() { Source = new Uri($"Dialogs/Menu/Themes/{theme}Theme.xaml", UriKind.Relative) };
+ }
+
+ private void ButtonClose_Click(object sender, EventArgs e)
+ {
+ this.Close();
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/Preferences.xaml b/Bloxstrap/Bloxstrap/Dialogs/Menu/Preferences.xaml
new file mode 100644
index 0000000..a815976
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/Preferences.xaml
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Leave a star on GitHub!
+
+
+
+
+
+
+
+
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/Preferences.xaml.cs b/Bloxstrap/Bloxstrap/Dialogs/Menu/Preferences.xaml.cs
new file mode 100644
index 0000000..bd56680
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/Preferences.xaml.cs
@@ -0,0 +1,427 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using System.Windows.Interop;
+using System.Windows.Media.Imaging;
+using System;
+using System.Windows.Forms;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+using Microsoft.Win32;
+
+using Bloxstrap.Enums;
+using Bloxstrap.Helpers;
+using Bloxstrap.Models;
+using System.Linq;
+
+namespace Bloxstrap.Dialogs.Menu
+{
+ ///
+ /// Interaction logic for PreferencesWPF.xaml
+ ///
+ public partial class Preferences : Window
+ {
+ public readonly PreferencesViewModel ViewModel;
+
+ public Preferences()
+ {
+ InitializeComponent();
+ SetTheme();
+
+ ViewModel = new(this);
+ this.DataContext = ViewModel;
+
+ App.SettingsManager.ShouldSave = false;
+
+ this.Icon = Imaging.CreateBitmapSourceFromHIcon(
+ Properties.Resources.IconBloxstrap_ico.Handle,
+ Int32Rect.Empty,
+ BitmapSizeOptions.FromEmptyOptions()
+ );
+
+ this.Title = App.ProjectName;
+
+ // just in case i guess?
+ if (!Environment.Is64BitOperatingSystem)
+ this.CheckBoxRFUEnabled.IsEnabled = false;
+ }
+
+ public void SetTheme()
+ {
+ string theme = "Light";
+
+ if (App.Settings.Theme.GetFinal() == Theme.Dark)
+ theme = "ColourfulDark";
+
+ this.Resources.MergedDictionaries[0] = new ResourceDictionary() { Source = new Uri($"Dialogs/Menu/Themes/{theme}Theme.xaml", UriKind.Relative) };
+ }
+
+ private void ButtonOpenReShadeFolder_Click(object sender, EventArgs e)
+ {
+ Process.Start("explorer.exe", Directories.ReShade);
+ }
+
+ private void ButtonOpenReShadeHelp_Click(object sender, EventArgs e)
+ {
+ new ReShadeHelp().Show();
+ }
+
+ private void ButtonOpenModFolder_Click(object sender, EventArgs e)
+ {
+ Process.Start("explorer.exe", Directories.Modifications);
+ }
+
+ private void ButtonOpenModHelp_Click(object sender, EventArgs e)
+ {
+ new ModHelp().Show();
+ }
+
+ private void ButtonLocationBrowse_Click(object sender, EventArgs e)
+ {
+ using (var dialog = new FolderBrowserDialog())
+ {
+ DialogResult result = dialog.ShowDialog();
+
+ if (result == System.Windows.Forms.DialogResult.OK)
+ ViewModel.InstallLocation = dialog.SelectedPath;
+ }
+ }
+
+ private void ButtonPreview_Click(object sender, EventArgs e)
+ {
+ //this.Visible = false;
+ App.Settings.BootstrapperStyle.Show();
+ //this.Visible = true;
+ }
+
+ private void ButtonCancel_Click(object sender, EventArgs e)
+ {
+ this.Close();
+ }
+
+ private void ButtonConfirm_Click(object sender, EventArgs e)
+ {
+ string installLocation = this.TextBoxInstallLocation.Text;
+
+ if (String.IsNullOrEmpty(installLocation))
+ {
+ App.ShowMessageBox("You must set an install location", MessageBoxImage.Error);
+ return;
+ }
+
+ try
+ {
+ // check if we can write to the directory (a bit hacky but eh)
+
+ string testPath = installLocation;
+ string testFile = Path.Combine(installLocation, "BloxstrapWriteTest.txt");
+ bool testPathExists = Directory.Exists(testPath);
+
+ if (!testPathExists)
+ Directory.CreateDirectory(testPath);
+
+ File.WriteAllText(testFile, "hi");
+ File.Delete(testFile);
+
+ if (!testPathExists)
+ Directory.Delete(testPath);
+ }
+ catch (UnauthorizedAccessException)
+ {
+ App.ShowMessageBox($"{App.ProjectName} does not have write access to the install location you selected. Please choose another install location.", MessageBoxImage.Error);
+ return;
+ }
+ catch (Exception ex)
+ {
+ App.ShowMessageBox(ex.Message, MessageBoxImage.Error);
+ return;
+ }
+
+ if (App.IsFirstRun)
+ {
+ // this will be set in the registry after first install
+ App.BaseDirectory = installLocation;
+ }
+ else
+ {
+ App.SettingsManager.ShouldSave = true;
+
+ if (App.BaseDirectory is not null && App.BaseDirectory != installLocation)
+ {
+ App.ShowMessageBox($"{App.ProjectName} will install to the new location you've set the next time it runs.", MessageBoxImage.Information);
+
+ App.Settings.VersionGuid = "";
+
+ using (RegistryKey registryKey = Registry.CurrentUser.CreateSubKey($@"Software\{App.ProjectName}"))
+ {
+ registryKey.SetValue("InstallLocation", installLocation);
+ registryKey.SetValue("OldInstallLocation", App.BaseDirectory);
+ }
+
+ // preserve settings
+ // we don't need to copy the bootstrapper over since the install process will do that automatically
+
+ App.SettingsManager.Save();
+
+ File.Copy(Path.Combine(App.BaseDirectory, "Settings.json"), Path.Combine(installLocation, "Settings.json"));
+ }
+ }
+
+ this.Close();
+ }
+
+ private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
+ {
+ Utilities.OpenWebsite(e.Uri.AbsoluteUri);
+ e.Handled = true;
+ }
+ }
+
+ public class PreferencesViewModel : INotifyPropertyChanged
+ {
+ private readonly Preferences _window;
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ public string BloxstrapVersion { get; } = $"Version {App.Version}";
+
+ #region Integrations
+ public bool DRPEnabled
+ {
+ get => App.Settings.UseDiscordRichPresence;
+ set
+ {
+ // if user wants discord rpc, auto-enable buttons by default
+ _window.CheckBoxDRPButtons.IsChecked = value;
+ App.Settings.UseDiscordRichPresence = value;
+ }
+ }
+
+ public bool DRPButtons
+ {
+ get => !App.Settings.HideRPCButtons;
+ set => App.Settings.HideRPCButtons = !value;
+ }
+
+ public bool RFUEnabled
+ {
+ get => App.Settings.RFUEnabled;
+ set
+ {
+ // if user wants to use rbxfpsunlocker, auto-enable autoclosing by default
+ _window.CheckBoxRFUAutoclose.IsChecked = value;
+ App.Settings.RFUEnabled = value;
+ }
+ }
+
+ public bool RFUAutoclose
+ {
+ get => App.Settings.RFUAutoclose;
+ set => App.Settings.RFUAutoclose = value;
+ }
+
+ public bool UseReShade
+ {
+ get => App.Settings.UseReShade;
+ set
+ {
+ // if user wants to use reshade, auto-enable use of extravi's presets by default
+ _window.CheckBoxUseReShadeExtraviPresets.IsChecked = value;
+ App.Settings.UseReShade = value;
+ }
+ }
+
+ public bool UseReShadeExtraviPresets
+ {
+ get => App.Settings.UseReShadeExtraviPresets;
+ set => App.Settings.UseReShadeExtraviPresets = value;
+ }
+
+ public bool ReShadeFolderButtonEnabled { get; } = !App.IsFirstRun;
+ public string ReShadeFolderButtonTooltip { get; } = App.IsFirstRun ? "Bloxstrap must first be installed before managing ReShade" : "This is the folder that contains all your ReShade resources for presets, shaders and textures.";
+ #endregion
+
+ #region Modifications
+ public bool ModOldDeathSound
+ {
+ get => App.Settings.UseOldDeathSound;
+ set => App.Settings.UseOldDeathSound = value;
+ }
+
+ public bool ModOldMouseCursor
+ {
+ get => App.Settings.UseOldMouseCursor;
+ set => App.Settings.UseOldMouseCursor = value;
+ }
+
+ public bool ModDisableAppPatch
+ {
+ get => App.Settings.UseDisableAppPatch;
+ set => App.Settings.UseDisableAppPatch = value;
+ }
+
+ public bool ModFolderButtonEnabled { get; } = !App.IsFirstRun;
+ public string ModFolderButtonTooltip { get; } = App.IsFirstRun ? "Bloxstrap must first be installed before managing mods" : "This is the folder that contains all your file modifications, including presets and any ReShade files needed.";
+ #endregion
+
+ #region Installation
+ private string installLocation = App.IsFirstRun ? Path.Combine(Directories.LocalAppData, App.ProjectName) : App.BaseDirectory;
+ public string InstallLocation
+ {
+ get => installLocation;
+ set
+ {
+ installLocation = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private bool showAllChannels = !DeployManager.ChannelsAbstracted.Contains(App.Settings.Channel);
+ public bool ShowAllChannels
+ {
+ get => showAllChannels;
+ set
+ {
+ if (value)
+ {
+ Channels = DeployManager.ChannelsAll;
+ }
+ else
+ {
+ Channels = DeployManager.ChannelsAbstracted;
+ Channel = DeployManager.DefaultChannel;
+ OnPropertyChanged("Channel");
+ }
+
+ showAllChannels = value;
+ }
+ }
+
+ private IEnumerable channels = DeployManager.ChannelsAbstracted.Contains(App.Settings.Channel) ? DeployManager.ChannelsAbstracted : DeployManager.ChannelsAll;
+ public IEnumerable Channels
+ {
+ get => channels;
+ set
+ {
+ channels = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string Channel
+ {
+ get => App.Settings.Channel;
+ set
+ {
+ Task.Run(() => GetChannelInfo(value));
+ App.Settings.Channel = value;
+ }
+ }
+
+ private string channelInfo = "Getting latest version info, please wait...\n";
+ public string ChannelInfo
+ {
+ get => channelInfo;
+ set
+ {
+ channelInfo = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool PromptChannelChange
+ {
+ get => App.Settings.PromptChannelChange;
+ set => App.Settings.PromptChannelChange = value;
+ }
+ #endregion
+
+ #region Bloxstrap
+ public IReadOnlyDictionary Themes { get; set; } = new Dictionary()
+ {
+ { "System Default", Enums.Theme.Default },
+ { "Light", Enums.Theme.Light },
+ { "Dark", Enums.Theme.Dark },
+ };
+
+ public string Theme
+ {
+ get => Themes.FirstOrDefault(x => x.Value == App.Settings.Theme).Key;
+ set
+ {
+ App.Settings.Theme = Themes[value];
+ _window.SetTheme();
+ }
+ }
+
+ public IReadOnlyDictionary Dialogs { get; set; } = new Dictionary()
+ {
+ { "Vista (2009 - 2011)", BootstrapperStyle.VistaDialog },
+ { "Legacy (2009 - 2011)", BootstrapperStyle.LegacyDialog2009 },
+ { "Legacy (2011 - 2014)", BootstrapperStyle.LegacyDialog2011 },
+ { "Progress (~2014)", BootstrapperStyle.ProgressDialog },
+ };
+
+ public string Dialog
+ {
+ get => Dialogs.FirstOrDefault(x => x.Value == App.Settings.BootstrapperStyle).Key;
+ set => App.Settings.BootstrapperStyle = Dialogs[value];
+ }
+
+ public IReadOnlyDictionary Icons { get; set; } = new Dictionary()
+ {
+ { "Bloxstrap", BootstrapperIcon.IconBloxstrap },
+ { "2009", BootstrapperIcon.Icon2009 },
+ { "2011", BootstrapperIcon.Icon2011 },
+ { "2015", BootstrapperIcon.IconEarly2015 },
+ { "2016", BootstrapperIcon.IconLate2015 },
+ { "2017", BootstrapperIcon.Icon2017 },
+ { "2019", BootstrapperIcon.Icon2019 },
+ { "2022", BootstrapperIcon.Icon2022 }
+ };
+
+ public string Icon
+ {
+ get => Icons.FirstOrDefault(x => x.Value == App.Settings.BootstrapperIcon).Key;
+ set => App.Settings.BootstrapperIcon = Icons[value];
+ }
+
+ public bool CreateDesktopIcon
+ {
+ get => App.Settings.CreateDesktopIcon;
+ set => App.Settings.CreateDesktopIcon = value;
+ }
+
+ public bool CheckForUpdates
+ {
+ get => App.Settings.CheckForUpdates;
+ set => App.Settings.CheckForUpdates = value;
+ }
+ #endregion
+
+ public string ConfirmButtonText { get; } = App.IsFirstRun ? "Install" : "Save";
+
+ public PreferencesViewModel(Preferences window)
+ {
+ _window = window;
+ Task.Run(() => GetChannelInfo(App.Settings.Channel));
+ }
+
+ protected void OnPropertyChanged([CallerMemberName] string? name = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+ }
+
+ private async Task GetChannelInfo(string channel)
+ {
+ ChannelInfo = "Getting latest version info, please wait...\n";
+
+ ClientVersion info = await DeployManager.GetLastDeploy(channel, true);
+ string? strTimestamp = info.Timestamp?.ToString("MM/dd/yyyy h:mm:ss tt", App.CultureFormat);
+
+ ChannelInfo = $"Version: v{info.Version} ({info.VersionGuid})\nDeployed: {strTimestamp}";
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/ReShadeHelp.xaml b/Bloxstrap/Bloxstrap/Dialogs/Menu/ReShadeHelp.xaml
new file mode 100644
index 0000000..b44ccfe
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/ReShadeHelp.xaml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/ReShadeHelp.xaml.cs b/Bloxstrap/Bloxstrap/Dialogs/Menu/ReShadeHelp.xaml.cs
new file mode 100644
index 0000000..f17de97
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/ReShadeHelp.xaml.cs
@@ -0,0 +1,34 @@
+using System.Windows;
+
+using Bloxstrap.Enums;
+using System;
+
+namespace Bloxstrap.Dialogs.Menu
+{
+ ///
+ /// Interaction logic for ReShadeHelp.xaml
+ ///
+ public partial class ReShadeHelp : Window
+ {
+ public ReShadeHelp()
+ {
+ InitializeComponent();
+ SetTheme();
+ }
+
+ public void SetTheme()
+ {
+ string theme = "Light";
+
+ if (App.Settings.Theme.GetFinal() == Theme.Dark)
+ theme = "ColourfulDark";
+
+ this.Resources.MergedDictionaries[0] = new ResourceDictionary() { Source = new Uri($"Dialogs/Menu/Themes/{theme}Theme.xaml", UriKind.Relative) };
+ }
+
+ private void ButtonClose_Click(object sender, EventArgs e)
+ {
+ this.Close();
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulDarkTheme.xaml b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulDarkTheme.xaml
new file mode 100644
index 0000000..47e7726
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulDarkTheme.xaml
@@ -0,0 +1,4505 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulDarkTheme.xaml.cs b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulDarkTheme.xaml.cs
new file mode 100644
index 0000000..3ef43c9
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulDarkTheme.xaml.cs
@@ -0,0 +1,43 @@
+using System.Windows;
+
+namespace REghZyFramework.Themes {
+ public partial class ColourfulDarkTheme {
+ private void CloseWindow_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ CloseWind(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ private void AutoMinimize_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ MaximizeRestore(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ private void Minimize_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ MinimizeWind(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ public void CloseWind(Window window) => window.Close();
+
+ public void MaximizeRestore(Window window) {
+ if (window.WindowState == WindowState.Maximized)
+ window.WindowState = WindowState.Normal;
+ else if (window.WindowState == WindowState.Normal)
+ window.WindowState = WindowState.Maximized;
+ }
+
+ public void MinimizeWind(Window window) => window.WindowState = WindowState.Minimized;
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulLightTheme.xaml b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulLightTheme.xaml
new file mode 100644
index 0000000..62a99b5
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulLightTheme.xaml
@@ -0,0 +1,4555 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulLightTheme.xaml.cs b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulLightTheme.xaml.cs
new file mode 100644
index 0000000..aec4554
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/ColourfulLightTheme.xaml.cs
@@ -0,0 +1,43 @@
+using System.Windows;
+
+namespace REghZyFramework.Themes {
+ public partial class ColourfulLightTheme {
+ private void CloseWindow_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ CloseWind(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ private void AutoMinimize_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ MaximizeRestore(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ private void Minimize_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ MinimizeWind(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ public void CloseWind(Window window) => window.Close();
+
+ public void MaximizeRestore(Window window) {
+ if (window.WindowState == WindowState.Maximized)
+ window.WindowState = WindowState.Normal;
+ else if (window.WindowState == WindowState.Normal)
+ window.WindowState = WindowState.Maximized;
+ }
+
+ public void MinimizeWind(Window window) => window.WindowState = WindowState.Minimized;
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/DarkTheme.xaml b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/DarkTheme.xaml
new file mode 100644
index 0000000..0261c0c
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/DarkTheme.xaml
@@ -0,0 +1,4644 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/DarkTheme.xaml.cs b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/DarkTheme.xaml.cs
new file mode 100644
index 0000000..8177624
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/DarkTheme.xaml.cs
@@ -0,0 +1,43 @@
+using System.Windows;
+
+namespace REghZyFramework.Themes {
+ public partial class DarkTheme {
+ private void CloseWindow_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ CloseWind(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ private void AutoMinimize_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ MaximizeRestore(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ private void Minimize_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ MinimizeWind(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ public void CloseWind(Window window) => window.Close();
+
+ public void MaximizeRestore(Window window) {
+ if (window.WindowState == WindowState.Maximized)
+ window.WindowState = WindowState.Normal;
+ else if (window.WindowState == WindowState.Normal)
+ window.WindowState = WindowState.Maximized;
+ }
+
+ public void MinimizeWind(Window window) => window.WindowState = WindowState.Minimized;
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/LightTheme.xaml b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/LightTheme.xaml
new file mode 100644
index 0000000..d514240
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/LightTheme.xaml
@@ -0,0 +1,4461 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/LightTheme.xaml.cs b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/LightTheme.xaml.cs
new file mode 100644
index 0000000..4ca2f6d
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Dialogs/Menu/Themes/LightTheme.xaml.cs
@@ -0,0 +1,43 @@
+using System.Windows;
+
+namespace REghZyFramework.Themes {
+ public partial class LightTheme {
+ private void CloseWindow_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ CloseWind(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ private void AutoMinimize_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ MaximizeRestore(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ private void Minimize_Event(object sender, RoutedEventArgs e) {
+ if (e.Source != null)
+ try {
+ MinimizeWind(Window.GetWindow((FrameworkElement) e.Source));
+ }
+ catch {
+ }
+ }
+
+ public void CloseWind(Window window) => window.Close();
+
+ public void MaximizeRestore(Window window) {
+ if (window.WindowState == WindowState.Maximized)
+ window.WindowState = WindowState.Normal;
+ else if (window.WindowState == WindowState.Normal)
+ window.WindowState = WindowState.Maximized;
+ }
+
+ public void MinimizeWind(Window window) => window.WindowState = WindowState.Minimized;
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Enums/BootstrapperIcon.cs b/Bloxstrap/Bloxstrap/Enums/BootstrapperIcon.cs
new file mode 100644
index 0000000..dc3897f
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Enums/BootstrapperIcon.cs
@@ -0,0 +1,81 @@
+using System.Drawing;
+
+namespace Bloxstrap.Enums
+{
+ public enum BootstrapperIcon
+ {
+ IconBloxstrap,
+ Icon2009,
+ Icon2011,
+ IconEarly2015,
+ IconLate2015,
+ Icon2017,
+ Icon2019,
+ Icon2022
+ }
+
+ public static class BootstrapperIconEx
+ {
+ public static Icon GetIcon(this BootstrapperIcon icon)
+ {
+ switch (icon)
+ {
+ case BootstrapperIcon.Icon2009:
+ return Properties.Resources.Icon2009_ico;
+
+ case BootstrapperIcon.Icon2011:
+ return Properties.Resources.Icon2011_ico;
+
+ case BootstrapperIcon.IconEarly2015:
+ return Properties.Resources.IconEarly2015_ico;
+
+ case BootstrapperIcon.IconLate2015:
+ return Properties.Resources.IconLate2015_ico;
+
+ case BootstrapperIcon.Icon2017:
+ return Properties.Resources.Icon2017_ico;
+
+ case BootstrapperIcon.Icon2019:
+ return Properties.Resources.Icon2019_ico;
+
+ case BootstrapperIcon.Icon2022:
+ return Properties.Resources.Icon2022_ico;
+
+ case BootstrapperIcon.IconBloxstrap:
+ default:
+ return Properties.Resources.IconBloxstrap_ico;
+ }
+ }
+
+ public static Bitmap GetBitmap(this BootstrapperIcon icon)
+ {
+ switch (icon)
+ {
+ case BootstrapperIcon.Icon2009:
+ return Properties.Resources.Icon2009_png;
+
+ case BootstrapperIcon.Icon2011:
+ return Properties.Resources.Icon2011_png;
+
+ case BootstrapperIcon.IconEarly2015:
+ return Properties.Resources.IconEarly2015_png;
+
+ case BootstrapperIcon.IconLate2015:
+ return Properties.Resources.IconLate2015_png;
+
+ case BootstrapperIcon.Icon2017:
+ return Properties.Resources.Icon2017_png;
+
+ case BootstrapperIcon.Icon2019:
+ return Properties.Resources.Icon2019_png;
+
+ case BootstrapperIcon.Icon2022:
+ return Properties.Resources.Icon2022_png;
+
+ case BootstrapperIcon.IconBloxstrap:
+ default:
+ return Properties.Resources.IconBloxstrap_png;
+ }
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Enums/BootstrapperStyle.cs b/Bloxstrap/Bloxstrap/Enums/BootstrapperStyle.cs
new file mode 100644
index 0000000..8fa6775
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Enums/BootstrapperStyle.cs
@@ -0,0 +1,50 @@
+using Bloxstrap.Dialogs.BootstrapperDialogs;
+using System.Windows.Forms;
+
+namespace Bloxstrap.Enums
+{
+ public enum BootstrapperStyle
+ {
+ VistaDialog,
+ LegacyDialog2009,
+ LegacyDialog2011,
+ ProgressDialog,
+ }
+
+ public static class BootstrapperStyleEx
+ {
+ public static void Show(this BootstrapperStyle bootstrapperStyle, Bootstrapper? bootstrapper = null)
+ {
+ Form dialog;
+
+ switch (bootstrapperStyle)
+ {
+ case BootstrapperStyle.VistaDialog:
+ dialog = new VistaDialog(bootstrapper);
+ break;
+
+ case BootstrapperStyle.LegacyDialog2009:
+ dialog = new LegacyDialog2009(bootstrapper);
+ break;
+
+ case BootstrapperStyle.LegacyDialog2011:
+ dialog = new LegacyDialog2011(bootstrapper);
+ break;
+
+ case BootstrapperStyle.ProgressDialog:
+ default:
+ dialog = new ProgressDialog(bootstrapper);
+ break;
+ }
+
+ if (bootstrapper is null)
+ {
+ dialog.ShowDialog();
+ }
+ else
+ {
+ Application.Run(dialog);
+ }
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Enums/Theme.cs b/Bloxstrap/Bloxstrap/Enums/Theme.cs
new file mode 100644
index 0000000..4e34f5a
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Enums/Theme.cs
@@ -0,0 +1,32 @@
+using Microsoft.Win32;
+
+namespace Bloxstrap.Enums
+{
+ public enum Theme
+ {
+ Default,
+ Light,
+ Dark
+ }
+
+ public static class DialogThemeEx
+ {
+ public static Theme GetFinal(this Theme dialogTheme)
+ {
+ if (dialogTheme != Theme.Default)
+ return dialogTheme;
+
+ RegistryKey? key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
+
+ if (key is not null)
+ {
+ var value = key.GetValue("AppsUseLightTheme");
+
+ if (value is not null && (int)value == 0)
+ return Theme.Dark;
+ }
+
+ return Theme.Light;
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/DeployManager.cs b/Bloxstrap/Bloxstrap/Helpers/DeployManager.cs
new file mode 100644
index 0000000..5e562f2
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/DeployManager.cs
@@ -0,0 +1,93 @@
+using System.Net.Http;
+using System.Text.Json;
+using Bloxstrap.Models;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System;
+using System.Linq;
+
+namespace Bloxstrap.Helpers
+{
+ public class DeployManager
+ {
+ #region Properties
+ public const string DefaultBaseUrl = "https://setup.rbxcdn.com";
+ public static string BaseUrl { get; private set; } = DefaultBaseUrl;
+
+ public const string DefaultChannel = "LIVE";
+ public static string Channel { set => BaseUrl = BuildBaseUrl(value); }
+
+ // basically any channel that has had a deploy within the past month with a windowsplayer build
+ public static readonly List ChannelsAbstracted = new List()
+ {
+ "LIVE",
+ "ZNext",
+ "ZCanary",
+ "ZIntegration"
+ };
+
+ // why not?
+ public static readonly List ChannelsAll = new List()
+ {
+ "LIVE",
+ "ZAvatarTeam",
+ "ZAvatarRelease",
+ "ZCanary",
+ "ZCanary1",
+ "ZCanary2",
+ "ZCanary3",
+ "ZCanaryApps",
+ "ZFlag",
+ "ZIntegration",
+ "ZIntegration1",
+ "ZLive",
+ "ZLive1",
+ "ZNext",
+ "ZSocialTeam",
+ "ZStudioInt1",
+ "ZStudioInt2"
+ };
+ #endregion
+
+ private static string BuildBaseUrl(string channel)
+ {
+ if (channel == DefaultChannel)
+ return DefaultBaseUrl;
+ else
+ return $"{DefaultBaseUrl}/channel/{channel.ToLower()}";
+ }
+
+ public static async Task GetLastDeploy(string channel, bool timestamp = false)
+ {
+ HttpResponseMessage deployInfoResponse = await App.HttpClient.GetAsync($"https://clientsettings.roblox.com/v2/client-version/WindowsPlayer/channel/{channel}");
+
+ if (!deployInfoResponse.IsSuccessStatusCode)
+ {
+ // 400 = Invalid binaryType.
+ // 404 = Could not find version details for binaryType.
+ // 500 = Error while fetching version information.
+ // either way, we throw
+ throw new Exception($"Could not get latest deploy for channel {channel}");
+ }
+
+ string rawJson = await deployInfoResponse.Content.ReadAsStringAsync();
+ ClientVersion clientVersion = JsonSerializer.Deserialize(rawJson)!;
+
+ // for preferences
+ if (timestamp)
+ {
+ string channelUrl = BuildBaseUrl(channel);
+
+ // get an approximate deploy time from rbxpkgmanifest's last modified date
+ HttpResponseMessage pkgResponse = await App.HttpClient.GetAsync($"{channelUrl}/{clientVersion.VersionGuid}-rbxPkgManifest.txt");
+ if (pkgResponse.Content.Headers.TryGetValues("last-modified", out var values))
+ {
+ string lastModified = values.First();
+ clientVersion.Timestamp = DateTime.Parse(lastModified);
+ }
+ }
+
+ return clientVersion;
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/Directories.cs b/Bloxstrap/Bloxstrap/Helpers/Directories.cs
new file mode 100644
index 0000000..1a44510
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/Directories.cs
@@ -0,0 +1,39 @@
+using System.IO;
+using System;
+
+namespace Bloxstrap.Helpers
+{
+ class Directories
+ {
+ // note that these are directories that aren't tethered to the basedirectory
+ // so these can safely be called before initialization
+ public static string LocalAppData { get => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); }
+ public static string Desktop { get => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); }
+ public static string StartMenu { get => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs", "Bloxstrap"); }
+
+ public static string Base { get; private set; } = "";
+ public static string Downloads { get; private set; } = "";
+ public static string Integrations { get; private set; } = "";
+ public static string Versions { get; private set; } = "";
+ public static string Modifications { get; private set; } = "";
+ public static string Updates { get; private set; } = "";
+ public static string ReShade { get; private set; } = "";
+
+ public static string App { get; private set; } = "";
+
+ public static bool Initialized { get => String.IsNullOrEmpty(Base); }
+
+ public static void Initialize(string baseDirectory)
+ {
+ Base = baseDirectory;
+ Downloads = Path.Combine(Base, "Downloads");
+ Integrations = Path.Combine(Base, "Integrations");
+ Versions = Path.Combine(Base, "Versions");
+ Modifications = Path.Combine(Base, "Modifications");
+ Updates = Path.Combine(Base, "Updates");
+ ReShade = Path.Combine(Base, "ReShade");
+
+ App = Path.Combine(Base, $"{"Bloxstrap"}.exe");
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/Integrations/DiscordRichPresence.cs b/Bloxstrap/Bloxstrap/Helpers/Integrations/DiscordRichPresence.cs
new file mode 100644
index 0000000..0bc4fb8
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/Integrations/DiscordRichPresence.cs
@@ -0,0 +1,210 @@
+using System.Diagnostics;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System;
+using System.Linq;
+using System.Threading;
+using System.Collections.Generic;
+
+using Bloxstrap.Models;
+
+using DiscordRPC;
+
+namespace Bloxstrap.Helpers.Integrations
+{
+ class DiscordRichPresence : IDisposable
+ {
+ readonly DiscordRpcClient RichPresence = new("1005469189907173486");
+
+ const string GameJoiningEntry = "[FLog::Output] ! Joining game";
+ const string GameJoinedEntry = "[FLog::Network] serverId:";
+ const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
+
+ const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)";
+ const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|([0-9]+)";
+
+ // these are values to use assuming the player isn't currently in a game
+ bool ActivityInGame = false;
+ long ActivityPlaceId = 0;
+ string ActivityJobId = "";
+ string ActivityMachineAddress = ""; // we're only really using this to confirm a place join. todo: maybe this could be used to see server location/ping?
+
+ public DiscordRichPresence()
+ {
+ RichPresence.Initialize();
+ }
+
+ private async Task ExamineLogEntry(string entry)
+ {
+ Debug.WriteLine(entry);
+
+ if (entry.Contains(GameJoiningEntry) && !ActivityInGame && ActivityPlaceId == 0)
+ {
+ Match match = Regex.Match(entry, GameJoiningEntryPattern);
+
+ if (match.Groups.Count != 4)
+ return;
+
+ ActivityInGame = false;
+ ActivityPlaceId = Int64.Parse(match.Groups[2].Value);
+ ActivityJobId = match.Groups[1].Value;
+ ActivityMachineAddress = match.Groups[3].Value;
+
+ Debug.WriteLine($"[DiscordRichPresence] Joining Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
+ }
+ else if (entry.Contains(GameJoinedEntry) && !ActivityInGame && ActivityPlaceId != 0)
+ {
+ Match match = Regex.Match(entry, GameJoinedEntryPattern);
+
+ if (match.Groups.Count != 3 || match.Groups[1].Value != ActivityMachineAddress)
+ return;
+
+ Debug.WriteLine($"[DiscordRichPresence] Joined Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
+
+ ActivityInGame = true;
+ await SetPresence();
+ }
+ else if (entry.Contains(GameDisconnectedEntry) && ActivityInGame && ActivityPlaceId != 0)
+ {
+ Debug.WriteLine($"[DiscordRichPresence] Disconnected from Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
+
+ ActivityInGame = false;
+ ActivityPlaceId = 0;
+ ActivityJobId = "";
+ ActivityMachineAddress = "";
+ await SetPresence();
+ }
+ }
+
+ public async void MonitorGameActivity()
+ {
+ // okay, here's the process:
+ //
+ // - tail the latest log file from %localappdata%\roblox\logs
+ // - check for specific lines to determine player's game activity as shown below:
+ //
+ // - get the place id, job id and machine address from '! Joining game '{{JOBID}}' place {{PLACEID}} at {{MACHINEADDRESS}}' entry
+ // - confirm place join with 'serverId: {{MACHINEADDRESS}}|{{MACHINEPORT}}' entry
+ // - check for leaves/disconnects with 'Time to disconnect replication data: {{TIME}}' entry
+ //
+ // we'll tail the log file continuously, monitoring for any log entries that we need to determine the current game activity
+
+ string logDirectory = Path.Combine(Directories.LocalAppData, "Roblox\\logs");
+
+ if (!Directory.Exists(logDirectory))
+ return;
+
+ FileInfo logFileInfo;
+
+ // we need to make sure we're fetching the absolute latest log file
+ // if roblox doesn't start quickly enough, we can wind up fetching the previous log file
+ // good rule of thumb is to find a log file that was created in the last 15 seconds or so
+
+ while (true)
+ {
+ logFileInfo = new DirectoryInfo(logDirectory).GetFiles().OrderByDescending(x => x.CreationTime).First();
+
+ if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now)
+ break;
+
+ await Task.Delay(1000);
+ }
+
+ FileStream logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+
+ AutoResetEvent logUpdatedEvent = new(false);
+ FileSystemWatcher logWatcher = new()
+ {
+ Path = logDirectory,
+ Filter = Path.GetFileName(logFileInfo.FullName),
+ EnableRaisingEvents = true
+ };
+ logWatcher.Changed += (s, e) => logUpdatedEvent.Set();
+
+ using (StreamReader sr = new(logFileStream))
+ {
+ string? log = null;
+
+ while (true)
+ {
+ log = await sr.ReadLineAsync();
+
+ if (String.IsNullOrEmpty(log))
+ {
+ logUpdatedEvent.WaitOne(1000);
+ }
+ else
+ {
+ //Debug.WriteLine(log);
+ await ExamineLogEntry(log);
+ }
+ }
+ }
+
+ // no need to close the event, its going to be finished with when the program closes anyway
+ }
+
+ public async Task SetPresence()
+ {
+ if (!ActivityInGame)
+ {
+ RichPresence.ClearPresence();
+ return true;
+ }
+
+ string placeThumbnail = "roblox";
+
+ var placeInfo = await Utilities.GetJson($"https://economy.roblox.com/v2/assets/{ActivityPlaceId}/details");
+
+ if (placeInfo is null || placeInfo.Creator is null)
+ return false;
+
+ var thumbnailInfo = await Utilities.GetJson($"https://thumbnails.roblox.com/v1/places/gameicons?placeIds={ActivityPlaceId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false");
+
+ if (thumbnailInfo is not null)
+ placeThumbnail = thumbnailInfo.Data![0].ImageUrl!;
+
+ List buttons = new()
+ {
+ new DiscordRPC.Button()
+ {
+ Label = "See Details",
+ Url = $"https://www.roblox.com/games/{ActivityPlaceId}"
+ }
+ };
+
+ if (!App.Settings.HideRPCButtons)
+ {
+ buttons.Insert(0, new DiscordRPC.Button()
+ {
+ Label = "Join",
+ Url = $"https://www.roblox.com/games/start?placeId={ActivityPlaceId}&gameInstanceId={ActivityJobId}&launchData=%7B%7D"
+ });
+ }
+
+ RichPresence.SetPresence(new RichPresence()
+ {
+ Details = placeInfo.Name,
+ State = $"by {placeInfo.Creator.Name}",
+ Timestamps = new Timestamps() { Start = DateTime.UtcNow },
+ Buttons = buttons.ToArray(),
+ Assets = new Assets()
+ {
+ LargeImageKey = placeThumbnail,
+ LargeImageText = placeInfo.Name,
+ SmallImageKey = "roblox",
+ SmallImageText = "Roblox"
+ }
+ });
+
+ return true;
+ }
+
+ public void Dispose()
+ {
+ RichPresence.ClearPresence();
+ RichPresence.Dispose();
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/Integrations/RbxFpsUnlocker.cs b/Bloxstrap/Bloxstrap/Helpers/Integrations/RbxFpsUnlocker.cs
new file mode 100644
index 0000000..b3d5fa9
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/Integrations/RbxFpsUnlocker.cs
@@ -0,0 +1,111 @@
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+using Bloxstrap.Models;
+using System;
+
+namespace Bloxstrap.Helpers.Integrations
+{
+ internal class RbxFpsUnlocker
+ {
+ public const string ApplicationName = "rbxfpsunlocker";
+ public const string ProjectRepository = "axstin/rbxfpsunlocker";
+
+ // default settings but with QuickStart set to true and CheckForUpdates set to false
+ private static readonly string Settings =
+ "UnlockClient=true\n" +
+ "UnlockStudio=false\n" +
+ "FPSCapValues=[30.000000, 60.000000, 75.000000, 120.000000, 144.000000, 165.000000, 240.000000, 360.000000]\n" +
+ "FPSCapSelection=0\n" +
+ "FPSCap=0.000000\n" +
+ "CheckForUpdates=false\n" +
+ "NonBlockingErrors=true\n" +
+ "SilentErrors=false\n" +
+ "QuickStart=true\n";
+
+ public static void CheckIfRunning()
+ {
+ Process[] processes = Process.GetProcessesByName(ApplicationName);
+
+ if (processes.Length == 0)
+ return;
+
+ try
+ {
+ // try/catch just in case process was closed before prompt was answered
+
+ foreach (Process process in processes)
+ {
+ if (process.MainModule is null || process.MainModule.FileName is null)
+ continue;
+
+ if (!process.MainModule.FileName.Contains(App.BaseDirectory))
+ continue;
+
+ process.Kill();
+ process.Close();
+ }
+ }
+ catch (Exception) { }
+ }
+
+ public static async Task CheckInstall()
+ {
+ if (App.BaseDirectory is null)
+ return;
+
+ string folderLocation = Path.Combine(App.BaseDirectory, "Integrations\\rbxfpsunlocker");
+ string fileLocation = Path.Combine(folderLocation, "rbxfpsunlocker.exe");
+ string settingsLocation = Path.Combine(folderLocation, "settings");
+
+ if (!App.Settings.RFUEnabled)
+ {
+ if (Directory.Exists(folderLocation))
+ {
+ CheckIfRunning();
+ Directory.Delete(folderLocation, true);
+ }
+
+ return;
+ }
+
+ var releaseInfo = await Utilities.GetJson($"https://api.github.com/repos/{ProjectRepository}/releases/latest");
+
+ if (releaseInfo is null || releaseInfo.Assets is null)
+ return;
+
+ string downloadUrl = releaseInfo.Assets[0].BrowserDownloadUrl;
+
+ Directory.CreateDirectory(folderLocation);
+
+ if (File.Exists(fileLocation))
+ {
+ // no new release published, return
+ if (App.Settings.RFUVersion == releaseInfo.TagName)
+ return;
+
+ CheckIfRunning();
+ File.Delete(fileLocation);
+ }
+
+ Debug.WriteLine("Installing/Updating rbxfpsunlocker...");
+
+ {
+ byte[] bytes = await App.HttpClient.GetByteArrayAsync(downloadUrl);
+
+ using MemoryStream zipStream = new(bytes);
+ using ZipArchive archive = new(zipStream);
+
+ archive.ExtractToDirectory(folderLocation, true);
+ }
+
+ if (!File.Exists(settingsLocation))
+ await File.WriteAllTextAsync(settingsLocation, Settings);
+
+ App.Settings.RFUVersion = releaseInfo.TagName;
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/Integrations/ReShade.cs b/Bloxstrap/Bloxstrap/Helpers/Integrations/ReShade.cs
new file mode 100644
index 0000000..6eb12a9
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/Integrations/ReShade.cs
@@ -0,0 +1,402 @@
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+
+using Bloxstrap.Models;
+
+using IniParser;
+using IniParser.Model;
+using System.Threading.Tasks;
+using System.Linq;
+using System.Collections.Generic;
+using System;
+
+namespace Bloxstrap.Helpers.Integrations
+{
+ internal class ReShade
+ {
+ // i havent even started this and i know for a fact this is gonna be a mess of an integration lol
+ // there's a lot of nuances involved in how reshade functionality is supposed to work (shader management, config management, etc)
+ // it's gonna be a bit of a pain in the ass, and i'm expecting a lot of bugs to arise from this...
+ // well, looks like v1.7.0 is gonna be held back for quite a while lol
+
+ // also, this is going to be fairly restrictive without a lot of heavy work
+ // reshade's official installer gives you a list of shader packs and lets you choose which ones you want to install
+ // and here we're effectively choosing for the user... hm...
+ // i mean, it should be fine? importing shaders is still gonna be a thing, though maybe not as simple, but most people would be looking to use extravi's presets anyway
+
+ private static string ShadersFolder { get => Path.Combine(Directories.ReShade, "Shaders"); }
+ private static string TexturesFolder { get => Path.Combine(Directories.ReShade, "Textures"); }
+ private static string ConfigLocation { get => Path.Combine(Directories.Modifications, "ReShade.ini"); }
+
+ // the base url that we're fetching all our remote configs and resources and stuff from
+ private const string BaseUrl = "https://raw.githubusercontent.com/Extravi/extravi.github.io/main/update";
+
+ // this is a list of selectable shaders to download:
+ // this should be formatted as { FolderName, GithubRepositoryUrl }
+ private static readonly IReadOnlyDictionary Shaders = new Dictionary()
+ {
+ { "Stock", "https://github.com/crosire/reshade-shaders/archive/refs/heads/master.zip" },
+
+ // shaders required for extravi's presets:
+ { "AlucardDH", "https://github.com/AlucardDH/dh-reshade-shaders/archive/refs/heads/master.zip" },
+ { "AstrayFX", "https://github.com/BlueSkyDefender/AstrayFX/archive/refs/heads/master.zip" },
+ { "Depth3D", "https://github.com/BlueSkyDefender/Depth3D/archive/refs/heads/master.zip" },
+ { "Glamarye", "https://github.com/rj200/Glamarye_Fast_Effects_for_ReShade/archive/refs/heads/main.zip" },
+ { "NiceGuy", "https://github.com/mj-ehsan/NiceGuy-Shaders/archive/refs/heads/main.zip" },
+ { "prod80", "https://github.com/prod80/prod80-ReShade-Repository/archive/refs/heads/master.zip" },
+ { "qUINT", "https://github.com/martymcmodding/qUINT/archive/refs/heads/master.zip" },
+ };
+
+ private static readonly string[] ExtraviPresetsShaders = new string[]
+ {
+ "AlucardDH",
+ "AstrayFX",
+ "Depth3D",
+ "Glamarye",
+ "NiceGuy",
+ "prod80",
+ "qUINT",
+ };
+
+ private static string GetSearchPath(string type, string name)
+ {
+ return $",..\\..\\ReShade\\{type}\\{name}";
+ }
+
+ public static async Task DownloadConfig()
+ {
+ Debug.WriteLine("[ReShade] Downloading/Upgrading config file...");
+
+ {
+ byte[] bytes = await App.HttpClient.GetByteArrayAsync($"{BaseUrl}/config.zip");
+
+ using MemoryStream zipStream = new(bytes);
+ using ZipArchive archive = new(zipStream);
+
+
+ archive.Entries.Where(x => x.FullName == "ReShade.ini").First().ExtractToFile(ConfigLocation, true);
+
+ // when we extract the file we have to make sure the last modified date is overwritten
+ // or else it will synchronize with the config in the version folder
+ // really the config adjustments below should do this for us, but this is just to be safe
+ File.SetLastWriteTime(ConfigLocation, DateTime.Now);
+
+ // we also gotta download the editor fonts
+ foreach (ZipArchiveEntry entry in archive.Entries.Where(x => x.FullName.EndsWith(".ttf")))
+ entry.ExtractToFile(Path.Combine(Directories.ReShade, "Fonts", entry.FullName), true);
+ }
+
+ // now we have to adjust the config file to use the paths that we need
+ // some of these can be removed later when the config file is better adjusted for bloxstrap by default
+
+ FileIniDataParser parser = new();
+ IniData data = parser.ReadFile(ConfigLocation);
+
+ data["GENERAL"]["EffectSearchPaths"] = "..\\..\\ReShade\\Shaders";
+ data["GENERAL"]["TextureSearchPaths"] = "..\\..\\ReShade\\Textures";
+ data["GENERAL"]["PresetPath"] = data["GENERAL"]["PresetPath"].Replace(".\\reshade-presets\\", "..\\..\\ReShade\\Presets\\");
+ data["SCREENSHOT"]["SavePath"] = "..\\..\\ReShade\\Screenshots";
+ data["STYLE"]["EditorFont"] = data["STYLE"]["EditorFont"].Replace(".\\", "..\\..\\ReShade\\Fonts\\");
+ data["STYLE"]["Font"] = data["STYLE"]["Font"].Replace(".\\", "..\\..\\ReShade\\Fonts\\");
+
+ // add search paths for shaders and textures
+
+ foreach (string name in Directory.GetDirectories(ShadersFolder).Select(x => Path.GetRelativePath(ShadersFolder, x)).ToArray())
+ data["GENERAL"]["EffectSearchPaths"] += GetSearchPath("Shaders", name);
+
+ foreach (string name in Directory.GetDirectories(TexturesFolder).Select(x => Path.GetRelativePath(TexturesFolder, x)).ToArray())
+ data["GENERAL"]["TextureSearchPaths"] += GetSearchPath("Textures", name);
+
+ parser.WriteFile(ConfigLocation, data);
+ }
+
+ public static void SynchronizeConfigFile()
+ {
+ Debug.WriteLine($"[ReShade] Synchronizing configuration file...");
+
+ // yeah, this is going to be a bit of a pain
+ // keep in mind the config file is going to be in two places: the mod folder and the version folder
+ // so we have to make sure the two below scenaros work flawlessly:
+ // - if the user manually updates their reshade config in the mod folder or it gets updated, it must be copied to the version folder
+ // - if the user updates their reshade settings ingame, the updated config must be copied to the mod folder
+ // the easiest way to manage this is to just compare the modification dates of the two
+ // anyway, this is where i'm expecting most of the bugs to arise from
+ // config synchronization will be done whenever roblox updates or whenever we launch roblox
+
+ string modFolderConfigPath = ConfigLocation;
+ string versionFolderConfigPath = Path.Combine(Directories.Versions, App.Settings.VersionGuid, "ReShade.ini");
+
+ // we shouldn't be here if the mod config doesn't already exist
+ if (!File.Exists(modFolderConfigPath))
+ {
+ Debug.WriteLine($"[ReShade] ReShade.ini in modifications folder does not exist, aborting sync");
+ return;
+ }
+
+ // copy to the version folder if it doesn't already exist there
+ if (!File.Exists(versionFolderConfigPath))
+ {
+ Debug.WriteLine($"[ReShade] ReShade.ini in version folder does not exist, synchronized with modifications folder");
+ File.Copy(modFolderConfigPath, versionFolderConfigPath);
+ }
+
+ // if both the mod and version configs match, then we don't need to do anything
+ if (Utilities.MD5File(modFolderConfigPath) == Utilities.MD5File(versionFolderConfigPath))
+ {
+ Debug.WriteLine($"[ReShade] ReShade.ini in version and modifications folder match");
+ return;
+ }
+
+ FileInfo modFolderConfigFile = new(modFolderConfigPath);
+ FileInfo versionFolderConfigFile = new(versionFolderConfigPath);
+
+ if (modFolderConfigFile.LastWriteTime > versionFolderConfigFile.LastWriteTime)
+ {
+ // overwrite version config if mod config was modified most recently
+ Debug.WriteLine($"[ReShade] ReShade.ini in version folder is older, synchronized with modifications folder");
+ File.Copy(modFolderConfigPath, versionFolderConfigPath, true);
+ }
+ else if (versionFolderConfigFile.LastWriteTime > modFolderConfigFile.LastWriteTime)
+ {
+ // overwrite mod config if version config was modified most recently
+ Debug.WriteLine($"[ReShade] ReShade.ini in modifications folder is older, synchronized with version folder");
+ File.Copy(versionFolderConfigPath, modFolderConfigPath, true);
+ }
+ }
+
+ public static async Task DownloadShaders(string name)
+ {
+ string downloadUrl = Shaders.First(x => x.Key == name).Value;
+
+ // not all shader packs have a textures folder, so here we're determining if they exist purely based on if they have a Shaders folder
+ if (Directory.Exists(Path.Combine(Directories.ReShade, "Shaders", name)))
+ return;
+
+ Debug.WriteLine($"[ReShade] Downloading shaders for {name}");
+
+ {
+ byte[] bytes = await App.HttpClient.GetByteArrayAsync(downloadUrl);
+
+ using MemoryStream zipStream = new(bytes);
+ using ZipArchive archive = new(zipStream);
+
+ foreach (ZipArchiveEntry entry in archive.Entries)
+ {
+ if (entry.FullName.EndsWith('/'))
+ continue;
+
+ // github branch zips have a root folder of the name of the branch, so let's just remove that
+ string fullPath = entry.FullName.Substring(entry.FullName.IndexOf('/') + 1);
+
+ // skip file if it's not in the Shaders or Textures folder
+ if (!fullPath.StartsWith("Shaders") && !fullPath.StartsWith("Textures"))
+ continue;
+
+ // ingore shaders with compiler errors
+ if (fullPath.EndsWith("dh_Lain.fx") || fullPath.EndsWith("dh_rtgi.fx"))
+ continue;
+
+ // and now we do it again because of how we're handling folder management
+ // e.g. reshade-shaders-master/Shaders/Vignette.fx should go to ReShade/Shaders/Stock/Vignette.fx
+ // so in this case, relativePath should just be "Vignette.fx"
+ string relativePath = fullPath.Substring(fullPath.IndexOf('/') + 1);
+
+ // now we stitch it all together
+ string extractionPath = Path.Combine(
+ Directories.ReShade,
+ fullPath.StartsWith("Shaders") ? "Shaders" : "Textures",
+ name,
+ relativePath
+ );
+
+ // make sure the folder that we're extracting it to exists
+ Directory.CreateDirectory(Path.GetDirectoryName(extractionPath)!);
+
+ // and now extract
+ await Task.Run(() => entry.ExtractToFile(extractionPath));
+ }
+ }
+
+ // now we have to update ReShade.ini and add the installed shaders to the search paths
+ FileIniDataParser parser = new();
+ IniData data = parser.ReadFile(ConfigLocation);
+
+ if (!data["GENERAL"]["EffectSearchPaths"].Contains(name))
+ data["GENERAL"]["EffectSearchPaths"] += GetSearchPath("Shaders", name);
+
+ // not every shader pack has a textures folder
+ if (Directory.Exists(Path.Combine(Directories.ReShade, "Textures", name)) && !data["GENERAL"]["TextureSearchPaths"].Contains(name))
+ data["GENERAL"]["TextureSearchPaths"] += GetSearchPath("Textures", name);
+
+ parser.WriteFile(ConfigLocation, data);
+ }
+
+ public static void DeleteShaders(string name)
+ {
+ Debug.WriteLine($"[ReShade] Deleting shaders for {name}");
+
+ string shadersPath = Path.Combine(Directories.ReShade, "Shaders", name);
+ string texturesPath = Path.Combine(Directories.ReShade, "Textures", name);
+
+ if (Directory.Exists(shadersPath))
+ Directory.Delete(shadersPath, true);
+
+ if (Directory.Exists(texturesPath))
+ Directory.Delete(texturesPath, true);
+
+ if (!File.Exists(ConfigLocation))
+ return;
+
+ // now we have to update ReShade.ini and remove the installed shaders from the search paths
+ FileIniDataParser parser = new();
+ IniData data = parser.ReadFile(ConfigLocation);
+
+ string shaderSearchPaths = data["GENERAL"]["EffectSearchPaths"];
+ string textureSearchPaths = data["GENERAL"]["TextureSearchPaths"];
+
+ if (shaderSearchPaths.Contains(name))
+ {
+ string searchPath = GetSearchPath("Shaders", name);
+ data["GENERAL"]["EffectSearchPaths"] = shaderSearchPaths.Remove(shaderSearchPaths.IndexOf(searchPath), searchPath.Length);
+ }
+
+ if (textureSearchPaths.Contains(name))
+ {
+ string searchPath = GetSearchPath("Textures", name);
+ data["GENERAL"]["TextureSearchPaths"] = textureSearchPaths.Remove(textureSearchPaths.IndexOf(searchPath), searchPath.Length);
+ }
+
+ parser.WriteFile(ConfigLocation, data);
+ }
+
+ public static async Task InstallExtraviPresets()
+ {
+ Debug.WriteLine("[ReShade] Installing Extravi's presets...");
+
+ foreach (string name in ExtraviPresetsShaders)
+ await DownloadShaders(name);
+
+ byte[] bytes = await App.HttpClient.GetByteArrayAsync($"{BaseUrl}/reshade-presets.zip");
+
+ using MemoryStream zipStream = new(bytes);
+ using ZipArchive archive = new(zipStream);
+
+ foreach (ZipArchiveEntry entry in archive.Entries)
+ {
+ if (entry.FullName.EndsWith('/'))
+ continue;
+
+ // remove containing folder
+ string filename = entry.FullName.Substring(entry.FullName.IndexOf('/') + 1);
+
+ await Task.Run(() => entry.ExtractToFile(Path.Combine(Directories.ReShade, "Presets", filename), true));
+ }
+ }
+
+ public static void UninstallExtraviPresets()
+ {
+ Debug.WriteLine("[ReShade] Uninstalling Extravi's presets...");
+
+ FileInfo[] presets = new DirectoryInfo(Path.Combine(Directories.ReShade, "Presets")).GetFiles();
+
+ foreach (FileInfo preset in presets)
+ {
+ if (preset.Name.StartsWith("Extravi"))
+ preset.Delete();
+ }
+
+ foreach (string name in ExtraviPresetsShaders)
+ DeleteShaders(name);
+ }
+
+ public static async Task CheckModifications()
+ {
+ Debug.WriteLine("[ReShade] Checking ReShade modifications... ");
+
+ string injectorLocation = Path.Combine(Directories.Modifications, "dxgi.dll");
+
+ // initialize directories
+ Directory.CreateDirectory(Directories.ReShade);
+ Directory.CreateDirectory(Path.Combine(Directories.ReShade, "Fonts"));
+ Directory.CreateDirectory(Path.Combine(Directories.ReShade, "Screenshots"));
+ Directory.CreateDirectory(Path.Combine(Directories.ReShade, "Shaders"));
+ Directory.CreateDirectory(Path.Combine(Directories.ReShade, "Textures"));
+ Directory.CreateDirectory(Path.Combine(Directories.ReShade, "Presets"));
+
+ if (!App.Settings.UseReShadeExtraviPresets)
+ {
+ UninstallExtraviPresets();
+ App.Settings.ExtraviPresetsVersion = "";
+ }
+
+ if (!App.Settings.UseReShade)
+ {
+ Debug.WriteLine("[ReShade] Uninstalling ReShade...");
+
+ // delete any stock config files
+ File.Delete(injectorLocation);
+ File.Delete(ConfigLocation);
+
+ App.Settings.ReShadeConfigVersion = "";
+
+ DeleteShaders("Stock");
+
+ return;
+ }
+
+ // the version manfiest contains the version of reshade available for download and the last date the presets were updated
+ var versionManifest = await Utilities.GetJson("https://raw.githubusercontent.com/Extravi/extravi.github.io/main/update/version.json");
+ bool shouldFetchReShade = false;
+ bool shouldFetchConfig = false;
+
+ if (!File.Exists(injectorLocation))
+ {
+ shouldFetchReShade = true;
+ }
+ else if (versionManifest is not null)
+ {
+ // check if an update for reshade is available
+ FileVersionInfo injectorVersionInfo = FileVersionInfo.GetVersionInfo(injectorLocation);
+
+ if (injectorVersionInfo.ProductVersion != versionManifest.ReShade)
+ shouldFetchReShade = true;
+ }
+
+ // check if we should download a fresh copy of the config
+ // extravi may need to update the config ota, in which case we'll redownload it
+ if (!File.Exists(ConfigLocation) || versionManifest is not null && App.Settings.ReShadeConfigVersion != versionManifest.ConfigFile)
+ shouldFetchConfig = true;
+
+ if (shouldFetchReShade)
+ {
+ Debug.WriteLine("[ReShade] Installing/Upgrading ReShade...");
+
+ {
+ byte[] bytes = await App.HttpClient.GetByteArrayAsync($"{BaseUrl}/dxgi.zip");
+ using MemoryStream zipStream = new(bytes);
+ using ZipArchive archive = new(zipStream);
+ archive.ExtractToDirectory(Directories.Modifications, true);
+ }
+ }
+
+ if (shouldFetchConfig)
+ {
+ await DownloadConfig();
+
+ if (versionManifest is not null)
+ App.Settings.ReShadeConfigVersion = versionManifest.ConfigFile;
+ }
+
+ await DownloadShaders("Stock");
+
+ if (App.Settings.UseReShadeExtraviPresets && App.Settings.ExtraviPresetsVersion != versionManifest!.Presets)
+ {
+ await InstallExtraviPresets();
+ App.Settings.ExtraviPresetsVersion = versionManifest.Presets;
+ }
+
+ SynchronizeConfigFile();
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/Protocol.cs b/Bloxstrap/Bloxstrap/Helpers/Protocol.cs
new file mode 100644
index 0000000..4fffdc0
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/Protocol.cs
@@ -0,0 +1,112 @@
+using System.Diagnostics;
+using System.Text;
+using System.Web;
+using Microsoft.Win32;
+using System.Collections.Generic;
+using System.Windows;
+using System;
+
+namespace Bloxstrap.Helpers
+{
+ public class Protocol
+ {
+ // map uri keys to command line args
+ private static readonly IReadOnlyDictionary UriKeyArgMap = new Dictionary()
+ {
+ // excluding roblox-player and browsertrackerid
+ { "launchmode", "--" },
+ { "gameinfo", "-t " },
+ { "placelauncherurl", "-j "},
+ // { "launchtime", "--launchtime=" }, we'll set this when launching the game client
+ { "robloxLocale", "--rloc " },
+ { "gameLocale", "--gloc " },
+ { "channel", "-channel " }
+ };
+
+ public static string ParseUri(string protocol)
+ {
+ string[] keyvalPair;
+ string key;
+ string val;
+ StringBuilder commandLine = new();
+
+ foreach (var parameter in protocol.Split('+'))
+ {
+ if (!parameter.Contains(':'))
+ continue;
+
+ keyvalPair = parameter.Split(':');
+ key = keyvalPair[0];
+ val = keyvalPair[1];
+
+ if (!UriKeyArgMap.ContainsKey(key) || string.IsNullOrEmpty(val))
+ continue;
+
+ if (key == "launchmode" && val == "play")
+ val = "app";
+
+ if (key == "placelauncherurl")
+ val = HttpUtility.UrlDecode(val).Replace("browserTrackerId", "lol");
+
+ if (key == "channel")
+ {
+ if (val.ToLower() != App.Settings.Channel.ToLower())
+ {
+ MessageBoxResult result = !App.Settings.PromptChannelChange ? MessageBoxResult.Yes : App.ShowMessageBox(
+ $"{App.ProjectName} was launched with the Roblox build channel set to {val}, however your current preferred channel is {App.Settings.Channel}.\n\n" +
+ $"Would you like to switch channels from {App.Settings.Channel} to {val}?",
+ MessageBoxImage.Question,
+ MessageBoxButton.YesNo
+ );
+
+ if (result == MessageBoxResult.Yes)
+ App.Settings.Channel = val;
+ }
+
+ // we'll set the arg when launching
+ continue;
+ }
+
+ commandLine.Append(UriKeyArgMap[key] + val + " ");
+ }
+
+ return commandLine.ToString();
+ }
+
+ public static void Register(string key, string name, string handler)
+ {
+ string handlerArgs = $"\"{handler}\" %1";
+ RegistryKey uriKey = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{key}");
+ RegistryKey uriIconKey = uriKey.CreateSubKey("DefaultIcon");
+ RegistryKey uriCommandKey = uriKey.CreateSubKey(@"shell\open\command");
+
+ if (uriKey.GetValue("") is null)
+ {
+ uriKey.SetValue("", $"URL: {name} Protocol");
+ uriKey.SetValue("URL Protocol", "");
+ }
+
+ if ((string?)uriCommandKey.GetValue("") != handlerArgs)
+ {
+ uriIconKey.SetValue("", handler);
+ uriCommandKey.SetValue("", handlerArgs);
+ }
+
+ uriKey.Close();
+ uriIconKey.Close();
+ uriCommandKey.Close();
+ }
+
+ public static void Unregister(string key)
+ {
+ try
+ {
+ Registry.CurrentUser.DeleteSubKeyTree($@"Software\Classes\{key}");
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine($"Failed to unregister {key}: {e}");
+ }
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/RSMM/Package.cs b/Bloxstrap/Bloxstrap/Helpers/RSMM/Package.cs
new file mode 100644
index 0000000..bb254b7
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/RSMM/Package.cs
@@ -0,0 +1,17 @@
+// https://github.com/MaximumADHD/Roblox-Studio-Mod-Manager/blob/main/ProjectSrc/Utility/Package.cs
+
+namespace Bloxstrap.Helpers.RSMM
+{
+ internal class Package
+ {
+ public string Name { get; set; } = "";
+ public string Signature { get; set; } = "";
+ public int PackedSize { get; set; }
+ public int Size { get; set; }
+
+ public override string ToString()
+ {
+ return $"[{Signature}] {Name}";
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/RSMM/PackageManifest.cs b/Bloxstrap/Bloxstrap/Helpers/RSMM/PackageManifest.cs
new file mode 100644
index 0000000..1920da9
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/RSMM/PackageManifest.cs
@@ -0,0 +1,60 @@
+// https://github.com/MaximumADHD/Roblox-Studio-Mod-Manager/blob/main/ProjectSrc/Bootstrapper/PackageManifest.cs
+
+using System.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System;
+
+namespace Bloxstrap.Helpers.RSMM
+{
+ internal class PackageManifest : List
+ {
+ private PackageManifest(string data)
+ {
+ using StringReader reader = new StringReader(data);
+ string? version = reader.ReadLine();
+
+ if (version != "v0")
+ throw new NotSupportedException($"Unexpected package manifest version: {version} (expected v0!)");
+
+ while (true)
+ {
+ string? fileName = reader.ReadLine();
+ string? signature = reader.ReadLine();
+
+ string? rawPackedSize = reader.ReadLine();
+ string? rawSize = reader.ReadLine();
+
+ if (string.IsNullOrEmpty(fileName) ||
+ string.IsNullOrEmpty(signature) ||
+ string.IsNullOrEmpty(rawPackedSize) ||
+ string.IsNullOrEmpty(rawSize))
+ break;
+
+ // ignore launcher
+ if (fileName == "RobloxPlayerLauncher.exe")
+ break;
+
+ int packedSize = int.Parse(rawPackedSize);
+ int size = int.Parse(rawSize);
+
+ Add(new Package
+ {
+ Name = fileName,
+ Signature = signature,
+ PackedSize = packedSize,
+ Size = size
+ });
+ }
+ }
+
+ public static async Task Get(string versionGuid)
+ {
+ string pkgManifestUrl = $"{DeployManager.BaseUrl}/{versionGuid}-rbxPkgManifest.txt";
+ var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl);
+
+ return new PackageManifest(pkgManifestData);
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/RSMM/SystemEvent.cs b/Bloxstrap/Bloxstrap/Helpers/RSMM/SystemEvent.cs
new file mode 100644
index 0000000..28e55a2
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/RSMM/SystemEvent.cs
@@ -0,0 +1,43 @@
+// https://github.com/MaximumADHD/Roblox-Studio-Mod-Manager/blob/main/ProjectSrc/Utility/SystemEvent.cs
+
+using System.Threading;
+using System.Threading.Tasks;
+using System;
+
+namespace Bloxstrap.Helpers.RSMM
+{
+ public class SystemEvent : EventWaitHandle
+ {
+ public string Name { get; private set; }
+
+ public SystemEvent(string name, bool init = false, EventResetMode mode = EventResetMode.AutoReset) : base(init, mode, name)
+ {
+ if (init)
+ Reset();
+ else
+ Set();
+
+ Name = name;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ public Task WaitForEvent()
+ {
+ return Task.Run(WaitOne);
+ }
+
+ public Task WaitForEvent(TimeSpan timeout, bool exitContext = false)
+ {
+ return Task.Run(() => WaitOne(timeout, exitContext));
+ }
+
+ public Task WaitForEvent(int millisecondsTimeout, bool exitContext = false)
+ {
+ return Task.Run(() => WaitOne(millisecondsTimeout, exitContext));
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/ResourceHelper.cs b/Bloxstrap/Bloxstrap/Helpers/ResourceHelper.cs
new file mode 100644
index 0000000..7d82c01
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/ResourceHelper.cs
@@ -0,0 +1,27 @@
+using System.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+using System.Linq;
+
+namespace Bloxstrap.Helpers
+{
+ internal class ResourceHelper
+ {
+ static readonly Assembly assembly = Assembly.GetExecutingAssembly();
+ static readonly string[] resourceNames = assembly.GetManifestResourceNames();
+
+ public static async Task Get(string name)
+ {
+ string path = resourceNames.Single(str => str.EndsWith(name));
+
+ using (Stream stream = assembly.GetManifestResourceStream(path)!)
+ {
+ using (MemoryStream memoryStream = new())
+ {
+ await stream.CopyToAsync(memoryStream);
+ return memoryStream.ToArray();
+ }
+ }
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/Updater.cs b/Bloxstrap/Bloxstrap/Helpers/Updater.cs
new file mode 100644
index 0000000..84b9733
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/Updater.cs
@@ -0,0 +1,65 @@
+using System.Diagnostics;
+using System.IO;
+
+using Bloxstrap.Dialogs.Menu;
+using System.Windows;
+using System;
+
+namespace Bloxstrap.Helpers
+{
+ public class Updater
+ {
+ public static void CheckInstalledVersion()
+ {
+ if (Environment.ProcessPath is null || !File.Exists(Directories.App) || Environment.ProcessPath == Directories.App)
+ return;
+
+ bool isAutoUpgrade = Environment.ProcessPath.StartsWith(Directories.Updates);
+
+ // if downloaded version doesn't match, replace installed version with downloaded version
+ FileVersionInfo currentVersionInfo = FileVersionInfo.GetVersionInfo(Environment.ProcessPath);
+ FileVersionInfo installedVersionInfo = FileVersionInfo.GetVersionInfo(Directories.App);
+
+ if (installedVersionInfo.ProductVersion == currentVersionInfo.ProductVersion)
+ return;
+
+
+ MessageBoxResult result;
+
+ // silently upgrade version if the command line flag is set or if we're launching from an auto update
+ if (App.IsUpgrade || isAutoUpgrade)
+ {
+ result = MessageBoxResult.Yes;
+ }
+ else
+ {
+ result = App.ShowMessageBox(
+ $"The version of {App.ProjectName} you've launched is different to the version you currently have installed.\nWould you like to upgrade your currently installed version?",
+ MessageBoxImage.Question,
+ MessageBoxButton.YesNo
+ );
+ }
+
+
+ if (result != MessageBoxResult.Yes)
+ return;
+
+ File.Delete(Directories.App);
+ File.Copy(Environment.ProcessPath, Directories.App);
+
+ Bootstrapper.Register();
+
+ if (App.IsQuiet || isAutoUpgrade)
+ return;
+
+ App.ShowMessageBox(
+ $"{App.ProjectName} has been updated to v{currentVersionInfo.ProductVersion}",
+ MessageBoxImage.Information,
+ MessageBoxButton.OK
+ );
+
+ new Preferences().ShowDialog();
+ App.Terminate();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Helpers/Utilities.cs b/Bloxstrap/Bloxstrap/Helpers/Utilities.cs
new file mode 100644
index 0000000..ac679fe
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/Utilities.cs
@@ -0,0 +1,84 @@
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Security.Cryptography;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System;
+using System.Linq;
+
+namespace Bloxstrap.Helpers
+{
+ public class Utilities
+ {
+ public static bool IsDirectoryEmpty(string path)
+ {
+ return !Directory.EnumerateFileSystemEntries(path).Any();
+ }
+
+ public static long GetFreeDiskSpace(string path)
+ {
+ foreach (DriveInfo drive in DriveInfo.GetDrives())
+ {
+ if (path.StartsWith(drive.Name))
+ return drive.AvailableFreeSpace;
+ }
+
+ return -1;
+ }
+
+ public static void OpenWebsite(string website)
+ {
+ Process.Start(new ProcessStartInfo { FileName = website, UseShellExecute = true });
+ }
+
+ public static async Task GetJson(string url)
+ {
+ try
+ {
+ string json = await App.HttpClient.GetStringAsync(url);
+ return JsonSerializer.Deserialize(json);
+ }
+ catch (Exception)
+ {
+ return default;
+ }
+ }
+
+ public static string MD5File(string filename)
+ {
+ using (MD5 md5 = MD5.Create())
+ {
+ using (FileStream stream = File.OpenRead(filename))
+ {
+ byte[] hash = md5.ComputeHash(stream);
+ return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
+ }
+ }
+ }
+
+ public static string MD5Data(byte[] data)
+ {
+ using (MD5 md5 = MD5.Create())
+ {
+ byte[] hash = md5.ComputeHash(data);
+ 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.Contains(delimiter))
+ return null;
+
+ return substr.Split(delimiter)[0];
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Helpers/WindowScaling.cs b/Bloxstrap/Bloxstrap/Helpers/WindowScaling.cs
new file mode 100644
index 0000000..dd2e843
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Helpers/WindowScaling.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Forms;
+
+namespace Bloxstrap.Helpers
+{
+ public class WindowScaling
+ {
+ public static double GetFactor()
+ {
+ return Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth;
+ }
+
+ public static int GetScaledNumber(int number)
+ {
+ return (int)Math.Ceiling(number * GetFactor());
+ }
+
+ public static System.Drawing.Size GetScaledSize(System.Drawing.Size size)
+ {
+ return new System.Drawing.Size(GetScaledNumber(size.Width), GetScaledNumber(size.Height));
+ }
+
+ public static System.Drawing.Point GetScaledPoint(System.Drawing.Point point)
+ {
+ return new System.Drawing.Point(GetScaledNumber(point.X), GetScaledNumber(point.Y));
+ }
+
+ public static Padding GetScaledPadding(Padding padding)
+ {
+ return new Padding(GetScaledNumber(padding.Left), GetScaledNumber(padding.Top), GetScaledNumber(padding.Right), GetScaledNumber(padding.Bottom));
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Models/ClientVersion.cs b/Bloxstrap/Bloxstrap/Models/ClientVersion.cs
new file mode 100644
index 0000000..8fa2166
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Models/ClientVersion.cs
@@ -0,0 +1,19 @@
+using System.Text.Json.Serialization;
+using System;
+
+namespace Bloxstrap.Models
+{
+ public class ClientVersion
+ {
+ [JsonPropertyName("version")]
+ public string Version { get; set; } = null!;
+
+ [JsonPropertyName("clientVersionUpload")]
+ public string VersionGuid { get; set; } = null!;
+
+ [JsonPropertyName("bootstrapperVersion")]
+ public string BootstrapperVersion { get; set; } = null!;
+
+ public DateTime? Timestamp { get; set; }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Models/GithubRelease.cs b/Bloxstrap/Bloxstrap/Models/GithubRelease.cs
new file mode 100644
index 0000000..e1c5058
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Models/GithubRelease.cs
@@ -0,0 +1,32 @@
+using System.Text.Json.Serialization;
+using System.Collections.Generic;
+
+namespace Bloxstrap.Models
+{
+ public class GithubRelease
+ {
+ [JsonPropertyName("tag_name")]
+ public string TagName { get; set; } = null!;
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
+
+ [JsonPropertyName("body")]
+ public string Body { get; set; } = null!;
+
+ [JsonPropertyName("created_at")]
+ public string CreatedAt { get; set; } = null!;
+
+ [JsonPropertyName("assets")]
+ public List? Assets { get; set; }
+ }
+
+ public class GithubReleaseAsset
+ {
+ [JsonPropertyName("browser_download_url")]
+ public string BrowserDownloadUrl { get; set; } = null!;
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Models/ReShadeVersionManifest.cs b/Bloxstrap/Bloxstrap/Models/ReShadeVersionManifest.cs
new file mode 100644
index 0000000..9110bf3
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Models/ReShadeVersionManifest.cs
@@ -0,0 +1,9 @@
+namespace Bloxstrap.Models
+{
+ public class ReShadeVersionManifest
+ {
+ public string ReShade { get; set; } = null!;
+ public string Presets { get; set; } = null!;
+ public string ConfigFile { get; set; } = null!;
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Models/RobloxAsset.cs b/Bloxstrap/Bloxstrap/Models/RobloxAsset.cs
new file mode 100644
index 0000000..8c4415c
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Models/RobloxAsset.cs
@@ -0,0 +1,13 @@
+namespace Bloxstrap.Models
+{
+ public class RobloxAsset
+ {
+ public string? Name { get; set; }
+ public RobloxAssetCreator? Creator { get; set; }
+ }
+
+ public class RobloxAssetCreator
+ {
+ public string? Name { get; set; }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Models/RobloxThumbnails.cs b/Bloxstrap/Bloxstrap/Models/RobloxThumbnails.cs
new file mode 100644
index 0000000..11e701c
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Models/RobloxThumbnails.cs
@@ -0,0 +1,17 @@
+using System.Text.Json.Serialization;
+using System.Collections.Generic;
+
+namespace Bloxstrap.Models
+{
+ public class RobloxThumbnails
+ {
+ [JsonPropertyName("data")]
+ public List? Data { get; set; }
+ }
+
+ public class RobloxThumbnail
+ {
+ [JsonPropertyName("imageUrl")]
+ public string? ImageUrl { get; set; }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Models/SettingsFormat.cs b/Bloxstrap/Bloxstrap/Models/SettingsFormat.cs
new file mode 100644
index 0000000..be00f45
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Models/SettingsFormat.cs
@@ -0,0 +1,41 @@
+using Bloxstrap.Enums;
+using Bloxstrap.Helpers;
+
+namespace Bloxstrap.Models
+{
+ public class SettingsFormat
+ {
+ // could these be moved to a separate file (something like State.json)?
+ // the only problem is i havent yet figured out a way to boil down the settings handler to reduce boilerplate
+ // as the Program class needs a Settings and a SettingsManager property
+ // once i figure that out, then ig i could move these
+ public string VersionGuid { get; set; } = "";
+ public string RFUVersion { get; set; } = "";
+ public string ReShadeConfigVersion { get; set; } = "";
+ public string ExtraviPresetsVersion { get; set; } = "";
+
+ // bloxstrap configuration
+ public BootstrapperStyle BootstrapperStyle { get; set; } = BootstrapperStyle.ProgressDialog;
+ public BootstrapperIcon BootstrapperIcon { get; set; } = BootstrapperIcon.IconBloxstrap;
+ public Theme Theme { get; set; } = Theme.Default;
+ public bool CheckForUpdates { get; set; } = true;
+ public bool CreateDesktopIcon { get; set; } = true;
+
+ // channel configuration
+ public string Channel { get; set; } = DeployManager.DefaultChannel;
+ public bool PromptChannelChange { get; set; } = false;
+
+ // integration configuration
+ public bool UseDiscordRichPresence { get; set; } = true;
+ public bool HideRPCButtons { get; set; } = false;
+ public bool RFUEnabled { get; set; } = false;
+ public bool RFUAutoclose { get; set; } = false;
+ public bool UseReShade { get; set; } = false;
+ public bool UseReShadeExtraviPresets { get; set; } = false;
+
+ // mod preset configuration
+ public bool UseOldDeathSound { get; set; } = true;
+ public bool UseOldMouseCursor { get; set; } = false;
+ public bool UseDisableAppPatch { get; set; } = false;
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Properties/Resources.Designer.cs b/Bloxstrap/Bloxstrap/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..eb97d68
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Properties/Resources.Designer.cs
@@ -0,0 +1,263 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Bloxstrap.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Bloxstrap.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap CancelButton {
+ get {
+ object obj = ResourceManager.GetObject("CancelButton", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap CancelButtonHover {
+ get {
+ object obj = ResourceManager.GetObject("CancelButtonHover", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap DarkCancelButton {
+ get {
+ object obj = ResourceManager.GetObject("DarkCancelButton", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap DarkCancelButtonHover {
+ get {
+ object obj = ResourceManager.GetObject("DarkCancelButtonHover", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
+ ///
+ internal static System.Drawing.Icon Icon2009_ico {
+ get {
+ object obj = ResourceManager.GetObject("Icon2009_ico", resourceCulture);
+ return ((System.Drawing.Icon)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap Icon2009_png {
+ get {
+ object obj = ResourceManager.GetObject("Icon2009_png", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
+ ///
+ internal static System.Drawing.Icon Icon2011_ico {
+ get {
+ object obj = ResourceManager.GetObject("Icon2011_ico", resourceCulture);
+ return ((System.Drawing.Icon)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap Icon2011_png {
+ get {
+ object obj = ResourceManager.GetObject("Icon2011_png", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
+ ///
+ internal static System.Drawing.Icon Icon2017_ico {
+ get {
+ object obj = ResourceManager.GetObject("Icon2017_ico", resourceCulture);
+ return ((System.Drawing.Icon)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap Icon2017_png {
+ get {
+ object obj = ResourceManager.GetObject("Icon2017_png", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
+ ///
+ internal static System.Drawing.Icon Icon2019_ico {
+ get {
+ object obj = ResourceManager.GetObject("Icon2019_ico", resourceCulture);
+ return ((System.Drawing.Icon)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap Icon2019_png {
+ get {
+ object obj = ResourceManager.GetObject("Icon2019_png", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
+ ///
+ internal static System.Drawing.Icon Icon2022_ico {
+ get {
+ object obj = ResourceManager.GetObject("Icon2022_ico", resourceCulture);
+ return ((System.Drawing.Icon)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap Icon2022_png {
+ get {
+ object obj = ResourceManager.GetObject("Icon2022_png", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
+ ///
+ internal static System.Drawing.Icon IconBloxstrap_ico {
+ get {
+ object obj = ResourceManager.GetObject("IconBloxstrap_ico", resourceCulture);
+ return ((System.Drawing.Icon)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap IconBloxstrap_png {
+ get {
+ object obj = ResourceManager.GetObject("IconBloxstrap_png", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
+ ///
+ internal static System.Drawing.Icon IconEarly2015_ico {
+ get {
+ object obj = ResourceManager.GetObject("IconEarly2015_ico", resourceCulture);
+ return ((System.Drawing.Icon)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap IconEarly2015_png {
+ get {
+ object obj = ResourceManager.GetObject("IconEarly2015_png", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
+ ///
+ internal static System.Drawing.Icon IconLate2015_ico {
+ get {
+ object obj = ResourceManager.GetObject("IconLate2015_ico", resourceCulture);
+ return ((System.Drawing.Icon)(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap IconLate2015_png {
+ get {
+ object obj = ResourceManager.GetObject("IconLate2015_png", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Properties/Resources.resx b/Bloxstrap/Bloxstrap/Properties/Resources.resx
new file mode 100644
index 0000000..50ed237
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Properties/Resources.resx
@@ -0,0 +1,181 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ ..\Resources\CancelButton.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\CancelButtonHover.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\DarkCancelButton.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\DarkCancelButtonHover.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Icon2009-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Icon2009-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Icon2011-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Icon2011-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Icon2017-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Icon2017-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Icon2019-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Icon2019-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Icon2022-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\Icon2022-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\IconBloxstrap-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\IconBloxstrap-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\IconEarly2015-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\IconEarly2015-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\IconLate2015-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\IconLate2015-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Properties/Settings.Designer.cs b/Bloxstrap/Bloxstrap/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..0e376d2
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Bloxstrap.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.0.3.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/Bloxstrap/Bloxstrap/Properties/Settings.settings b/Bloxstrap/Bloxstrap/Properties/Settings.settings
new file mode 100644
index 0000000..049245f
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Properties/Settings.settings
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Bloxstrap/Bloxstrap/Properties/launchSettings.json b/Bloxstrap/Bloxstrap/Properties/launchSettings.json
new file mode 100644
index 0000000..59170c9
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Bloxstrap": {
+ "commandName": "Project",
+ "commandLineArgs": "-foo -bar"
+ }
+ }
+}
\ No newline at end of file
diff --git a/Bloxstrap/Bloxstrap/Resources/CancelButton.png b/Bloxstrap/Bloxstrap/Resources/CancelButton.png
new file mode 100644
index 0000000..966103b
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/CancelButton.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/CancelButtonHover.png b/Bloxstrap/Bloxstrap/Resources/CancelButtonHover.png
new file mode 100644
index 0000000..80b79a4
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/CancelButtonHover.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/DarkCancelButton.png b/Bloxstrap/Bloxstrap/Resources/DarkCancelButton.png
new file mode 100644
index 0000000..4a75277
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/DarkCancelButton.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/DarkCancelButtonHover.png b/Bloxstrap/Bloxstrap/Resources/DarkCancelButtonHover.png
new file mode 100644
index 0000000..4686f0d
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/DarkCancelButtonHover.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Icon2009-ico.ico b/Bloxstrap/Bloxstrap/Resources/Icon2009-ico.ico
new file mode 100644
index 0000000..f9f0a6b
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Icon2009-ico.ico differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Icon2009-png.png b/Bloxstrap/Bloxstrap/Resources/Icon2009-png.png
new file mode 100644
index 0000000..b0092c7
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Icon2009-png.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Icon2011-ico.ico b/Bloxstrap/Bloxstrap/Resources/Icon2011-ico.ico
new file mode 100644
index 0000000..a2876df
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Icon2011-ico.ico differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Icon2011-png.png b/Bloxstrap/Bloxstrap/Resources/Icon2011-png.png
new file mode 100644
index 0000000..d28f26a
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Icon2011-png.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Icon2017-ico.ico b/Bloxstrap/Bloxstrap/Resources/Icon2017-ico.ico
new file mode 100644
index 0000000..885a743
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Icon2017-ico.ico differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Icon2017-png.png b/Bloxstrap/Bloxstrap/Resources/Icon2017-png.png
new file mode 100644
index 0000000..a589af2
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Icon2017-png.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Icon2019-ico.ico b/Bloxstrap/Bloxstrap/Resources/Icon2019-ico.ico
new file mode 100644
index 0000000..d218fb9
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Icon2019-ico.ico differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Icon2019-png.png b/Bloxstrap/Bloxstrap/Resources/Icon2019-png.png
new file mode 100644
index 0000000..99cf288
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Icon2019-png.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Icon2022-ico.ico b/Bloxstrap/Bloxstrap/Resources/Icon2022-ico.ico
new file mode 100644
index 0000000..877b76b
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Icon2022-ico.ico differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Icon2022-png.png b/Bloxstrap/Bloxstrap/Resources/Icon2022-png.png
new file mode 100644
index 0000000..7f26209
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Icon2022-png.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/IconBloxstrap-ico.ico b/Bloxstrap/Bloxstrap/Resources/IconBloxstrap-ico.ico
new file mode 100644
index 0000000..1b6988c
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/IconBloxstrap-ico.ico differ
diff --git a/Bloxstrap/Bloxstrap/Resources/IconBloxstrap-png.png b/Bloxstrap/Bloxstrap/Resources/IconBloxstrap-png.png
new file mode 100644
index 0000000..1e3a10a
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/IconBloxstrap-png.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/IconEarly2015-ico.ico b/Bloxstrap/Bloxstrap/Resources/IconEarly2015-ico.ico
new file mode 100644
index 0000000..de52a19
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/IconEarly2015-ico.ico differ
diff --git a/Bloxstrap/Bloxstrap/Resources/IconEarly2015-png.png b/Bloxstrap/Bloxstrap/Resources/IconEarly2015-png.png
new file mode 100644
index 0000000..1de7759
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/IconEarly2015-png.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/IconLate2015-ico.ico b/Bloxstrap/Bloxstrap/Resources/IconLate2015-ico.ico
new file mode 100644
index 0000000..634a089
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/IconLate2015-ico.ico differ
diff --git a/Bloxstrap/Bloxstrap/Resources/IconLate2015-png.png b/Bloxstrap/Bloxstrap/Resources/IconLate2015-png.png
new file mode 100644
index 0000000..316bf53
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/IconLate2015-png.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Mods/OldCursor.png b/Bloxstrap/Bloxstrap/Resources/Mods/OldCursor.png
new file mode 100644
index 0000000..694c26a
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Mods/OldCursor.png differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Mods/OldDeath.ogg b/Bloxstrap/Bloxstrap/Resources/Mods/OldDeath.ogg
new file mode 100644
index 0000000..193677b
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Mods/OldDeath.ogg differ
diff --git a/Bloxstrap/Bloxstrap/Resources/Mods/OldFarCursor.png b/Bloxstrap/Bloxstrap/Resources/Mods/OldFarCursor.png
new file mode 100644
index 0000000..daf5471
Binary files /dev/null and b/Bloxstrap/Bloxstrap/Resources/Mods/OldFarCursor.png differ
diff --git a/Bloxstrap/Bloxstrap/SettingsManager.cs b/Bloxstrap/Bloxstrap/SettingsManager.cs
new file mode 100644
index 0000000..dde5a0e
--- /dev/null
+++ b/Bloxstrap/Bloxstrap/SettingsManager.cs
@@ -0,0 +1,83 @@
+using System.Diagnostics;
+using System.IO;
+using System.Text.Json;
+
+using Bloxstrap.Models;
+using System;
+using System.Threading;
+
+namespace Bloxstrap
+{
+ public class SettingsManager
+ {
+ public SettingsFormat Settings = new();
+ public bool ShouldSave = false;
+ private bool IsSaving = false;
+
+ private string? _saveLocation;
+ public string? SaveLocation
+ {
+ get => _saveLocation;
+
+ set
+ {
+ if (!String.IsNullOrEmpty(_saveLocation))
+ return;
+
+ _saveLocation = value;
+
+ string settingsJson = "";
+
+ if (File.Exists(_saveLocation))
+ settingsJson = File.ReadAllText(_saveLocation);
+
+ Debug.WriteLine(settingsJson);
+
+ try
+ {
+ var settings = JsonSerializer.Deserialize(settingsJson);
+
+ if (settings is null)
+ throw new Exception("Deserialization returned null");
+
+ Settings = settings;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Failed to fetch settings! Reverting to defaults... ({ex.Message})");
+ // Settings = new();
+ }
+ }
+ }
+
+ public void Save()
+ {
+ if (IsSaving)
+ {
+ // sometimes Save() is called at the same time from both Main() and Exit(),
+ // so this is here to avoid the program exiting before saving
+
+ Thread.Sleep(1000);
+ return;
+ }
+
+ IsSaving = true;
+
+ Debug.WriteLine("Attempting to save...");
+
+ string SettingsJson = JsonSerializer.Serialize(Settings, new JsonSerializerOptions { WriteIndented = true });
+ Debug.WriteLine(SettingsJson);
+
+ if (!ShouldSave || SaveLocation is null)
+ {
+ Debug.WriteLine("ShouldSave set to false, not saving...");
+ return;
+ }
+
+ // save settings
+ File.WriteAllText(SaveLocation, SettingsJson);
+
+ IsSaving = false;
+ }
+ }
+}