Initial code

Version 1.0.0
This commit is contained in:
pizzaboxer 2022-08-04 12:01:12 +01:00
parent cdc7517679
commit 32227cfb55
46 changed files with 3153 additions and 0 deletions

25
Bloxstrap.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bloxstrap", "Bloxstrap\Bloxstrap.csproj", "{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{646D1D58-C9CA-48C9-BBCD-30585A1DAAF1}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F989AC04-B48F-4BB4-B940-1E7D082F14DA}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<PlatformTarget>AnyCPU</PlatformTarget>
<Platforms>AnyCPU;x86</Platforms>
<ApplicationIcon>Bloxstrap.ico</ApplicationIcon>
<Version>1.0.0</Version>
<FileVersion>1.0.0.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<Content Include="Bloxstrap.ico" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>

BIN
Bloxstrap/Bloxstrap.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

655
Bloxstrap/Bootstrapper.cs Normal file
View File

@ -0,0 +1,655 @@
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,108 @@
namespace Bloxstrap.Dialogs.BootstrapperStyles
{
partial class LegacyDialogStyle
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Message = new System.Windows.Forms.Label();
this.ProgressBar = new System.Windows.Forms.ProgressBar();
this.IconBox = new System.Windows.Forms.PictureBox();
this.CancelButton = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.IconBox)).BeginInit();
this.SuspendLayout();
//
// Message
//
this.Message.Location = new System.Drawing.Point(55, 23);
this.Message.Name = "Message";
this.Message.Size = new System.Drawing.Size(287, 17);
this.Message.TabIndex = 0;
this.Message.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;
//
// CancelButton
//
this.CancelButton.Enabled = false;
this.CancelButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.CancelButton.Location = new System.Drawing.Point(271, 83);
this.CancelButton.Name = "CancelButton";
this.CancelButton.Size = new System.Drawing.Size(75, 23);
this.CancelButton.TabIndex = 3;
this.CancelButton.Text = "Cancel";
this.CancelButton.UseVisualStyleBackColor = true;
this.CancelButton.Visible = false;
this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click);
//
// LegacyDialogStyle
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(362, 131);
this.Controls.Add(this.CancelButton);
this.Controls.Add(this.IconBox);
this.Controls.Add(this.ProgressBar);
this.Controls.Add(this.Message);
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 = "LegacyDialogStyle";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "LegacyDialogStyle";
((System.ComponentModel.ISupportInitialize)(this.IconBox)).EndInit();
this.ResumeLayout(false);
}
#endregion
private Label Message;
private ProgressBar ProgressBar;
private PictureBox IconBox;
private Button CancelButton;
}
}

View File

