Features and bugfixes for v1.1.0

- Features
    - Add Discord Rich Presence support (the nuget package is like a year and a half out of date so submodule it is lol)
    - Add update checker
    - Add start menu folder creation

- Bugfixes
   - Fix "Directory is not empty" error when updating Roblox
   - Fix uninstalling sometimes not working properly

- Quality of Life
   - Split Bootstrapper class into partial files
   - Renamed TaskDialogStyle to VistaDialog for name simplification
This commit is contained in:
pizzaboxer 2022-08-11 08:26:28 +01:00
parent f06e38eddb
commit bacb650ddc
26 changed files with 1177 additions and 829 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "DiscordRPC"]
path = DiscordRPC
url = https://github.com/Lachee/discord-rpc-csharp.git

View File

@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bloxstrap", "Bloxstrap\Bloxstrap.csproj", "{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bloxstrap", "Bloxstrap\Bloxstrap.csproj", "{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordRPC", "DiscordRPC\DiscordRPC\DiscordRPC.csproj", "{BDB66971-35FA-45BD-ABD6-70B814D2E55C}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -15,6 +17,10 @@ Global
{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}.Debug|Any CPU.Build.0 = Debug|Any CPU {646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}.Release|Any CPU.ActiveCfg = Release|Any CPU {646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}.Release|Any CPU.Build.0 = Release|Any CPU {646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}.Release|Any CPU.Build.0 = Release|Any CPU
{BDB66971-35FA-45BD-ABD6-70B814D2E55C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDB66971-35FA-45BD-ABD6-70B814D2E55C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDB66971-35FA-45BD-ABD6-70B814D2E55C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDB66971-35FA-45BD-ABD6-70B814D2E55C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -9,14 +9,23 @@
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<Platforms>AnyCPU;x86</Platforms> <Platforms>AnyCPU;x86</Platforms>
<ApplicationIcon>Bloxstrap.ico</ApplicationIcon> <ApplicationIcon>Bloxstrap.ico</ApplicationIcon>
<Version>1.0.0</Version> <Version>1.1.0</Version>
<FileVersion>1.0.0.0</FileVersion> <FileVersion>1.1.0.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="Bloxstrap.ico" /> <Content Include="Bloxstrap.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="securifybv.ShellLink" Version="0.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DiscordRPC\DiscordRPC\DiscordRPC.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Properties\Resources.Designer.cs"> <Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>

View File

@ -1,655 +0,0 @@
using System.Diagnostics;
using System.IO.Compression;
using System.Security.Cryptography;
using Microsoft.Win32;
using Bloxstrap.Enums;
using Bloxstrap.Dialogs.BootstrapperStyles;
using Bloxstrap.Helpers;
using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap
{
public class Bootstrapper
{
private string? LaunchCommandLine;
private string VersionGuid;
private PackageManifest VersionPackageManifest;
private FileManifest VersionFileManifest;
private string VersionFolder;
private readonly string DownloadsFolder;
private readonly bool FreshInstall;
private int ProgressIncrement;
private bool CancelFired = false;
private static readonly HttpClient Client = new();
// in case a new package is added, you can find the corresponding directory
// by opening the stock bootstrapper in a hex editor
// TODO - there ideally should be a less static way to do this that's not hardcoded?
private static readonly IReadOnlyDictionary<string, string> PackageDirectories = new Dictionary<string, string>()
{
{ "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 =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<Settings>\n" +
" <ContentFolder>content</ContentFolder>\n" +
" <BaseUrl>http://www.roblox.com</BaseUrl>\n" +
"</Settings>\n";
public event EventHandler PromptShutdownEvent;
public event ChangeEventHandler<string> ShowSuccessEvent;
public event ChangeEventHandler<string> MessageChanged;
public event ChangeEventHandler<int> ProgressBarValueChanged;
public event ChangeEventHandler<ProgressBarStyle> ProgressBarStyleChanged;
public event ChangeEventHandler<bool> CancelEnabledChanged;
private string _message;
private int _progress = 0;
private ProgressBarStyle _progressStyle = ProgressBarStyle.Marquee;
private bool _cancelEnabled = false;
public string Message
{
get => _message;
private set
{
if (_message == value)
return;
MessageChanged.Invoke(this, new ChangeEventArgs<string>(value));
_message = value;
}
}
public int Progress
{
get => _progress;
private set
{
if (_progress == value)
return;
ProgressBarValueChanged.Invoke(this, new ChangeEventArgs<int>(value));
_progress = value;
}
}
public ProgressBarStyle ProgressStyle
{
get => _progressStyle;
private set
{
if (_progressStyle == value)
return;
ProgressBarStyleChanged.Invoke(this, new ChangeEventArgs<ProgressBarStyle>(value));
_progressStyle = value;
}
}
public bool CancelEnabled
{
get => _cancelEnabled;
private set
{
if (_cancelEnabled == value)
return;
CancelEnabledChanged.Invoke(this, new ChangeEventArgs<bool>(value));
_cancelEnabled = value;
}
}
public Bootstrapper(BootstrapperStyle bootstrapperStyle, string? launchCommandLine = null)
{
Debug.WriteLine("Initializing bootstrapper");
FreshInstall = String.IsNullOrEmpty(Program.Settings.VersionGuid);
LaunchCommandLine = launchCommandLine;
DownloadsFolder = Path.Combine(Program.BaseDirectory, "Downloads");
Client.Timeout = TimeSpan.FromMinutes(10);
switch (bootstrapperStyle)
{
case BootstrapperStyle.TaskDialog:
new TaskDialogStyle(this);
break;
case BootstrapperStyle.LegacyDialog:
Application.Run(new LegacyDialogStyle(this));
break;
case BootstrapperStyle.ProgressDialog:
Application.Run(new ProgressDialogStyle(this));
break;
}
}
public async Task Run()
{
if (LaunchCommandLine == "-uninstall")
{
Uninstall();
return;
}
await CheckLatestVersion();
if (!Directory.Exists(VersionFolder) || Program.Settings.VersionGuid != VersionGuid)
{
Debug.WriteLineIf(!Directory.Exists(VersionFolder), $"Installing latest version (!Directory.Exists({VersionFolder}))");
Debug.WriteLineIf(Program.Settings.VersionGuid != VersionGuid, $"Installing latest version ({Program.Settings.VersionGuid} != {VersionGuid})");
await InstallLatestVersion();
}
// yes, doing this for every start is stupid, but the death sound mod is dynamically toggleable after all
ApplyModifications();
if (Program.IsFirstRun)
Program.SettingsManager.ShouldSave = true;
if (Program.IsFirstRun || FreshInstall)
Register();
CheckInstall();
await StartRoblox();
}
private void CheckIfRunning()
{
Process[] processes = Process.GetProcessesByName("RobloxPlayerBeta");
if (processes.Length > 0)
PromptShutdown();
try
{
// try/catch just in case process was closed before prompt was answered
foreach (Process process in processes)
{
process.CloseMainWindow();
process.Close();
}
}
catch (Exception) { }
}
private async Task StartRoblox()
{
string startEventName = Program.ProjectName.Replace(" ", "") + "StartEvent";
Message = "Starting Roblox...";
// launch time isn't really required for all launches, but it's usually just safest to do this
LaunchCommandLine += " --launchtime=" + DateTimeOffset.Now.ToUnixTimeSeconds() + " -startEvent " + startEventName;
Debug.WriteLine($"Starting game client with command line '{LaunchCommandLine}'");
using (SystemEvent startEvent = new(startEventName))
{
Process.Start(Path.Combine(VersionFolder, "RobloxPlayerBeta.exe"), LaunchCommandLine);
Debug.WriteLine($"Waiting for {startEventName} event to be fired...");
bool startEventFired = await startEvent.WaitForEvent();
startEvent.Close();
if (startEventFired)
{
Debug.WriteLine($"{startEventName} event fired! Exiting in 5 seconds...");
await Task.Delay(5000);
Program.Exit();
}
}
}
// Bootstrapper Installing
public static void Register()
{
RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{Program.ProjectName}");
// new install location selected, delete old one
string? oldInstallLocation = (string?)applicationKey.GetValue("OldInstallLocation");
if (!String.IsNullOrEmpty(oldInstallLocation) && oldInstallLocation != Program.BaseDirectory)
{
try
{
if (Directory.Exists(oldInstallLocation))
Directory.Delete(oldInstallLocation, true);
}
catch (Exception) { }
applicationKey.DeleteValue("OldInstallLocation");
}
applicationKey.SetValue("InstallLocation", Program.BaseDirectory);
applicationKey.Close();
// set uninstall key
RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Program.ProjectName}");
uninstallKey.SetValue("DisplayIcon", $"{Program.FilePath},0");
uninstallKey.SetValue("DisplayName", Program.ProjectName);
uninstallKey.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
uninstallKey.SetValue("InstallLocation", Program.BaseDirectory);
// uninstallKey.SetValue("NoModify", 1);
uninstallKey.SetValue("NoRepair", 1);
uninstallKey.SetValue("Publisher", Program.ProjectName);
uninstallKey.SetValue("ModifyPath", $"\"{Program.FilePath}\" -preferences");
uninstallKey.SetValue("UninstallString", $"\"{Program.FilePath}\" -uninstall");
uninstallKey.Close();
}
public static void CheckInstall()
{
// check if launch uri is set to our bootstrapper
// this doesn't go under register, so we check every launch
// just in case the stock bootstrapper changes it back
Protocol.Register("roblox", "Roblox", Program.FilePath);
Protocol.Register("roblox-player", "Roblox", Program.FilePath);
// in case the user is reinstalling
if (File.Exists(Program.FilePath) && Program.IsFirstRun)
File.Delete(Program.FilePath);
// check to make sure bootstrapper is in the install folder
if (!File.Exists(Program.FilePath) && Environment.ProcessPath is not null)
File.Copy(Environment.ProcessPath, Program.FilePath);
}
private void Uninstall()
{
CheckIfRunning();
// lots of try/catches here... lol
Message = $"Uninstalling {Program.ProjectName}...";
Program.SettingsManager.ShouldSave = false;
// check if stock bootstrapper is still installed
RegistryKey? bootstrapperKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-player");
if (bootstrapperKey is null)
{
Protocol.Unregister("roblox");
Protocol.Unregister("roblox-player");
}
else
{
// revert launch uri handler to stock bootstrapper
string bootstrapperLocation = (string?)bootstrapperKey.GetValue("InstallLocation") + "RobloxPlayerLauncher.exe";
Protocol.Register("roblox", "Roblox", bootstrapperLocation);
Protocol.Register("roblox-player", "Roblox", bootstrapperLocation);
}
try
{
// delete application key
Registry.CurrentUser.DeleteSubKey($@"Software\{Program.ProjectName}");
}
catch (Exception) { }
try
{
// delete installation folder
// (should delete everything except bloxstrap itself)
Directory.Delete(Program.BaseDirectory, true);
}
catch (Exception) { }
try
{
// delete uninstall key
Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Program.ProjectName}");
}
catch (Exception) { }
ShowSuccess($"{Program.ProjectName} has been uninstalled");
Program.Exit();
}
// Roblox Installing
private async Task CheckLatestVersion()
{
Message = "Connecting to Roblox...";
Debug.WriteLine($"Checking latest version...");
VersionGuid = await Client.GetStringAsync($"{Program.BaseUrlSetup}/version");
VersionFolder = Path.Combine(Program.BaseDirectory, "Versions", VersionGuid);
Debug.WriteLine($"Latest version is {VersionGuid}");
Debug.WriteLine("Getting package manifest...");
VersionPackageManifest = await PackageManifest.Get(VersionGuid);
Debug.WriteLine("Getting file manifest...");
VersionFileManifest = await FileManifest.Get(VersionGuid);
}
private async Task InstallLatestVersion()
{
CheckIfRunning();
if (FreshInstall)
Message = "Installing Roblox...";
else
Message = "Upgrading Roblox...";
Directory.CreateDirectory(Program.BaseDirectory);
CancelEnabled = true;
// i believe the original bootstrapper bases the progress bar off zip
// extraction progress, but here i'm doing package download progress
ProgressStyle = ProgressBarStyle.Continuous;
ProgressIncrement = (int)Math.Floor((decimal) 1 / VersionPackageManifest.Count * 100);
Debug.WriteLine($"Progress Increment is {ProgressIncrement}");
Directory.CreateDirectory(Path.Combine(Program.BaseDirectory, "Downloads"));
foreach (Package package in VersionPackageManifest)
{
// no await, download all the packages at once
DownloadPackage(package);
}
do
{
// wait for download to finish (and also round off the progress bar if needed)
if (Progress == ProgressIncrement * VersionPackageManifest.Count)
Progress = 100;
await Task.Delay(1000);
}
while (Progress != 100);
ProgressStyle = ProgressBarStyle.Marquee;
Debug.WriteLine("Finished downloading");
Directory.CreateDirectory(Path.Combine(Program.BaseDirectory, "Versions"));
foreach (Package package in VersionPackageManifest)
{
// extract all the packages at once (shouldn't be too heavy on cpu?)
ExtractPackage(package);
}
Debug.WriteLine("Finished extracting packages");
Message = "Configuring Roblox...";
string appSettingsLocation = Path.Combine(VersionFolder, "AppSettings.xml");
await File.WriteAllTextAsync(appSettingsLocation, AppSettings);
if (!FreshInstall)
{
// let's take this opportunity to delete any packages we don't need anymore
foreach (string filename in Directory.GetFiles(DownloadsFolder))
{
if (!VersionPackageManifest.Exists(package => filename.Contains(package.Signature)))
File.Delete(filename);
}
// and also to delete our old version folder
Directory.Delete(Path.Combine(Program.BaseDirectory, "Versions", Program.Settings.VersionGuid));
}
CancelEnabled = false;
Program.Settings.VersionGuid = VersionGuid;
}
private async void ApplyModifications()
{
// i guess we can just assume that if the hash does not match the manifest, then it's a mod
// probably not the best way to do this? don't think file corruption is that much of a worry here
// TODO - i'm thinking i could have a manifest on my website like rbxManifest.txt
// for integrity checking and to quickly fix/alter stuff (like ouch.ogg being renamed)
// but that probably wouldn't be great to check on every run in case my webserver ever goes down
// interesting idea nonetheless, might add it sometime
// TODO - i'm hoping i can take this idea of content mods much further
// for stuff like easily installing (community-created?) texture/shader/audio mods
// but for now, let's just keep it at this
string fileContentName = "ouch.ogg";
string fileContentLocation = "content\\sounds\\ouch.ogg";
string fileLocation = Path.Combine(VersionFolder, fileContentLocation);
string officialDeathSoundHash = VersionFileManifest[fileContentLocation];
string currentDeathSoundHash = CalculateMD5(fileLocation);
if (Program.Settings.UseOldDeathSound && currentDeathSoundHash == officialDeathSoundHash)
{
// let's get the old one!
Debug.WriteLine($"Fetching old death sound...");
var response = await Client.GetAsync($"{Program.BaseUrlApplication}/mods/{fileContentLocation}");
if (File.Exists(fileLocation))
File.Delete(fileLocation);
using (var fileStream = new FileStream(fileLocation, FileMode.CreateNew))
{
await response.Content.CopyToAsync(fileStream);
}
}
else if (!Program.Settings.UseOldDeathSound && currentDeathSoundHash != officialDeathSoundHash)
{
// who's lame enough to ever do this?
// well, we need to re-extract the one that's in the content-sounds.zip package
Debug.WriteLine("Fetching current death sound...");
var package = VersionPackageManifest.Find(x => x.Name == "content-sounds.zip");
if (package is null)
{
Debug.WriteLine("Failed to find content-sounds.zip package! Aborting...");
return;
}
DownloadPackage(package);
string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
using (ZipArchive archive = ZipFile.OpenRead(packageLocation))
{
ZipArchiveEntry? entry = archive.Entries.Where(x => x.FullName == fileContentName).FirstOrDefault();
if (entry is null)
{
Debug.WriteLine("Failed to find file entry in content-sounds.zip! Aborting...");
return;
}
if (File.Exists(fileLocation))
File.Delete(fileLocation);
entry.ExtractToFile(fileLocation);
}
}
}
private async void DownloadPackage(Package package)
{
string packageUrl = $"{Program.BaseUrlSetup}/{VersionGuid}-{package.Name}";
string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
string robloxPackageLocation = Path.Combine(Program.LocalAppData, "Roblox", "Downloads", package.Signature);
if (File.Exists(packageLocation))
{
FileInfo file = new(packageLocation);
string calculatedMD5 = CalculateMD5(packageLocation);
if (calculatedMD5 != package.Signature)
{
Debug.WriteLine($"{package.Name} is corrupted ({calculatedMD5} != {package.Signature})! Deleting and re-downloading...");
file.Delete();
}
else
{
Debug.WriteLine($"{package.Name} is already downloaded, skipping...");
Progress += ProgressIncrement;
return;
}
}
else if (File.Exists(robloxPackageLocation))
{
// let's cheat! if the stock bootstrapper already previously downloaded the file,
// then we can just copy the one from there
Debug.WriteLine($"Found existing version of {package.Name} ({robloxPackageLocation})! Copying to Downloads folder...");
File.Copy(robloxPackageLocation, packageLocation);
Progress += ProgressIncrement;
return;
}
if (!File.Exists(packageLocation))
{
Debug.WriteLine($"Downloading {package.Name}...");
var response = await Client.GetAsync(packageUrl);
if (CancelFired)
return;
using (var fileStream = new FileStream(packageLocation, FileMode.CreateNew))
{
await response.Content.CopyToAsync(fileStream);
}
Debug.WriteLine($"Finished downloading {package.Name}!");
Progress += ProgressIncrement;
}
}
private void ExtractPackage(Package package)
{
if (CancelFired)
return;
string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
string extractPath;
Debug.WriteLine($"Extracting {package.Name} to {packageFolder}...");
using (ZipArchive archive = ZipFile.OpenRead(packageLocation))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (CancelFired)
return;
if (entry.FullName.EndsWith(@"\"))
continue;
extractPath = Path.Combine(packageFolder, entry.FullName);
Debug.WriteLine($"[{package.Name}] Writing {extractPath}...");
Directory.CreateDirectory(Path.GetDirectoryName(extractPath));
if (File.Exists(extractPath))
File.Delete(extractPath);
entry.ExtractToFile(extractPath);
}
}
}
// Dialog Events
public void CancelButtonClicked()
{
CancelFired = true;
try
{
if (Program.IsFirstRun)
Directory.Delete(Program.BaseDirectory, true);
else if (Directory.Exists(VersionFolder))
Directory.Delete(VersionFolder, true);
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to cleanup install!\n\n{ex}");
}
Program.Exit();
}
private void ShowSuccess(string message)
{
ShowSuccessEvent.Invoke(this, new ChangeEventArgs<string>(message));
}
private void PromptShutdown()
{
PromptShutdownEvent.Invoke(this, new EventArgs());
}
// Utilities
private static string CalculateMD5(string filename)
{
using (MD5 md5 = MD5.Create())
{
using (FileStream stream = File.OpenRead(filename))
{
byte[] hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
}
}
}

View File

@ -0,0 +1,128 @@
using Microsoft.Win32;
using Bloxstrap.Helpers;
namespace Bloxstrap
{
partial class Bootstrapper
{
public static void Register()
{
if (Program.BaseDirectory is null)
return;
RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{Program.ProjectName}");
// new install location selected, delete old one
string? oldInstallLocation = (string?)applicationKey.GetValue("OldInstallLocation");
if (!String.IsNullOrEmpty(oldInstallLocation) && oldInstallLocation != Program.BaseDirectory)
{
try
{
if (Directory.Exists(oldInstallLocation))
Directory.Delete(oldInstallLocation, true);
}
catch (Exception) { }
applicationKey.DeleteValue("OldInstallLocation");
}
applicationKey.SetValue("InstallLocation", Program.BaseDirectory);
applicationKey.Close();
// set uninstall key
RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Program.ProjectName}");
uninstallKey.SetValue("DisplayIcon", $"{Program.FilePath},0");
uninstallKey.SetValue("DisplayName", Program.ProjectName);
uninstallKey.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
uninstallKey.SetValue("InstallLocation", Program.BaseDirectory);
uninstallKey.SetValue("NoRepair", 1);
uninstallKey.SetValue("Publisher", Program.ProjectName);
uninstallKey.SetValue("ModifyPath", $"\"{Program.FilePath}\" -preferences");
uninstallKey.SetValue("UninstallString", $"\"{Program.FilePath}\" -uninstall");
uninstallKey.Close();
}
public static void CheckInstall()
{
// check if launch uri is set to our bootstrapper
// this doesn't go under register, so we check every launch
// just in case the stock bootstrapper changes it back
Protocol.Register("roblox", "Roblox", Program.FilePath);
Protocol.Register("roblox-player", "Roblox", Program.FilePath);
// in case the user is reinstalling
if (File.Exists(Program.FilePath) && Program.IsFirstRun)
File.Delete(Program.FilePath);
// check to make sure bootstrapper is in the install folder
if (!File.Exists(Program.FilePath) && Environment.ProcessPath is not null)
File.Copy(Environment.ProcessPath, Program.FilePath);
// this SHOULD go under Register(),
// but then people who have Bloxstrap v1.0.0 installed won't have this without a reinstall
// maybe in a later version?
if (!Directory.Exists(Program.StartMenuDirectory))
{
Directory.CreateDirectory(Program.StartMenuDirectory);
ShellLink.Shortcut.CreateShortcut(Program.FilePath, "", Program.FilePath, 0)
.WriteToFile(Path.Combine(Program.StartMenuDirectory, "Play Roblox.lnk"));
ShellLink.Shortcut.CreateShortcut(Program.FilePath, "-preferences", Program.FilePath, 0)
.WriteToFile(Path.Combine(Program.StartMenuDirectory, "Configure Bloxstrap.lnk"));
}
}
private void Uninstall()
{
if (Program.BaseDirectory is null)
return;
CheckIfRunning();
// lots of try/catches here... lol
Message = $"Uninstalling {Program.ProjectName}...";
Program.SettingsManager.ShouldSave = false;
// check if stock bootstrapper is still installed
RegistryKey? bootstrapperKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-player");
if (bootstrapperKey is null)
{
Protocol.Unregister("roblox");
Protocol.Unregister("roblox-player");
}
else
{
// revert launch uri handler to stock bootstrapper
string bootstrapperLocation = (string?)bootstrapperKey.GetValue("InstallLocation") + "RobloxPlayerLauncher.exe";
Protocol.Register("roblox", "Roblox", bootstrapperLocation);
Protocol.Register("roblox-player", "Roblox", bootstrapperLocation);
}
try
{
// delete application key
Registry.CurrentUser.DeleteSubKey($@"Software\{Program.ProjectName}");
// delete start menu folder
Directory.Delete(Program.StartMenuDirectory, true);
// delete uninstall key
Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Program.ProjectName}");
// delete installation folder
// (should delete everything except bloxstrap itself)
Directory.Delete(Program.BaseDirectory, true);
}
catch (Exception) { }
ShowSuccess($"{Program.ProjectName} has been uninstalled");
Program.Exit();
}
}
}

View File

@ -0,0 +1,130 @@
using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap
{
partial class Bootstrapper
{
private string? LaunchCommandLine;
private string VersionGuid;
private PackageManifest VersionPackageManifest;
private FileManifest VersionFileManifest;
private string VersionFolder;
private readonly string DownloadsFolder;
private readonly bool FreshInstall;
private int ProgressIncrement;
private bool CancelFired = false;
private static readonly HttpClient Client = new();
// in case a new package is added, you can find the corresponding directory
// by opening the stock bootstrapper in a hex editor
// TODO - there ideally should be a less static way to do this that's not hardcoded?
private static readonly IReadOnlyDictionary<string, string> PackageDirectories = new Dictionary<string, string>()
{
{ "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 =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<Settings>\n" +
" <ContentFolder>content</ContentFolder>\n" +
" <BaseUrl>http://www.roblox.com</BaseUrl>\n" +
"</Settings>\n";
public event EventHandler CloseDialogEvent;
public event EventHandler PromptShutdownEvent;
public event ChangeEventHandler<string> ShowSuccessEvent;
public event ChangeEventHandler<string> MessageChanged;
public event ChangeEventHandler<int> ProgressBarValueChanged;
public event ChangeEventHandler<ProgressBarStyle> ProgressBarStyleChanged;
public event ChangeEventHandler<bool> CancelEnabledChanged;
private string _message;
private int _progress = 0;
private ProgressBarStyle _progressStyle = ProgressBarStyle.Marquee;
private bool _cancelEnabled = false;
public string Message
{
get => _message;
private set
{
if (_message == value)
return;
MessageChanged.Invoke(this, new ChangeEventArgs<string>(value));
_message = value;
}
}
public int Progress
{
get => _progress;
private set
{
if (_progress == value)
return;
ProgressBarValueChanged.Invoke(this, new ChangeEventArgs<int>(value));
_progress = value;
}
}
public ProgressBarStyle ProgressStyle
{
get => _progressStyle;
private set
{
if (_progressStyle == value)
return;
ProgressBarStyleChanged.Invoke(this, new ChangeEventArgs<ProgressBarStyle>(value));
_progressStyle = value;
}
}
public bool CancelEnabled
{
get => _cancelEnabled;
private set
{
if (_cancelEnabled == value)
return;
CancelEnabledChanged.Invoke(this, new ChangeEventArgs<bool>(value));
_cancelEnabled = value;
}
}
}
}

View File

@ -0,0 +1,208 @@
using System.Diagnostics;
using System.IO.Compression;
using Bloxstrap.Helpers;
using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap
{
partial class Bootstrapper
{
private async Task CheckLatestVersion()
{
if (Program.BaseDirectory is null)
return;
Message = "Connecting to Roblox...";
VersionGuid = await Client.GetStringAsync($"{Program.BaseUrlSetup}/version");
VersionFolder = Path.Combine(Program.BaseDirectory, "Versions", VersionGuid);
VersionPackageManifest = await PackageManifest.Get(VersionGuid);
VersionFileManifest = await FileManifest.Get(VersionGuid);
}
private async Task InstallLatestVersion()
{
if (Program.BaseDirectory is null)
return;
CheckIfRunning();
if (FreshInstall)
Message = "Installing Roblox...";
else
Message = "Upgrading Roblox...";
Directory.CreateDirectory(Program.BaseDirectory);
CancelEnabled = true;
// i believe the original bootstrapper bases the progress bar off zip
// extraction progress, but here i'm doing package download progress
ProgressStyle = ProgressBarStyle.Continuous;
ProgressIncrement = (int)Math.Floor((decimal)1 / VersionPackageManifest.Count * 100);
Directory.CreateDirectory(Path.Combine(Program.BaseDirectory, "Downloads"));
foreach (Package package in VersionPackageManifest)
{
// no await, download all the packages at once
DownloadPackage(package);
}
do
{
// wait for download to finish (and also round off the progress bar if needed)
if (Progress == ProgressIncrement * VersionPackageManifest.Count)
Progress = 100;
await Task.Delay(1000);
}
while (Progress != 100);
ProgressStyle = ProgressBarStyle.Marquee;
Debug.WriteLine("Finished downloading");
Directory.CreateDirectory(Path.Combine(Program.BaseDirectory, "Versions"));
foreach (Package package in VersionPackageManifest)
{
// extract all the packages at once (shouldn't be too heavy on cpu?)
ExtractPackage(package);
}
Debug.WriteLine("Finished extracting packages");
Message = "Configuring Roblox...";
string appSettingsLocation = Path.Combine(VersionFolder, "AppSettings.xml");
await File.WriteAllTextAsync(appSettingsLocation, AppSettings);
if (!FreshInstall)
{
// let's take this opportunity to delete any packages we don't need anymore
foreach (string filename in Directory.GetFiles(DownloadsFolder))
{
if (!VersionPackageManifest.Exists(package => filename.Contains(package.Signature)))
File.Delete(filename);
}
// and also to delete our old version folder
Directory.Delete(Path.Combine(Program.BaseDirectory, "Versions", Program.Settings.VersionGuid), true);
}
CancelEnabled = false;
Program.Settings.VersionGuid = VersionGuid;
}
private async void ApplyModifications()
{
// i guess we can just assume that if the hash does not match the manifest, then it's a mod
// probably not the best way to do this? don't think file corruption is that much of a worry here
// TODO - i'm thinking i could have a manifest on my website like rbxManifest.txt
// for integrity checking and to quickly fix/alter stuff (like ouch.ogg being renamed)
// but that probably wouldn't be great to check on every run in case my webserver ever goes down
// interesting idea nonetheless, might add it sometime
// TODO - i'm hoping i can take this idea of content mods much further
// for stuff like easily installing (community-created?) texture/shader/audio mods
// but for now, let's just keep it at this
await ModifyDeathSound();
}
private async void DownloadPackage(Package package)
{
string packageUrl = $"{Program.BaseUrlSetup}/{VersionGuid}-{package.Name}";
string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
string robloxPackageLocation = Path.Combine(Program.LocalAppData, "Roblox", "Downloads", package.Signature);
if (File.Exists(packageLocation))
{
FileInfo file = new(packageLocation);
string calculatedMD5 = Utilities.CalculateMD5(packageLocation);
if (calculatedMD5 != package.Signature)
{
Debug.WriteLine($"{package.Name} is corrupted ({calculatedMD5} != {package.Signature})! Deleting and re-downloading...");
file.Delete();
}
else
{
Debug.WriteLine($"{package.Name} is already downloaded, skipping...");
Progress += ProgressIncrement;
return;
}
}
else if (File.Exists(robloxPackageLocation))
{
// let's cheat! if the stock bootstrapper already previously downloaded the file,
// then we can just copy the one from there
Debug.WriteLine($"Found existing version of {package.Name} ({robloxPackageLocation})! Copying to Downloads folder...");
File.Copy(robloxPackageLocation, packageLocation);
Progress += ProgressIncrement;
return;
}
if (!File.Exists(packageLocation))
{
Debug.WriteLine($"Downloading {package.Name}...");
var response = await Client.GetAsync(packageUrl);
if (CancelFired)
return;
using (var fileStream = new FileStream(packageLocation, FileMode.CreateNew))
{
await response.Content.CopyToAsync(fileStream);
}
Debug.WriteLine($"Finished downloading {package.Name}!");
Progress += ProgressIncrement;
}
}
private void ExtractPackage(Package package)
{
if (CancelFired)
return;
string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
string extractPath;
Debug.WriteLine($"Extracting {package.Name} to {packageFolder}...");
using (ZipArchive archive = ZipFile.OpenRead(packageLocation))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (CancelFired)
return;
if (entry.FullName.EndsWith(@"\"))
continue;
extractPath = Path.Combine(packageFolder, entry.FullName);
Debug.WriteLine($"[{package.Name}] Writing {extractPath}...");
Directory.CreateDirectory(Path.GetDirectoryName(extractPath));
if (File.Exists(extractPath))
File.Delete(extractPath);
entry.ExtractToFile(extractPath);
}
}
}
}
}

View File

@ -0,0 +1,62 @@
using System.IO.Compression;
using Bloxstrap.Helpers;
namespace Bloxstrap
{
partial class Bootstrapper
{
private async Task ModifyDeathSound()
{
string fileContentName = "ouch.ogg";
string fileContentLocation = "content\\sounds\\ouch.ogg";
string fileLocation = Path.Combine(VersionFolder, fileContentLocation);
string officialDeathSoundHash = VersionFileManifest[fileContentLocation];
string currentDeathSoundHash = Utilities.CalculateMD5(fileLocation);
if (Program.Settings.UseOldDeathSound && currentDeathSoundHash == officialDeathSoundHash)
{
// let's get the old one!
var response = await Client.GetAsync($"{Program.BaseUrlApplication}/mods/{fileContentLocation}");
if (File.Exists(fileLocation))
File.Delete(fileLocation);
using (var fileStream = new FileStream(fileLocation, FileMode.CreateNew))
{
await response.Content.CopyToAsync(fileStream);
}
}
else if (!Program.Settings.UseOldDeathSound && currentDeathSoundHash != officialDeathSoundHash)
{
// who's lame enough to ever do this?
// well, we need to re-extract the one that's in the content-sounds.zip package
var package = VersionPackageManifest.Find(x => x.Name == "content-sounds.zip");
if (package is null)
return;
DownloadPackage(package);
string packageLocation = Path.Combine(DownloadsFolder, package.Signature);
string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
using (ZipArchive archive = ZipFile.OpenRead(packageLocation))
{
ZipArchiveEntry? entry = archive.Entries.Where(x => x.FullName == fileContentName).FirstOrDefault();
if (entry is null)
return;
if (File.Exists(fileLocation))
File.Delete(fileLocation);
entry.ExtractToFile(fileLocation);
}
}
}
}
}

View File

@ -0,0 +1,178 @@
using System.Diagnostics;
using Bloxstrap.Enums;
using Bloxstrap.Dialogs.BootstrapperStyles;
using Bloxstrap.Helpers;
using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap
{
public partial class Bootstrapper
{
public Bootstrapper()
{
if (Program.BaseDirectory is null)
return;
FreshInstall = String.IsNullOrEmpty(Program.Settings.VersionGuid);
DownloadsFolder = Path.Combine(Program.BaseDirectory, "Downloads");
Client.Timeout = TimeSpan.FromMinutes(10);
}
public void Initialize(BootstrapperStyle bootstrapperStyle, string? launchCommandLine = null)
{
LaunchCommandLine = launchCommandLine;
switch (bootstrapperStyle)
{
case BootstrapperStyle.VistaDialog:
new VistaDialog(this);
break;
case BootstrapperStyle.LegacyDialog:
Application.Run(new LegacyDialog(this));
break;
case BootstrapperStyle.ProgressDialog:
Application.Run(new ProgressDialog(this));
break;
}
}
public async Task Run()
{
if (LaunchCommandLine == "-uninstall")
{
Uninstall();
return;
}
await CheckLatestVersion();
if (!Directory.Exists(VersionFolder) || Program.Settings.VersionGuid != VersionGuid)
{
Debug.WriteLineIf(!Directory.Exists(VersionFolder), $"Installing latest version (!Directory.Exists({VersionFolder}))");
Debug.WriteLineIf(Program.Settings.VersionGuid != VersionGuid, $"Installing latest version ({Program.Settings.VersionGuid} != {VersionGuid})");
await InstallLatestVersion();
}
// yes, doing this for every start is stupid, but the death sound mod is dynamically toggleable after all
ApplyModifications();
if (Program.IsFirstRun)
Program.SettingsManager.ShouldSave = true;
if (Program.IsFirstRun || FreshInstall)
Register();
CheckInstall();
await StartRoblox();
Program.Exit();
}
private void CheckIfRunning()
{
Process[] processes = Process.GetProcessesByName("RobloxPlayerBeta");
if (processes.Length > 0)
PromptShutdown();
try
{
// try/catch just in case process was closed before prompt was answered
foreach (Process process in processes)
{
process.CloseMainWindow();
process.Close();
}
}
catch (Exception) { }
}
private async Task StartRoblox()
{
string startEventName = Program.ProjectName.Replace(" ", "") + "StartEvent";
Message = "Starting Roblox...";
// launch time isn't really required for all launches, but it's usually just safest to do this
LaunchCommandLine += " --launchtime=" + DateTimeOffset.Now.ToUnixTimeSeconds() + " -startEvent " + startEventName;
using (SystemEvent startEvent = new(startEventName))
{
Process gameClient = Process.Start(Path.Combine(VersionFolder, "RobloxPlayerBeta.exe"), LaunchCommandLine);
bool startEventFired = await startEvent.WaitForEvent();
startEvent.Close();
if (!startEventFired)
return;
// event fired, wait for 6 seconds then close
await Task.Delay(6000);
// now we move onto handling rich presence
// except beta app launch since we have to rely strictly on website launch
if (!Program.Settings.UseDiscordRichPresence || LaunchCommandLine.Contains("--app"))
return;
// probably not the most ideal way to do this
string? placeId = Utilities.GetKeyValue(LaunchCommandLine, "placeId=", '&');
if (placeId is null)
return;
// keep bloxstrap open to handle rich presence
using (DiscordRichPresence richPresence = new())
{
bool presenceSet = await richPresence.SetPresence(placeId);
if (!presenceSet)
return;
CloseDialog();
await gameClient.WaitForExitAsync();
}
}
}
public void CancelButtonClicked()
{
if (Program.BaseDirectory is null)
return;
CancelFired = true;
try
{
if (Program.IsFirstRun)
Directory.Delete(Program.BaseDirectory, true);
else if (Directory.Exists(VersionFolder))
Directory.Delete(VersionFolder, true);
}
catch (Exception) { }
Program.Exit();
}
private void ShowSuccess(string message)
{
ShowSuccessEvent.Invoke(this, new ChangeEventArgs<string>(message));
}
private void PromptShutdown()
{
PromptShutdownEvent.Invoke(this, new EventArgs());
}
private void CloseDialog()
{
CloseDialogEvent.Invoke(this, new EventArgs());
}
}
}

View File

@ -1,6 +1,6 @@
namespace Bloxstrap.Dialogs.BootstrapperStyles namespace Bloxstrap.Dialogs.BootstrapperStyles
{ {
partial class LegacyDialogStyle partial class LegacyDialog
{ {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
@ -31,7 +31,7 @@
this.Message = new System.Windows.Forms.Label(); this.Message = new System.Windows.Forms.Label();
this.ProgressBar = new System.Windows.Forms.ProgressBar(); this.ProgressBar = new System.Windows.Forms.ProgressBar();
this.IconBox = new System.Windows.Forms.PictureBox(); this.IconBox = new System.Windows.Forms.PictureBox();
this.CancelButton = new System.Windows.Forms.Button(); this.ButtonCancel = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.IconBox)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.IconBox)).BeginInit();
this.SuspendLayout(); this.SuspendLayout();
// //
@ -62,25 +62,25 @@
this.IconBox.TabIndex = 2; this.IconBox.TabIndex = 2;
this.IconBox.TabStop = false; this.IconBox.TabStop = false;
// //
// CancelButton // ButtonCancel
// //
this.CancelButton.Enabled = false; this.ButtonCancel.Enabled = false;
this.CancelButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.ButtonCancel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.CancelButton.Location = new System.Drawing.Point(271, 83); this.ButtonCancel.Location = new System.Drawing.Point(271, 83);
this.CancelButton.Name = "CancelButton"; this.ButtonCancel.Name = "ButtonCancel";
this.CancelButton.Size = new System.Drawing.Size(75, 23); this.ButtonCancel.Size = new System.Drawing.Size(75, 23);
this.CancelButton.TabIndex = 3; this.ButtonCancel.TabIndex = 3;
this.CancelButton.Text = "Cancel"; this.ButtonCancel.Text = "Cancel";
this.CancelButton.UseVisualStyleBackColor = true; this.ButtonCancel.UseVisualStyleBackColor = true;
this.CancelButton.Visible = false; this.ButtonCancel.Visible = false;
this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click); this.ButtonCancel.Click += new System.EventHandler(this.ButtonCancel_Click);
// //
// LegacyDialogStyle // LegacyDialogStyle
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(362, 131); this.ClientSize = new System.Drawing.Size(362, 131);
this.Controls.Add(this.CancelButton); this.Controls.Add(this.ButtonCancel);
this.Controls.Add(this.IconBox); this.Controls.Add(this.IconBox);
this.Controls.Add(this.ProgressBar); this.Controls.Add(this.ProgressBar);
this.Controls.Add(this.Message); this.Controls.Add(this.Message);
@ -103,6 +103,6 @@
private Label Message; private Label Message;
private ProgressBar ProgressBar; private ProgressBar ProgressBar;
private PictureBox IconBox; private PictureBox IconBox;
private Button CancelButton; private Button ButtonCancel;
} }
} }

View File

@ -11,27 +11,17 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
// but once winforms code is cleaned up we could also do the 2009 version too // but once winforms code is cleaned up we could also do the 2009 version too
// example: https://youtu.be/VpduiruysuM?t=18 // example: https://youtu.be/VpduiruysuM?t=18
public partial class LegacyDialogStyle : Form public partial class LegacyDialog : Form
{ {
private Bootstrapper? Bootstrapper; private readonly Bootstrapper? Bootstrapper;
public LegacyDialogStyle(Bootstrapper? bootstrapper = null) public LegacyDialog(Bootstrapper? bootstrapper = null)
{ {
InitializeComponent(); InitializeComponent();
if (bootstrapper is not null)
{
Bootstrapper = bootstrapper; Bootstrapper = bootstrapper;
Bootstrapper.PromptShutdownEvent += new EventHandler(PromptShutdown);
Bootstrapper.ShowSuccessEvent += new ChangeEventHandler<string>(ShowSuccess);
Bootstrapper.MessageChanged += new ChangeEventHandler<string>(MessageChanged);
Bootstrapper.ProgressBarValueChanged += new ChangeEventHandler<int>(ProgressBarValueChanged);
Bootstrapper.ProgressBarStyleChanged += new ChangeEventHandler<ProgressBarStyle>(ProgressBarStyleChanged);
Bootstrapper.CancelEnabledChanged += new ChangeEventHandler<bool>(CancelEnabledChanged);
}
Icon icon = IconManager.GetIconResource(); Icon icon = IconManager.GetIconResource();
this.Text = Program.ProjectName; this.Text = Program.ProjectName;
this.Icon = icon; this.Icon = icon;
this.IconBox.Image = icon.ToBitmap(); this.IconBox.Image = icon.ToBitmap();
@ -39,17 +29,28 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
if (Bootstrapper is null) if (Bootstrapper is null)
{ {
this.Message.Text = "Click the Cancel button to return to preferences"; this.Message.Text = "Click the Cancel button to return to preferences";
this.CancelButton.Enabled = true; this.ButtonCancel.Enabled = true;
this.CancelButton.Visible = true; this.ButtonCancel.Visible = true;
} }
else else
{ {
Bootstrapper.CloseDialogEvent += new EventHandler(CloseDialog);
Bootstrapper.PromptShutdownEvent += new EventHandler(PromptShutdown);
Bootstrapper.ShowSuccessEvent += new ChangeEventHandler<string>(ShowSuccess);
Bootstrapper.MessageChanged += new ChangeEventHandler<string>(MessageChanged);
Bootstrapper.ProgressBarValueChanged += new ChangeEventHandler<int>(ProgressBarValueChanged);
Bootstrapper.ProgressBarStyleChanged += new ChangeEventHandler<ProgressBarStyle>(ProgressBarStyleChanged);
Bootstrapper.CancelEnabledChanged += new ChangeEventHandler<bool>(CancelEnabledChanged);
Task.Run(() => RunBootstrapper()); Task.Run(() => RunBootstrapper());
} }
} }
public async void RunBootstrapper() public async void RunBootstrapper()
{ {
if (Bootstrapper is null)
return;
try try
{ {
await Bootstrapper.Run(); await Bootstrapper.Run();
@ -84,6 +85,11 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
); );
} }
private void CloseDialog(object? sender, EventArgs e)
{
this.Close();
}
private void PromptShutdown(object? sender, EventArgs e) private void PromptShutdown(object? sender, EventArgs e)
{ {
DialogResult result = MessageBox.Show( DialogResult result = MessageBox.Show(
@ -138,19 +144,19 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
private void CancelEnabledChanged(object sender, ChangeEventArgs<bool> e) private void CancelEnabledChanged(object sender, ChangeEventArgs<bool> e)
{ {
if (this.CancelButton.InvokeRequired) if (this.ButtonCancel.InvokeRequired)
{ {
ChangeEventHandler<bool> handler = new(CancelEnabledChanged); ChangeEventHandler<bool> handler = new(CancelEnabledChanged);
this.CancelButton.Invoke(handler, sender, e); this.ButtonCancel.Invoke(handler, sender, e);
} }
else else
{ {
this.CancelButton.Enabled = e.Value; this.ButtonCancel.Enabled = e.Value;
this.CancelButton.Visible = e.Value; this.ButtonCancel.Visible = e.Value;
} }
} }
private void CancelButton_Click(object sender, EventArgs e) private void ButtonCancel_Click(object sender, EventArgs e)
{ {
if (Bootstrapper is null) if (Bootstrapper is null)
this.Close(); this.Close();

View File

@ -1,6 +1,6 @@
namespace Bloxstrap.Dialogs.BootstrapperStyles namespace Bloxstrap.Dialogs.BootstrapperStyles
{ {
partial class ProgressDialogStyle partial class ProgressDialog
{ {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
@ -31,10 +31,10 @@
this.ProgressBar = new System.Windows.Forms.ProgressBar(); this.ProgressBar = new System.Windows.Forms.ProgressBar();
this.Message = new System.Windows.Forms.Label(); this.Message = new System.Windows.Forms.Label();
this.IconBox = new System.Windows.Forms.PictureBox(); this.IconBox = new System.Windows.Forms.PictureBox();
this.CancelButton = new System.Windows.Forms.PictureBox(); this.ButtonCancel = new System.Windows.Forms.PictureBox();
this.panel1 = new System.Windows.Forms.Panel(); this.panel1 = new System.Windows.Forms.Panel();
((System.ComponentModel.ISupportInitialize)(this.IconBox)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.IconBox)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.CancelButton)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.ButtonCancel)).BeginInit();
this.panel1.SuspendLayout(); this.panel1.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
@ -69,26 +69,26 @@
this.IconBox.TabIndex = 2; this.IconBox.TabIndex = 2;
this.IconBox.TabStop = false; this.IconBox.TabStop = false;
// //
// CancelButton // ButtonCancel
// //
this.CancelButton.Enabled = false; this.ButtonCancel.Enabled = false;
this.CancelButton.Image = global::Bloxstrap.Properties.Resources.CancelButton; this.ButtonCancel.Image = global::Bloxstrap.Properties.Resources.CancelButton;
this.CancelButton.Location = new System.Drawing.Point(194, 264); this.ButtonCancel.Location = new System.Drawing.Point(194, 264);
this.CancelButton.Name = "CancelButton"; this.ButtonCancel.Name = "ButtonCancel";
this.CancelButton.Size = new System.Drawing.Size(130, 44); this.ButtonCancel.Size = new System.Drawing.Size(130, 44);
this.CancelButton.TabIndex = 3; this.ButtonCancel.TabIndex = 3;
this.CancelButton.TabStop = false; this.ButtonCancel.TabStop = false;
this.CancelButton.Visible = false; this.ButtonCancel.Visible = false;
this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click); this.ButtonCancel.Click += new System.EventHandler(this.ButtonCancel_Click);
this.CancelButton.MouseEnter += new System.EventHandler(this.CancelButton_MouseEnter); this.ButtonCancel.MouseEnter += new System.EventHandler(this.ButtonCancel_MouseEnter);
this.CancelButton.MouseLeave += new System.EventHandler(this.CancelButton_MouseLeave); this.ButtonCancel.MouseLeave += new System.EventHandler(this.ButtonCancel_MouseLeave);
// //
// panel1 // panel1
// //
this.panel1.BackColor = System.Drawing.SystemColors.Window; this.panel1.BackColor = System.Drawing.SystemColors.Window;
this.panel1.Controls.Add(this.Message); this.panel1.Controls.Add(this.Message);
this.panel1.Controls.Add(this.IconBox); this.panel1.Controls.Add(this.IconBox);
this.panel1.Controls.Add(this.CancelButton); this.panel1.Controls.Add(this.ButtonCancel);
this.panel1.Controls.Add(this.ProgressBar); this.panel1.Controls.Add(this.ProgressBar);
this.panel1.Location = new System.Drawing.Point(1, 1); this.panel1.Location = new System.Drawing.Point(1, 1);
this.panel1.Name = "panel1"; this.panel1.Name = "panel1";
@ -109,7 +109,7 @@
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "ProgressDialogStyle"; this.Text = "ProgressDialogStyle";
((System.ComponentModel.ISupportInitialize)(this.IconBox)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.IconBox)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.CancelButton)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.ButtonCancel)).EndInit();
this.panel1.ResumeLayout(false); this.panel1.ResumeLayout(false);
this.ResumeLayout(false); this.ResumeLayout(false);
@ -120,7 +120,7 @@
private ProgressBar ProgressBar; private ProgressBar ProgressBar;
private Label Message; private Label Message;
private PictureBox IconBox; private PictureBox IconBox;
private PictureBox CancelButton; private PictureBox ButtonCancel;
private Panel panel1; private Panel panel1;
} }
} }

View File

@ -1,15 +1,17 @@
using Bloxstrap.Helpers; using System.Diagnostics;
using Bloxstrap.Helpers;
using Bloxstrap.Helpers.RSMM; using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap.Dialogs.BootstrapperStyles namespace Bloxstrap.Dialogs.BootstrapperStyles
{ {
// TODO - universal implementation for winforms-based styles? (to reduce duplicate code) // TODO - universal implementation for winforms-based styles? (to reduce duplicate code)
public partial class ProgressDialogStyle : Form public partial class ProgressDialog : Form
{ {
private Bootstrapper? Bootstrapper; private readonly Bootstrapper? Bootstrapper;
public ProgressDialogStyle(Bootstrapper? bootstrapper = null) public ProgressDialog(Bootstrapper? bootstrapper = null)
{ {
InitializeComponent(); InitializeComponent();
@ -22,11 +24,12 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
if (Bootstrapper is null) if (Bootstrapper is null)
{ {
this.Message.Text = "Click the Cancel button to return to preferences"; this.Message.Text = "Click the Cancel button to return to preferences";
this.CancelButton.Enabled = true; this.ButtonCancel.Enabled = true;
this.CancelButton.Visible = true; this.ButtonCancel.Visible = true;
} }
else else
{ {
Bootstrapper.CloseDialogEvent += new EventHandler(CloseDialog);
Bootstrapper.PromptShutdownEvent += new EventHandler(PromptShutdown); Bootstrapper.PromptShutdownEvent += new EventHandler(PromptShutdown);
Bootstrapper.ShowSuccessEvent += new ChangeEventHandler<string>(ShowSuccess); Bootstrapper.ShowSuccessEvent += new ChangeEventHandler<string>(ShowSuccess);
Bootstrapper.MessageChanged += new ChangeEventHandler<string>(MessageChanged); Bootstrapper.MessageChanged += new ChangeEventHandler<string>(MessageChanged);
@ -40,6 +43,9 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
public async void RunBootstrapper() public async void RunBootstrapper()
{ {
if (Bootstrapper is null)
return;
try try
{ {
await Bootstrapper.Run(); await Bootstrapper.Run();
@ -74,6 +80,11 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
); );
} }
private void CloseDialog(object? sender, EventArgs e)
{
this.Hide();
}
private void PromptShutdown(object? sender, EventArgs e) private void PromptShutdown(object? sender, EventArgs e)
{ {
DialogResult result = MessageBox.Show( DialogResult result = MessageBox.Show(
@ -128,19 +139,19 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
private void CancelEnabledChanged(object sender, ChangeEventArgs<bool> e) private void CancelEnabledChanged(object sender, ChangeEventArgs<bool> e)
{ {
if (this.CancelButton.InvokeRequired) if (this.ButtonCancel.InvokeRequired)
{ {
ChangeEventHandler<bool> handler = new(CancelEnabledChanged); ChangeEventHandler<bool> handler = new(CancelEnabledChanged);
this.CancelButton.Invoke(handler, sender, e); this.ButtonCancel.Invoke(handler, sender, e);
} }
else else
{ {
this.CancelButton.Enabled = e.Value; this.ButtonCancel.Enabled = e.Value;
this.CancelButton.Visible = e.Value; this.ButtonCancel.Visible = e.Value;
} }
} }
private void CancelButton_Click(object sender, EventArgs e) private void ButtonCancel_Click(object sender, EventArgs e)
{ {
if (Bootstrapper is null) if (Bootstrapper is null)
this.Close(); this.Close();
@ -148,14 +159,14 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
Task.Run(() => Bootstrapper.CancelButtonClicked()); Task.Run(() => Bootstrapper.CancelButtonClicked());
} }
private void CancelButton_MouseEnter(object sender, EventArgs e) private void ButtonCancel_MouseEnter(object sender, EventArgs e)
{ {
this.CancelButton.Image = Properties.Resources.CancelButtonHover; this.ButtonCancel.Image = Properties.Resources.CancelButtonHover;
} }
private void CancelButton_MouseLeave(object sender, EventArgs e) private void ButtonCancel_MouseLeave(object sender, EventArgs e)
{ {
this.CancelButton.Image = Properties.Resources.CancelButton; this.ButtonCancel.Image = Properties.Resources.CancelButton;
} }
} }
} }

View File

@ -5,10 +5,6 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
{ {
// example: https://youtu.be/h0_AL95Sc3o?t=48 // example: https://youtu.be/h0_AL95Sc3o?t=48
// i suppose a better name for this here would be "VistaDialog" rather than "TaskDialog"?
// having this named as BootstrapperStyles.TaskDialog would conflict with Forms.TaskDialog
// so naming it VistaDialog would let us drop the ~Style suffix on every style name
// this currently doesn't work because c# is stupid // this currently doesn't work because c# is stupid
// technically, task dialogs are treated as winforms controls, but they don't classify as winforms controls at all // technically, task dialogs are treated as winforms controls, but they don't classify as winforms controls at all
// all winforms controls have the ability to be invoked from another thread, but task dialogs don't // all winforms controls have the ability to be invoked from another thread, but task dialogs don't
@ -17,12 +13,12 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
// for now, just stick to legacydialog and progressdialog // for now, just stick to legacydialog and progressdialog
public class TaskDialogStyle public class VistaDialog
{ {
private Bootstrapper Bootstrapper; private readonly Bootstrapper Bootstrapper;
private TaskDialogPage Dialog; private TaskDialogPage Dialog;
public TaskDialogStyle(Bootstrapper bootstrapper) public VistaDialog(Bootstrapper bootstrapper)
{ {
Bootstrapper = bootstrapper; Bootstrapper = bootstrapper;
Bootstrapper.ShowSuccessEvent += new ChangeEventHandler<string>(ShowSuccess); Bootstrapper.ShowSuccessEvent += new ChangeEventHandler<string>(ShowSuccess);

View File

@ -28,9 +28,12 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
this.components = new System.ComponentModel.Container();
this.label1 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label();
this.Tabs = new System.Windows.Forms.TabControl(); this.Tabs = new System.Windows.Forms.TabControl();
this.DialogTab = new System.Windows.Forms.TabPage(); this.DialogTab = new System.Windows.Forms.TabPage();
this.groupBox5 = new System.Windows.Forms.GroupBox();
this.ToggleDiscordRichPresence = new System.Windows.Forms.CheckBox();
this.groupBox3 = new System.Windows.Forms.GroupBox(); this.groupBox3 = new System.Windows.Forms.GroupBox();
this.IconPreview = new System.Windows.Forms.PictureBox(); this.IconPreview = new System.Windows.Forms.PictureBox();
this.IconSelection = new System.Windows.Forms.ListBox(); this.IconSelection = new System.Windows.Forms.ListBox();
@ -38,23 +41,24 @@
this.StyleSelection = new System.Windows.Forms.ListBox(); this.StyleSelection = new System.Windows.Forms.ListBox();
this.InstallationTab = new System.Windows.Forms.TabPage(); this.InstallationTab = new System.Windows.Forms.TabPage();
this.groupBox4 = new System.Windows.Forms.GroupBox(); this.groupBox4 = new System.Windows.Forms.GroupBox();
this.ModifyDeathSoundToggle = new System.Windows.Forms.CheckBox(); this.ToggleDeathSound = new System.Windows.Forms.CheckBox();
this.groupBox1 = new System.Windows.Forms.GroupBox(); this.GroupBoxInstallLocation = new System.Windows.Forms.GroupBox();
this.InstallLocationBrowseButton = new System.Windows.Forms.Button(); this.InstallLocationBrowseButton = new System.Windows.Forms.Button();
this.InstallLocation = new System.Windows.Forms.TextBox(); this.InstallLocation = new System.Windows.Forms.TextBox();
this.SaveButton = new System.Windows.Forms.Button(); this.SaveButton = new System.Windows.Forms.Button();
this.panel1 = new System.Windows.Forms.Panel(); this.panel1 = new System.Windows.Forms.Panel();
this.label2 = new System.Windows.Forms.Label();
this.PreviewButton = new System.Windows.Forms.Button(); this.PreviewButton = new System.Windows.Forms.Button();
this.InstallLocationBrowseDialog = new System.Windows.Forms.FolderBrowserDialog(); this.InstallLocationBrowseDialog = new System.Windows.Forms.FolderBrowserDialog();
this.InfoTooltip = new System.Windows.Forms.ToolTip(this.components);
this.Tabs.SuspendLayout(); this.Tabs.SuspendLayout();
this.DialogTab.SuspendLayout(); this.DialogTab.SuspendLayout();
this.groupBox5.SuspendLayout();
this.groupBox3.SuspendLayout(); this.groupBox3.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.IconPreview)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.IconPreview)).BeginInit();
this.groupBox2.SuspendLayout(); this.groupBox2.SuspendLayout();
this.InstallationTab.SuspendLayout(); this.InstallationTab.SuspendLayout();
this.groupBox4.SuspendLayout(); this.groupBox4.SuspendLayout();
this.groupBox1.SuspendLayout(); this.GroupBoxInstallLocation.SuspendLayout();
this.panel1.SuspendLayout(); this.panel1.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
@ -66,7 +70,7 @@
this.label1.Name = "label1"; this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(237, 23); this.label1.Size = new System.Drawing.Size(237, 23);
this.label1.TabIndex = 1; this.label1.TabIndex = 1;
this.label1.Text = "Configure Preferences"; this.label1.Text = "Configure Bloxstrap";
// //
// Tabs // Tabs
// //
@ -75,21 +79,45 @@
this.Tabs.Location = new System.Drawing.Point(12, 40); this.Tabs.Location = new System.Drawing.Point(12, 40);
this.Tabs.Name = "Tabs"; this.Tabs.Name = "Tabs";
this.Tabs.SelectedIndex = 0; this.Tabs.SelectedIndex = 0;
this.Tabs.Size = new System.Drawing.Size(442, 176); this.Tabs.Size = new System.Drawing.Size(442, 226);
this.Tabs.TabIndex = 2; this.Tabs.TabIndex = 2;
// //
// DialogTab // DialogTab
// //
this.DialogTab.Controls.Add(this.groupBox5);
this.DialogTab.Controls.Add(this.groupBox3); this.DialogTab.Controls.Add(this.groupBox3);
this.DialogTab.Controls.Add(this.groupBox2); this.DialogTab.Controls.Add(this.groupBox2);
this.DialogTab.Location = new System.Drawing.Point(4, 24); this.DialogTab.Location = new System.Drawing.Point(4, 24);
this.DialogTab.Name = "DialogTab"; this.DialogTab.Name = "DialogTab";
this.DialogTab.Padding = new System.Windows.Forms.Padding(3); this.DialogTab.Padding = new System.Windows.Forms.Padding(3);
this.DialogTab.Size = new System.Drawing.Size(434, 148); this.DialogTab.Size = new System.Drawing.Size(434, 198);
this.DialogTab.TabIndex = 0; this.DialogTab.TabIndex = 0;
this.DialogTab.Text = "Bootstrapper"; this.DialogTab.Text = "Bootstrapper";
this.DialogTab.UseVisualStyleBackColor = true; this.DialogTab.UseVisualStyleBackColor = true;
// //
// groupBox5
//
this.groupBox5.Controls.Add(this.ToggleDiscordRichPresence);
this.groupBox5.Location = new System.Drawing.Point(5, 146);
this.groupBox5.Name = "groupBox5";
this.groupBox5.Size = new System.Drawing.Size(422, 46);
this.groupBox5.TabIndex = 7;
this.groupBox5.TabStop = false;
this.groupBox5.Text = "Launch";
//
// ToggleDiscordRichPresence
//
this.ToggleDiscordRichPresence.AutoSize = true;
this.ToggleDiscordRichPresence.Checked = true;
this.ToggleDiscordRichPresence.CheckState = System.Windows.Forms.CheckState.Checked;
this.ToggleDiscordRichPresence.Location = new System.Drawing.Point(9, 19);
this.ToggleDiscordRichPresence.Name = "ToggleDiscordRichPresence";
this.ToggleDiscordRichPresence.Size = new System.Drawing.Size(274, 19);
this.ToggleDiscordRichPresence.TabIndex = 0;
this.ToggleDiscordRichPresence.Text = "Show game activity with Discord Rich Presence";
this.ToggleDiscordRichPresence.UseVisualStyleBackColor = true;
this.ToggleDiscordRichPresence.CheckedChanged += new System.EventHandler(this.ToggleDiscordRichPresence_CheckedChanged);
//
// groupBox3 // groupBox3
// //
this.groupBox3.Controls.Add(this.IconPreview); this.groupBox3.Controls.Add(this.IconPreview);
@ -144,49 +172,49 @@
// InstallationTab // InstallationTab
// //
this.InstallationTab.Controls.Add(this.groupBox4); this.InstallationTab.Controls.Add(this.groupBox4);
this.InstallationTab.Controls.Add(this.groupBox1); this.InstallationTab.Controls.Add(this.GroupBoxInstallLocation);
this.InstallationTab.Location = new System.Drawing.Point(4, 24); this.InstallationTab.Location = new System.Drawing.Point(4, 24);
this.InstallationTab.Name = "InstallationTab"; this.InstallationTab.Name = "InstallationTab";
this.InstallationTab.Padding = new System.Windows.Forms.Padding(3); this.InstallationTab.Padding = new System.Windows.Forms.Padding(3);
this.InstallationTab.Size = new System.Drawing.Size(434, 148); this.InstallationTab.Size = new System.Drawing.Size(434, 198);
this.InstallationTab.TabIndex = 2; this.InstallationTab.TabIndex = 2;
this.InstallationTab.Text = "Installation"; this.InstallationTab.Text = "Installation";
this.InstallationTab.UseVisualStyleBackColor = true; this.InstallationTab.UseVisualStyleBackColor = true;
// //
// groupBox4 // groupBox4
// //
this.groupBox4.Controls.Add(this.ModifyDeathSoundToggle); this.groupBox4.Controls.Add(this.ToggleDeathSound);
this.groupBox4.Location = new System.Drawing.Point(5, 59); this.groupBox4.Location = new System.Drawing.Point(5, 60);
this.groupBox4.Name = "groupBox4"; this.groupBox4.Name = "groupBox4";
this.groupBox4.Size = new System.Drawing.Size(422, 84); this.groupBox4.Size = new System.Drawing.Size(422, 46);
this.groupBox4.TabIndex = 2; this.groupBox4.TabIndex = 2;
this.groupBox4.TabStop = false; this.groupBox4.TabStop = false;
this.groupBox4.Text = "Modifications"; this.groupBox4.Text = "Modifications";
// //
// ModifyDeathSoundToggle // ToggleDeathSound
// //
this.ModifyDeathSoundToggle.AutoSize = true; this.ToggleDeathSound.AutoSize = true;
this.ModifyDeathSoundToggle.Checked = true; this.ToggleDeathSound.Checked = true;
this.ModifyDeathSoundToggle.CheckState = System.Windows.Forms.CheckState.Checked; this.ToggleDeathSound.CheckState = System.Windows.Forms.CheckState.Checked;
this.ModifyDeathSoundToggle.Location = new System.Drawing.Point(9, 21); this.ToggleDeathSound.Location = new System.Drawing.Point(9, 19);
this.ModifyDeathSoundToggle.Margin = new System.Windows.Forms.Padding(2); this.ToggleDeathSound.Margin = new System.Windows.Forms.Padding(2);
this.ModifyDeathSoundToggle.Name = "ModifyDeathSoundToggle"; this.ToggleDeathSound.Name = "ToggleDeathSound";
this.ModifyDeathSoundToggle.Size = new System.Drawing.Size(138, 19); this.ToggleDeathSound.Size = new System.Drawing.Size(134, 19);
this.ModifyDeathSoundToggle.TabIndex = 1; this.ToggleDeathSound.TabIndex = 1;
this.ModifyDeathSoundToggle.Text = "Use Old Death Sound"; this.ToggleDeathSound.Text = "Use old death sound";
this.ModifyDeathSoundToggle.UseVisualStyleBackColor = true; this.ToggleDeathSound.UseVisualStyleBackColor = true;
this.ModifyDeathSoundToggle.CheckedChanged += new System.EventHandler(this.ModifyDeathSoundToggle_CheckedChanged); this.ToggleDeathSound.CheckedChanged += new System.EventHandler(this.ToggleDeathSound_CheckedChanged);
// //
// groupBox1 // GroupBoxInstallLocation
// //
this.groupBox1.Controls.Add(this.InstallLocationBrowseButton); this.GroupBoxInstallLocation.Controls.Add(this.InstallLocationBrowseButton);
this.groupBox1.Controls.Add(this.InstallLocation); this.GroupBoxInstallLocation.Controls.Add(this.InstallLocation);
this.groupBox1.Location = new System.Drawing.Point(5, 3); this.GroupBoxInstallLocation.Location = new System.Drawing.Point(5, 3);
this.groupBox1.Name = "groupBox1"; this.GroupBoxInstallLocation.Name = "GroupBoxInstallLocation";
this.groupBox1.Size = new System.Drawing.Size(422, 53); this.GroupBoxInstallLocation.Size = new System.Drawing.Size(422, 54);
this.groupBox1.TabIndex = 0; this.GroupBoxInstallLocation.TabIndex = 0;
this.groupBox1.TabStop = false; this.GroupBoxInstallLocation.TabStop = false;
this.groupBox1.Text = "Install Location"; this.GroupBoxInstallLocation.Text = "Install Location";
// //
// InstallLocationBrowseButton // InstallLocationBrowseButton
// //
@ -225,24 +253,13 @@
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.panel1.BackColor = System.Drawing.SystemColors.Control; this.panel1.BackColor = System.Drawing.SystemColors.Control;
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panel1.Controls.Add(this.label2);
this.panel1.Controls.Add(this.PreviewButton); this.panel1.Controls.Add(this.PreviewButton);
this.panel1.Controls.Add(this.SaveButton); this.panel1.Controls.Add(this.SaveButton);
this.panel1.Location = new System.Drawing.Point(-1, 227); this.panel1.Location = new System.Drawing.Point(-1, 277);
this.panel1.Name = "panel1"; this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(466, 42); this.panel1.Size = new System.Drawing.Size(466, 42);
this.panel1.TabIndex = 6; this.panel1.TabIndex = 6;
// //
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 13);
this.label2.Margin = new System.Windows.Forms.Padding(0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(221, 15);
this.label2.TabIndex = 6;
this.label2.Text = "made by pizzaboxer - i think this works...";
//
// PreviewButton // PreviewButton
// //
this.PreviewButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.PreviewButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
@ -254,12 +271,18 @@
this.PreviewButton.UseVisualStyleBackColor = true; this.PreviewButton.UseVisualStyleBackColor = true;
this.PreviewButton.Click += new System.EventHandler(this.PreviewButton_Click); this.PreviewButton.Click += new System.EventHandler(this.PreviewButton_Click);
// //
// InfoTooltip
//
this.InfoTooltip.ShowAlways = true;
this.InfoTooltip.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info;
this.InfoTooltip.ToolTipTitle = "Information";
//
// Preferences // Preferences
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.SystemColors.Window; this.BackColor = System.Drawing.SystemColors.Window;
this.ClientSize = new System.Drawing.Size(464, 268); this.ClientSize = new System.Drawing.Size(464, 318);
this.Controls.Add(this.panel1); this.Controls.Add(this.panel1);
this.Controls.Add(this.Tabs); this.Controls.Add(this.Tabs);
this.Controls.Add(this.label1); this.Controls.Add(this.label1);
@ -271,16 +294,17 @@
this.Text = "Preferences"; this.Text = "Preferences";
this.Tabs.ResumeLayout(false); this.Tabs.ResumeLayout(false);
this.DialogTab.ResumeLayout(false); this.DialogTab.ResumeLayout(false);
this.groupBox5.ResumeLayout(false);
this.groupBox5.PerformLayout();
this.groupBox3.ResumeLayout(false); this.groupBox3.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.IconPreview)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.IconPreview)).EndInit();
this.groupBox2.ResumeLayout(false); this.groupBox2.ResumeLayout(false);
this.InstallationTab.ResumeLayout(false); this.InstallationTab.ResumeLayout(false);
this.groupBox4.ResumeLayout(false); this.groupBox4.ResumeLayout(false);
this.groupBox4.PerformLayout(); this.groupBox4.PerformLayout();
this.groupBox1.ResumeLayout(false); this.GroupBoxInstallLocation.ResumeLayout(false);
this.groupBox1.PerformLayout(); this.GroupBoxInstallLocation.PerformLayout();
this.panel1.ResumeLayout(false); this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
} }
@ -294,7 +318,7 @@
private Button SaveButton; private Button SaveButton;
private Panel panel1; private Panel panel1;
private ListBox StyleSelection; private ListBox StyleSelection;
private GroupBox groupBox1; private GroupBox GroupBoxInstallLocation;
private Button InstallLocationBrowseButton; private Button InstallLocationBrowseButton;
private TextBox InstallLocation; private TextBox InstallLocation;
private FolderBrowserDialog InstallLocationBrowseDialog; private FolderBrowserDialog InstallLocationBrowseDialog;
@ -303,8 +327,10 @@
private PictureBox IconPreview; private PictureBox IconPreview;
private ListBox IconSelection; private ListBox IconSelection;
private Button PreviewButton; private Button PreviewButton;
private Label label2; private CheckBox ToggleDeathSound;
private CheckBox ModifyDeathSoundToggle;
private GroupBox groupBox4; private GroupBox groupBox4;
private GroupBox groupBox5;
private CheckBox ToggleDiscordRichPresence;
private ToolTip InfoTooltip;
} }
} }

View File

@ -25,24 +25,10 @@ namespace Bloxstrap.Dialogs
{ "2019", BootstrapperIcon.Icon2019 }, { "2019", BootstrapperIcon.Icon2019 },
}; };
private bool _useOldDeathSound = true;
private BootstrapperStyle? _selectedStyle; private BootstrapperStyle? _selectedStyle;
private BootstrapperIcon? _selectedIcon; private BootstrapperIcon? _selectedIcon;
private bool _useDiscordRichPresence = true;
private bool UseOldDeathSound private bool _useOldDeathSound = true;
{
get => _useOldDeathSound;
set
{
if (_useOldDeathSound == value)
return;
_useOldDeathSound = value;
this.ModifyDeathSoundToggle.Checked = value;
}
}
private BootstrapperStyle SelectedStyle private BootstrapperStyle SelectedStyle
{ {
@ -77,16 +63,48 @@ namespace Bloxstrap.Dialogs
} }
} }
private bool UseDiscordRichPresence
{
get => _useDiscordRichPresence;
set
{
if (_useDiscordRichPresence == value)
return;
_useDiscordRichPresence = value;
this.ToggleDiscordRichPresence.Checked = value;
}
}
private bool UseOldDeathSound
{
get => _useOldDeathSound;
set
{
if (_useOldDeathSound == value)
return;
_useOldDeathSound = value;
this.ToggleDeathSound.Checked = value;
}
}
public Preferences() public Preferences()
{ {
InitializeComponent(); InitializeComponent();
Program.SettingsManager.ShouldSave = false;
this.Icon = Properties.Resources.IconBloxstrap_ico; this.Icon = Properties.Resources.IconBloxstrap_ico;
this.Text = Program.ProjectName; this.Text = Program.ProjectName;
if (Program.IsFirstRun) if (Program.IsFirstRun)
{ {
this.SaveButton.Text = "Continue"; this.SaveButton.Text = "Install";
this.InstallLocation.Text = Path.Combine(Program.LocalAppData, Program.ProjectName); this.InstallLocation.Text = Path.Combine(Program.LocalAppData, Program.ProjectName);
} }
else else
@ -104,14 +122,15 @@ namespace Bloxstrap.Dialogs
this.IconSelection.Items.Add(icon.Key); this.IconSelection.Items.Add(icon.Key);
} }
UseOldDeathSound = Program.Settings.UseOldDeathSound; this.InfoTooltip.SetToolTip(this.StyleSelection, "Choose how the bootstrapper dialog should look.");
this.InfoTooltip.SetToolTip(this.IconSelection, "Choose what icon the bootstrapper should use.");
this.InfoTooltip.SetToolTip(this.GroupBoxInstallLocation, "Choose where Bloxstrap should install to.\nThis is useful if you typically install all your games to a separate storage drive.");
this.InfoTooltip.SetToolTip(this.ToggleDiscordRichPresence, "Choose whether to show what game you're playing on your Discord profile.\nThis will ONLY work when you launch a game from the website, and is not supported in the Beta App.");
SelectedStyle = Program.Settings.BootstrapperStyle; SelectedStyle = Program.Settings.BootstrapperStyle;
SelectedIcon = Program.Settings.BootstrapperIcon; SelectedIcon = Program.Settings.BootstrapperIcon;
} UseDiscordRichPresence = Program.Settings.UseDiscordRichPresence;
UseOldDeathSound = Program.Settings.UseOldDeathSound;
private void ShowDialog(MessageBoxIcon icon, string message)
{
MessageBox.Show(message, Program.ProjectName, MessageBoxButtons.OK, icon);
} }
private void InstallLocationBrowseButton_Click(object sender, EventArgs e) private void InstallLocationBrowseButton_Click(object sender, EventArgs e)
@ -140,7 +159,7 @@ namespace Bloxstrap.Dialogs
if (String.IsNullOrEmpty(installLocation)) if (String.IsNullOrEmpty(installLocation))
{ {
ShowDialog(MessageBoxIcon.Error, "You must set an install location"); Program.ShowMessageBox(MessageBoxIcon.Error, "You must set an install location");
return; return;
} }
@ -163,12 +182,12 @@ namespace Bloxstrap.Dialogs
} }
catch (UnauthorizedAccessException) catch (UnauthorizedAccessException)
{ {
ShowDialog(MessageBoxIcon.Error, $"{Program.ProjectName} does not have write access to the install location you selected. Please choose another install location."); Program.ShowMessageBox(MessageBoxIcon.Error, $"{Program.ProjectName} does not have write access to the install location you selected. Please choose another install location.");
return; return;
} }
catch (Exception ex) catch (Exception ex)
{ {
ShowDialog(MessageBoxIcon.Error, ex.Message); Program.ShowMessageBox(MessageBoxIcon.Error, ex.Message);
return; return;
} }
@ -179,7 +198,7 @@ namespace Bloxstrap.Dialogs
} }
else if (Program.BaseDirectory != installLocation) else if (Program.BaseDirectory != installLocation)
{ {
ShowDialog(MessageBoxIcon.Information, $"{Program.ProjectName} will install to the new location you've set the next time it runs."); Program.ShowMessageBox(MessageBoxIcon.Information, $"{Program.ProjectName} will install to the new location you've set the next time it runs.");
Program.Settings.VersionGuid = ""; Program.Settings.VersionGuid = "";
@ -196,9 +215,13 @@ namespace Bloxstrap.Dialogs
File.Copy(Path.Combine(Program.BaseDirectory, "Settings.json"), Path.Combine(installLocation, "Settings.json")); File.Copy(Path.Combine(Program.BaseDirectory, "Settings.json"), Path.Combine(installLocation, "Settings.json"));
} }
Program.Settings.UseOldDeathSound = UseOldDeathSound; if (!Program.IsFirstRun)
Program.SettingsManager.ShouldSave = true;
Program.Settings.BootstrapperStyle = SelectedStyle; Program.Settings.BootstrapperStyle = SelectedStyle;
Program.Settings.BootstrapperIcon = SelectedIcon; Program.Settings.BootstrapperIcon = SelectedIcon;
Program.Settings.UseDiscordRichPresence = UseDiscordRichPresence;
Program.Settings.UseOldDeathSound = UseOldDeathSound;
this.Close(); this.Close();
} }
@ -214,11 +237,11 @@ namespace Bloxstrap.Dialogs
switch (SelectedStyle) switch (SelectedStyle)
{ {
case BootstrapperStyle.LegacyDialog: case BootstrapperStyle.LegacyDialog:
new LegacyDialogStyle().ShowDialog(); new LegacyDialog().ShowDialog();
break; break;
case BootstrapperStyle.ProgressDialog: case BootstrapperStyle.ProgressDialog:
new ProgressDialogStyle().ShowDialog(); new ProgressDialog().ShowDialog();
break; break;
} }
@ -227,9 +250,14 @@ namespace Bloxstrap.Dialogs
this.Visible = true; this.Visible = true;
} }
private void ModifyDeathSoundToggle_CheckedChanged(object sender, EventArgs e) private void ToggleDiscordRichPresence_CheckedChanged(object sender, EventArgs e)
{ {
UseOldDeathSound = this.ModifyDeathSoundToggle.Checked; UseDiscordRichPresence = this.ToggleDiscordRichPresence.Checked;
}
private void ToggleDeathSound_CheckedChanged(object sender, EventArgs e)
{
UseOldDeathSound = this.ToggleDeathSound.Checked;
} }
} }
} }

