Merge pull request #578 from pizzaboxer/version-2.5.1

Version 2.5.1
This commit is contained in:
pizzaboxer 2023-08-24 23:25:30 +01:00 committed by GitHub
commit 8f084958e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 596 additions and 366 deletions

View File

@ -49,6 +49,8 @@ namespace Bloxstrap
) )
); );
private static bool _showingExceptionDialog = false;
public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS) public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
{ {
if (IsFirstRun) if (IsFirstRun)
@ -85,6 +87,11 @@ namespace Bloxstrap
#if DEBUG #if DEBUG
throw exception; throw exception;
#else #else
if (_showingExceptionDialog)
return;
_showingExceptionDialog = true;
if (!IsQuiet) if (!IsQuiet)
Controls.ShowExceptionDialog(exception); Controls.ShowExceptionDialog(exception);
@ -149,33 +156,6 @@ namespace Bloxstrap
} }
} }
if (!IsMenuLaunch)
{
Logger.WriteLine(LOG_IDENT, "Performing connectivity check...");
try
{
HttpClient.GetAsync("https://detectportal.firefox.com").Wait();
Logger.WriteLine(LOG_IDENT, "Connectivity check finished");
}
catch (Exception ex)
{
Logger.WriteLine(LOG_IDENT, "Connectivity check failed!");
Logger.WriteException(LOG_IDENT, ex);
if (ex.GetType() == typeof(AggregateException))
ex = ex.InnerException!;
Controls.ShowConnectivityDialog(
"the internet",
$"Something may be preventing {ProjectName} from connecting to the internet, or you are currently offline. Please check and try again.",
ex
);
Terminate(ErrorCode.ERROR_CANCELLED);
}
}
using (var checker = new InstallChecker()) using (var checker = new InstallChecker())
{ {
checker.Check(); checker.Check();

View File

@ -7,8 +7,8 @@
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<UseWindowsForms>True</UseWindowsForms> <UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>Bloxstrap.ico</ApplicationIcon> <ApplicationIcon>Bloxstrap.ico</ApplicationIcon>
<Version>2.5.0</Version> <Version>2.5.1</Version>
<FileVersion>2.5.0.0</FileVersion> <FileVersion>2.5.1.0</FileVersion>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>

View File

@ -60,6 +60,7 @@ namespace Bloxstrap
private string _latestVersionGuid = null!; private string _latestVersionGuid = null!;
private PackageManifest _versionPackageManifest = null!; private PackageManifest _versionPackageManifest = null!;
private FileManifest _versionFileManifest = null!;
private string _versionFolder = null!; private string _versionFolder = null!;
private bool _isInstalling = false; private bool _isInstalling = false;
@ -114,6 +115,39 @@ namespace Bloxstrap
return; return;
} }
// connectivity check
App.Logger.WriteLine(LOG_IDENT, "Performing connectivity check...");
SetStatus("Connecting to Roblox...");
try
{
await RobloxDeployment.GetInfo(RobloxDeployment.DefaultChannel);
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, "Connectivity check failed!");
App.Logger.WriteException(LOG_IDENT, ex);
string message = $"It's possible that something is preventing {App.ProjectName} from connecting to the internet. Please check and try again.";
if (ex.GetType() == typeof(HttpResponseException))
message = "Roblox may be down right now. See status.roblox.com for more information. Please try again later.";
else if (ex.GetType() == typeof(TaskCanceledException))
message = "Bloxstrap timed out when trying to connect to three different Roblox deployment mirrors, indicating a poor internet connection. Please try again later.";
else if (ex.GetType() == typeof(AggregateException))
ex = ex.InnerException!;
Controls.ShowConnectivityDialog("Roblox", message, ex);
App.Terminate(ErrorCode.ERROR_CANCELLED);
}
finally
{
App.Logger.WriteLine(LOG_IDENT, "Connectivity check finished");
}
#if !DEBUG #if !DEBUG
if (!App.IsFirstRun && App.Settings.Prop.CheckForUpdates) if (!App.IsFirstRun && App.Settings.Prop.CheckForUpdates)
await CheckForUpdates(); await CheckForUpdates();
@ -126,8 +160,9 @@ namespace Bloxstrap
try try
{ {
Mutex.OpenExisting("Bloxstrap_BootstrapperMutex").Close(); Mutex.OpenExisting("Bloxstrap_SingletonMutex").Close();
App.Logger.WriteLine(LOG_IDENT, "Bloxstrap_BootstrapperMutex mutex exists, waiting..."); App.Logger.WriteLine(LOG_IDENT, "Bloxstrap_SingletonMutex mutex exists, waiting...");
SetStatus("Waiting for other instances...");
mutexExists = true; mutexExists = true;
} }
catch (Exception) catch (Exception)
@ -136,7 +171,7 @@ namespace Bloxstrap
} }
// wait for mutex to be released if it's not yet // wait for mutex to be released if it's not yet
await using AsyncMutex mutex = new("Bloxstrap_BootstrapperMutex"); await using var mutex = new AsyncMutex(true, "Bloxstrap_SingletonMutex");
await mutex.AcquireAsync(_cancelTokenSource.Token); await mutex.AcquireAsync(_cancelTokenSource.Token);
// reload our configs since they've likely changed by now // reload our configs since they've likely changed by now
@ -148,8 +183,6 @@ namespace Bloxstrap
await CheckLatestVersion(); await CheckLatestVersion();
CheckInstallMigration();
// install/update roblox if we're running for the first time, needs updating, or the player location doesn't exist // install/update roblox if we're running for the first time, needs updating, or the player location doesn't exist
if (App.IsFirstRun || _latestVersionGuid != App.State.Prop.VersionGuid || !File.Exists(_playerLocation)) if (App.IsFirstRun || _latestVersionGuid != App.State.Prop.VersionGuid || !File.Exists(_playerLocation))
await InstallLatestVersion(); await InstallLatestVersion();
@ -187,7 +220,7 @@ namespace Bloxstrap
private async Task CheckLatestVersion() private async Task CheckLatestVersion()
{ {
SetStatus("Connecting to Roblox..."); const string LOG_IDENT = "Bootstrapper::CheckLatestVersion";
ClientVersion clientVersion; ClientVersion clientVersion;
@ -195,17 +228,14 @@ namespace Bloxstrap
{ {
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel); clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel);
} }
catch (Exception ex) catch (HttpResponseException ex)
{ {
string message = "It's possible that Roblox is being blocked by a firewall. Please check and try again."; if (ex.ResponseMessage.StatusCode != HttpStatusCode.NotFound)
throw;
if (ex.GetType() == typeof(HttpResponseException)) App.Logger.WriteLine(LOG_IDENT, $"Reverting enrolled channel to {RobloxDeployment.DefaultChannel} because a WindowsPlayer build does not exist for {App.Settings.Prop.Channel}");
message = "Roblox may be down right now. See status.roblox.com for more information. Please try again later."; App.Settings.Prop.Channel = RobloxDeployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel);
Controls.ShowConnectivityDialog("Roblox", message, ex);
App.Terminate(ErrorCode.ERROR_CANCELLED);
return;
} }
if (clientVersion.IsBehindDefaultChannel) if (clientVersion.IsBehindDefaultChannel)
@ -235,6 +265,7 @@ namespace Bloxstrap
_latestVersionGuid = clientVersion.VersionGuid; _latestVersionGuid = clientVersion.VersionGuid;
_versionFolder = Path.Combine(Paths.Versions, _latestVersionGuid); _versionFolder = Path.Combine(Paths.Versions, _latestVersionGuid);
_versionPackageManifest = await PackageManifest.Get(_latestVersionGuid); _versionPackageManifest = await PackageManifest.Get(_latestVersionGuid);
_versionFileManifest = await FileManifest.Get(_latestVersionGuid);
} }
private async Task StartRoblox() private async Task StartRoblox()
@ -266,8 +297,12 @@ namespace Bloxstrap
_launchCommandLine = _launchCommandLine.Replace("LAUNCHTIMEPLACEHOLDER", DateTimeOffset.Now.ToUnixTimeMilliseconds().ToString()); _launchCommandLine = _launchCommandLine.Replace("LAUNCHTIMEPLACEHOLDER", DateTimeOffset.Now.ToUnixTimeMilliseconds().ToString());
if (App.Settings.Prop.Channel.ToLowerInvariant() != RobloxDeployment.DefaultChannel.ToLowerInvariant()) _launchCommandLine += " -channel ";
_launchCommandLine += " -channel " + App.Settings.Prop.Channel.ToLowerInvariant();
if (App.Settings.Prop.Channel.ToLowerInvariant() == RobloxDeployment.DefaultChannel.ToLowerInvariant())
_launchCommandLine += "production";
else
_launchCommandLine += App.Settings.Prop.Channel.ToLowerInvariant();
// whether we should wait for roblox to exit to handle stuff in the background or clean up after roblox closes // whether we should wait for roblox to exit to handle stuff in the background or clean up after roblox closes
bool shouldWait = false; bool shouldWait = false;
@ -391,6 +426,8 @@ namespace Bloxstrap
App.Logger.WriteException(LOG_IDENT, ex); App.Logger.WriteException(LOG_IDENT, ex);
} }
Dialog?.CloseBootstrapper();
App.Terminate(ErrorCode.ERROR_CANCELLED); App.Terminate(ErrorCode.ERROR_CANCELLED);
} }
#endregion #endregion
@ -444,71 +481,6 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, $"Registered as {totalSize} KB"); App.Logger.WriteLine(LOG_IDENT, $"Registered as {totalSize} KB");
} }
private void CheckInstallMigration()
{
const string LOG_IDENT = "Bootstrapper::CheckInstallMigration";
// check if we've changed our install location since the last time we started
// in which case, we'll have to copy over all our folders so we don't lose any mods and stuff
using RegistryKey? applicationKey = Registry.CurrentUser.OpenSubKey($@"Software\{App.ProjectName}", true);
string? oldInstallLocation = (string?)applicationKey?.GetValue("OldInstallLocation");
if (applicationKey is null || oldInstallLocation is null || oldInstallLocation == Paths.Base)
return;
SetStatus("Migrating install location...");
if (Directory.Exists(oldInstallLocation))
{
App.Logger.WriteLine(LOG_IDENT, $"Moving all files in {oldInstallLocation} to {Paths.Base}...");
foreach (string oldFileLocation in Directory.GetFiles(oldInstallLocation, "*.*", SearchOption.AllDirectories))
{
string relativeFile = oldFileLocation.Substring(oldInstallLocation.Length + 1);
string newFileLocation = Path.Combine(Paths.Base, relativeFile);
string? newDirectory = Path.GetDirectoryName(newFileLocation);
try
{
if (!String.IsNullOrEmpty(newDirectory))
Directory.CreateDirectory(newDirectory);
File.Move(oldFileLocation, newFileLocation, true);
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to move {oldFileLocation} to {newFileLocation}! {ex}");
}
}
try
{
Directory.Delete(oldInstallLocation, true);
App.Logger.WriteLine(LOG_IDENT, "Deleted old install location");
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to delete old install location! {ex}");
}
}
applicationKey.DeleteValue("OldInstallLocation");
// allow shortcuts to be re-registered
if (Directory.Exists(Paths.StartMenu))
Directory.Delete(Paths.StartMenu, true);
if (File.Exists(DesktopShortcutLocation))
{
File.Delete(DesktopShortcutLocation);
App.Settings.Prop.CreateDesktopIcon = true;
}
App.Logger.WriteLine(LOG_IDENT, "Finished migrating install location!");
}
public static void CheckInstall() public static void CheckInstall()
{ {
const string LOG_IDENT = "Bootstrapper::CheckInstall"; const string LOG_IDENT = "Bootstrapper::CheckInstall";
@ -713,7 +685,7 @@ namespace Bloxstrap
// if the folder we're installed to does not end with "Bloxstrap", we're installed to a user-selected folder // if the folder we're installed to does not end with "Bloxstrap", we're installed to a user-selected folder
// in which case, chances are they chose to install to somewhere they didn't really mean to (prior to the added warning in 2.4.0) // in which case, chances are they chose to install to somewhere they didn't really mean to (prior to the added warning in 2.4.0)
// if so, we're walking on eggshells and have to ensure we only clean up what we need to clean up // if so, we're walking on eggshells and have to ensure we only clean up what we need to clean up
bool cautiousUninstall = !Paths.Base.EndsWith(App.ProjectName); bool cautiousUninstall = !Paths.Base.ToLower().EndsWith(App.ProjectName.ToLower());
var cleanupSequence = new List<Action> var cleanupSequence = new List<Action>
{ {
@ -783,7 +755,7 @@ namespace Bloxstrap
}; };
} }
Dialog?.ShowSuccess($"{App.ProjectName} has succesfully uninstalled", callback); Dialog?.ShowSuccess($"{App.ProjectName} has successfully uninstalled", callback);
} }
#endregion #endregion
@ -906,8 +878,17 @@ namespace Bloxstrap
continue; continue;
App.Logger.WriteLine(LOG_IDENT, $"Removing old version folder for {dir.Name}"); App.Logger.WriteLine(LOG_IDENT, $"Removing old version folder for {dir.Name}");
try
{
dir.Delete(true); dir.Delete(true);
} }
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to delete version folder!");
App.Logger.WriteException(LOG_IDENT, ex);
}
}
} }
} }
@ -999,6 +980,12 @@ namespace Bloxstrap
{ {
const string LOG_IDENT = "Bootstrapper::ApplyModifications"; const string LOG_IDENT = "Bootstrapper::ApplyModifications";
if (Process.GetProcessesByName("RobloxPlayerBeta").Where(x => x.MainModule!.FileName == _playerLocation).Any())
{
App.Logger.WriteLine(LOG_IDENT, "Roblox is running, aborting mod check");
return;
}
SetStatus("Applying Roblox modifications..."); SetStatus("Applying Roblox modifications...");
// set executable flags for fullscreen optimizations // set executable flags for fullscreen optimizations
@ -1332,6 +1319,9 @@ namespace Bloxstrap
for (int i = 1; i <= maxTries; i++) for (int i = 1; i <= maxTries; i++)
{ {
if (_cancelFired)
return;
int totalBytesRead = 0; int totalBytesRead = 0;
try try
@ -1370,6 +1360,7 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, $"An exception occurred after downloading {totalBytesRead} bytes. ({i}/{maxTries})"); App.Logger.WriteLine(LOG_IDENT, $"An exception occurred after downloading {totalBytesRead} bytes. ({i}/{maxTries})");
App.Logger.WriteException(LOG_IDENT, ex); App.Logger.WriteException(LOG_IDENT, ex);
if (File.Exists(packageLocation))
File.Delete(packageLocation); File.Delete(packageLocation);
if (i >= maxTries) if (i >= maxTries)
@ -1415,6 +1406,16 @@ namespace Bloxstrap
if (directory is not null) if (directory is not null)
Directory.CreateDirectory(directory); Directory.CreateDirectory(directory);
if (File.Exists(extractPath))
{
var fileManifest = _versionFileManifest.FirstOrDefault(x => x.Name == Path.Combine(PackageDirectories[package.Name], entry.FullName));
if (fileManifest is not null && MD5Hash.FromFile(extractPath) == fileManifest.Signature)
continue;
File.Delete(extractPath);
}
entry.ExtractToFile(extractPath, true); entry.ExtractToFile(extractPath, true);
} }

View File

@ -14,6 +14,8 @@ namespace Bloxstrap
public static IReadOnlyDictionary<string, string> PresetFlags = new Dictionary<string, string> public static IReadOnlyDictionary<string, string> PresetFlags = new Dictionary<string, string>
{ {
{ "Network.Log", "FLogNetwork" },
{ "HTTP.Log", "DFLogHttpTraceLight" }, { "HTTP.Log", "DFLogHttpTraceLight" },
{ "HTTP.Proxy.Enable", "DFFlagDebugEnableHttpProxy" }, { "HTTP.Proxy.Enable", "DFFlagDebugEnableHttpProxy" },
@ -25,7 +27,6 @@ namespace Bloxstrap
{ "Rendering.ManualFullscreen", "FFlagHandleAltEnterFullscreenManually" }, { "Rendering.ManualFullscreen", "FFlagHandleAltEnterFullscreenManually" },
{ "Rendering.TexturePack", "FStringPartTexturePackTable2022" }, { "Rendering.TexturePack", "FStringPartTexturePackTable2022" },
{ "Rendering.DisableScaling", "DFFlagDisableDPIScale" }, { "Rendering.DisableScaling", "DFFlagDisableDPIScale" },
{ "Rendering.MSAA", "FIntDebugForceMSAASamples" },
{ "Rendering.Mode.D3D11", "FFlagDebugGraphicsPreferD3D11" }, { "Rendering.Mode.D3D11", "FFlagDebugGraphicsPreferD3D11" },
{ "Rendering.Mode.D3D10", "FFlagDebugGraphicsPreferD3D11FL10" }, { "Rendering.Mode.D3D10", "FFlagDebugGraphicsPreferD3D11FL10" },
@ -43,7 +44,12 @@ namespace Bloxstrap
{ "UI.Menu.GraphicsSlider", "FFlagFixGraphicsQuality" }, { "UI.Menu.GraphicsSlider", "FFlagFixGraphicsQuality" },
{ "UI.Menu.Style.DisableV2", "FFlagDisableNewIGMinDUA" }, { "UI.Menu.Style.DisableV2", "FFlagDisableNewIGMinDUA" },
{ "UI.Menu.Style.EnableV4", "FFlagEnableInGameMenuControls" } { "UI.Menu.Style.EnableV4", "FFlagEnableInGameMenuControls" },
{ "UI.Menu.Style.ABTest.1", "FFlagEnableMenuControlsABTest" },
{ "UI.Menu.Style.ABTest.2", "FFlagEnableMenuModernizationABTest" },
{ "UI.Menu.Style.ABTest.3", "FFlagEnableMenuModernizationABTest2" },
{ "UI.Menu.Style.ABTest.4", "FFlagEnableV3MenuABTest3" }
}; };
// only one missing here is Metal because lol // only one missing here is Metal because lol
@ -82,7 +88,8 @@ namespace Bloxstrap
new Dictionary<string, string?> new Dictionary<string, string?>
{ {
{ "DisableV2", null }, { "DisableV2", null },
{ "EnableV4", null } { "EnableV4", null },
{ "ABTest", null }
} }
}, },
@ -91,7 +98,8 @@ namespace Bloxstrap
new Dictionary<string, string?> new Dictionary<string, string?>
{ {
{ "DisableV2", "True" }, { "DisableV2", "True" },
{ "EnableV4", "False" } { "EnableV4", "False" },
{ "ABTest", "False" }
} }
}, },
@ -100,7 +108,8 @@ namespace Bloxstrap
new Dictionary<string, string?> new Dictionary<string, string?>
{ {
{ "DisableV2", "False" }, { "DisableV2", "False" },
{ "EnableV4", "False" } { "EnableV4", "False" },
{ "ABTest", "False" }
} }
}, },
@ -109,7 +118,8 @@ namespace Bloxstrap
new Dictionary<string, string?> new Dictionary<string, string?>
{ {
{ "DisableV2", "True" }, { "DisableV2", "True" },
{ "EnableV4", "True" } { "EnableV4", "True" },
{ "ABTest", "False" }
} }
} }
}; };
@ -205,6 +215,11 @@ namespace Bloxstrap
CheckManualFullscreenPreset(); CheckManualFullscreenPreset();
// TODO - remove when activity tracking has been revamped
if (GetPreset("Network.Log") != "7")
SetPreset("Network.Log", "7");
if (GetPreset("Rendering.Framerate") is not null) if (GetPreset("Rendering.Framerate") is not null)
return; return;