@ -0,0 +1,161 @@
using Bloxstrap.Helpers;
using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap.Dialogs.BootstrapperStyles
{
// TODO - universal implementation for winforms-based styles? (to reduce duplicate code)
// example: https://youtu.be/3K9oCEMHj2s?t=35
// so this specifically emulates the 2011 version of the legacy dialog,
// but once winforms code is cleaned up we could also do the 2009 version too
// example: https://youtu.be/VpduiruysuM?t=18
public partial class LegacyDialogStyle : Form
{
private Bootstrapper? Bootstrapper;
public LegacyDialogStyle(Bootstrapper? bootstrapper = null)
{
InitializeComponent();
if (bootstrapper is not null)
{
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();
this.Text = Program.ProjectName;
this.Icon = icon;
this.IconBox.Image = icon.ToBitmap();
if (Bootstrapper is null)
{
this.Message.Text = "Click the Cancel button to return to preferences";
this.CancelButton.Enabled = true;
this.CancelButton.Visible = true;
}
else
{
Task.Run(() => RunBootstrapper());
}
}
public async void RunBootstrapper()
{
try
{
await Bootstrapper.Run();
}
catch (Exception ex)
{
// string message = String.Format("{0}: {1}", ex.GetType(), ex.Message);
string message = ex.ToString();
ShowError(message);
Program.Exit();
}
}
private void ShowError(string message)
{
MessageBox.Show(
$"An error occurred while starting Roblox\n\nDetails: {message}",
Program.ProjectName,
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
private void ShowSuccess(object sender, ChangeEventArgs<string> e)
{
MessageBox.Show(
e.Value,
Program.ProjectName,
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
}
private void PromptShutdown(object? sender, EventArgs e)
{
DialogResult result = MessageBox.Show(
"Roblox is currently running, but needs to close. Would you like close Roblox now?",
Program.ProjectName,
MessageBoxButtons.OKCancel,
MessageBoxIcon.Information
);
if (result != DialogResult.OK)
Environment.Exit(0);
}
private void MessageChanged(object sender, ChangeEventArgs<string> e)
{
if (this.InvokeRequired)
{
ChangeEventHandler<string> handler = new(MessageChanged);
this.Message.Invoke(handler, sender, e);
}
else
{
this.Message.Text = e.Value;
}
}
private void ProgressBarValueChanged(object sender, ChangeEventArgs<int> e)
{
if (this.ProgressBar.InvokeRequired)
{
ChangeEventHandler<int> handler = new(ProgressBarValueChanged);
this.ProgressBar.Invoke(handler, sender, e);
}
else
{
this.ProgressBar.Value = e.Value;
}
}
private void ProgressBarStyleChanged(object sender, ChangeEventArgs<ProgressBarStyle> e)
{
if (this.ProgressBar.InvokeRequired)
{
ChangeEventHandler<ProgressBarStyle> handler = new(this.ProgressBarStyleChanged);
this.ProgressBar.Invoke(handler, sender, e);
}
else
{
this.ProgressBar.Style = e.Value;
}
}
private void CancelEnabledChanged(object sender, ChangeEventArgs<bool> e)
{
if (this.CancelButton.InvokeRequired)
{
ChangeEventHandler<bool> handler = new(CancelEnabledChanged);
this.CancelButton.Invoke(handler, sender, e);
}
else
{
this.CancelButton.Enabled = e.Value;
this.CancelButton.Visible = e.Value;
}
}
private void CancelButton_Click(object sender, EventArgs e)
{
if (Bootstrapper is null)
this.Close();
else
Task.Run(() => Bootstrapper.CancelButtonClicked());
}
}
}

View File

@ -0,0 +1,60 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,126 @@
namespace Bloxstrap.Dialogs.BootstrapperStyles
{
partial class ProgressDialogStyle
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.ProgressBar = new System.Windows.Forms.ProgressBar();
this.Message = new System.Windows.Forms.Label();
this.IconBox = new System.Windows.Forms.PictureBox();
this.CancelButton = new System.Windows.Forms.PictureBox();
this.panel1 = new System.Windows.Forms.Panel();
((System.ComponentModel.ISupportInitialize)(this.IconBox)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.CancelButton)).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;
//
// Message
//
this.Message.Font = new System.Drawing.Font("Tahoma", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.Message.Location = new System.Drawing.Point(29, 199);
this.Message.Name = "Message";
this.Message.Size = new System.Drawing.Size(460, 18);
this.Message.TabIndex = 1;
this.Message.Text = "Please wait...";
this.Message.TextAlign = System.Drawing.ContentAlignment.TopCenter;
this.Message.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;
//
// CancelButton
//
this.CancelButton.Enabled = false;
this.CancelButton.Image = global::Bloxstrap.Properties.Resources.CancelButton;
this.CancelButton.Location = new System.Drawing.Point(194, 264);
this.CancelButton.Name = "CancelButton";
this.CancelButton.Size = new System.Drawing.Size(130, 44);
this.CancelButton.TabIndex = 3;
this.CancelButton.TabStop = false;
this.CancelButton.Visible = false;
this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click);
this.CancelButton.MouseEnter += new System.EventHandler(this.CancelButton_MouseEnter);
this.CancelButton.MouseLeave += new System.EventHandler(this.CancelButton_MouseLeave);
//
// panel1
//
this.panel1.BackColor = System.Drawing.SystemColors.Window;
this.panel1.Controls.Add(this.Message);
this.panel1.Controls.Add(this.IconBox);
this.panel1.Controls.Add(this.CancelButton);
this.panel1.Controls.Add(this.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;
//
// ProgressDialogStyle
//
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 = "ProgressDialogStyle";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "ProgressDialogStyle";
((System.ComponentModel.ISupportInitialize)(this.IconBox)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.CancelButton)).EndInit();
this.panel1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private ProgressBar ProgressBar;
private Label Message;
private PictureBox IconBox;
private PictureBox CancelButton;
private Panel panel1;
}
}

View File

@ -0,0 +1,161 @@
using Bloxstrap.Helpers;
using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap.Dialogs.BootstrapperStyles
{
// TODO - universal implementation for winforms-based styles? (to reduce duplicate code)
public partial class ProgressDialogStyle : Form
{
private Bootstrapper? Bootstrapper;
public ProgressDialogStyle(Bootstrapper? bootstrapper = null)
{
InitializeComponent();
Bootstrapper = bootstrapper;
this.Text = Program.ProjectName;
this.Icon = IconManager.GetIconResource();
this.IconBox.BackgroundImage = IconManager.GetBitmapResource();
if (Bootstrapper is null)
{
this.Message.Text = "Click the Cancel button to return to preferences";
this.CancelButton.Enabled = true;
this.CancelButton.Visible = true;
}
else
{
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());
}
}
public async void RunBootstrapper()
{
try
{
await Bootstrapper.Run();
}
catch (Exception ex)
{
// string message = String.Format("{0}: {1}", ex.GetType(), ex.Message);
string message = ex.ToString();
ShowError(message);
Program.Exit();
}
}
private void ShowError(string message)
{
MessageBox.Show(
$"An error occurred while starting Roblox\n\nDetails: {message}",
Program.ProjectName,
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
private void ShowSuccess(object sender, ChangeEventArgs<string> e)
{
MessageBox.Show(
e.Value,
Program.ProjectName,
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
}
private void PromptShutdown(object? sender, EventArgs e)
{
DialogResult result = MessageBox.Show(
"Roblox is currently running, but needs to close. Would you like close Roblox now?",
Program.ProjectName,
MessageBoxButtons.OKCancel,
MessageBoxIcon.Information
);
if (result != DialogResult.OK)
Environment.Exit(0);
}
private void MessageChanged(object sender, ChangeEventArgs<string> e)
{
if (this.InvokeRequired)
{
ChangeEventHandler<string> handler = new(MessageChanged);
this.Message.Invoke(handler, sender, e);
}
else
{
this.Message.Text = e.Value;
}
}
private void ProgressBarValueChanged(object sender, ChangeEventArgs<int> e)
{
if (this.ProgressBar.InvokeRequired)
{
ChangeEventHandler<int> handler = new(ProgressBarValueChanged);
this.ProgressBar.Invoke(handler, sender, e);
}
else
{
this.ProgressBar.Value = e.Value;
}
}
private void ProgressBarStyleChanged(object sender, ChangeEventArgs<ProgressBarStyle> e)
{
if (this.ProgressBar.InvokeRequired)
{
ChangeEventHandler<ProgressBarStyle> handler = new(ProgressBarStyleChanged);
this.ProgressBar.Invoke(handler, sender, e);
}
else
{
this.ProgressBar.Style = e.Value;
}
}
private void CancelEnabledChanged(object sender, ChangeEventArgs<bool> e)
{
if (this.CancelButton.InvokeRequired)
{
ChangeEventHandler<bool> handler = new(CancelEnabledChanged);
this.CancelButton.Invoke(handler, sender, e);
}
else
{
this.CancelButton.Enabled = e.Value;
this.CancelButton.Visible = e.Value;
}
}
private void CancelButton_Click(object sender, EventArgs e)
{
if (Bootstrapper is null)
this.Close();
else
Task.Run(() => Bootstrapper.CancelButtonClicked());
}
private void CancelButton_MouseEnter(object sender, EventArgs e)
{
this.CancelButton.Image = Properties.Resources.CancelButtonHover;
}
private void CancelButton_MouseLeave(object sender, EventArgs e)
{
this.CancelButton.Image = Properties.Resources.CancelButton;
}
}
}

View File

@ -0,0 +1,60 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,134 @@
using Bloxstrap.Helpers;
using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap.Dialogs.BootstrapperStyles
{
// example: https://youtu.be/h0_AL95Sc3o?t=48
// i suppose a better name for this here would be "VistaDialog" rather than "TaskDialog"?
// having this named as BootstrapperStyles.TaskDialog would conflict with Forms.TaskDialog
// so naming it VistaDialog would let us drop the ~Style suffix on every style name
// this currently doesn't work because c# is stupid
// technically, task dialogs are treated as winforms controls, but they don't classify as winforms controls at all
// all winforms controls have the ability to be invoked from another thread, but task dialogs don't
// so we're just kind of stuck with this not working in multithreaded use
// (unless we want the bootstrapper to freeze during package extraction)
// for now, just stick to legacydialog and progressdialog
public class TaskDialogStyle
{
private Bootstrapper Bootstrapper;
private TaskDialogPage Dialog;
public TaskDialogStyle(Bootstrapper bootstrapper)
{
Bootstrapper = bootstrapper;
Bootstrapper.ShowSuccessEvent += new ChangeEventHandler<string>(ShowSuccess);
Bootstrapper.MessageChanged += new ChangeEventHandler<string>(MessageChanged);
Bootstrapper.ProgressBarValueChanged += new ChangeEventHandler<int>(ProgressBarValueChanged);
Bootstrapper.ProgressBarStyleChanged += new ChangeEventHandler<ProgressBarStyle>(ProgressBarStyleChanged);
Dialog = new TaskDialogPage()
{
Icon = new TaskDialogIcon(IconManager.GetIconResource()),
Caption = Program.ProjectName,
Heading = "Please wait...",
Buttons = { TaskDialogButton.Cancel },
ProgressBar = new TaskDialogProgressBar()
{
State = TaskDialogProgressBarState.Marquee
}
};
Task.Run(() => RunBootstrapper());
TaskDialog.ShowDialog(Dialog);
}
public async void RunBootstrapper()
{
try
{
await Bootstrapper.Run();
}
catch (Exception ex)
{
// string message = String.Format("{0}: {1}", ex.GetType(), ex.Message);
string message = ex.ToString();
ShowError(message);
Program.Exit();
}
}
public void ShowError(string message)
{
TaskDialogPage errorDialog = new()
{
Icon = TaskDialogIcon.Error,
Caption = Program.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
}
};
Dialog.Navigate(errorDialog);
Dialog = errorDialog;
}
public void ShowSuccess(object sender, ChangeEventArgs<string> e)
{
TaskDialogPage successDialog = new()
{
Icon = TaskDialogIcon.ShieldSuccessGreenBar,
Caption = Program.ProjectName,
Heading = e.Value
};
Dialog.Navigate(successDialog);
Dialog = successDialog;
}
private void MessageChanged(object sender, ChangeEventArgs<string> e)
{
if (Dialog is null)
return;
Dialog.Heading = e.Value;
}
private void ProgressBarValueChanged(object sender, ChangeEventArgs<int> e)
{
if (Dialog is null || Dialog.ProgressBar is null)
return;
Dialog.ProgressBar.Value = e.Value;
}
private void ProgressBarStyleChanged(object sender, ChangeEventArgs<ProgressBarStyle> e)
{
if (Dialog is null || Dialog.ProgressBar is null)
return;
switch (e.Value)
{
case ProgressBarStyle.Continuous:
case ProgressBarStyle.Blocks:
Dialog.ProgressBar.State = TaskDialogProgressBarState.Normal;
break;
case ProgressBarStyle.Marquee:
Dialog.ProgressBar.State = TaskDialogProgressBarState.Marquee;
break;
}
}
}
}

310
Bloxstrap/Dialogs/Preferences.Designer.cs generated Normal file
View File

@ -0,0 +1,310 @@
namespace Bloxstrap.Dialogs
{
partial class Preferences
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.Tabs = new System.Windows.Forms.TabControl();
this.DialogTab = new System.Windows.Forms.TabPage();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.IconPreview = new System.Windows.Forms.PictureBox();
this.IconSelection = new System.Windows.Forms.ListBox();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.StyleSelection = new System.Windows.Forms.ListBox();
this.InstallationTab = new System.Windows.Forms.TabPage();
this.groupBox4 = new System.Windows.Forms.GroupBox();
this.ModifyDeathSoundToggle = new System.Windows.Forms.CheckBox();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.InstallLocationBrowseButton = new System.Windows.Forms.Button();
this.InstallLocation = new System.Windows.Forms.TextBox();
this.SaveButton = new System.Windows.Forms.Button();
this.panel1 = new System.Windows.Forms.Panel();
this.label2 = new System.Windows.Forms.Label();
this.PreviewButton = new System.Windows.Forms.Button();
this.InstallLocationBrowseDialog = new System.Windows.Forms.FolderBrowserDialog();
this.Tabs.SuspendLayout();
this.DialogTab.SuspendLayout();
this.groupBox3.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.IconPreview)).BeginInit();
this.groupBox2.SuspendLayout();
this.InstallationTab.SuspendLayout();
this.groupBox4.SuspendLayout();
this.groupBox1.SuspendLayout();
this.panel1.SuspendLayout();
this.SuspendLayout();
//
// label1
//
this.label1.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label1.ForeColor = System.Drawing.Color.Navy;
this.label1.Location = new System.Drawing.Point(8, 9);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(237, 23);
this.label1.TabIndex = 1;
this.label1.Text = "Configure Preferences";
//
// Tabs
//
this.Tabs.Controls.Add(this.DialogTab);
this.Tabs.Controls.Add(this.InstallationTab);
this.Tabs.Location = new System.Drawing.Point(12, 40);
this.Tabs.Name = "Tabs";
this.Tabs.SelectedIndex = 0;
this.Tabs.Size = new System.Drawing.Size(442, 176);
this.Tabs.TabIndex = 2;
//
// DialogTab
//
this.DialogTab.Controls.Add(this.groupBox3);
this.DialogTab.Controls.Add(this.groupBox2);
this.DialogTab.Location = new System.Drawing.Point(4, 24);
this.DialogTab.Name = "DialogTab";
this.DialogTab.Padding = new System.Windows.Forms.Padding(3);
this.DialogTab.Size = new System.Drawing.Size(434, 148);
this.DialogTab.TabIndex = 0;
this.DialogTab.Text = "Bootstrapper";
this.DialogTab.UseVisualStyleBackColor = true;
//
// groupBox3
//
this.groupBox3.Controls.Add(this.IconPreview);
this.groupBox3.Controls.Add(this.IconSelection);
this.groupBox3.Location = new System.Drawing.Point(192, 3);
this.groupBox3.Name = "groupBox3";
this.groupBox3.Size = new System.Drawing.Size(235, 140);
this.groupBox3.TabIndex = 6;
this.groupBox3.TabStop = false;
this.groupBox3.Text = "Icon";
//
// IconPreview
//
this.IconPreview.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
this.IconPreview.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.IconPreview.Location = new System.Drawing.Point(117, 21);
this.IconPreview.Name = "IconPreview";
this.IconPreview.Size = new System.Drawing.Size(109, 109);
this.IconPreview.TabIndex = 3;
this.IconPreview.TabStop = false;
//
// IconSelection
//
this.IconSelection.FormattingEnabled = true;
this.IconSelection.ItemHeight = 15;
this.IconSelection.Location = new System.Drawing.Point(9, 21);
this.IconSelection.Name = "IconSelection";
this.IconSelection.Size = new System.Drawing.Size(100, 109);
this.IconSelection.TabIndex = 4;
this.IconSelection.SelectedIndexChanged += new System.EventHandler(this.IconSelection_SelectedIndexChanged);
//
// groupBox2
//
this.groupBox2.Controls.Add(this.StyleSelection);
this.groupBox2.Location = new System.Drawing.Point(5, 3);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(179, 140);
this.groupBox2.TabIndex = 5;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Style";
//
// StyleSelection
//
this.StyleSelection.FormattingEnabled = true;
this.StyleSelection.ItemHeight = 15;
this.StyleSelection.Location = new System.Drawing.Point(9, 21);
this.StyleSelection.Name = "StyleSelection";
this.StyleSelection.Size = new System.Drawing.Size(161, 109);
this.StyleSelection.TabIndex = 3;
this.StyleSelection.SelectedIndexChanged += new System.EventHandler(this.StyleSelection_SelectedIndexChanged);
//
// InstallationTab
//
this.InstallationTab.Controls.Add(this.groupBox4);
this.InstallationTab.Controls.Add(this.groupBox1);
this.InstallationTab.Location = new System.Drawing.Point(4, 24);
this.InstallationTab.Name = "InstallationTab";
this.InstallationTab.Padding = new System.Windows.Forms.Padding(3);
this.InstallationTab.Size = new System.Drawing.Size(434, 148);
this.InstallationTab.TabIndex = 2;
this.InstallationTab.Text = "Installation";
this.InstallationTab.UseVisualStyleBackColor = true;
//
// groupBox4
//
this.groupBox4.Controls.Add(this.ModifyDeathSoundToggle);
this.groupBox4.Location = new System.Drawing.Point(5, 59);
this.groupBox4.Name = "groupBox4";
this.groupBox4.Size = new System.Drawing.Size(422, 84);
this.groupBox4.TabIndex = 2;
this.groupBox4.TabStop = false;
this.groupBox4.Text = "Modifications";
//
// ModifyDeathSoundToggle
//
this.ModifyDeathSoundToggle.AutoSize = true;
this.ModifyDeathSoundToggle.Checked = true;
this.ModifyDeathSoundToggle.CheckState = System.Windows.Forms.CheckState.Checked;
this.ModifyDeathSoundToggle.Location = new System.Drawing.Point(9, 21);
this.ModifyDeathSoundToggle.Margin = new System.Windows.Forms.Padding(2);
this.ModifyDeathSoundToggle.Name = "ModifyDeathSoundToggle";
this.ModifyDeathSoundToggle.Size = new System.Drawing.Size(138, 19);
this.ModifyDeathSoundToggle.TabIndex = 1;
this.ModifyDeathSoundToggle.Text = "Use Old Death Sound";
this.ModifyDeathSoundToggle.UseVisualStyleBackColor = true;
this.ModifyDeathSoundToggle.CheckedChanged += new System.EventHandler(this.ModifyDeathSoundToggle_CheckedChanged);
//
// groupBox1
//
this.groupBox1.Controls.Add(this.InstallLocationBrowseButton);
this.groupBox1.Controls.Add(this.InstallLocation);
this.groupBox1.Location = new System.Drawing.Point(5, 3);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(422, 53);
this.groupBox1.TabIndex = 0;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Install Location";
//
// InstallLocationBrowseButton
//
this.InstallLocationBrowseButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.InstallLocationBrowseButton.Location = new System.Drawing.Point(328, 20);
this.InstallLocationBrowseButton.Name = "InstallLocationBrowseButton";
this.InstallLocationBrowseButton.Size = new System.Drawing.Size(86, 25);
this.InstallLocationBrowseButton.TabIndex = 5;
this.InstallLocationBrowseButton.Text = "Browse...";
this.InstallLocationBrowseButton.UseVisualStyleBackColor = true;
this.InstallLocationBrowseButton.Click += new System.EventHandler(this.InstallLocationBrowseButton_Click);
//
// InstallLocation
//
this.InstallLocation.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.InstallLocation.Location = new System.Drawing.Point(9, 21);
this.InstallLocation.MaxLength = 255;
this.InstallLocation.Name = "InstallLocation";
this.InstallLocation.Size = new System.Drawing.Size(312, 23);
this.InstallLocation.TabIndex = 4;
//
// SaveButton
//
this.SaveButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.SaveButton.Location = new System.Drawing.Point(380, 9);
this.SaveButton.Name = "SaveButton";
this.SaveButton.Size = new System.Drawing.Size(73, 23);
this.SaveButton.TabIndex = 6;
this.SaveButton.Text = "Save";
this.SaveButton.UseVisualStyleBackColor = true;
this.SaveButton.Click += new System.EventHandler(this.SaveButton_Click);
//
// panel1
//
this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panel1.BackColor = System.Drawing.SystemColors.Control;
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panel1.Controls.Add(this.label2);
this.panel1.Controls.Add(this.PreviewButton);
this.panel1.Controls.Add(this.SaveButton);
this.panel1.Location = new System.Drawing.Point(-1, 227);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(466, 42);
this.panel1.TabIndex = 6;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 13);
this.label2.Margin = new System.Windows.Forms.Padding(0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(221, 15);
this.label2.TabIndex = 6;
this.label2.Text = "made by pizzaboxer - i think this works...";
//
// PreviewButton
//
this.PreviewButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.PreviewButton.Location = new System.Drawing.Point(297, 9);
this.PreviewButton.Name = "PreviewButton";
this.PreviewButton.Size = new System.Drawing.Size(73, 23);
this.PreviewButton.TabIndex = 5;
this.PreviewButton.Text = "Preview";
this.PreviewButton.UseVisualStyleBackColor = true;
this.PreviewButton.Click += new System.EventHandler(this.PreviewButton_Click);
//
// Preferences
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.SystemColors.Window;
this.ClientSize = new System.Drawing.Size(464, 268);
this.Controls.Add(this.panel1);
this.Controls.Add(this.Tabs);
this.Controls.Add(this.label1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "Preferences";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Preferences";
this.Tabs.ResumeLayout(false);
this.DialogTab.ResumeLayout(false);
this.groupBox3.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.IconPreview)).EndInit();
this.groupBox2.ResumeLayout(false);
this.InstallationTab.ResumeLayout(false);
this.groupBox4.ResumeLayout(false);
this.groupBox4.PerformLayout();
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private Label label1;
private TabControl Tabs;
private TabPage DialogTab;
private TabPage InstallationTab;
private Button SaveButton;
private Panel panel1;
private ListBox StyleSelection;
private GroupBox groupBox1;
private Button InstallLocationBrowseButton;
private TextBox InstallLocation;
private FolderBrowserDialog InstallLocationBrowseDialog;
private GroupBox groupBox2;
private GroupBox groupBox3;
private PictureBox IconPreview;
private ListBox IconSelection;
private Button PreviewButton;
private Label label2;
private CheckBox ModifyDeathSoundToggle;
private GroupBox groupBox4;
}
}

