Updates and bugfixes for v1.3.0

- Features
    - Added integration with rbxfpsunlocker
    - Added support for user-applicable mods
    - Added ability to disable auto-update checking

 - Misc
    - Removed Bloxstrap branding from Discord Rich Presence
    - Mod presets for old death sound and mouse cursor are now statically stored as base64 strings, eliminating reliance on the website and the old cursors still existing

 - Bugfixes
    - Fixed vista bootstrapper style not hiding properly and improper behavior when closed
    - Fixed forms not being brought to the front when shown

 - Code
    - Reconsolidated Bootstrapper to a single file, using regions instead of partials
This commit is contained in:
pizzaboxer 2022-08-21 22:41:16 +01:00
parent 85de225226
commit 92b159d682
32 changed files with 1232 additions and 919 deletions

View File

@ -4,13 +4,14 @@
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<PlatformTarget>AnyCPU</PlatformTarget>
<Platforms>AnyCPU;x86</Platforms>
<ApplicationIcon>Bloxstrap.ico</ApplicationIcon>
<Version>1.2.0</Version>
<FileVersion>1.2.0.0</FileVersion>
<Version>1.3.0</Version>
<FileVersion>1.3.0.0</FileVersion>
<UseWPF>True</UseWPF>
</PropertyGroup>
<ItemGroup>
@ -27,12 +28,8 @@
</ItemGroup>
<ItemGroup>
<Compile Update="Dialogs\BootstrapperStyles\LegacyDialog2009.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Dialogs\BootstrapperStyles\ProgressDialogDark.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Dialogs\BootstrapperStyles\LegacyDialog2009.cs" />
<Compile Update="Dialogs\BootstrapperStyles\ProgressDialogDark.cs" />
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>

795
Bloxstrap/Bootstrapper.cs Normal file
View File