View File

@ -18,8 +18,9 @@ global using Bloxstrap.Enums;
global using Bloxstrap.Exceptions; global using Bloxstrap.Exceptions;
global using Bloxstrap.Extensions; global using Bloxstrap.Extensions;
global using Bloxstrap.Models; global using Bloxstrap.Models;
global using Bloxstrap.Models.BloxstrapRPC;
global using Bloxstrap.Models.Attributes; global using Bloxstrap.Models.Attributes;
global using Bloxstrap.Models.BloxstrapRPC;
global using Bloxstrap.Models.RobloxApi; global using Bloxstrap.Models.RobloxApi;
global using Bloxstrap.Models.Manifest;
global using Bloxstrap.UI; global using Bloxstrap.UI;
global using Bloxstrap.Utility; global using Bloxstrap.Utility;

View File

@ -1,5 +1,4 @@
using System.Windows; using System.Windows;
using Microsoft.Win32; using Microsoft.Win32;
namespace Bloxstrap namespace Bloxstrap
@ -29,9 +28,23 @@ namespace Bloxstrap
return; return;
} }
App.Logger.WriteLine(LOG_IDENT, "Installation registry key is likely malformed");
_installLocation = Path.GetDirectoryName(Paths.Process)!; _installLocation = Path.GetDirectoryName(Paths.Process)!;
App.Logger.WriteLine(LOG_IDENT, $"Registry key is likely malformed. Setting install location as '{_installLocation}'"); var result = Controls.ShowMessageBox(
$"It appears as if {App.ProjectName} hasn't been properly installed. Is it supposed to be installed at {_installLocation}?",
MessageBoxImage.Warning,
MessageBoxButton.YesNo
);
if (result != MessageBoxResult.Yes)
{
FirstTimeRun();
return;
}
App.Logger.WriteLine(LOG_IDENT, $"Setting install location as '{_installLocation}'");
if (_registryKey is null) if (_registryKey is null)
_registryKey = Registry.CurrentUser.CreateSubKey($"Software\\{App.ProjectName}"); _registryKey = Registry.CurrentUser.CreateSubKey($"Software\\{App.ProjectName}");
@ -199,12 +212,24 @@ namespace Bloxstrap
// update migrations // update migrations
if (App.BuildMetadata.CommitRef.StartsWith("tag") && existingVersionInfo.ProductVersion == "2.4.0") if (App.BuildMetadata.CommitRef.StartsWith("tag"))
{
if (existingVersionInfo.ProductVersion == "2.4.0")
{ {
App.FastFlags.SetValue("DFFlagDisableDPIScale", null); App.FastFlags.SetValue("DFFlagDisableDPIScale", null);
App.FastFlags.SetValue("DFFlagVariableDPIScale2", null); App.FastFlags.SetValue("DFFlagVariableDPIScale2", null);
App.FastFlags.Save(); App.FastFlags.Save();
} }
else if (existingVersionInfo.ProductVersion == "2.5.0")
{
App.FastFlags.SetValue("FIntDebugForceMSAASamples", null);
if (App.FastFlags.GetPreset("UI.Menu.Style.DisableV2") is not null)
App.FastFlags.SetPreset("UI.Menu.Style.ABTest", false);
App.FastFlags.Save();
}
}
if (isAutoUpgrade) if (isAutoUpgrade)
{ {

View File

@ -9,6 +9,7 @@ namespace Bloxstrap.Integrations
private DiscordRPC.RichPresence? _currentPresence; private DiscordRPC.RichPresence? _currentPresence;
private DiscordRPC.RichPresence? _currentPresenceCopy; private DiscordRPC.RichPresence? _currentPresenceCopy;
private Message? _stashedRPCMessage;
private bool _visible = true; private bool _visible = true;
private long _currentUniverseId; private long _currentUniverseId;
@ -55,6 +56,13 @@ namespace Bloxstrap.Integrations
if (_currentPresence is null || _currentPresenceCopy is null) if (_currentPresence is null || _currentPresenceCopy is null)
{ {
if (_activityWatcher.ActivityInGame)
{
App.Logger.WriteLine(LOG_IDENT, "Presence is not yet set, but is currently in game, stashing presence set request");
_stashedRPCMessage = message;
return;
}
App.Logger.WriteLine(LOG_IDENT, "Presence is not set, aborting"); App.Logger.WriteLine(LOG_IDENT, "Presence is not set, aborting");
return; return;
} }
@ -173,7 +181,10 @@ namespace Bloxstrap.Integrations
if (!_activityWatcher.ActivityInGame) if (!_activityWatcher.ActivityInGame)
{ {
App.Logger.WriteLine(LOG_IDENT, "Not in game, clearing presence"); App.Logger.WriteLine(LOG_IDENT, "Not in game, clearing presence");
_currentPresence = _currentPresenceCopy = null; _currentPresence = _currentPresenceCopy = null;
_stashedRPCMessage = null;
UpdatePresence(); UpdatePresence();
return true; return true;
} }
@ -250,6 +261,9 @@ namespace Bloxstrap.Integrations
_ => $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""), _ => $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""),
}; };
if (universeDetails.Name.Length < 2)
universeDetails.Name = $"{universeDetails.Name}\x2800\x2800\x2800";
_currentPresence = new DiscordRPC.RichPresence _currentPresence = new DiscordRPC.RichPresence
{ {
Details = $"Playing {universeDetails.Name}", Details = $"Playing {universeDetails.Name}",
@ -268,7 +282,16 @@ namespace Bloxstrap.Integrations
// this is used for configuration from BloxstrapRPC // this is used for configuration from BloxstrapRPC
_currentPresenceCopy = _currentPresence.Clone(); _currentPresenceCopy = _currentPresence.Clone();
if (_stashedRPCMessage is not null)
{
App.Logger.WriteLine(LOG_IDENT, "Found stashed RPC message, invoking presence set command now");
ProcessRPCMessage(_stashedRPCMessage);
_stashedRPCMessage = null;
}
else
{
UpdatePresence(); UpdatePresence();
}
return true; return true;
} }