View File

@ -0,0 +1,235 @@
using Microsoft.Win32;
using Bloxstrap.Helpers;
using Bloxstrap.Enums;
using Bloxstrap.Dialogs.BootstrapperStyles;
namespace Bloxstrap.Dialogs
{
public partial class Preferences : Form
{
private static readonly IReadOnlyDictionary<string, BootstrapperStyle> SelectableStyles = new Dictionary<string, BootstrapperStyle>()
{
{ "Legacy (2011 - 2014)", BootstrapperStyle.LegacyDialog },
{ "Progress (~2014)", BootstrapperStyle.ProgressDialog },
};
private static readonly IReadOnlyDictionary<string, BootstrapperIcon> SelectableIcons = new Dictionary<string, BootstrapperIcon>()
{
{ "Bloxstrap", BootstrapperIcon.IconBloxstrap },
{ "2009", BootstrapperIcon.Icon2009 },
{ "2011", BootstrapperIcon.Icon2011 },
{ "Early 2015", BootstrapperIcon.IconEarly2015 },
{ "Late 2015", BootstrapperIcon.IconLate2015 },
{ "2017", BootstrapperIcon.Icon2017 },
{ "2019", BootstrapperIcon.Icon2019 },
};
private bool _useOldDeathSound = true;
private BootstrapperStyle? _selectedStyle;
private BootstrapperIcon? _selectedIcon;
private bool UseOldDeathSound
{
get => _useOldDeathSound;
set
{
if (_useOldDeathSound == value)
return;
_useOldDeathSound = value;
this.ModifyDeathSoundToggle.Checked = value;
}
}
private BootstrapperStyle SelectedStyle
{
get => (BootstrapperStyle)_selectedStyle;
set
{
if (_selectedStyle == value)
return;
_selectedStyle = value;
int index = SelectableStyles.Values.ToList().IndexOf(value);
this.StyleSelection.SetSelected(index, true);
}
}
private BootstrapperIcon SelectedIcon
{
get => (BootstrapperIcon)_selectedIcon;
set
{
if (_selectedIcon == value)
return;
_selectedIcon = value;
int index = SelectableIcons.Values.ToList().IndexOf(value);
this.IconSelection.SetSelected(index, true);
this.IconPreview.BackgroundImage = IconManager.GetBitmapResource(value);
}
}
public Preferences()
{
InitializeComponent();
this.Icon = Properties.Resources.IconBloxstrap_ico;
this.Text = Program.ProjectName;
if (Program.IsFirstRun)
{
this.SaveButton.Text = "Continue";
this.InstallLocation.Text = Path.Combine(Program.LocalAppData, Program.ProjectName);
}
else
{
this.InstallLocation.Text = Program.BaseDirectory;
}
foreach (var style in SelectableStyles)
{
this.StyleSelection.Items.Add(style.Key);
}
foreach (var icon in SelectableIcons)
{
this.IconSelection.Items.Add(icon.Key);
}
UseOldDeathSound = Program.Settings.UseOldDeathSound;
SelectedStyle = Program.Settings.BootstrapperStyle;
SelectedIcon = Program.Settings.BootstrapperIcon;
}
private void ShowDialog(MessageBoxIcon icon, string message)
{
MessageBox.Show(message, Program.ProjectName, MessageBoxButtons.OK, icon);
}
private void InstallLocationBrowseButton_Click(object sender, EventArgs e)
{
DialogResult result = this.InstallLocationBrowseDialog.ShowDialog();
if (result == DialogResult.OK)
this.InstallLocation.Text = this.InstallLocationBrowseDialog.SelectedPath;
}
private void StyleSelection_SelectedIndexChanged(object sender, EventArgs e)
{
string selected = this.StyleSelection.Text;
SelectedStyle = SelectableStyles[selected];
}
private void IconSelection_SelectedIndexChanged(object sender, EventArgs e)
{
string selected = this.IconSelection.Text;
SelectedIcon = SelectableIcons[selected];
}
private void SaveButton_Click(object sender, EventArgs e)
{
string installLocation = this.InstallLocation.Text;
if (String.IsNullOrEmpty(installLocation))
{
ShowDialog(MessageBoxIcon.Error, "You must set an install location");
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)
{
ShowDialog(MessageBoxIcon.Error, $"{Program.ProjectName} does not have write access to the install location you selected. Please choose another install location.");
return;
}
catch (Exception ex)
{
ShowDialog(MessageBoxIcon.Error, ex.Message);
return;
}
if (Program.IsFirstRun)
{
// this will be set in the registry after first install
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.Settings.VersionGuid = "";
using (RegistryKey registryKey = Registry.CurrentUser.CreateSubKey($@"Software\{Program.ProjectName}"))
{
registryKey.SetValue("InstallLocation", installLocation);
registryKey.SetValue("OldInstallLocation", Program.BaseDirectory);
}
// preserve settings
// we don't need to copy the bootstrapper over since the install process will do that automatically
Program.SettingsManager.Save();
File.Copy(Path.Combine(Program.BaseDirectory, "Settings.json"), Path.Combine(installLocation, "Settings.json"));
}
Program.Settings.UseOldDeathSound = UseOldDeathSound;
Program.Settings.BootstrapperStyle = SelectedStyle;
Program.Settings.BootstrapperIcon = SelectedIcon;
this.Close();
}
private void PreviewButton_Click(object sender, EventArgs e)
{
// small hack to get the icon to show in the preview without saving to settings
BootstrapperIcon savedIcon = Program.Settings.BootstrapperIcon;
Program.Settings.BootstrapperIcon = SelectedIcon;
this.Visible = false;
switch (SelectedStyle)
{
case BootstrapperStyle.LegacyDialog:
new LegacyDialogStyle().ShowDialog();
break;
case BootstrapperStyle.ProgressDialog:
new ProgressDialogStyle().ShowDialog();
break;
}
Program.Settings.BootstrapperIcon = savedIcon;
this.Visible = true;
}
private void ModifyDeathSoundToggle_CheckedChanged(object sender, EventArgs e)
{
UseOldDeathSound = this.ModifyDeathSoundToggle.Checked;
}
}
}