@ -0,0 +1,795 @@
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using Microsoft.Win32;
using Bloxstrap.Enums;
using Bloxstrap.Dialogs.BootstrapperStyles;
using Bloxstrap.Helpers;
using Bloxstrap.Helpers.Integrations;
using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap
{
public partial class Bootstrapper
{
#region Properties
private string? LaunchCommandLine;
private string VersionGuid;
private PackageManifest VersionPackageManifest;
private FileManifest VersionFileManifest;
private string VersionFolder;
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";
private static readonly string ModReadme =
"This is where you can modify your Roblox files while preserving modifications\n" +
"whenever Roblox updates.\n" +
"\n" +
"For example, Modifications\\content\\sounds\\ouch.ogg will\n" +
"overwrite Versions\\version-xxxxxxxxxxxxxxxx\\content\\sounds\\ouch.ogg\n" +
"\n" +
"If you remove a file mod from here, Bloxstrap will restore the stock version\n" +
"of the file the next time it's launched.\n" +
"\n" +
"Any files added here to the root modification directory are ignored.\n" +
"\n" +
"By default, two mod presets are provided for restoring the old death\n" +
"sound and the old mouse cursor.\n";
// TODO: reduce reliance on event handlers for signalling property changes to the bootstrapper dialog
// i mean, chances are we can just use IBootstrapperDialog now?
// public IBootstrapperDialog BootstrapperDialog;
public event EventHandler CloseDialogEvent;
public event EventHandler PromptShutdownEvent;
public event ChangeEventHandler<string> ShowSuccessEvent;
public event ChangeEventHandler<string> MessageChanged;
public event ChangeEventHandler<int> ProgressBarValueChanged;
public event ChangeEventHandler<ProgressBarStyle> ProgressBarStyleChanged;
public event ChangeEventHandler<bool> CancelEnabledChanged;
private string _message;
private int _progress = 0;
private ProgressBarStyle _progressStyle = ProgressBarStyle.Marquee;
private bool _cancelEnabled = false;
public string Message
{
get => _message;
private set
{
if (_message == value)
return;
MessageChanged.Invoke(this, new ChangeEventArgs<string>(value));
_message = value;
}
}
public int Progress
{
get => _progress;
private set
{
if (_progress == value)
return;
ProgressBarValueChanged.Invoke(this, new ChangeEventArgs<int>(value));
_progress = value;
}
}
public ProgressBarStyle ProgressStyle
{
get => _progressStyle;
private set
{
if (_progressStyle == value)
return;
ProgressBarStyleChanged.Invoke(this, new ChangeEventArgs<ProgressBarStyle>(value));
_progressStyle = value;
}
}
public bool CancelEnabled
{
get => _cancelEnabled;
private set
{
if (_cancelEnabled == value)
return;
CancelEnabledChanged.Invoke(this, new ChangeEventArgs<bool>(value));
_cancelEnabled = value;
}
}
#endregion
#region Core
public Bootstrapper()
{
FreshInstall = String.IsNullOrEmpty(Program.Settings.VersionGuid);
Client.Timeout = TimeSpan.FromMinutes(10);
}
public void Initialize(BootstrapperStyle bootstrapperStyle, string? launchCommandLine = null)
{
LaunchCommandLine = launchCommandLine;
switch (bootstrapperStyle)
{
case BootstrapperStyle.VistaDialog:
Application.Run(new VistaDialog(this));
break;
case BootstrapperStyle.LegacyDialog2009:
Application.Run(new LegacyDialog2009(this));
break;
case BootstrapperStyle.LegacyDialog2011:
Application.Run(new LegacyDialog2011(this));
break;
case BootstrapperStyle.ProgressDialog:
Application.Run(new ProgressDialog(this));
break;
case BootstrapperStyle.ProgressDialogDark:
Application.Run(new ProgressDialogDark(this));
break;
}
}
public async Task Run()
{
/* Message = "hi";
Progress = 42;
ProgressStyle = ProgressBarStyle.Blocks;
CancelEnabled = true;
BootstrapperDialog.Message = "hi";
BootstrapperDialog.ProgressValue = 42;
BootstrapperDialog.ProgressStyle = ProgressBarStyle.Blocks;
BootstrapperDialog.CancelEnabled = true;
return; */
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();
}
ApplyModifications();
if (Program.IsFirstRun)
Program.SettingsManager.ShouldSave = true;
if (Program.IsFirstRun || FreshInstall)
Register();
CheckInstall();
await RbxFpsUnlocker.CheckInstall();
await StartRoblox();
Program.Exit();
}
private async Task CheckLatestVersion()
{
Message = "Connecting to Roblox...";
VersionGuid = await Client.GetStringAsync($"{Program.BaseUrlSetup}/version");
VersionFolder = Path.Combine(Directories.Versions, VersionGuid);
VersionPackageManifest = await PackageManifest.Get(VersionGuid);
VersionFileManifest = await FileManifest.Get(VersionGuid);
}
private void CheckIfRunning()
{
Process[] processes = Process.GetProcessesByName("RobloxPlayerBeta");
if (processes.Length > 0)
PromptShutdown();
try
{
// try/catch just in case process was closed before prompt was answered
foreach (Process process in processes)
{
process.CloseMainWindow();
process.Close();
}
}
catch (Exception) { }
}
private async Task StartRoblox()
{
string startEventName = Program.ProjectName.Replace(" ", "") + "StartEvent";
Message = "Starting Roblox...";
// launch time isn't really required for all launches, but it's usually just safest to do this
LaunchCommandLine += " --launchtime=" + DateTimeOffset.Now.ToUnixTimeSeconds() + " -startEvent " + startEventName;
using (SystemEvent startEvent = new(startEventName))
{
bool shouldWait = false;
Process gameClient = Process.Start(Path.Combine(VersionFolder, "RobloxPlayerBeta.exe"), LaunchCommandLine);
Process? rbxFpsUnlocker = null;
DiscordRichPresence? richPresence = null;
bool startEventFired = await startEvent.WaitForEvent();
startEvent.Close();
if (!startEventFired)
return;
if (Program.Settings.RFUEnabled && Process.GetProcessesByName("rbxfpsunlocker").Length == 0)
{
ProcessStartInfo startInfo = new();
startInfo.FileName = Path.Combine(Directories.Integrations, @"rbxfpsunlocker\rbxfpsunlocker.exe");
startInfo.WorkingDirectory = Path.Combine(Directories.Integrations, "rbxfpsunlocker");
rbxFpsUnlocker = Process.Start(startInfo);
if (Program.Settings.RFUAutoclose)
shouldWait = true;
}
// event fired, wait for 6 seconds then close
await Task.Delay(6000);
// now we move onto handling rich presence
// except beta app launch since we have to rely strictly on website launch
if (Program.Settings.UseDiscordRichPresence && !LaunchCommandLine.Contains("--app"))
{
// probably not the most ideal way to do this
string? placeId = Utilities.GetKeyValue(LaunchCommandLine, "placeId=", '&');
if (placeId is not null)
{
richPresence = new DiscordRichPresence();
bool presenceSet = await richPresence.SetPresence(placeId);
if (presenceSet)
shouldWait = true;
else
richPresence.Dispose();
}
}
if (!shouldWait)
return;
// keep bloxstrap open in the background
CloseDialog();
await gameClient.WaitForExitAsync();
if (richPresence is not null)
richPresence.Dispose();
if (Program.Settings.RFUAutoclose && rbxFpsUnlocker is not null)
rbxFpsUnlocker.Kill();
}
}
public void CancelButtonClicked()
{
if (!CancelEnabled)
{
Program.Exit();
return;
}
CancelFired = true;
try
{
if (Program.IsFirstRun)
Directory.Delete(Directories.Base, true);
else if (Directory.Exists(VersionFolder))
Directory.Delete(VersionFolder, true);
}
catch (Exception) { }
Program.Exit();
}
private void ShowSuccess(string message)
{
ShowSuccessEvent.Invoke(this, new ChangeEventArgs<string>(message));
}
private void PromptShutdown()
{
PromptShutdownEvent.Invoke(this, new EventArgs());
}
private void CloseDialog()
{
CloseDialogEvent.Invoke(this, new EventArgs());
}
#endregion
#region App Install
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 != Directories.Base)
{
try
{
if (Directory.Exists(oldInstallLocation))
Directory.Delete(oldInstallLocation, true);
}
catch (Exception) { }
applicationKey.DeleteValue("OldInstallLocation");
}
applicationKey.SetValue("InstallLocation", Directories.Base);
applicationKey.Close();
// set uninstall key
RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Program.ProjectName}");
uninstallKey.SetValue("DisplayIcon", $"{Directories.App},0");
uninstallKey.SetValue("DisplayName", Program.ProjectName);
uninstallKey.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
uninstallKey.SetValue("InstallLocation", Directories.Base);
uninstallKey.SetValue("NoRepair", 1);
uninstallKey.SetValue("Publisher", Program.ProjectName);
uninstallKey.SetValue("ModifyPath", $"\"{Directories.App}\" -preferences");
uninstallKey.SetValue("UninstallString", $"\"{Directories.App}\" -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", Directories.App);
Protocol.Register("roblox-player", "Roblox", Directories.App);
// in case the user is reinstalling
if (File.Exists(Directories.App) && Program.IsFirstRun)
File.Delete(Directories.App);
// check to make sure bootstrapper is in the install folder
if (!File.Exists(Directories.App) && Environment.ProcessPath is not null)
File.Copy(Environment.ProcessPath, Directories.App);
// this SHOULD go under Register(),
// but then people who have Bloxstrap v1.0.0 installed won't have this without a reinstall
// maybe in a later version?
if (!Directory.Exists(Program.StartMenu))
{
Directory.CreateDirectory(Program.StartMenu);
ShellLink.Shortcut.CreateShortcut(Directories.App, "", Directories.App, 0)
.WriteToFile(Path.Combine(Program.StartMenu, "Play Roblox.lnk"));
ShellLink.Shortcut.CreateShortcut(Directories.App, "-preferences", Directories.App, 0)
.WriteToFile(Path.Combine(Program.StartMenu, $"Configure {Program.ProjectName}.lnk"));
}
}
private void Uninstall()
{
CheckIfRunning();
Message = $"Uninstalling {Program.ProjectName}...";
Program.SettingsManager.ShouldSave = false;
// check if stock bootstrapper is still installed
RegistryKey? bootstrapperKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-player");
if (bootstrapperKey is null)
{
Protocol.Unregister("roblox");
Protocol.Unregister("roblox-player");
}
else
{
// revert launch uri handler to stock bootstrapper
string bootstrapperLocation = (string?)bootstrapperKey.GetValue("InstallLocation") + "RobloxPlayerLauncher.exe";
Protocol.Register("roblox", "Roblox", bootstrapperLocation);
Protocol.Register("roblox-player", "Roblox", bootstrapperLocation);
}
try
{
// delete application key
Registry.CurrentUser.DeleteSubKey($@"Software\{Program.ProjectName}");
// delete start menu folder
Directory.Delete(Program.StartMenu, true);
// delete uninstall key
Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Program.ProjectName}");
// delete installation folder
// (should delete everything except bloxstrap itself)
Directory.Delete(Directories.Base, true);
}
catch (Exception) { }
ShowSuccess($"{Program.ProjectName} has been uninstalled");
}
#endregion
#region Roblox Install
private async Task InstallLatestVersion()
{
CheckIfRunning();
if (FreshInstall)
Message = "Installing Roblox...";
else
Message = "Upgrading Roblox...";
Directory.CreateDirectory(Directories.Base);
CancelEnabled = true;
// i believe the original bootstrapper bases the progress bar off zip
// extraction progress, but here i'm doing package download progress
ProgressStyle = ProgressBarStyle.Continuous;
ProgressIncrement = (int)Math.Floor((decimal)1 / VersionPackageManifest.Count * 100);
Directory.CreateDirectory(Directories.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(Directories.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(Directories.Downloads))
{
if (!VersionPackageManifest.Exists(package => filename.Contains(package.Signature)))
File.Delete(filename);
}
if (VersionGuid != Program.Settings.VersionGuid)
{
// and also to delete our old version folder
Directory.Delete(Path.Combine(Directories.Versions, Program.Settings.VersionGuid), true);
}
}
CancelEnabled = false;
Program.Settings.VersionGuid = VersionGuid;
}
private void ApplyModifications()
{
string modFolder = Path.Combine(Directories.Modifications);
string manifestFile = Path.Combine(Directories.Base, "ModManifest.txt");
List<string> manifestFiles = new();
List<string> modFolderFiles = new();
if (!Directory.Exists(modFolder))
{
Directory.CreateDirectory(modFolder);
File.WriteAllText(Path.Combine(modFolder, "README.txt"), ModReadme);
}
CheckModPreset(Program.Settings.UseOldDeathSound, @"content\sounds\ouch.ogg", Program.Base64OldDeathSound);
CheckModPreset(Program.Settings.UseOldMouseCursor, @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", Program.Base64OldArrowCursor);
CheckModPreset(Program.Settings.UseOldMouseCursor, @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", Program.Base64OldArrowFarCursor);
foreach (string file in Directory.GetFiles(modFolder, "*.*", SearchOption.AllDirectories))
{
// get relative directory path
string relativeFile = file.Substring(modFolder.Length + 1);
// ignore files placed in the root directory
if (!relativeFile.Contains(@"\"))
continue;
modFolderFiles.Add(relativeFile);
}
// the manifest is primarily here to keep track of what files have been
// deleted from the modifications folder, so that we know when to restore the
// original files from the downloaded packages
if (File.Exists(manifestFile))
manifestFiles = File.ReadAllLines(manifestFile).ToList<string>();
else
manifestFiles = modFolderFiles;
// copy and overwrite
foreach (string file in modFolderFiles)
{
string fileModFolder = Path.Combine(modFolder, file);
string fileVersionFolder = Path.Combine(VersionFolder, file);
if (File.Exists(fileVersionFolder))
{
if (Utilities.MD5File(fileModFolder) == Utilities.MD5File(fileVersionFolder))
continue;
}
File.Copy(fileModFolder, fileVersionFolder, true);
}
// now we check for files that have been deleted from the mod folder
foreach (string fileLocation in manifestFiles)
{
if (modFolderFiles.Contains(fileLocation))
continue;
KeyValuePair<string, string> packageDirectory;
try
{
packageDirectory = PackageDirectories.First(x => x.Key != "RobloxApp.zip" && fileLocation.StartsWith(x.Value));
}
catch (InvalidOperationException)
{
// package doesn't exist, likely mistakenly placed file
continue;
}
// restore original file
string fileName = fileLocation.Substring(packageDirectory.Value.Length);
ExtractFileFromPackage(packageDirectory.Key, fileName);
}
File.WriteAllLines(manifestFile, modFolderFiles);
}
private void CheckModPreset(bool condition, string location, string base64Contents)
{
string modFolderLocation = Path.Combine(Directories.Modifications, location);
Directory.CreateDirectory(Path.GetDirectoryName(modFolderLocation));
if (condition)
{
if (!File.Exists(modFolderLocation))
{
File.WriteAllBytes(modFolderLocation, Convert.FromBase64String(base64Contents));
}
}
else if (File.Exists(modFolderLocation))
{
File.Delete(modFolderLocation);
}
}
private async void DownloadPackage(Package package)
{
string packageUrl = $"{Program.BaseUrlSetup}/{VersionGuid}-{package.Name}";
string packageLocation = Path.Combine(Directories.Downloads, package.Signature);
string robloxPackageLocation = Path.Combine(Program.LocalAppData, "Roblox", "Downloads", package.Signature);
if (File.Exists(packageLocation))
{
FileInfo file = new(packageLocation);
string calculatedMD5 = Utilities.MD5File(packageLocation);
if (calculatedMD5 != package.Signature)
{
Debug.WriteLine($"{package.Name} is corrupted ({calculatedMD5} != {package.Signature})! Deleting and re-downloading...");
file.Delete();
}
else
{
Debug.WriteLine($"{package.Name} is already downloaded, skipping...");
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(Directories.Downloads, 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);
}
}
}
private void ExtractFileFromPackage(string packageName, string fileName)
{
Package? package = VersionPackageManifest.Find(x => x.Name == packageName);
if (package is null)
return;
DownloadPackage(package);
string packageLocation = Path.Combine(Directories.Downloads, package.Signature);
string packageFolder = Path.Combine(VersionFolder, PackageDirectories[package.Name]);
using (ZipArchive archive = ZipFile.OpenRead(packageLocation))
{
ZipArchiveEntry? entry = archive.Entries.Where(x => x.FullName == fileName).FirstOrDefault();
if (entry is null)
return;
string fileLocation = Path.Combine(packageFolder, entry.FullName);
if (File.Exists(fileLocation))
File.Delete(fileLocation);
entry.ExtractToFile(fileLocation);
}
}
#endregion
}
}

View File

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

View File

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

View File

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

View File

@ -1,64 +0,0 @@
using Bloxstrap.Helpers;
namespace Bloxstrap
{
partial class Bootstrapper
{
private async Task ModifyDeathSound()
{
string fileContentName = "ouch.ogg";
string fileContentLocation = "content\\sounds\\ouch.ogg";
string fileLocation = Path.Combine(VersionFolder, fileContentLocation);
string officialHash = VersionFileManifest[fileContentLocation];
string currentHash = Utilities.CalculateMD5(fileLocation);
if (Program.Settings.UseOldDeathSound && currentHash == officialHash)
{
// let's get the old one!
var response = await Client.GetAsync($"{Program.BaseUrlApplication}/mods/{fileContentLocation}");
if (File.Exists(fileLocation))
File.Delete(fileLocation);
using (var fileStream = new FileStream(fileLocation, FileMode.CreateNew))
{
await response.Content.CopyToAsync(fileStream);
}
}
else if (!Program.Settings.UseOldDeathSound && currentHash != officialHash)
{
// who's lame enough to ever do this?
// well, we need to re-extract the one that's in the content-sounds.zip package
string[] files = { fileContentName };
ExtractFilesFromPackage("content-sounds.zip", files);
}
}
private async Task ModifyMouseCursor()
{
string baseFolder = Path.Combine(VersionFolder, "content\\textures\\");
string arrowCursor = "Cursors\\KeyboardMouse\\ArrowCursor.png";
string arrowFarCursor = "Cursors\\KeyboardMouse\\ArrowFarCursor.png";
string officialHash = VersionFileManifest["content\\textures\\Cursors\\KeyboardMouse\\ArrowCursor.png"];
string currentHash = Utilities.CalculateMD5(Path.Combine(baseFolder, arrowCursor));
if (Program.Settings.UseOldMouseCursor && currentHash == officialHash)
{
// the old cursors are actually still in the content\textures\ folder, so we can just get them from there
File.Copy(Path.Combine(baseFolder, "ArrowCursor.png"), Path.Combine(baseFolder, arrowCursor), true);
File.Copy(Path.Combine(baseFolder, "ArrowFarCursor.png"), Path.Combine(baseFolder, arrowFarCursor), true);
}
else if (!Program.Settings.UseOldMouseCursor && currentHash != officialHash)
{
string[] files = { arrowCursor, arrowFarCursor };
ExtractFilesFromPackage("content-textures2.zip", files);
}
}
}
}

View File

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

View File

@ -1,11 +1,9 @@
using System.Diagnostics;
using Bloxstrap.Helpers;
using Bloxstrap.Helpers;
using Bloxstrap.Helpers.RSMM;
namespace Bloxstrap.Dialogs.BootstrapperStyles
{
public class BootstrapperStyleForm : Form, IBootstrapperStyle
public class BootstrapperStyleForm : Form, IBootstrapperDialog
{
public Bootstrapper? Bootstrapper { get; set; }

View File

@ -2,7 +2,7 @@
namespace Bloxstrap.Dialogs.BootstrapperStyles
{
interface IBootstrapperStyle
public interface IBootstrapperDialog
{
Bootstrapper? Bootstrapper { get; set; }

View File

@ -80,6 +80,7 @@
this.Name = "LegacyDialog2009";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "LegacyDialog2009";
this.Load += new System.EventHandler(this.LegacyDialog2009_Load);
this.ResumeLayout(false);
}

View File

@ -37,5 +37,10 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
SetupDialog();
}
private void LegacyDialog2009_Load(object sender, EventArgs e)
{
this.Activate();
}
}
}

View File

@ -75,7 +75,7 @@
this.buttonCancel.Visible = false;
this.buttonCancel.Click += new System.EventHandler(this.ButtonCancel_Click);
//
// LegacyDialog
// LegacyDialog2011
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
@ -93,6 +93,7 @@
this.Name = "LegacyDialog2011";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "LegacyDialog2011";
this.Load += new System.EventHandler(this.LegacyDialog2011_Load);
((System.ComponentModel.ISupportInitialize)(this.IconBox)).EndInit();
this.ResumeLayout(false);

View File

@ -41,5 +41,10 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
SetupDialog();
}
private void LegacyDialog2011_Load(object sender, EventArgs e)
{
this.Activate();
}
}
}