View File

@ -43,6 +43,7 @@
catch (IOException) catch (IOException)
{ {
WriteLine(LOG_IDENT, "Failed to initialize because log file already exists"); WriteLine(LOG_IDENT, "Failed to initialize because log file already exists");
return;
} }
@ -64,8 +65,17 @@
continue; continue;
WriteLine(LOG_IDENT, $"Cleaning up old log file '{log.Name}'"); WriteLine(LOG_IDENT, $"Cleaning up old log file '{log.Name}'");
try
{
log.Delete(); log.Delete();
} }
catch (Exception ex)
{
WriteLine(LOG_IDENT, "Failed to delete log!");
WriteException(LOG_IDENT, ex);
}
}
} }
} }

View File

@ -0,0 +1,33 @@
namespace Bloxstrap.Models.Manifest
{
public class FileManifest : List<ManifestFile>
{
private FileManifest(string data)
{
using StringReader reader = new StringReader(data);
while (true)
{
string? fileName = reader.ReadLine();
string? signature = reader.ReadLine();
if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(signature))
break;
Add(new ManifestFile
{
Name = fileName,
Signature = signature
});
}
}
public static async Task<FileManifest> Get(string versionGuid)
{
string pkgManifestUrl = RobloxDeployment.GetLocation($"/{versionGuid}-rbxManifest.txt");
var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl);
return new FileManifest(pkgManifestData);
}
}
}