View File

@ -0,0 +1,63 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="InstallLocationBrowseDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@ -0,0 +1,13 @@
namespace Bloxstrap.Enums
{
public enum BootstrapperIcon
{
IconBloxstrap,
Icon2009,
Icon2011,
IconEarly2015,
IconLate2015,
Icon2017,
Icon2019
}
}

View File

@ -0,0 +1,9 @@
namespace Bloxstrap.Enums
{
public enum BootstrapperStyle
{
TaskDialog,
LegacyDialog,
ProgressDialog
}
}

View File

@ -0,0 +1,45 @@
using Bloxstrap.Enums;
namespace Bloxstrap.Helpers
{
internal class IconManager
{
public static Icon GetIconResource()
{
return GetIconResource(Program.Settings.BootstrapperIcon);
}
public static Icon GetIconResource(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.IconBloxstrap: default: return Properties.Resources.IconBloxstrap_ico;
}
}
public static Bitmap GetBitmapResource()
{
return GetBitmapResource(Program.Settings.BootstrapperIcon);
}
public static Bitmap GetBitmapResource(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.IconBloxstrap: default: return Properties.Resources.IconBloxstrap_png;
}
}
}
}

View File

@ -0,0 +1,81 @@
using System.Web;
using Microsoft.Win32;
namespace Bloxstrap.Helpers
{
public class Protocol
{
// map uri keys to command line args
private static readonly IReadOnlyDictionary<string, string> UriKeyArgMap = new Dictionary<string, string>()
{
// excluding roblox-player, browsertrackerid and channel
{ "launchmode", "--" },
{ "gameinfo", "-t " },
{ "placelauncherurl", "-j "},
// { "launchtime", "--launchtime=" }, we'll set this when launching the game client
{ "robloxLocale", "--rloc " },
{ "gameLocale", "--gloc " },
};
public static string Parse(string protocol)
{
string[] keyvalPair;
string key;
string val;
string commandLine = "";
foreach (var parameter in protocol.Split('+'))
{
if (!parameter.Contains(':'))
continue;
keyvalPair = parameter.Split(':');
key = keyvalPair[0];
val = keyvalPair[1];
if (!UriKeyArgMap.ContainsKey(key))
continue;
if (key == "placelauncherurl")
val = HttpUtility.UrlDecode(val).Replace("browserTrackerId", "lol");
commandLine += UriKeyArgMap[key] + val + " ";
}
return commandLine;
}
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.DeleteSubKey($@"Software\Classes\{key}");
}
catch (Exception) { }
}
}
}