View File

@ -108,6 +108,7 @@
this.Name = "ProgressDialog";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "ProgressDialog";
this.Load += new System.EventHandler(this.ProgressDialog_Load);
((System.ComponentModel.ISupportInitialize)(this.IconBox)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.buttonCancel)).EndInit();
this.panel1.ResumeLayout(false);

View File

@ -50,5 +50,10 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
{
this.buttonCancel.Image = Properties.Resources.CancelButton;
}
private void ProgressDialog_Load(object sender, EventArgs e)
{
this.Activate();
}
}
}

View File

@ -109,6 +109,7 @@
this.Name = "ProgressDialogDark";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "ProgressDialog";
this.Load += new System.EventHandler(this.ProgressDialogDark_Load);
((System.ComponentModel.ISupportInitialize)(this.IconBox)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.buttonCancel)).EndInit();
this.panel1.ResumeLayout(false);

View File

@ -50,5 +50,10 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
{
this.buttonCancel.Image = Properties.Resources.DarkCancelButton;
}
private void ProgressDialogDark_Load(object sender, EventArgs e)
{
this.Activate();
}
}
}

View File

@ -30,15 +30,17 @@
{
this.SuspendLayout();
//
// TestDialog
// VistaDialog
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(0, 0);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Name = "VistaDialog";
this.Opacity = 0D;
this.ShowInTaskbar = false;
this.Text = "VistaDialog";
this.WindowState = System.Windows.Forms.FormWindowState.Minimized;
this.Load += new System.EventHandler(this.TestDialog_Load);
this.ResumeLayout(false);

View File

@ -163,7 +163,6 @@ namespace Bloxstrap.Dialogs.BootstrapperStyles
private void TestDialog_Load(object sender, EventArgs e)
{
this.Hide();
TaskDialog.ShowDialog(Dialog);
}
}