View File

@ -0,0 +1,13 @@
namespace Bloxstrap.Models.Manifest
{
public class ManifestFile
{
public string Name { get; set; } = "";
public string Signature { get; set; } = "";
public override string ToString()
{
return $"[{Signature}] {Name}";
}
}
}

View File

@ -4,7 +4,7 @@
* Copyright (c) 2015-present MaximumADHD * Copyright (c) 2015-present MaximumADHD
*/ */
namespace Bloxstrap.Models namespace Bloxstrap.Models.Manifest
{ {
public class Package public class Package
{ {

View File

@ -4,7 +4,7 @@
* Copyright (c) 2015-present MaximumADHD * Copyright (c) 2015-present MaximumADHD
*/ */
namespace Bloxstrap.Models namespace Bloxstrap.Models.Manifest
{ {
public class PackageManifest : List<Package> public class PackageManifest : List<Package>
{ {

View File

@ -7,7 +7,7 @@
private static Dictionary<string, ClientVersion> ClientVersionCache = new(); private static Dictionary<string, ClientVersion> ClientVersionCache = new();
// a list of roblox delpoyment locations that we check for, in case one of them don't work // a list of roblox deployment locations that we check for, in case one of them don't work
private static List<string> BaseUrls = new() private static List<string> BaseUrls = new()
{ {
"https://setup.rbxcdn.com", "https://setup.rbxcdn.com",
@ -52,18 +52,6 @@
return _baseUrl; return _baseUrl;
} }
} }
// most commonly used/interesting channels
public static readonly List<string> SelectableChannels = new()
{
"LIVE",
"ZFlag",
"ZNext",
"ZCanary",
"ZIntegration",
"ZAvatarTeam",
"ZSocialTeam"
};
#endregion #endregion
public static string GetLocation(string resource, string? channel = null) public static string GetLocation(string resource, string? channel = null)

View File

@ -96,7 +96,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
private void RichPresenceMenuItem_Click(object sender, RoutedEventArgs e) => _richPresenceHandler?.SetVisibility(((MenuItem)sender).IsChecked); private void RichPresenceMenuItem_Click(object sender, RoutedEventArgs e) => _richPresenceHandler?.SetVisibility(((MenuItem)sender).IsChecked);
private void InviteDeeplinkMenuItem_Click(object sender, RoutedEventArgs e) => Clipboard.SetText($"roblox://experiences/start?placeId={_activityWatcher?.ActivityPlaceId}&gameInstanceId={_activityWatcher?.ActivityJobId}"); private void InviteDeeplinkMenuItem_Click(object sender, RoutedEventArgs e) => Clipboard.SetDataObject($"roblox://experiences/start?placeId={_activityWatcher?.ActivityPlaceId}&gameInstanceId={_activityWatcher?.ActivityJobId}");
private void ServerDetailsMenuItem_Click(object sender, RoutedEventArgs e) => ShowServerInformationWindow(); private void ServerDetailsMenuItem_Click(object sender, RoutedEventArgs e) => ShowServerInformationWindow();

View File

@ -33,10 +33,10 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" MinWidth="100" Text="Flag name" Margin="0,0,0,12" /> <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" MinWidth="100" Text="Name" Margin="0,0,0,12" />
<TextBox Grid.Row="0" Grid.Column="1" Name="FlagNameTextBox" Margin="0,0,0,12" /> <TextBox Grid.Row="0" Grid.Column="1" Name="FlagNameTextBox" Margin="0,0,0,12" />
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" MinWidth="100" Text="Flag value" /> <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" MinWidth="100" Text="Value" />
<TextBox Grid.Row="1" Grid.Column="1" Name="FlagValueTextBox" /> <TextBox Grid.Row="1" Grid.Column="1" Name="FlagValueTextBox" />
</Grid> </Grid>

View File

@ -0,0 +1,60 @@
<ui:UiWindow x:Class="Bloxstrap.UI.Elements.Dialogs.BulkAddFastFlagDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Dialogs"
mc:Ignorable="d"
Title="Import JSON"
MinHeight="0"
Width="480"
SizeToContent="Height"
ResizeMode="NoResize"
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
ExtendsContentIntoTitleBar="True"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ui:TitleBar Grid.Row="0" Grid.ColumnSpan="2" Padding="8" Title="Import JSON" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" KeyboardNavigation.TabNavigation="None" />
<Grid Grid.Row="1" Margin="8,4,8,4">
<TextBox x:Name="JsonTextBox" Margin="5" AcceptsTab="True" AcceptsReturn="True" MinHeight="80" MaxHeight="480" />
<TextBlock IsHitTestVisible="False" Margin="18,14,0,0" Foreground="DarkGray">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=JsonTextBox}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
Paste in your JSON here...
</TextBlock>
</Grid>
<Border Grid.Row="3" Margin="0,10,0,0" Padding="15" Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
<StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Right">
<Button MinWidth="100" Content="OK" Click="OKButton_Click">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=JsonTextBox, Path=Text.Length}" Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button MinWidth="100" Margin="12,0,0,0" Content="Cancel" IsCancel="True" />
</StackPanel>
</Border>
</Grid>
</ui:UiWindow>

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Bloxstrap.UI.Elements.Dialogs
{
/// <summary>
/// Interaction logic for BulkAddFastFlagDialog.xaml
/// </summary>
public partial class BulkAddFastFlagDialog
{
public MessageBoxResult Result = MessageBoxResult.Cancel;
public BulkAddFastFlagDialog()
{
InitializeComponent();
}
private void OKButton_Click(object sender, RoutedEventArgs e)
{
Result = MessageBoxResult.OK;
Close();
}
}
}

View File

@ -16,17 +16,12 @@ namespace Bloxstrap.UI.Elements.Dialogs
{ {
public ConnectivityDialog(string targetName, string description, Exception exception) public ConnectivityDialog(string targetName, string description, Exception exception)
{ {
Exception? innerException = exception.InnerException;
InitializeComponent(); InitializeComponent();
TitleTextBlock.Text = $"{App.ProjectName} is unable to connect to {targetName}"; TitleTextBlock.Text = $"{App.ProjectName} is unable to connect to {targetName}";
DescriptionTextBlock.Text = description; DescriptionTextBlock.Text = description;
ErrorRichTextBox.Selection.Text = $"{exception.GetType()}: {exception.Message}"; AddException(exception);
if (innerException is not null)
ErrorRichTextBox.Selection.Text += $"\n\n===== Inner Exception =====\n{innerException.GetType()}: {innerException.Message}";
CloseButton.Click += delegate CloseButton.Click += delegate
{ {
@ -41,5 +36,18 @@ namespace Bloxstrap.UI.Elements.Dialogs
PInvoke.FlashWindow((HWND)hWnd, true); PInvoke.FlashWindow((HWND)hWnd, true);
}; };
} }
private void AddException(Exception exception, bool inner = false)
{
if (!inner)
ErrorRichTextBox.Selection.Text = $"{exception.GetType()}: {exception.Message}";
if (exception.InnerException is null)
return;
ErrorRichTextBox.Selection.Text += $"\n\n[Inner Exception]\n{exception.InnerException.GetType()}: {exception.InnerException.Message}";
AddException(exception.InnerException, true);
}
} }
} }

View File

@ -6,7 +6,7 @@
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Dialogs" xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Dialogs"
mc:Ignorable="d" mc:Ignorable="d"
Width="480" Width="540"
MinHeight="0" MinHeight="0"
SizeToContent="Height" SizeToContent="Height"
Background="{ui:ThemeResource ApplicationBackgroundBrush}" Background="{ui:ThemeResource ApplicationBackgroundBrush}"

View File

@ -22,10 +22,8 @@ namespace Bloxstrap.UI.Elements.Dialogs
InitializeComponent(); InitializeComponent();
Title = RootTitleBar.Title = $"{App.ProjectName} Exception"; Title = RootTitleBar.Title = $"{App.ProjectName} Exception";
ErrorRichTextBox.Selection.Text = $"{exception.GetType()}: {exception.Message}";
if (innerException is not null) AddException(exception);
ErrorRichTextBox.Selection.Text += $"\n\n===== Inner Exception =====\n{innerException.GetType()}: {innerException.Message}";
if (!App.Logger.Initialized) if (!App.Logger.Initialized)
LocateLogFileButton.Content = "Copy log contents"; LocateLogFileButton.Content = "Copy log contents";
@ -35,7 +33,7 @@ namespace Bloxstrap.UI.Elements.Dialogs
if (App.Logger.Initialized) if (App.Logger.Initialized)
Process.Start("explorer.exe", $"/select,\"{App.Logger.FileLocation}\""); Process.Start("explorer.exe", $"/select,\"{App.Logger.FileLocation}\"");
else else
Clipboard.SetText(String.Join("\r\n", App.Logger.Backlog)); Clipboard.SetDataObject(String.Join("\r\n", App.Logger.Backlog));
}; };
ReportOptions.DropDownClosed += (sender, e) => ReportOptions.DropDownClosed += (sender, e) =>
@ -66,5 +64,18 @@ namespace Bloxstrap.UI.Elements.Dialogs
PInvoke.FlashWindow((HWND)hWnd, true); PInvoke.FlashWindow((HWND)hWnd, true);
}; };
} }
private void AddException(Exception exception, bool inner = false)
{
if (!inner)
ErrorRichTextBox.Selection.Text = $"{exception.GetType()}: {exception.Message}";
if (exception.InnerException is null)
return;
ErrorRichTextBox.Selection.Text += $"\n\n[Inner Exception]\n{exception.InnerException.GetType()}: {exception.InnerException.Message}";
AddException(exception.InnerException, true);
}
} }
} }