View File

@ -0,0 +1,16 @@
// https://github.com/MaximumADHD/Roblox-Studio-Mod-Manager/blob/main/ProjectSrc/Events/ChangeEvent.cs
namespace Bloxstrap.Helpers.RSMM
{
public delegate void ChangeEventHandler<T>(object sender, ChangeEventArgs<T> e);
public class ChangeEventArgs<T>
{
public T Value { get; }
public ChangeEventArgs(T value)
{
Value = value;
}
}
}

View File

@ -0,0 +1,62 @@
// https://github.com/MaximumADHD/Roblox-Studio-Mod-Manager/blob/main/ProjectSrc/Bootstrapper/FileManifest.cs
using System.Runtime.Serialization;
namespace Bloxstrap.Helpers.RSMM
{
[Serializable]
internal class FileManifest : Dictionary<string, string>
{
public string RawData { get; set; }
protected FileManifest(SerializationInfo info, StreamingContext context)
: base(info, context) { }
private FileManifest(string data, bool remapExtraContent = false)
{
using (var reader = new StringReader(data))
{
bool eof = false;
var readLine = new Func<string>(() =>
{
string line = reader.ReadLine();
if (line == null)
eof = true;
return line;
});
while (!eof)
{
string path = readLine();
string signature = readLine();
if (eof)
break;
else if (remapExtraContent && path.StartsWith("ExtraContent", Program.StringFormat))
path = path.Replace("ExtraContent", "content");
Add(path, signature);
}
}
RawData = data;
}
public static async Task<FileManifest> Get(string versionGuid, bool remapExtraContent = false)
{
string fileManifestUrl = $"{Program.BaseUrlSetup}/{versionGuid}-rbxManifest.txt";
string fileManifestData;
using (HttpClient http = new())
{
var download = http.GetStringAsync(fileManifestUrl);
fileManifestData = await download.ConfigureAwait(false);
}
return new FileManifest(fileManifestData, remapExtraContent);
}
}
}