View File

@ -32,6 +32,10 @@
this.label1 = new System.Windows.Forms.Label();
this.Tabs = new System.Windows.Forms.TabControl();
this.DialogTab = new System.Windows.Forms.TabPage();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.RFUWebsite = new System.Windows.Forms.LinkLabel();
this.ToggleRFUAutoclose = new System.Windows.Forms.CheckBox();
this.ToggleRFUEnabled = new System.Windows.Forms.CheckBox();
this.groupBox5 = new System.Windows.Forms.GroupBox();
this.ToggleRPCButtons = new System.Windows.Forms.CheckBox();
this.ToggleDiscordRichPresence = new System.Windows.Forms.CheckBox();
@ -42,6 +46,8 @@
this.StyleSelection = new System.Windows.Forms.ListBox();
this.InstallationTab = new System.Windows.Forms.TabPage();
this.groupBox4 = new System.Windows.Forms.GroupBox();
this.LabelModFolderInstall = new System.Windows.Forms.Label();
this.ButtonOpenModFolder = new System.Windows.Forms.Button();
this.ToggleMouseCursor = new System.Windows.Forms.CheckBox();
this.ToggleDeathSound = new System.Windows.Forms.CheckBox();
this.GroupBoxInstallLocation = new System.Windows.Forms.GroupBox();
@ -49,11 +55,13 @@
this.InstallLocation = new System.Windows.Forms.TextBox();
this.SaveButton = new System.Windows.Forms.Button();
this.panel1 = new System.Windows.Forms.Panel();
this.ToggleCheckForUpdates = new System.Windows.Forms.CheckBox();
this.PreviewButton = new System.Windows.Forms.Button();
this.InstallLocationBrowseDialog = new System.Windows.Forms.FolderBrowserDialog();
this.InfoTooltip = new System.Windows.Forms.ToolTip(this.components);
this.Tabs.SuspendLayout();
this.DialogTab.SuspendLayout();
this.groupBox1.SuspendLayout();
this.groupBox5.SuspendLayout();
this.groupBox3.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.IconPreview)).BeginInit();
@ -86,6 +94,7 @@
//
// DialogTab
//
this.DialogTab.Controls.Add(this.groupBox1);
this.DialogTab.Controls.Add(this.groupBox5);
this.DialogTab.Controls.Add(this.groupBox3);
this.DialogTab.Controls.Add(this.groupBox2);
@ -97,13 +106,70 @@
this.DialogTab.Text = "Bootstrapper";
this.DialogTab.UseVisualStyleBackColor = true;
//
// groupBox1
//
this.groupBox1.Controls.Add(this.RFUWebsite);
this.groupBox1.Controls.Add(this.ToggleRFUAutoclose);
this.groupBox1.Controls.Add(this.ToggleRFUEnabled);
this.groupBox1.Location = new System.Drawing.Point(192, 146);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(235, 67);
this.groupBox1.TabIndex = 8;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "FPS Unlocker";
//
// RFUWebsite
//
this.RFUWebsite.BackColor = System.Drawing.Color.White;
this.RFUWebsite.Cursor = System.Windows.Forms.Cursors.Hand;
this.RFUWebsite.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline;
this.RFUWebsite.Location = new System.Drawing.Point(174, 0);
this.RFUWebsite.Margin = new System.Windows.Forms.Padding(0);
this.RFUWebsite.Name = "RFUWebsite";
this.RFUWebsite.Size = new System.Drawing.Size(55, 18);
this.RFUWebsite.TabIndex = 2;
this.RFUWebsite.TabStop = true;
this.RFUWebsite.Tag = "";
this.RFUWebsite.Text = "(website)";
this.RFUWebsite.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.RFUWebsite_LinkClicked);
//
// ToggleRFUAutoclose
//
this.ToggleRFUAutoclose.AutoSize = true;
this.ToggleRFUAutoclose.Checked = true;
this.ToggleRFUAutoclose.CheckState = System.Windows.Forms.CheckState.Checked;
this.ToggleRFUAutoclose.Location = new System.Drawing.Point(9, 40);
this.ToggleRFUAutoclose.Name = "ToggleRFUAutoclose";
this.ToggleRFUAutoclose.Size = new System.Drawing.Size(209, 19);
this.ToggleRFUAutoclose.TabIndex = 1;
this.ToggleRFUAutoclose.Text = "Automatically close on Roblox exit";
this.InfoTooltip.SetToolTip(this.ToggleRFUAutoclose, "If enabled, rbxfpsunlocker will automatically close when Roblox is closed.");
this.ToggleRFUAutoclose.UseVisualStyleBackColor = true;
this.ToggleRFUAutoclose.CheckedChanged += new System.EventHandler(this.ToggleRFUAutoclose_CheckedChanged);
//
// ToggleRFUEnabled
//
this.ToggleRFUEnabled.AutoSize = true;
this.ToggleRFUEnabled.Checked = true;
this.ToggleRFUEnabled.CheckState = System.Windows.Forms.CheckState.Checked;
this.ToggleRFUEnabled.Location = new System.Drawing.Point(9, 19);
this.ToggleRFUEnabled.Name = "ToggleRFUEnabled";
this.ToggleRFUEnabled.Size = new System.Drawing.Size(127, 19);
this.ToggleRFUEnabled.TabIndex = 0;
this.ToggleRFUEnabled.Text = "Use rbxfpsunlocker";
this.InfoTooltip.SetToolTip(this.ToggleRFUEnabled, "If enabled, rbxfpsunlocker is downloaded.\r\nWhen Roblox is started, rbxfpsunlocker" +
" will automatically start too, \r\nbeing minimized to your system tray by default." +
"");
this.ToggleRFUEnabled.UseVisualStyleBackColor = true;
this.ToggleRFUEnabled.CheckedChanged += new System.EventHandler(this.ToggleRFUEnabled_CheckedChanged);
//
// groupBox5
//
this.groupBox5.Controls.Add(this.ToggleRPCButtons);
this.groupBox5.Controls.Add(this.ToggleDiscordRichPresence);
this.groupBox5.Location = new System.Drawing.Point(5, 146);
this.groupBox5.Name = "groupBox5";
this.groupBox5.Size = new System.Drawing.Size(422, 67);
this.groupBox5.Size = new System.Drawing.Size(179, 67);
this.groupBox5.TabIndex = 7;
this.groupBox5.TabStop = false;
this.groupBox5.Text = "Discord Rich Presence";
@ -113,9 +179,11 @@
this.ToggleRPCButtons.AutoSize = true;
this.ToggleRPCButtons.Location = new System.Drawing.Point(9, 40);
this.ToggleRPCButtons.Name = "ToggleRPCButtons";
this.ToggleRPCButtons.Size = new System.Drawing.Size(196, 19);
this.ToggleRPCButtons.Size = new System.Drawing.Size(155, 19);
this.ToggleRPCButtons.TabIndex = 1;
this.ToggleRPCButtons.Text = "Hide activity interaction buttons";
this.ToggleRPCButtons.Text = "Hide interaction buttons";
this.InfoTooltip.SetToolTip(this.ToggleRPCButtons, "Choose whether the buttons to play/view game details should be hidden from your a" +
"ctivity status.");
this.ToggleRPCButtons.UseVisualStyleBackColor = true;
this.ToggleRPCButtons.CheckedChanged += new System.EventHandler(this.ToggleRPCButtons_CheckedChanged);
//
@ -129,6 +197,9 @@
this.ToggleDiscordRichPresence.Size = new System.Drawing.Size(129, 19);
this.ToggleDiscordRichPresence.TabIndex = 0;
this.ToggleDiscordRichPresence.Text = "Show game activity";
this.InfoTooltip.SetToolTip(this.ToggleDiscordRichPresence, "Choose whether to show what game you\'re playing on your Discord activity status.\r" +
"\nThis will only work when you launch a game from the website, and is not support" +
"ed in the Beta App.");
this.ToggleDiscordRichPresence.UseVisualStyleBackColor = true;
this.ToggleDiscordRichPresence.CheckedChanged += new System.EventHandler(this.ToggleDiscordRichPresence_CheckedChanged);
//
@ -161,6 +232,7 @@
this.IconSelection.Name = "IconSelection";
this.IconSelection.Size = new System.Drawing.Size(100, 109);
this.IconSelection.TabIndex = 4;
this.InfoTooltip.SetToolTip(this.IconSelection, "Choose what icon the bootstrapper should use.");
this.IconSelection.SelectedIndexChanged += new System.EventHandler(this.IconSelection_SelectedIndexChanged);
//
// groupBox2
@ -181,6 +253,8 @@
this.StyleSelection.Name = "StyleSelection";
this.StyleSelection.Size = new System.Drawing.Size(161, 109);
this.StyleSelection.TabIndex = 3;
this.InfoTooltip.SetToolTip(this.StyleSelection, "Choose how the bootstrapper dialog should look.\r\nYou can use the \'Preview\' button" +
" to preview the bootstrapper look.");
this.StyleSelection.SelectedIndexChanged += new System.EventHandler(this.StyleSelection_SelectedIndexChanged);
//
// InstallationTab
@ -197,15 +271,40 @@
//
// groupBox4
//
this.groupBox4.Controls.Add(this.LabelModFolderInstall);
this.groupBox4.Controls.Add(this.ButtonOpenModFolder);
this.groupBox4.Controls.Add(this.ToggleMouseCursor);
this.groupBox4.Controls.Add(this.ToggleDeathSound);
this.groupBox4.Location = new System.Drawing.Point(5, 60);
this.groupBox4.Name = "groupBox4";
this.groupBox4.Size = new System.Drawing.Size(422, 65);
this.groupBox4.Size = new System.Drawing.Size(422, 95);
this.groupBox4.TabIndex = 2;
this.groupBox4.TabStop = false;
this.groupBox4.Text = "Modifications";
//
// LabelModFolderInstall
//
this.LabelModFolderInstall.AutoSize = true;
this.LabelModFolderInstall.Location = new System.Drawing.Point(6, 67);
this.LabelModFolderInstall.Margin = new System.Windows.Forms.Padding(0);
this.LabelModFolderInstall.Name = "LabelModFolderInstall";
this.LabelModFolderInstall.Size = new System.Drawing.Size(329, 15);
this.LabelModFolderInstall.TabIndex = 7;
this.LabelModFolderInstall.Text = "Other modifications can be added once Bloxstrap is installed.";
this.LabelModFolderInstall.Visible = false;
//
// ButtonOpenModFolder
//
this.ButtonOpenModFolder.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.ButtonOpenModFolder.Location = new System.Drawing.Point(8, 62);
this.ButtonOpenModFolder.Name = "ButtonOpenModFolder";
this.ButtonOpenModFolder.Size = new System.Drawing.Size(122, 25);
this.ButtonOpenModFolder.TabIndex = 6;
this.ButtonOpenModFolder.Text = "Open Mod Folder";
this.InfoTooltip.SetToolTip(this.ButtonOpenModFolder, "Open the folder for applying Roblox client modifications.");
this.ButtonOpenModFolder.UseVisualStyleBackColor = true;
this.ButtonOpenModFolder.Click += new System.EventHandler(this.ButtonOpenModFolder_Click);
//
// ToggleMouseCursor
//
this.ToggleMouseCursor.AutoSize = true;
@ -246,9 +345,9 @@
// 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.Location = new System.Drawing.Point(335, 20);
this.InstallLocationBrowseButton.Name = "InstallLocationBrowseButton";
this.InstallLocationBrowseButton.Size = new System.Drawing.Size(86, 25);
this.InstallLocationBrowseButton.Size = new System.Drawing.Size(79, 25);
this.InstallLocationBrowseButton.TabIndex = 5;
this.InstallLocationBrowseButton.Text = "Browse...";
this.InstallLocationBrowseButton.UseVisualStyleBackColor = true;
@ -260,8 +359,10 @@
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.Size = new System.Drawing.Size(319, 23);
this.InstallLocation.TabIndex = 4;
this.InfoTooltip.SetToolTip(this.InstallLocation, "Choose where Bloxstrap should install to.\r\nThis is useful if you typically instal" +
"l all your games to a separate storage drive.");
//
// SaveButton
//
@ -279,6 +380,7 @@
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.Controls.Add(this.ToggleCheckForUpdates);
this.panel1.Controls.Add(this.PreviewButton);
this.panel1.Controls.Add(this.SaveButton);
this.panel1.Location = new System.Drawing.Point(-1, 298);
@ -286,6 +388,19 @@
this.panel1.Size = new System.Drawing.Size(466, 42);
this.panel1.TabIndex = 6;
//
// ToggleCheckForUpdates
//
this.ToggleCheckForUpdates.AutoSize = true;
this.ToggleCheckForUpdates.Checked = true;
this.ToggleCheckForUpdates.CheckState = System.Windows.Forms.CheckState.Checked;
this.ToggleCheckForUpdates.Location = new System.Drawing.Point(14, 12);
this.ToggleCheckForUpdates.Name = "ToggleCheckForUpdates";
this.ToggleCheckForUpdates.Size = new System.Drawing.Size(179, 19);
this.ToggleCheckForUpdates.TabIndex = 7;
this.ToggleCheckForUpdates.Text = "Check for updates on startup";
this.ToggleCheckForUpdates.UseVisualStyleBackColor = true;
this.ToggleCheckForUpdates.CheckedChanged += new System.EventHandler(this.ToggleCheckForUpdates_CheckedChanged);
//
// PreviewButton
//
this.PreviewButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
@ -318,8 +433,11 @@
this.Name = "Preferences";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Preferences";
this.Load += new System.EventHandler(this.Preferences_Load);
this.Tabs.ResumeLayout(false);
this.DialogTab.ResumeLayout(false);
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.groupBox5.ResumeLayout(false);
this.groupBox5.PerformLayout();
this.groupBox3.ResumeLayout(false);
@ -331,6 +449,7 @@
this.GroupBoxInstallLocation.ResumeLayout(false);
this.GroupBoxInstallLocation.PerformLayout();
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.ResumeLayout(false);
}
@ -360,5 +479,12 @@
private ToolTip InfoTooltip;
private CheckBox ToggleMouseCursor;
private CheckBox ToggleRPCButtons;
private GroupBox groupBox1;
private LinkLabel RFUWebsite;
private CheckBox ToggleRFUAutoclose;
private CheckBox ToggleRFUEnabled;
private CheckBox ToggleCheckForUpdates;
private Button ButtonOpenModFolder;
private Label LabelModFolderInstall;
}
}