View File

@ -78,7 +78,5 @@
<ui:Button Content="Cancel" Command="{Binding CloseWindowCommand, Mode=OneWay}" /> <ui:Button Content="Cancel" Command="{Binding CloseWindowCommand, Mode=OneWay}" />
</StatusBarItem> </StatusBarItem>
</StatusBar> </StatusBar>
<ui:Dialog x:Name="RootDialog" Title="WPF UI" Grid.Row="1" Grid.RowSpan="2" ButtonLeftVisibility="Collapsed" ButtonRightName="Continue" DialogHeight="225" DialogWidth="430" />
</Grid> </Grid>
</ui:UiWindow> </ui:UiWindow>

View File

@ -15,16 +15,14 @@ namespace Bloxstrap.UI.Elements.Menu
public partial class MainWindow : INavigationWindow public partial class MainWindow : INavigationWindow
{ {
private readonly IThemeService _themeService = new ThemeService(); private readonly IThemeService _themeService = new ThemeService();
private readonly IDialogService _dialogService = new DialogService();
public MainWindow() public MainWindow()
{ {
App.Logger.WriteLine("MainWindow::MainWindow", "Initializing menu"); App.Logger.WriteLine("MainWindow::MainWindow", "Initializing menu");
DataContext = new MainWindowViewModel(this, _dialogService); DataContext = new MainWindowViewModel(this);
SetTheme(); SetTheme();
InitializeComponent(); InitializeComponent();
_dialogService.SetDialogControl(RootDialog);
} }
public void SetTheme() public void SetTheme()

View File

@ -46,7 +46,7 @@
<TextBlock FontSize="14" Text="Channel" /> <TextBlock FontSize="14" Text="Channel" />
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Choose which deployment channel Roblox should be downloaded from." Foreground="{DynamicResource TextFillColorTertiaryBrush}" /> <TextBlock Margin="0,2,0,0" FontSize="12" Text="Choose which deployment channel Roblox should be downloaded from." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
</StackPanel> </StackPanel>
<ComboBox Grid.Column="1" Margin="8,0,8,0" Padding="10,5,10,5" Width="200" IsEditable="True" ItemsSource="{Binding Channels, Mode=OneWay}" Text="{Binding SelectedChannel, Mode=TwoWay, Delay=250}" /> <ui:TextBox Grid.Column="1" Margin="8,0,8,0" Padding="10,5,10,5" Width="200" Text="{Binding SelectedChannel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Delay=250}" />
</Grid> </Grid>
</ui:CardExpander.Header> </ui:CardExpander.Header>

View File

@ -1,5 +1,4 @@
using Bloxstrap.UI.ViewModels.Menu; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -14,6 +13,8 @@ using System.Windows.Media.Imaging;
using System.Windows.Navigation; using System.Windows.Navigation;
using System.Windows.Shapes; using System.Windows.Shapes;
using Bloxstrap.UI.ViewModels.Menu;
namespace Bloxstrap.UI.Elements.Menu.Pages namespace Bloxstrap.UI.Elements.Menu.Pages
{ {
/// <summary> /// <summary>

View File

@ -17,7 +17,7 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="0,0,0,16" Text="Manage your own FastFlags. Double click the value column to edit." FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" /> <TextBlock Grid.Row="0" Margin="0,0,0,16" Text="Manage your own FastFlags. Double click a column to edit." FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<StackPanel Grid.Row="1" Margin="0,0,0,16" Orientation="Horizontal"> <StackPanel Grid.Row="1" Margin="0,0,0,16" Orientation="Horizontal">
<ui:Button Icon="ArrowLeft48" Content="Back" Click="BackButton_Click" /> <ui:Button Icon="ArrowLeft48" Content="Back" Click="BackButton_Click" />
@ -87,7 +87,7 @@
</DataGrid.CellStyle> </DataGrid.CellStyle>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" /> <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Value" Binding="{Binding Value}" Width="*" /> <DataGridTextColumn Header="Value" Binding="{Binding Value}" Width="*" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>

View File

@ -8,6 +8,7 @@ using Microsoft.Win32;
using Wpf.Ui.Mvvm.Contracts; using Wpf.Ui.Mvvm.Contracts;
using Bloxstrap.UI.Elements.Dialogs; using Bloxstrap.UI.Elements.Dialogs;
using System.Xml.Linq;
namespace Bloxstrap.UI.Elements.Menu.Pages namespace Bloxstrap.UI.Elements.Menu.Pages
{ {
@ -111,11 +112,28 @@ namespace Bloxstrap.UI.Elements.Menu.Pages
break; */ break; */
case "Name": case "Name":
string newName = ((TextBox)e.EditingElement).Text; var textbox = e.EditingElement as TextBox;
App.FastFlags.SetValue(entry.Name, null); string oldName = entry.Name;
string newName = textbox!.Text;
if (newName == oldName)
return;
if (App.FastFlags.GetValue(newName) is not null)
{
Controls.ShowMessageBox("A FastFlag with this name already exists.", MessageBoxImage.Information);
e.Cancel = true;
textbox.Text = oldName;
return;
}
App.FastFlags.SetValue(oldName, null);
App.FastFlags.SetValue(newName, entry.Value); App.FastFlags.SetValue(newName, entry.Value);
if (!newName.Contains(_searchFilter))
ClearSearch();
break; break;
case "Value": case "Value":
@ -215,20 +233,38 @@ namespace Bloxstrap.UI.Elements.Menu.Pages
private void ImportJSONButton_Click(object sender, RoutedEventArgs e) private void ImportJSONButton_Click(object sender, RoutedEventArgs e)
{ {
var dialog = new OpenFileDialog string json = "";
{ Dictionary<string, object>? list = null;
Filter = "JSON files|*.json|All files|*.*"
};
if (dialog.ShowDialog() != true) while (list is null)
{
var dialog = new BulkAddFastFlagDialog();
dialog.JsonTextBox.Text = json;
dialog.ShowDialog();
if (dialog.Result != MessageBoxResult.OK)
return; return;
json = dialog.JsonTextBox.Text;
try try
{ {
var list = JsonSerializer.Deserialize<Dictionary<string, object>>(File.ReadAllText(dialog.FileName)); list = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
if (list is null) if (list is null)
throw new Exception("JSON deserialization returned null"); throw new Exception("JSON deserialization returned null");
}
catch (Exception ex)
{
Controls.ShowMessageBox(
"The JSON you've entered does not appear to be valid. Please double check it and try again.\n" +
"\n" +
"More information:\n" +
$"{ex.Message}",
MessageBoxImage.Error
);
}
}
var conflictingFlags = App.FastFlags.Prop.Where(x => list.ContainsKey(x.Key)).Select(x => x.Key); var conflictingFlags = App.FastFlags.Prop.Where(x => list.ContainsKey(x.Key)).Select(x => x.Key);
bool overwriteConflicting = false; bool overwriteConflicting = false;
@ -257,17 +293,6 @@ namespace Bloxstrap.UI.Elements.Menu.Pages
ClearSearch(); ClearSearch();
} }
catch (Exception ex)
{
Controls.ShowMessageBox(
"The file you've selected does not appear to be valid JSON. Please double check the file contents and try again.\n" +
"\n" +
"More information:\n" +
$"{ex.Message}",
MessageBoxImage.Error
);
}
}
private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e) private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
{ {

View File

@ -171,15 +171,6 @@
</ui:CardControl.Header> </ui:CardControl.Header>
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding RenderingModes.Keys, Mode=OneTime}" Text="{Binding SelectedRenderingMode, Mode=TwoWay}" /> <ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding RenderingModes.Keys, Mode=OneTime}" Text="{Binding SelectedRenderingMode, Mode=TwoWay}" />
</ui:CardControl> </ui:CardControl>
<ui:CardControl Margin="0,8,0,0">
<ui:CardControl.Header>
<StackPanel>
<TextBlock FontSize="14" Text="Antialiasing quality" />
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Forces the amount of MSAA samples that are taken." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
</StackPanel>
</ui:CardControl.Header>
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding MSAAModes.Keys, Mode=OneTime}" Text="{Binding SelectedMSAAMode, Mode=TwoWay}" />
</ui:CardControl>
<ui:CardControl Margin="0,8,0,0"> <ui:CardControl Margin="0,8,0,0">
<ui:CardControl.Header> <ui:CardControl.Header>
<StackPanel> <StackPanel>

View File

@ -3,16 +3,29 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Menu.Pages"
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels"
xmlns:viewmodels="clr-namespace:Bloxstrap.UI.ViewModels.Menu"
d:DataContext="{d:DesignInstance Type=viewmodels:InstallationViewModel}"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
Title="InstallationPage" Title="InstallationPage">
Scrollable="True">
<StackPanel Margin="0,0,14,14"> <StackPanel Margin="0,0,14,14">
<TextBlock Text="Configure how Bloxstrap/Roblox is installed." FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" /> <TextBlock Text="Configure how Bloxstrap/Roblox is installed." FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<ui:CardExpander Margin="0,16,0,0" IsExpanded="True"> <ui:CardExpander Margin="0,16,0,0" IsExpanded="True">
<ui:CardExpander.Style>
<Style TargetType="ui:CardExpander" BasedOn="{StaticResource {x:Type ui:CardExpander}}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Source={x:Static models:GlobalViewModel.IsNotFirstRun}}" Value="False">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ui:CardExpander.Style>
<ui:CardExpander.Header> <ui:CardExpander.Header>
<StackPanel> <StackPanel>
<TextBlock FontSize="14" Text="Install Location" /> <TextBlock FontSize="14" Text="Install Location" />

View File

@ -1,4 +1,19 @@
using Bloxstrap.UI.ViewModels.Menu; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Bloxstrap.UI.ViewModels.Menu;
namespace Bloxstrap.UI.Elements.Menu.Pages namespace Bloxstrap.UI.Elements.Menu.Pages
{ {

View File

@ -13,7 +13,7 @@
<StackPanel Margin="0,0,14,14"> <StackPanel Margin="0,0,14,14">
<TextBlock Text="Configure quick and easy ways to improve the Roblox gameplay experience." FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" /> <TextBlock Text="Configure quick and easy ways to improve the Roblox gameplay experience." FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<TextBlock Text="Roblox activity tracking" FontSize="16" FontWeight="Medium" Margin="0,16,0,0" /> <TextBlock Text="Activity tracking" FontSize="16" FontWeight="Medium" Margin="0,16,0,0" />
<ui:CardControl Margin="0,8,0,0"> <ui:CardControl Margin="0,8,0,0">
<ui:CardControl.Header> <ui:CardControl.Header>
<StackPanel> <StackPanel>

View File

@ -82,7 +82,7 @@ namespace Bloxstrap.UI
string serverLocation = await _activityWatcher!.GetServerLocation(); string serverLocation = await _activityWatcher!.GetServerLocation();
ShowAlert( ShowAlert(
$"Connnected to {_activityWatcher.ActivityServerType.ToString().ToLower()} server", $"Connected to {_activityWatcher.ActivityServerType.ToString().ToLower()} server",
$"Located at {serverLocation}\nClick for more information", $"Located at {serverLocation}\nClick for more information",
10, 10,
(_, _) => _menuContainer?.ShowServerInformationWindow() (_, _) => _menuContainer?.ShowServerInformationWindow()
@ -124,7 +124,7 @@ namespace Bloxstrap.UI
if (_alertClickHandler == clickHandler) if (_alertClickHandler == clickHandler)
_alertClickHandler = null; _alertClickHandler = null;
else else
App.Logger.WriteLine(LOG_IDENT, "Click handler has been overriden by another alert"); App.Logger.WriteLine(LOG_IDENT, "Click handler has been overridden by another alert");
}); });
} }

View File

@ -30,6 +30,6 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
}); });
} }
private void CopyInstanceId() => Clipboard.SetText(InstanceId); private void CopyInstanceId() => Clipboard.SetDataObject(InstanceId);
} }
} }