View File

@ -0,0 +1,22 @@
// 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 bool Exists { get; set; }
public bool ShouldInstall { get; set; }
internal byte[] Data { get; set; }
public override string ToString()
{
return $"[{Signature}] {Name}";
}
}
}

View File

@ -0,0 +1,85 @@
// https://github.com/MaximumADHD/Roblox-Studio-Mod-Manager/blob/main/ProjectSrc/Bootstrapper/PackageManifest.cs
namespace Bloxstrap.Helpers.RSMM
{
internal class PackageManifest : List<Package>
{
public string RawData { get; private set; }
private PackageManifest(string data)
{
using (var reader = new StringReader(data))
{
string version = reader.ReadLine();
if (version != "v0")
{
string errorMsg = $"Unexpected package manifest version: {version} (expected v0!)\n" +
"Please contact MaximumADHD if you see this error.";
throw new NotSupportedException(errorMsg);
}
bool eof = false;
var readLine = new Func<string>(() =>
{
string line = reader.ReadLine();
if (line == null)
eof = true;
return line;
});
while (!eof)
{
string fileName = readLine();
string signature = readLine();
string rawPackedSize = readLine();
string rawSize = readLine();
if (eof)
break;
if (!int.TryParse(rawPackedSize, out int packedSize))
break;
if (!int.TryParse(rawSize, out int size))
break;
if (fileName == "RobloxPlayerLauncher.exe")
break;
var package = new Package()
{
Name = fileName,
Signature = signature,
PackedSize = packedSize,
Size = size
};
Add(package);
}
}
RawData = data;
}
public static async Task<PackageManifest> Get(string versionGuid)
{
string pkgManifestUrl = $"{Program.BaseUrlSetup}/{versionGuid}-rbxPkgManifest.txt";
string pkgManifestData;
using (HttpClient http = new())
{
var getData = http.GetStringAsync(pkgManifestUrl);
pkgManifestData = await getData.ConfigureAwait(false);
}
return new PackageManifest(pkgManifestData);
}
}
}