View File

@ -1,8 +1,12 @@
using Microsoft.Win32;
using System.IO;
using System.Diagnostics;
using Microsoft.Win32;
using Bloxstrap.Helpers;
using Bloxstrap.Enums;
using Bloxstrap.Dialogs.BootstrapperStyles;
using Bloxstrap.Enums;
using Bloxstrap.Helpers;
using Bloxstrap.Helpers.Integrations;
namespace Bloxstrap.Dialogs
{
@ -30,10 +34,6 @@ namespace Bloxstrap.Dialogs
private BootstrapperStyle? _selectedStyle;
private BootstrapperIcon? _selectedIcon;
private bool _useDiscordRichPresence = true;
private bool _hideRPCButtons = false;
private bool _useOldDeathSound = true;
private bool _useOldMouseCursor = false;
private BootstrapperStyle SelectedStyle
{
@ -68,67 +68,6 @@ namespace Bloxstrap.Dialogs
}
}
private bool UseDiscordRichPresence
{
get => _useDiscordRichPresence;
set
{
if (_useDiscordRichPresence == value)
return;
_useDiscordRichPresence = value;
this.ToggleDiscordRichPresence.Checked = value;
this.ToggleRPCButtons.Enabled = value;
}
}
private bool HideRPCButtons
{
get => _hideRPCButtons;
set
{
if (_hideRPCButtons == value)
return;
_hideRPCButtons = value;
this.ToggleRPCButtons.Checked = value;
}
}
private bool UseOldDeathSound
{
get => _useOldDeathSound;
set
{
if (_useOldDeathSound == value)
return;
_useOldDeathSound = value;
this.ToggleDeathSound.Checked = value;
}
}
private bool UseOldMouseCursor
{
get => _useOldMouseCursor;
set
{
if (_useOldMouseCursor == value)
return;
_useOldMouseCursor = value;
this.ToggleMouseCursor.Checked = value;
}
}
public Preferences()
{
InitializeComponent();
@ -142,6 +81,8 @@ namespace Bloxstrap.Dialogs
{
this.SaveButton.Text = "Install";
this.InstallLocation.Text = Path.Combine(Program.LocalAppData, Program.ProjectName);
this.ButtonOpenModFolder.Visible = false;
this.LabelModFolderInstall.Visible = true;
}
else
{
@ -158,18 +99,22 @@ namespace Bloxstrap.Dialogs
this.IconSelection.Items.Add(icon.Key);
}
this.InfoTooltip.SetToolTip(this.StyleSelection, "Choose how the bootstrapper dialog should look.\nYou can use the 'Preview' button to preview the bootstrapper look.");
this.InfoTooltip.SetToolTip(this.IconSelection, "Choose what icon the bootstrapper should use.");
this.InfoTooltip.SetToolTip(this.GroupBoxInstallLocation, "Choose where Bloxstrap should install to.\nThis is useful if you typically install all your games to a separate storage drive.");
this.InfoTooltip.SetToolTip(this.ToggleDiscordRichPresence, "Choose whether to show what game you're playing on your Discord activity status.\nThis will only work when you launch a game from the website, and is not supported in the Beta App.");
this.InfoTooltip.SetToolTip(this.ToggleRPCButtons, "Choose whether the buttons to play/view game info should be hidden from activity status.");
if (!Environment.Is64BitOperatingSystem)
this.ToggleRFUEnabled.Enabled = false;
SelectedStyle = Program.Settings.BootstrapperStyle;
SelectedIcon = Program.Settings.BootstrapperIcon;
UseDiscordRichPresence = Program.Settings.UseDiscordRichPresence;
HideRPCButtons = Program.Settings.HideRPCButtons;
UseOldDeathSound = Program.Settings.UseOldDeathSound;
UseOldMouseCursor = Program.Settings.UseOldMouseCursor;
this.ToggleCheckForUpdates.Checked = Program.Settings.CheckForUpdates;
this.ToggleDiscordRichPresence.Checked = Program.Settings.UseDiscordRichPresence;
this.ToggleRPCButtons.Checked = Program.Settings.HideRPCButtons;
this.ToggleRFUEnabled.Checked = Program.Settings.RFUEnabled;
this.ToggleRFUAutoclose.Checked = Program.Settings.RFUAutoclose;
this.ToggleDeathSound.Checked = Program.Settings.UseOldDeathSound;
this.ToggleMouseCursor.Checked = Program.Settings.UseOldMouseCursor;
}
private void InstallLocationBrowseButton_Click(object sender, EventArgs e)
@ -239,7 +184,7 @@ namespace Bloxstrap.Dialogs
{
Program.SettingsManager.ShouldSave = true;
if (Program.BaseDirectory != installLocation)
if (Program.BaseDirectory is not null && Program.BaseDirectory != installLocation)
{
Program.ShowMessageBox(MessageBoxIcon.Information, $"{Program.ProjectName} will install to the new location you've set the next time it runs.");
@ -262,18 +207,12 @@ namespace Bloxstrap.Dialogs
Program.Settings.BootstrapperStyle = SelectedStyle;
Program.Settings.BootstrapperIcon = SelectedIcon;
Program.Settings.UseDiscordRichPresence = UseDiscordRichPresence;
Program.Settings.HideRPCButtons = HideRPCButtons;
Program.Settings.UseOldDeathSound = UseOldDeathSound;
Program.Settings.UseOldMouseCursor = UseOldMouseCursor;
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;
@ -301,29 +240,57 @@ namespace Bloxstrap.Dialogs
break;
}
Program.Settings.BootstrapperIcon = savedIcon;
this.Visible = true;
}
private void ToggleDiscordRichPresence_CheckedChanged(object sender, EventArgs e)
{
UseDiscordRichPresence = this.ToggleDiscordRichPresence.Checked;
Program.Settings.UseDiscordRichPresence = this.ToggleRPCButtons.Enabled = this.ToggleDiscordRichPresence.Checked;
}
private void ToggleRPCButtons_CheckedChanged(object sender, EventArgs e)
{
HideRPCButtons = this.ToggleRPCButtons.Checked;
Program.Settings.HideRPCButtons = this.ToggleRPCButtons.Checked;
}
private void ToggleRFUEnabled_CheckedChanged(object sender, EventArgs e)
{
Program.Settings.RFUEnabled = this.ToggleRFUAutoclose.Enabled = this.ToggleRFUEnabled.Checked;
}
private void ToggleRFUAutoclose_CheckedChanged(object sender, EventArgs e)
{
Program.Settings.RFUAutoclose = this.ToggleRFUAutoclose.Checked;
}
private void ToggleDeathSound_CheckedChanged(object sender, EventArgs e)
{
UseOldDeathSound = this.ToggleDeathSound.Checked;
Program.Settings.UseOldDeathSound = this.ToggleDeathSound.Checked;
}
private void ToggleMouseCursor_CheckedChanged(object sender, EventArgs e)
{
UseOldMouseCursor = this.ToggleMouseCursor.Checked;
Program.Settings.UseOldMouseCursor = this.ToggleMouseCursor.Checked;
}
private void ToggleCheckForUpdates_CheckedChanged(object sender, EventArgs e)
{
Program.Settings.CheckForUpdates = this.ToggleCheckForUpdates.Checked;
}
private void RFUWebsite_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Utilities.OpenWebsite($"https://github.com/{RbxFpsUnlocker.ProjectRepository}");
}
private void ButtonOpenModFolder_Click(object sender, EventArgs e)
{
Process.Start("explorer.exe", Directories.Modifications);
}
private void Preferences_Load(object sender, EventArgs e)
{
this.Activate();
}
}
}