View File

@ -60,4 +60,7 @@
<metadata name="InstallLocationBrowseDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="InstallLocationBrowseDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value> <value>17, 17</value>
</metadata> </metadata>
<metadata name="InfoTooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>222, 17</value>
</metadata>
</root> </root>

View File

@ -2,7 +2,7 @@
{ {
public enum BootstrapperStyle public enum BootstrapperStyle
{ {
TaskDialog, VistaDialog,
LegacyDialog, LegacyDialog,
ProgressDialog ProgressDialog
} }

View File

@ -0,0 +1,73 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using DiscordRPC;
namespace Bloxstrap.Helpers
{
internal class DiscordRichPresence : IDisposable
{
readonly DiscordRpcClient RichPresence = new("1005469189907173486");
public async Task<bool> SetPresence(string placeId)
{
string placeName;
string placeThumbnail;
string creatorName;
// null checking could probably be a lot more concrete here
using (HttpClient client = new())
{
JObject placeInfo = await Utilities.GetJson($"https://economy.roblox.com/v2/assets/{placeId}/details");
placeName = placeInfo["Name"].Value<string>();
creatorName = placeInfo["Creator"]["Name"].Value<string>();
JObject thumbnailInfo = await Utilities.GetJson($"https://thumbnails.roblox.com/v1/places/gameicons?placeIds={placeId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false");
if (thumbnailInfo["data"] is null)
return false;
placeThumbnail = thumbnailInfo["data"][0]["imageUrl"].Value<string>();
}
RichPresence.Initialize();
RichPresence.SetPresence(new RichPresence()
{
Details = placeName,
State = $"by {creatorName}",
Timestamps = new Timestamps() { Start = DateTime.UtcNow },
Assets = new Assets()
{
LargeImageKey = placeThumbnail,
LargeImageText = placeName,
SmallImageKey = "bloxstrap",
SmallImageText = "Rich Presence provided by Bloxstrap"
},
Buttons = new DiscordRPC.Button[]
{
new DiscordRPC.Button()
{
Label = "Play",
Url = $"https://www.roblox.com/games/start?placeId={placeId}&launchData=%7B%7D"
},
new DiscordRPC.Button()
{
Label = "View Details",
Url = $"https://www.roblox.com/games/{placeId}"
}
}
});
return true;
}
public void Dispose()
{
RichPresence.Dispose();
}
}
}

View File

@ -0,0 +1,76 @@
using System.Diagnostics;
using Newtonsoft.Json.Linq;
namespace Bloxstrap.Helpers
{
public class UpdateChecker
{
public static void CheckInstalledVersion()
{
if (Environment.ProcessPath is null || !File.Exists(Program.FilePath))
return;
// if downloaded version doesn't match, replace installed version with downloaded version
FileVersionInfo currentVersionInfo = FileVersionInfo.GetVersionInfo(Environment.ProcessPath);
FileVersionInfo installedVersionInfo = FileVersionInfo.GetVersionInfo(Program.FilePath);
if (installedVersionInfo != currentVersionInfo)
{
DialogResult result = MessageBox.Show(
$"The version of {Program.ProjectName} you've launched is newer than the version you currently have installed.\nWould you like to update your installed version of {Program.ProjectName}?",
Program.ProjectName,
MessageBoxButtons.YesNo,
MessageBoxIcon.Question
);
if (result == DialogResult.Yes)
{
File.Delete(Program.FilePath);
File.Copy(Environment.ProcessPath, Program.FilePath);
}
}
}
public static async Task Check()
{
if (Environment.ProcessPath is null)
return;
FileVersionInfo currentVersionInfo = FileVersionInfo.GetVersionInfo(Environment.ProcessPath);
string currentVersion = $"Bloxstrap v{currentVersionInfo.ProductVersion}";
string latestVersion;
string releaseNotes;
// get the latest version according to the latest github release info
// it should contain the latest product version, which we can check against
try
{
JObject releaseInfo = await Utilities.GetJson($"https://api.github.com/repos/{Program.ProjectRepository}/releases/latest");
latestVersion = releaseInfo["name"].Value<string>();
releaseNotes = releaseInfo["body"].Value<string>();
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to fetch latest version info! ({ex.Message})");
return;
}
if (currentVersion != latestVersion)
{
DialogResult result = MessageBox.Show(
$"A new version of {Program.ProjectName} is available\n\nRelease notes:\n{releaseNotes}\n\nDo you want to download {latestVersion}?",
Program.ProjectName,
MessageBoxButtons.YesNo,
MessageBoxIcon.Question
);
if (result == DialogResult.Yes)
{
Process.Start(new ProcessStartInfo { FileName = $"https://github.com/{Program.ProjectRepository}/releases/latest", UseShellExecute = true });
Program.Exit();
}
}
}
}
}

View File

@ -0,0 +1,48 @@
using System.Security.Cryptography;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Bloxstrap.Helpers
{
public class Utilities
{
public static async Task<JObject> GetJson(string url)
{
using (HttpClient client = new())
{
client.DefaultRequestHeaders.Add("User-Agent", Program.ProjectRepository);
string jsonString = await client.GetStringAsync(url);
return (JObject)JsonConvert.DeserializeObject(jsonString);
}
}
public static string CalculateMD5(string filename)
{
using (MD5 md5 = MD5.Create())
{
using (FileStream stream = File.OpenRead(filename))
{
byte[] hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
}
// quick and hacky way of getting a value from any key/value pair formatted list
// (command line args, uri params, etc)
public static string? GetKeyValue(string subject, string key, char delimiter)
{
if (subject.LastIndexOf(key) == -1)
return null;
string substr = subject.Substring(subject.LastIndexOf(key) + key.Length);
if (substr.IndexOf(delimiter) == -1)
return null;
return substr.Split(delimiter)[0];
}
}
}

View File

@ -1,5 +1,5 @@
using Microsoft.Win32;
using System.Diagnostics; using System.Diagnostics;
using Microsoft.Win32;
using Bloxstrap.Helpers; using Bloxstrap.Helpers;
namespace Bloxstrap namespace Bloxstrap
@ -19,11 +19,17 @@ namespace Bloxstrap
public static string? BaseDirectory; public static string? BaseDirectory;
public static string LocalAppData { get; private set; } public static string LocalAppData { get; private set; }
public static string FilePath { get; private set; } public static string FilePath { get; private set; }
public static string StartMenuDirectory { get; private set; }
public static bool IsFirstRun { get; private set; } = false; public static bool IsFirstRun { get; private set; } = false;
public static SettingsFormat Settings; public static SettingsFormat Settings;
public static SettingsManager SettingsManager = new(); public static SettingsManager SettingsManager = new();
public static void ShowMessageBox(MessageBoxIcon icon, string message)
{
MessageBox.Show(message, Program.ProjectName, MessageBoxButtons.OK, icon);
}
public static void Exit() public static void Exit()
{ {
SettingsManager.Save(); SettingsManager.Save();
@ -40,16 +46,17 @@ namespace Bloxstrap
// see https://aka.ms/applicationconfiguration. // see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize(); ApplicationConfiguration.Initialize();
// ensure only one is running if (Process.GetProcessesByName(ProjectName).Length > 1)
Process[] processes = Process.GetProcessesByName(ProjectName); {
if (processes.Length > 1) ShowMessageBox(MessageBoxIcon.Error, $"{ProjectName} is already running. Please close any currently open {ProjectName} window.\nIf you have Discord Rich Presence enabled, then close Roblox if it's running.");
return; return;
}
// Task.Run(() => Updater.CheckForUpdates()).Wait(); UpdateChecker.Check().Wait();
// return;
LocalAppData = Environment.GetEnvironmentVariable("localappdata"); LocalAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
// check if installed
RegistryKey? registryKey = Registry.CurrentUser.OpenSubKey($@"Software\{ProjectName}"); RegistryKey? registryKey = Registry.CurrentUser.OpenSubKey($@"Software\{ProjectName}");
if (registryKey is null) if (registryKey is null)
@ -64,18 +71,20 @@ namespace Bloxstrap
registryKey.Close(); registryKey.Close();
} }
// selection dialog was closed // preferences dialog was closed, and so base directory was never set
// (this doesnt account for the registry value not existing but thats basically never gonna happen) // (this doesnt account for the registry value not existing but thats basically never gonna happen)
if (BaseDirectory is null) if (BaseDirectory is null)
return; return;
SettingsManager.SaveLocation = Path.Combine(BaseDirectory, "Settings.json"); SettingsManager.SaveLocation = Path.Combine(BaseDirectory, "Settings.json");
FilePath = Path.Combine(BaseDirectory, $"{ProjectName}.exe"); FilePath = Path.Combine(BaseDirectory, $"{ProjectName}.exe");
StartMenuDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs", ProjectName);
// we shouldn't save settings on the first run until the first installation is finished, // 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 // just in case the user decides to cancel the install
if (!IsFirstRun) if (!IsFirstRun)
{ {
UpdateChecker.CheckInstalledVersion();
Settings = SettingsManager.Settings; Settings = SettingsManager.Settings;
SettingsManager.ShouldSave = true; SettingsManager.ShouldSave = true;
} }
@ -107,7 +116,7 @@ namespace Bloxstrap
} }
if (!String.IsNullOrEmpty(commandLine)) if (!String.IsNullOrEmpty(commandLine))
new Bootstrapper(Settings.BootstrapperStyle, commandLine); new Bootstrapper().Initialize(Settings.BootstrapperStyle, commandLine);
SettingsManager.Save(); SettingsManager.Save();
} }

View File

@ -7,9 +7,11 @@ namespace Bloxstrap
public class SettingsFormat public class SettingsFormat
{ {
public string VersionGuid { get; set; } public string VersionGuid { get; set; }
public bool UseOldDeathSound { get; set; } = true;
public BootstrapperStyle BootstrapperStyle { get; set; } = BootstrapperStyle.ProgressDialog; public BootstrapperStyle BootstrapperStyle { get; set; } = BootstrapperStyle.ProgressDialog;
public BootstrapperIcon BootstrapperIcon { get; set; } = BootstrapperIcon.IconBloxstrap; public BootstrapperIcon BootstrapperIcon { get; set; } = BootstrapperIcon.IconBloxstrap;
public bool UseDiscordRichPresence { get; set; } = true;
public bool UseOldDeathSound { get; set; } = true;
} }
public class SettingsManager public class SettingsManager

1
DiscordRPC Submodule

@ -0,0 +1 @@
Subproject commit a9fcc8d1e85738bc6493474a62a961842fa8dbc3