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