View File

@ -57,10 +57,10 @@
<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>
<metadata name="InfoTooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>222, 17</value>
</metadata>
<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,28 @@
using System.IO;
namespace Bloxstrap.Helpers
{
internal class Directories
{
public static string Base { get; private set; } = "";
public static string Downloads { get; private set; } = "";
public static string Integrations { get; private set; } = "";
public static string Versions { get; private set; } = "";
public static string Modifications { get; private set; } = "";
public static string App { get; private set; } = "";
public static bool Initialized { get => String.IsNullOrEmpty(Base); }
public static void Initialize(string baseDirectory)
{
Base = baseDirectory;
Downloads = Path.Combine(Base, "Downloads");
Integrations = Path.Combine(Base, "Integrations");
Versions = Path.Combine(Base, "Versions");
Modifications = Path.Combine(Base, "Modifications");
App = Path.Combine(Base, $"{Program.ProjectName}.exe");
}
}
}

View File

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

View File

@ -0,0 +1,95 @@
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using Newtonsoft.Json.Linq;
namespace Bloxstrap.Helpers.Integrations
{
internal class RbxFpsUnlocker
{
public const string ProjectRepository = "axstin/rbxfpsunlocker";
// default settings but with QuickStart set to true and CheckForUpdates set to false
private static readonly string Settings =
"UnlockClient=true\n" +
"UnlockStudio=false\n" +
"FPSCapValues=[30.000000, 60.000000, 75.000000, 120.000000, 144.000000, 165.000000, 240.000000, 360.000000]\n" +
"FPSCapSelection=0\n" +
"FPSCap=0.000000\n" +
"CheckForUpdates=false\n" +
"NonBlockingErrors=true\n" +
"SilentErrors=false\n" +
"QuickStart=true\n";
public static async Task CheckInstall()
{
if (Program.BaseDirectory is null)
return;
string folderLocation = Path.Combine(Program.BaseDirectory, "Integrations", "rbxfpsunlocker");
string fileLocation = Path.Combine(folderLocation, "rbxfpsunlocker.exe");
string settingsLocation = Path.Combine(folderLocation, "settings");
if (!Program.Settings.RFUEnabled)
{
if (Directory.Exists(folderLocation))
{
Directory.Delete(folderLocation, true);
}
return;
}
DateTime lastReleasePublish;
string downloadUrl;
try
{
JObject releaseInfo = await Utilities.GetJson($"https://api.github.com/repos/{ProjectRepository}/releases/latest");
// so... rbxfpsunlocker does not actually have any version info for the executable
// meaning the best way we can check for a new version is comparing time last download to time last release published
lastReleasePublish = DateTime.Parse(releaseInfo["created_at"].Value<string>());
downloadUrl = releaseInfo["assets"][0]["browser_download_url"].Value<string>();
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to fetch latest version info! ({ex.Message})");
return;
}
Directory.CreateDirectory(folderLocation);
if (File.Exists(fileLocation))
{
DateTime lastDownload = File.GetCreationTimeUtc(fileLocation);
// no new release published, return
if (lastDownload > lastReleasePublish)
return;
File.Delete(fileLocation);
}
Debug.WriteLine("Installing/Updating rbxfpsunlocker...");
using (HttpClient client = new())
{
byte[] bytes = await client.GetByteArrayAsync(downloadUrl);
using (MemoryStream zipStream = new MemoryStream(bytes))
{
ZipArchive zip = new ZipArchive(zipStream);
zip.ExtractToDirectory(folderLocation, true);
}
}
if (!File.Exists(settingsLocation))
{
await File.WriteAllTextAsync(settingsLocation, Settings);
}
}
}
}