View File

@ -81,18 +81,12 @@
set => App.Settings.Prop.CheckForUpdates = value; set => App.Settings.Prop.CheckForUpdates = value;
} }
public IEnumerable<string> Channels => RobloxDeployment.SelectableChannels;
public string SelectedChannel public string SelectedChannel
{ {
get => App.Settings.Prop.Channel; get => App.Settings.Prop.Channel;
set set
{ {
value = value.Trim(); value = value.Trim();
if (String.IsNullOrEmpty(value))
value = RobloxDeployment.DefaultChannel;
Task.Run(() => LoadChannelDeployInfo(value)); Task.Run(() => LoadChannelDeployInfo(value));
App.Settings.Prop.Channel = value; App.Settings.Prop.Channel = value;
} }

View File

@ -101,9 +101,12 @@ namespace Bloxstrap.UI.ViewModels.Menu
foreach (var flag in version.Value) foreach (var flag in version.Value)
{ {
if (App.FastFlags.GetPreset($"UI.Menu.Style.{flag.Key}") != flag.Value) foreach (var presetFlag in FastFlagManager.PresetFlags.Where(x => x.Key.StartsWith($"UI.Menu.Style.{flag.Key}")))
{
if (App.FastFlags.GetValue(presetFlag.Value) != flag.Value)
flagsMatch = false; flagsMatch = false;
} }
}
if (flagsMatch) if (flagsMatch)
return version.Key; return version.Key;
@ -127,14 +130,6 @@ namespace Bloxstrap.UI.ViewModels.Menu
set => App.FastFlags.SetPresetEnum("Rendering.Lighting", LightingModes[value], "True"); set => App.FastFlags.SetPresetEnum("Rendering.Lighting", LightingModes[value], "True");
} }
public IReadOnlyDictionary<string, string?> MSAAModes => FastFlagManager.MSAAModes;
public string SelectedMSAAMode
{
get => MSAAModes.First(x => x.Value == App.FastFlags.GetPreset("Rendering.MSAA")).Key ?? MSAAModes.First().Key;
set => App.FastFlags.SetPreset("Rendering.MSAA", MSAAModes[value]);
}
public bool GuiHidingEnabled public bool GuiHidingEnabled
{ {
get => App.FastFlags.GetPreset("UI.Hide") == "32380007"; get => App.FastFlags.GetPreset("UI.Hide") == "32380007";

View File

@ -14,8 +14,6 @@ namespace Bloxstrap.UI.ViewModels.Menu
public class MainWindowViewModel : NotifyPropertyChangedViewModel public class MainWindowViewModel : NotifyPropertyChangedViewModel
{ {
private readonly Window _window; private readonly Window _window;
private readonly IDialogService _dialogService;
private readonly string _originalBaseDirectory = App.BaseDirectory; // we need this to check if the basedirectory changes
public ICommand CloseWindowCommand => new RelayCommand(CloseWindow); public ICommand CloseWindowCommand => new RelayCommand(CloseWindow);
public ICommand ConfirmSettingsCommand => new RelayCommand(ConfirmSettings); public ICommand ConfirmSettingsCommand => new RelayCommand(ConfirmSettings);
@ -24,25 +22,31 @@ namespace Bloxstrap.UI.ViewModels.Menu
public string ConfirmButtonText => App.IsFirstRun ? "Install" : "Save"; public string ConfirmButtonText => App.IsFirstRun ? "Install" : "Save";
public bool ConfirmButtonEnabled { get; set; } = true; public bool ConfirmButtonEnabled { get; set; } = true;
public MainWindowViewModel(Window window, IDialogService dialogService) public MainWindowViewModel(Window window)
{ {
_window = window; _window = window;
_dialogService = dialogService;
} }
private void CloseWindow() => _window.Close(); private void CloseWindow() => _window.Close();
private void ConfirmSettings() private void ConfirmSettings()
{ {
if (!App.IsFirstRun)
{
App.ShouldSaveConfigs = true;
App.FastFlags.Save();
CloseWindow();
return;
}
if (string.IsNullOrEmpty(App.BaseDirectory)) if (string.IsNullOrEmpty(App.BaseDirectory))
{ {
Controls.ShowMessageBox("You must set an install location", MessageBoxImage.Error); Controls.ShowMessageBox("You must set an install location", MessageBoxImage.Error);
return; return;
} }
bool shouldCheckInstallLocation = App.IsFirstRun || App.BaseDirectory != _originalBaseDirectory; if (NavigationVisibility == Visibility.Visible)
if (shouldCheckInstallLocation && NavigationVisibility == Visibility.Visible)
{ {
try try
{ {
@ -87,10 +91,24 @@ namespace Bloxstrap.UI.ViewModels.Menu
else if (result == MessageBoxResult.Cancel) else if (result == MessageBoxResult.Cancel)
return; return;
} }
if (
App.BaseDirectory.Length <= 3 || // prevent from installing to the root of a drive
App.BaseDirectory.StartsWith("\\\\") || // i actually haven't encountered anyone doing this and i dont even know if this is possible but this is just to be safe lmao
App.BaseDirectory.ToLowerInvariant().Contains("onedrive") || // prevent from installing to a onedrive folder
Directory.GetParent(App.BaseDirectory)!.ToString().ToLowerInvariant() == Paths.UserProfile.ToLowerInvariant() // prevent from installing to an essential user profile folder
)
{
Controls.ShowMessageBox(
$"{App.ProjectName} cannot be installed here. Please choose a different location, or resort to using the default location by clicking the reset button.",
MessageBoxImage.Error,
MessageBoxButton.OK
);
return;
}
} }
if (App.IsFirstRun)
{
if (NavigationVisibility == Visibility.Visible) if (NavigationVisibility == Visibility.Visible)
{ {
((INavigationWindow)_window).Navigate(typeof(PreInstallPage)); ((INavigationWindow)_window).Navigate(typeof(PreInstallPage));
@ -115,28 +133,5 @@ namespace Bloxstrap.UI.ViewModels.Menu
CloseWindow(); CloseWindow();
} }
} }
else
{
App.ShouldSaveConfigs = true;
App.FastFlags.Save();
if (shouldCheckInstallLocation)
{
App.Logger.WriteLine("MainWindowViewModel::ConfirmSettings", $"Changing install location from {_originalBaseDirectory} to {App.BaseDirectory}");
Controls.ShowMessageBox(
$"{App.ProjectName} will install to the new location you've set the next time it runs.",
MessageBoxImage.Information
);
using RegistryKey registryKey = Registry.CurrentUser.CreateSubKey($@"Software\{App.ProjectName}");
registryKey.SetValue("InstallLocation", App.BaseDirectory);
registryKey.SetValue("OldInstallLocation", _originalBaseDirectory);
Paths.Initialize(App.BaseDirectory);
}
CloseWindow();
}
}
} }
} }

View File

@ -4,13 +4,15 @@
public sealed class AsyncMutex : IAsyncDisposable public sealed class AsyncMutex : IAsyncDisposable
{ {
private readonly bool _initiallyOwned;
private readonly string _name; private readonly string _name;
private Task? _mutexTask; private Task? _mutexTask;
private ManualResetEventSlim? _releaseEvent; private ManualResetEventSlim? _releaseEvent;
private CancellationTokenSource? _cancellationTokenSource; private CancellationTokenSource? _cancellationTokenSource;
public AsyncMutex(string name) public AsyncMutex(bool initiallyOwned, string name)
{ {
_initiallyOwned = initiallyOwned;
_name = name; _name = name;
} }
@ -31,7 +33,7 @@
try try
{ {
CancellationToken cancellationToken = _cancellationTokenSource.Token; CancellationToken cancellationToken = _cancellationTokenSource.Token;
using var mutex = new Mutex(false, _name); using var mutex = new Mutex(_initiallyOwned, _name);
try try
{ {
// Wait for either the mutex to be acquired, or cancellation // Wait for either the mutex to be acquired, or cancellation