View File

@ -0,0 +1,39 @@
// https://github.com/MaximumADHD/Roblox-Studio-Mod-Manager/blob/main/ProjectSrc/Utility/SystemEvent.cs
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<bool> WaitForEvent()
{
return Task.Run(WaitOne);
}
public Task<bool> WaitForEvent(TimeSpan timeout, bool exitContext = false)
{
return Task.Run(() => WaitOne(timeout, exitContext));
}
public Task<bool> WaitForEvent(int millisecondsTimeout, bool exitContext = false)
{
return Task.Run(() => WaitOne(millisecondsTimeout, exitContext));
}
}
}

115
Bloxstrap/Program.cs Normal file
View File

@ -0,0 +1,115 @@
using Microsoft.Win32;
using System.Diagnostics;
using Bloxstrap.Helpers;
namespace Bloxstrap
{
internal static class Program
{
public const StringComparison StringFormat = StringComparison.InvariantCulture;
// ideally for the application website, i would prefer something other than my own hosted website?
// i don't really have many other options though - github doesn't make much sense for something like this
public const string ProjectName = "Bloxstrap";
public const string ProjectRepository = "pizzaboxer/bloxstrap";
public const string BaseUrlApplication = "https://bloxstrap.pizzaboxer.xyz";
public const string BaseUrlSetup = "https://s3.amazonaws.com/setup.roblox.com";
public static string? BaseDirectory;
public static string LocalAppData { get; private set; }
public static string FilePath { get; private set; }
public static bool IsFirstRun { get; private set; } = false;
public static SettingsFormat Settings;
public static SettingsManager SettingsManager = new();
public static void Exit()
{
SettingsManager.Save();
Environment.Exit(0);
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
// ensure only one is running
Process[] processes = Process.GetProcessesByName(ProjectName);
if (processes.Length > 1)
return;
// Task.Run(() => Updater.CheckForUpdates()).Wait();
// return;
LocalAppData = Environment.GetEnvironmentVariable("localappdata");
RegistryKey? registryKey = Registry.CurrentUser.OpenSubKey($@"Software\{ProjectName}");
if (registryKey is null)
{
IsFirstRun = true;
Settings = SettingsManager.Settings;
Application.Run(new Dialogs.Preferences());
}
else
{
BaseDirectory = (string?)registryKey.GetValue("InstallLocation");
registryKey.Close();
}
// selection dialog was closed
// (this doesnt account for the registry value not existing but thats basically never gonna happen)
if (BaseDirectory is null)
return;
SettingsManager.SaveLocation = Path.Combine(BaseDirectory, "Settings.json");
FilePath = Path.Combine(BaseDirectory, $"{ProjectName}.exe");
// 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;
}
string commandLine = "";
if (args.Length > 0)
{
if (args[0] == "-preferences")
{
Application.Run(new Dialogs.Preferences());
}
else if (args[0].StartsWith("roblox-player:"))
{
commandLine = Protocol.Parse(args[0]);
}
else if (args[0].StartsWith("roblox:"))
{
commandLine = $"--app --deeplink {args[0]}";
}
else
{
commandLine = String.Join(" ", args);
}
}
else
{
commandLine = "--app";
}
if (!String.IsNullOrEmpty(commandLine))
new Bootstrapper(Settings.BootstrapperStyle, commandLine);
SettingsManager.Save();
}
}
}