View File

@ -1,4 +1,5 @@
using System.Web;
using System.Text;
using System.Web;
using Microsoft.Win32;
namespace Bloxstrap.Helpers
@ -9,11 +10,11 @@ namespace Bloxstrap.Helpers
private static readonly IReadOnlyDictionary<string, string> UriKeyArgMap = new Dictionary<string, string>()
{
// excluding roblox-player, browsertrackerid and channel
{ "launchmode", "--" },
{ "launchmode", "--" },
{ "gameinfo", "-t " },
{ "placelauncherurl", "-j "},
// { "launchtime", "--launchtime=" }, we'll set this when launching the game client
{ "robloxLocale", "--rloc " },
// { "launchtime", "--launchtime=" }, we'll set this when launching the game client
{ "robloxLocale", "--rloc " },
{ "gameLocale", "--gloc " },
};
@ -22,7 +23,7 @@ namespace Bloxstrap.Helpers
string[] keyvalPair;
string key;
string val;
string commandLine = "";
StringBuilder commandLine = new();
foreach (var parameter in protocol.Split('+'))
{
@ -39,10 +40,10 @@ namespace Bloxstrap.Helpers
if (key == "placelauncherurl")
val = HttpUtility.UrlDecode(val).Replace("browserTrackerId", "lol");
commandLine += UriKeyArgMap[key] + val + " ";
commandLine.Append(UriKeyArgMap[key] + val + " ");
}
return commandLine;
return commandLine.ToString();
}
public static void Register(string key, string name, string handler)