223
Bloxstrap/Properties/Resources.Designer.cs generated Normal file
View File

@ -0,0 +1,223 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Bloxstrap.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap CancelButton {
get {
object obj = ResourceManager.GetObject("CancelButton", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap CancelButtonHover {
get {
object obj = ResourceManager.GetObject("CancelButtonHover", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon Icon2009_ico {
get {
object obj = ResourceManager.GetObject("Icon2009_ico", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Icon2009_png {
get {
object obj = ResourceManager.GetObject("Icon2009_png", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon Icon2011_ico {
get {
object obj = ResourceManager.GetObject("Icon2011_ico", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Icon2011_png {
get {
object obj = ResourceManager.GetObject("Icon2011_png", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon Icon2017_ico {
get {
object obj = ResourceManager.GetObject("Icon2017_ico", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Icon2017_png {
get {
object obj = ResourceManager.GetObject("Icon2017_png", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon Icon2019_ico {
get {
object obj = ResourceManager.GetObject("Icon2019_ico", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Icon2019_png {
get {
object obj = ResourceManager.GetObject("Icon2019_png", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon IconBloxstrap_ico {
get {
object obj = ResourceManager.GetObject("IconBloxstrap_ico", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap IconBloxstrap_png {
get {
object obj = ResourceManager.GetObject("IconBloxstrap_png", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon IconEarly2015_ico {
get {
object obj = ResourceManager.GetObject("IconEarly2015_ico", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap IconEarly2015_png {
get {
object obj = ResourceManager.GetObject("IconEarly2015_png", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon IconLate2015_ico {
get {
object obj = ResourceManager.GetObject("IconLate2015_ico", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap IconLate2015_png {
get {
object obj = ResourceManager.GetObject("IconLate2015_png", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

View File

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="CancelButton" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\CancelButton.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="CancelButtonHover" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\CancelButtonHover.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Icon2009_ico" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icon2009-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Icon2009_png" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icon2009-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Icon2011_ico" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icon2011-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Icon2011_png" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icon2011-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Icon2017_ico" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icon2017-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Icon2017_png" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icon2017-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Icon2019_ico" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icon2019-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Icon2019_png" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Icon2019-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="IconBloxstrap_ico" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\IconBloxstrap-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="IconBloxstrap_png" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\IconBloxstrap-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="IconEarly2015_ico" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\IconEarly2015-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="IconEarly2015_png" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\IconEarly2015-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="IconLate2015_ico" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\IconLate2015-ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="IconLate2015_png" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\IconLate2015-png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

View File

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
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;
}
}
}
}

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
</SettingsFile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

68
Bloxstrap/Settings.cs Normal file
View File

@ -0,0 +1,68 @@
using System.Diagnostics;
using System.Text.Json;
using Bloxstrap.Enums;
namespace Bloxstrap
{
public class SettingsFormat
{
public string VersionGuid { get; set; }
public bool UseOldDeathSound { get; set; } = true;
public BootstrapperStyle BootstrapperStyle { get; set; } = BootstrapperStyle.ProgressDialog;
public BootstrapperIcon BootstrapperIcon { get; set; } = BootstrapperIcon.IconBloxstrap;
}
public class SettingsManager
{
public SettingsFormat Settings = new();
public bool ShouldSave = 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
{
Settings = JsonSerializer.Deserialize<SettingsFormat>(settingsJson);
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to fetch settings! Reverting to defaults... ({ex.Message})");
// Settings = new();
}
}
}
public void Save()
{
Debug.WriteLine("Attempting to save...");
string SettingsJson = JsonSerializer.Serialize(Settings, new JsonSerializerOptions { WriteIndented = true });
Debug.WriteLine(SettingsJson);
if (!ShouldSave)
{
Debug.WriteLine("ShouldSave set to false, not saving...");
return;
}
// save settings
File.WriteAllText(SaveLocation, SettingsJson);
}
}
}

29
README.md Normal file
View File

@ -0,0 +1,29 @@
# Bloxstrap
An open, customizable, feature-packed alternative bootstrapper for Roblox.
## Usage
This is intended to be a seamless replacement for the stock Roblox bootstrapper, working more or less how you'd expect it to.
When you first run it, you'll see the preferences menu. If you want to configure preferences again after you've already installed it, open up Apps and Features, look for Bloxstrap and select Modify.
Bloxstrap can be uninstalled, and reverts to the stock boostrapper when uninstalled, so it's relatively harmless.
Please keep in mind that **Bloxstrap is in very early development**, and you'll no doubt encounter some bugs. If you do, please submit an issue!
## Features
Here's some of the features that Bloxstrap provides over the stock Roblox bootstrapper:
* Bootstrapper style can be customized (including being able to emulate older version looks)
* Install location can be selected (useful if you usually install all your games on a separate drive)
* Support for the old death sound (no messing around with your install folder, and it persists on updates!)
Some more features that we hope to add include more custom styles, and Discord Rich Presence support.
## Installing
Bloxstrap requires the [x86 .NET 6 Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-6.0.7-windows-x86-installer). If you don't already have it installed, you'll be prompted to install it when running Bloxstrap.
As of right now, Bloxstrap is only supported for Windows. Mac support is possible, but I don't currently have a Mac to test this on.
## Contributions
* [Roblox Studio Mod Manager](/MaximumADHD/Roblox-Studio-Mod-Manager) by [MaximumADHD](https://www.roblox.com/users/2032622/profile) - some utility code was borrowed to help with Bloxstrap's bootstrapper functionality. They're all under Bloxstrap.Helpers.RSMM, with some having slight modifications. Besides, it's a great project.
* [skulyire](https://www.roblox.com/users/2485612194/profile) - Making the Bloxstrap logo