View File

@ -1,5 +1,7 @@
// https://github.com/MaximumADHD/Roblox-Studio-Mod-Manager/blob/main/ProjectSrc/Bootstrapper/FileManifest.cs
using System.IO;
using System.Net.Http;
using System.Runtime.Serialization;
namespace Bloxstrap.Helpers.RSMM

View File

@ -1,5 +1,7 @@
// https://github.com/MaximumADHD/Roblox-Studio-Mod-Manager/blob/main/ProjectSrc/Bootstrapper/PackageManifest.cs
using System.IO;
using System.Net.Http;
namespace Bloxstrap.Helpers.RSMM
{

View File

@ -1,18 +1,20 @@
using System.Diagnostics;
using System.IO;
using Newtonsoft.Json.Linq;
namespace Bloxstrap.Helpers
{
public class UpdateChecker
public class Updater
{
public static void CheckInstalledVersion()
public static bool CheckInstalledVersion()
{
if (Environment.ProcessPath is null || !File.Exists(Program.FilePath))
return;
if (Environment.ProcessPath is null || !File.Exists(Directories.App) || Environment.ProcessPath == Directories.App)
return false;
// if downloaded version doesn't match, replace installed version with downloaded version
FileVersionInfo currentVersionInfo = FileVersionInfo.GetVersionInfo(Environment.ProcessPath);
FileVersionInfo installedVersionInfo = FileVersionInfo.GetVersionInfo(Program.FilePath);
FileVersionInfo installedVersionInfo = FileVersionInfo.GetVersionInfo(Directories.App);
if (installedVersionInfo.ProductVersion != currentVersionInfo.ProductVersion)
{
@ -25,10 +27,13 @@ namespace Bloxstrap.Helpers
if (result == DialogResult.Yes)
{
File.Delete(Program.FilePath);
File.Copy(Environment.ProcessPath, Program.FilePath);
File.Delete(Directories.App);
File.Copy(Environment.ProcessPath, Directories.App);
return true;
}
}
return false;
}
public static async Task Check()
@ -36,6 +41,12 @@ namespace Bloxstrap.Helpers
if (Environment.ProcessPath is null)
return;
if (!Program.IsFirstRun && CheckInstalledVersion())
return;
if (!Program.Settings.CheckForUpdates)
return;
FileVersionInfo currentVersionInfo = FileVersionInfo.GetVersionInfo(Environment.ProcessPath);
string currentVersion = $"Bloxstrap v{currentVersionInfo.ProductVersion}";
string latestVersion;
@ -67,7 +78,7 @@ namespace Bloxstrap.Helpers
if (result == DialogResult.Yes)
{
Process.Start(new ProcessStartInfo { FileName = $"https://github.com/{Program.ProjectRepository}/releases/latest", UseShellExecute = true });
Utilities.OpenWebsite($"https://github.com/{Program.ProjectRepository}/releases/latest");
Program.Exit();
}
}

View File

@ -1,4 +1,7 @@
using System.Security.Cryptography;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -7,6 +10,11 @@ namespace Bloxstrap.Helpers
{
public class Utilities
{
public static void OpenWebsite(string website)
{
Process.Start(new ProcessStartInfo { FileName = website, UseShellExecute = true });
}
public static async Task<JObject> GetJson(string url)
{
using (HttpClient client = new())
@ -18,7 +26,7 @@ namespace Bloxstrap.Helpers
}
}
public static string CalculateMD5(string filename)
public static string MD5File(string filename)
{
using (MD5 md5 = MD5.Create())
{

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,7 @@
using System.Diagnostics;
using System.IO;
using System.Text.Json;
using Bloxstrap.Enums;
namespace Bloxstrap
@ -8,10 +10,16 @@ namespace Bloxstrap
{
public string VersionGuid { get; set; }
public bool CheckForUpdates { get; set; } = true;
public BootstrapperStyle BootstrapperStyle { get; set; } = BootstrapperStyle.ProgressDialog;
public BootstrapperIcon BootstrapperIcon { get; set; } = BootstrapperIcon.IconBloxstrap;
public bool UseDiscordRichPresence { get; set; } = true;
public bool HideRPCButtons { get; set; } = false;
public bool RFUEnabled { get; set; } = false;
public bool RFUAutoclose { get; set; } = false;
public bool UseOldDeathSound { get; set; } = true;
public bool UseOldMouseCursor { get; set; } = false;
}