Merge remote-tracking branch 'real/main' into user-pfp-discord-rpc

This commit is contained in:
axellse 2024-09-21 21:09:50 +02:00
commit 530bb84095
77 changed files with 1030 additions and 878 deletions

View File

@ -8,6 +8,7 @@ body:
### **Preliminary instructions**
- Before opening an issue, please [check the Wiki first](https://github.com/pizzaboxer/bloxstrap/wiki/) to see if your problem has been addressed there.
- If it isn't, please confirm which pages that you read that were relevant to your issue.
- Your issue ***will*** be closed without warning if there's a Wiki page addressing your problem.
- If your problem is with Roblox itself (i.e. it crashes or doesn't launch), [check to see if it happens without Bloxstrap](https://github.com/pizzaboxer/bloxstrap/wiki/Roblox-crashes-or-does-not-launch).
- Please only open an issue if your problem happens only with Bloxstrap, and state clearly that this is the case, as anything else is out of my control.
- If you are getting a Bloxstrap Exception error, please attach a copy of the provided log file. There is a button on the dialog that locates it for you.

View File

@ -57,21 +57,13 @@ namespace Bloxstrap
private static bool _showingExceptionDialog = false;
private static bool _terminating = false;
public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
{
if (_terminating)
return;
int exitCodeNum = (int)exitCode;
Logger.WriteLine("App::Terminate", $"Terminating with exit code {exitCodeNum} ({exitCode})");
Current.Dispatcher.Invoke(() => Current.Shutdown(exitCodeNum));
// Environment.Exit(exitCodeNum);
_terminating = true;
Environment.Exit(exitCodeNum);
}
void GlobalExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs e)
@ -101,8 +93,7 @@ namespace Bloxstrap
_showingExceptionDialog = true;
if (!LaunchSettings.QuietFlag.Active)
Frontend.ShowExceptionDialog(ex);
Frontend.ShowExceptionDialog(ex);
Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
}
@ -110,6 +101,7 @@ namespace Bloxstrap
public static async Task<GithubRelease?> GetLatestRelease()
{
const string LOG_IDENT = "App::GetLatestRelease";
try
{
var releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{ProjectRepository}/releases/latest");
@ -199,6 +191,26 @@ namespace Bloxstrap
}
}
if (fixInstallLocation && installLocation is not null)
{
var installer = new Installer
{
InstallLocation = installLocation,
IsImplicitInstall = true
};
if (installer.CheckInstallLocation())
{
Logger.WriteLine(LOG_IDENT, $"Changing install location to '{installLocation}'");
installer.DoInstall();
}
else
{
// force reinstall
installLocation = null;
}
}
if (installLocation is null)
{
Logger.Initialize(true);
@ -206,21 +218,6 @@ namespace Bloxstrap
}
else
{
if (fixInstallLocation)
{
var installer = new Installer
{
InstallLocation = installLocation,
IsImplicitInstall = true
};
if (installer.CheckInstallLocation())
{
Logger.WriteLine(LOG_IDENT, $"Changing install location to '{installLocation}'");
installer.DoInstall();
}
}
Paths.Initialize(installLocation);
// ensure executable is in the install directory
@ -247,10 +244,8 @@ namespace Bloxstrap
Locale.Set(Settings.Prop.Locale);
#if !DEBUG
if (!LaunchSettings.BypassUpdateCheck)
Installer.HandleUpgrade();
#endif
LaunchHandler.ProcessLaunchArgs();
}

View File

@ -39,8 +39,17 @@ namespace Bloxstrap.AppData
{ "extracontent-places.zip", @"ExtraContent\places\" },
};
public virtual string ExecutableName { get; } = null!;
public virtual string Directory { get; } = null!;
public string LockFilePath => Path.Combine(Directory, "Bloxstrap.lock");
public string ExecutablePath => Path.Combine(Directory, ExecutableName);
public virtual IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; }
public CommonAppData()
{
if (PackageDirectoryMap is null)

View File

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.AppData
namespace Bloxstrap.AppData
{
internal interface IAppData
{
@ -18,6 +12,14 @@ namespace Bloxstrap.AppData
string StartEvent { get; }
string Directory { get; }
string LockFilePath { get; }
string ExecutablePath { get; }
AppState State { get; }
IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; }
}
}

View File

@ -8,15 +8,19 @@ namespace Bloxstrap.AppData
{
public class RobloxPlayerData : CommonAppData, IAppData
{
public string ProductName { get; } = "Roblox";
public string ProductName => "Roblox";
public string BinaryType { get; } = "WindowsPlayer";
public string BinaryType => "WindowsPlayer";
public string RegistryName { get; } = "RobloxPlayer";
public string RegistryName => "RobloxPlayer";
public string ExecutableName { get; } = "RobloxPlayerBeta.exe";
public override string ExecutableName => "RobloxPlayerBeta.exe";
public string StartEvent { get; } = "www.roblox.com/robloxStartedEvent";
public string StartEvent => "www.roblox.com/robloxStartedEvent";
public override string Directory => Path.Combine(Paths.Roblox, "Player");
public AppState State => App.State.Prop.Player;
public override IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
{

View File

@ -1,22 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.AppData
namespace Bloxstrap.AppData
{
public class RobloxStudioData : CommonAppData, IAppData
{
public string ProductName { get; } = "Roblox Studio";
public string ProductName => "Roblox Studio";
public string BinaryType { get; } = "WindowsStudio64";
public string BinaryType => "WindowsStudio64";
public string RegistryName { get; } = "RobloxStudio";
public string RegistryName => "RobloxStudio";
public string ExecutableName { get; } = "RobloxStudioBeta.exe";
public override string ExecutableName => "RobloxStudioBeta.exe";
public string StartEvent { get; } = "www.roblox.com/robloxStudioStartedEvent";
public string StartEvent => "www.roblox.com/robloxStudioStartedEvent";
public override string Directory => Path.Combine(Paths.Roblox, "Studio");
public AppState State => App.State.Prop.Studio;
public override IReadOnlyDictionary<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
{

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.Exceptions
{
internal class HttpResponseException : Exception
{
public HttpResponseMessage ResponseMessage { get; }
public HttpResponseException(HttpResponseMessage responseMessage)
: base($"Could not connect to {responseMessage.RequestMessage!.RequestUri} because it returned HTTP {(int)responseMessage.StatusCode} ({responseMessage.ReasonPhrase})")
{
ResponseMessage = responseMessage;
}
}
}

View File

@ -9,15 +9,10 @@ namespace Bloxstrap.Extensions
if (dialogTheme != Theme.Default)
return dialogTheme;
RegistryKey? key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
using var key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
if (key is not null)
{
var value = key.GetValue("AppsUseLightTheme");
if (value is not null && (int)value == 0)
return Theme.Dark;
}
if (key?.GetValue("AppsUseLightTheme") is int value && value == 0)
return Theme.Dark;
return Theme.Light;
}

View File

@ -49,7 +49,6 @@ namespace Bloxstrap
{ "UI.FlagState", "FStringDebugShowFlagState" },
#endif
{ "UI.Menu.GraphicsSlider", "FFlagFixGraphicsQuality" },
{ "UI.FullscreenTitlebarDelay", "FIntFullscreenTitleBarTriggerDelayMillis" },
{ "UI.Menu.Style.V2Rollout", "FIntNewInGameMenuPercentRollout3" },
@ -62,7 +61,6 @@ namespace Bloxstrap
{ "UI.Menu.Style.ABTest.3", "FFlagEnableInGameMenuChromeABTest3" }
};
// only one missing here is Metal because lol
public static IReadOnlyDictionary<RenderingMode, string> RenderingModes => new Dictionary<RenderingMode, string>
{
{ RenderingMode.Default, "None" },

View File

@ -2,8 +2,6 @@
{
public static class GlobalCache
{
public static readonly Dictionary<string, Task> PendingTasks = new();
public static readonly Dictionary<string, string> ServerLocation = new();
public static readonly Dictionary<string, string?> ServerLocation = new();
}
}

View File

@ -3,7 +3,6 @@ global using System.Collections.Generic;
global using System.Diagnostics;
global using System.Globalization;
global using System.IO;
global using System.IO.Compression;
global using System.Text;
global using System.Text.Json;
global using System.Text.Json.Serialization;
@ -18,10 +17,16 @@ global using Bloxstrap.Enums;
global using Bloxstrap.Exceptions;
global using Bloxstrap.Extensions;
global using Bloxstrap.Models;
global using Bloxstrap.Models.APIs.Config;
global using Bloxstrap.Models.APIs.GitHub;
global using Bloxstrap.Models.APIs.Roblox;
global using Bloxstrap.Models.Attributes;
global using Bloxstrap.Models.BloxstrapRPC;
global using Bloxstrap.Models.RobloxApi;
global using Bloxstrap.Models.Entities;
global using Bloxstrap.Models.Manifest;
global using Bloxstrap.Models.Persistable;
global using Bloxstrap.Models.SettingTasks;
global using Bloxstrap.Models.SettingTasks.Base;
global using Bloxstrap.Resources;
global using Bloxstrap.UI;
global using Bloxstrap.Utility;

View File

@ -35,7 +35,19 @@ namespace Bloxstrap
if (!IsImplicitInstall)
{
Filesystem.AssertReadOnly(Paths.Application);
File.Copy(Paths.Process, Paths.Application, true);
try
{
File.Copy(Paths.Process, Paths.Application, true);
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, "Could not overwrite executable");
App.Logger.WriteException(LOG_IDENT, ex);
Frontend.ShowMessageBox(Strings.Installer_Install_CannotOverwrite, MessageBoxImage.Error);
App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
}
}
// TODO: registry access checks, i'll need to look back on issues to see what the error looks like
@ -63,10 +75,7 @@ namespace Bloxstrap
// only register player, for the scenario where the user installs bloxstrap, closes it,
// and then launches from the website expecting it to work
// studio can be implicitly registered when it's first launched manually
ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\"");
ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\"");
// TODO: implicit installation needs to reregister studio
WindowsRegistry.RegisterPlayer();
if (CreateDesktopShortcuts)
Shortcut.Create(Paths.Application, "", DesktopShortcut);
@ -79,6 +88,9 @@ namespace Bloxstrap
App.State.Load(false);
App.FastFlags.Load(false);
if (!String.IsNullOrEmpty(App.State.Prop.Studio.VersionGuid))
WindowsRegistry.RegisterStudio();
App.Logger.WriteLine(LOG_IDENT, "Installation finished");
}
@ -92,6 +104,10 @@ namespace Bloxstrap
if (InstallLocation.StartsWith("\\\\"))
return false;
if (InstallLocation.StartsWith(Path.GetTempPath(), StringComparison.InvariantCultureIgnoreCase)
|| InstallLocation.Contains("\\Temp\\", StringComparison.InvariantCultureIgnoreCase))
return false;
// prevent from installing to a onedrive folder
if (InstallLocation.Contains("OneDrive", StringComparison.InvariantCultureIgnoreCase))
return false;
@ -162,11 +178,12 @@ namespace Bloxstrap
const string LOG_IDENT = "Installer::DoUninstall";
var processes = new List<Process>();
processes.AddRange(Process.GetProcessesByName(App.RobloxPlayerAppName));
#if STUDIO_FEATURES
processes.AddRange(Process.GetProcessesByName(App.RobloxStudioAppName));
#endif
if (!String.IsNullOrEmpty(App.State.Prop.Player.VersionGuid))
processes.AddRange(Process.GetProcessesByName(App.RobloxPlayerAppName));
if (!String.IsNullOrEmpty(App.State.Prop.Studio.VersionGuid))
processes.AddRange(Process.GetProcessesByName(App.RobloxStudioAppName));
// prompt to shutdown roblox if its currently running
if (processes.Any())
@ -179,7 +196,10 @@ namespace Bloxstrap
);
if (result != MessageBoxResult.OK)
{
App.Terminate(ErrorCode.ERROR_CANCELLED);
return;
}
try
{
@ -207,44 +227,38 @@ namespace Bloxstrap
{
playerStillInstalled = false;
ProtocolHandler.Unregister("roblox");
ProtocolHandler.Unregister("roblox-player");
WindowsRegistry.Unregister("roblox");
WindowsRegistry.Unregister("roblox-player");
}
else
{
// revert launch uri handler to stock bootstrapper
string playerPath = Path.Combine((string)playerFolder, "RobloxPlayerBeta.exe");
ProtocolHandler.Register("roblox", "Roblox", playerPath);
ProtocolHandler.Register("roblox-player", "Roblox", playerPath);
WindowsRegistry.RegisterPlayer(playerPath, "%1");
}
using RegistryKey? studioBootstrapperKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-studio");
if (studioBootstrapperKey is null)
using var studioKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\roblox-studio");
var studioFolder = studioKey?.GetValue("InstallLocation");
if (studioKey is null || studioFolder is not string)
{
studioStillInstalled = false;
#if STUDIO_FEATURES
ProtocolHandler.Unregister("roblox-studio");
ProtocolHandler.Unregister("roblox-studio-auth");
WindowsRegistry.Unregister("roblox-studio");
WindowsRegistry.Unregister("roblox-studio-auth");
ProtocolHandler.Unregister("Roblox.Place");
ProtocolHandler.Unregister(".rbxl");
ProtocolHandler.Unregister(".rbxlx");
#endif
WindowsRegistry.Unregister("Roblox.Place");
WindowsRegistry.Unregister(".rbxl");
WindowsRegistry.Unregister(".rbxlx");
}
#if STUDIO_FEATURES
else
{
string studioLocation = (string?)studioBootstrapperKey.GetValue("InstallLocation") + "RobloxStudioBeta.exe"; // points to studio exe instead of bootstrapper
ProtocolHandler.Register("roblox-studio", "Roblox", studioLocation);
ProtocolHandler.Register("roblox-studio-auth", "Roblox", studioLocation);
string studioPath = Path.Combine((string)studioFolder, "RobloxStudioBeta.exe");
string studioLauncherPath = Path.Combine((string)studioFolder, "RobloxStudioLauncherBeta.exe");
ProtocolHandler.RegisterRobloxPlace(studioLocation);
WindowsRegistry.RegisterStudioProtocol(studioPath, "%1");
WindowsRegistry.RegisterStudioFileClass(studioPath, "-ide \"%1\"");
}
#endif
var cleanupSequence = new List<Action>
{
@ -261,8 +275,10 @@ namespace Bloxstrap
() => File.Delete(StartMenuShortcut),
() => Directory.Delete(Paths.Versions, true),
() => Directory.Delete(Paths.Downloads, true),
() => Directory.Delete(Paths.Roblox, true),
() => File.Delete(App.State.FileLocation)
};
if (!keepData)
@ -272,8 +288,7 @@ namespace Bloxstrap
() => Directory.Delete(Paths.Modifications, true),
() => Directory.Delete(Paths.Logs, true),
() => File.Delete(App.Settings.FileLocation),
() => File.Delete(App.State.FileLocation), // TODO: maybe this should always be deleted? not sure yet
() => File.Delete(App.Settings.FileLocation)
});
}
@ -383,15 +398,30 @@ namespace Bloxstrap
}
}
try
// prior to 2.8.0, auto-updating was handled with this... bruteforce method
// now it's handled with the system mutex you see above, but we need to keep this logic for <2.8.0 versions
for (int i = 1; i <= 10; i++)
{
File.Copy(Paths.Process, Paths.Application, true);
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not replace executable)");
App.Logger.WriteException(LOG_IDENT, ex);
return;
try
{
File.Copy(Paths.Process, Paths.Application, true);
break;
}
catch (Exception ex)
{
if (i == 1)
{
App.Logger.WriteLine(LOG_IDENT, "Waiting for write permissions to update version");
}
else if (i == 10)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not get write permissions after 10 tries/5 seconds)");
App.Logger.WriteException(LOG_IDENT, ex);
return;
}
Thread.Sleep(500);
}
}
using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey))
@ -512,8 +542,7 @@ namespace Bloxstrap
Registry.CurrentUser.DeleteSubKeyTree("Software\\Bloxstrap", false);
ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\"");
ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\"");
WindowsRegistry.RegisterPlayer();
string? oldV2Val = App.FastFlags.GetValue("FFlagDisableNewIGMinDUA");
@ -526,6 +555,10 @@ namespace Bloxstrap
App.FastFlags.SetValue("FFlagDisableNewIGMinDUA", null);
}
App.FastFlags.SetValue("FFlagFixGraphicsQuality", null);
Directory.Delete(Path.Combine(Paths.Base, "Versions"));
}
App.Settings.Save();

View File

@ -1,6 +1,4 @@
using System.Windows;
namespace Bloxstrap.Integrations
namespace Bloxstrap.Integrations
{
public class ActivityWatcher : IDisposable
{
@ -40,7 +38,6 @@ namespace Bloxstrap.Integrations
public event EventHandler? OnAppClose;
public event EventHandler<Message>? OnRPCMessage;
private readonly Dictionary<string, string> GeolocationCache = new();
private DateTime LastRPCRequest;
public string LogLocation = null!;

View File

@ -60,7 +60,21 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, $"Saving to {FileLocation}...");
Directory.CreateDirectory(Path.GetDirectoryName(FileLocation)!);
File.WriteAllText(FileLocation, JsonSerializer.Serialize(Prop, new JsonSerializerOptions { WriteIndented = true }));
try
{
File.WriteAllText(FileLocation, JsonSerializer.Serialize(Prop, new JsonSerializerOptions { WriteIndented = true }));
}
catch (IOException ex)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to save");
App.Logger.WriteException(LOG_IDENT, ex);
string errorMessage = string.Format(Resources.Strings.Bootstrapper_JsonManagerSaveFailed, ClassName, ex.Message);
Frontend.ShowMessageBox(errorMessage, System.Windows.MessageBoxImage.Warning);
return;
}
App.Logger.WriteLine(LOG_IDENT, "Save complete!");
}

View File

@ -1,6 +1,5 @@
using System.Windows;
using Microsoft.Win32;
using Windows.Win32;
using Windows.Win32.Foundation;
@ -43,6 +42,8 @@ namespace Bloxstrap
LaunchRoblox();
else if (!App.LaunchSettings.QuietFlag.Active)
LaunchMenu();
else
App.Terminate();
}
public static void LaunchInstaller()
@ -52,6 +53,7 @@ namespace Bloxstrap
if (!interlock.IsAcquired)
{
Frontend.ShowMessageBox(Strings.Dialog_AlreadyRunning_Installer, MessageBoxImage.Stop);
App.Terminate();
return;
}
@ -96,6 +98,7 @@ namespace Bloxstrap
if (!interlock.IsAcquired)
{
Frontend.ShowMessageBox(Strings.Dialog_AlreadyRunning_Uninstaller, MessageBoxImage.Stop);
App.Terminate();
return;
}
@ -116,7 +119,10 @@ namespace Bloxstrap
}
if (!confirmed)
{
App.Terminate();
return;
}
Installer.DoUninstall(keepData);
@ -134,7 +140,9 @@ namespace Bloxstrap
if (interlock.IsAcquired)
{
bool showAlreadyRunningWarning = Process.GetProcessesByName(App.ProjectName).Length > 1;
new UI.Elements.Settings.MainWindow(showAlreadyRunningWarning).Show();
var window = new UI.Elements.Settings.MainWindow(showAlreadyRunningWarning);
window.Show();
}
else
{
@ -169,15 +177,6 @@ namespace Bloxstrap
App.Terminate(ErrorCode.ERROR_FILE_NOT_FOUND);
}
bool installWebView2 = false;
{
using var hklmKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
using var hkcuKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
if (hklmKey is null && hkcuKey is null)
installWebView2 = Frontend.ShowMessageBox(Strings.Bootstrapper_WebView2NotFound, MessageBoxImage.Warning, MessageBoxButton.YesNo, MessageBoxResult.Yes) == MessageBoxResult.Yes;
}
if (App.Settings.Prop.ConfirmLaunches && Mutex.TryOpenExisting("ROBLOX_singletonMutex", out var _))
{
// this currently doesn't work very well since it relies on checking the existence of the singleton mutex
@ -195,7 +194,7 @@ namespace Bloxstrap
// start bootstrapper and show the bootstrapper modal if we're not running silently
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
var bootstrapper = new Bootstrapper(installWebView2);
var bootstrapper = new Bootstrapper();
IBootstrapperDialog? dialog = null;
if (!App.LaunchSettings.QuietFlag.Active)

View File

@ -28,7 +28,11 @@ namespace Bloxstrap
public LaunchFlag StudioFlag { get; } = new("studio");
#if DEBUG
public bool BypassUpdateCheck => true;
#else
public bool BypassUpdateCheck => UninstallFlag.Active || WatcherFlag.Active;
#endif
public LaunchMode RobloxLaunchMode { get; set; } = LaunchMode.None;

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models
namespace Bloxstrap.Models.APIs.Config
{
public class Supporter
{

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models
namespace Bloxstrap.Models.APIs.Config
{
public class SupporterData
{

View File

@ -0,0 +1,8 @@
public class GithubReleaseAsset
{
[JsonPropertyName("browser_download_url")]
public string BrowserDownloadUrl { get; set; } = null!;
[JsonPropertyName("name")]
public string Name { get; set; } = null!;
}

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models
namespace Bloxstrap.Models.APIs.GitHub
{
public class GithubRelease
{
@ -17,13 +17,4 @@
[JsonPropertyName("assets")]
public List<GithubReleaseAsset>? Assets { get; set; }
}
public class GithubReleaseAsset
{
[JsonPropertyName("browser_download_url")]
public string BrowserDownloadUrl { get; set; } = null!;
[JsonPropertyName("name")]
public string Name { get; set; } = null!;
}
}

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models
namespace Bloxstrap.Models.APIs
{
public class IPInfoResponse
{

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models.RobloxApi
namespace Bloxstrap.Models.APIs.Roblox
{
/// <summary>
/// Roblox.Web.WebAPI.Models.ApiArrayResponse

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models
namespace Bloxstrap.Models.APIs.Roblox
{
public class ClientFlagSettings
{

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models
namespace Bloxstrap.Models.APIs.Roblox
{
public class ClientVersion
{

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models.RobloxApi
namespace Bloxstrap.Models.APIs.Roblox
{
/// <summary>
/// Roblox.Games.Api.Models.Response.GameCreator

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models.RobloxApi
namespace Bloxstrap.Models.APIs.Roblox
{
/// <summary>

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models.RobloxApi
namespace Bloxstrap.Models.APIs.Roblox
{
/// <summary>
/// Roblox.Web.Responses.Thumbnails.ThumbnailResponse

View File

@ -1,4 +1,4 @@
namespace Bloxstrap.Models.RobloxApi
namespace Bloxstrap.Models.APIs.Roblox
{
// lmao its just one property
public class UniverseIdResponse

View File

@ -1,10 +1,10 @@
using System.Web;
using System.Windows;
using System.Windows.Input;
using Bloxstrap.Models.APIs;
using CommunityToolkit.Mvvm.Input;
namespace Bloxstrap.Models
namespace Bloxstrap.Models.Entities
{
public class ActivityData
{
@ -28,18 +28,18 @@ namespace Bloxstrap.Models
public long PlaceId { get; set; } = 0;
public string JobId { get; set; } = String.Empty;
public string JobId { get; set; } = string.Empty;
/// <summary>
/// This will be empty unless the server joined is a private server
/// </summary>
public string AccessCode { get; set; } = String.Empty;
public string MachineAddress { get; set; } = String.Empty;
public string AccessCode { get; set; } = string.Empty;
public string UserId { get; set; } = String.Empty;
public bool MachineAddressValid => !String.IsNullOrEmpty(MachineAddress) && !MachineAddress.StartsWith("10.");
public string MachineAddress { get; set; } = string.Empty;
public bool MachineAddressValid => !string.IsNullOrEmpty(MachineAddress) && !MachineAddress.StartsWith("10.");
public bool IsTeleport { get; set; } = false;
@ -54,7 +54,7 @@ namespace Bloxstrap.Models
/// <summary>
/// This is intended only for other people to use, i.e. context menu invite link, rich presence joining
/// </summary>
public string RPCLaunchData { get; set; } = String.Empty;
public string RPCLaunchData { get; set; } = string.Empty;
public UniverseDetails? UniverseDetails { get; set; }
@ -62,7 +62,7 @@ namespace Bloxstrap.Models
{
get
{
string desc = String.Format("{0} • {1} - {2}", UniverseDetails?.Data.Creator.Name, TimeJoined.ToString("h:mm tt"), TimeLeft?.ToString("h:mm tt"));
string desc = string.Format("{0} • {1} - {2}", UniverseDetails?.Data.Creator.Name, TimeJoined.ToString("h:mm tt"), TimeLeft?.ToString("h:mm tt"));
if (ServerType != ServerType.Public)
desc += " • " + ServerType.ToTranslatedString();
@ -73,6 +73,8 @@ namespace Bloxstrap.Models
public ICommand RejoinServerCommand => new RelayCommand(RejoinServer);
private SemaphoreSlim serverQuerySemaphore = new(1, 1);
public string GetInviteDeeplink(bool launchData = true)
{
string deeplink = $"roblox://experiences/start?placeId={PlaceId}";
@ -82,37 +84,32 @@ namespace Bloxstrap.Models
else
deeplink += "&gameInstanceId=" + JobId;
if (launchData && !String.IsNullOrEmpty(RPCLaunchData))
if (launchData && !string.IsNullOrEmpty(RPCLaunchData))
deeplink += "&launchData=" + HttpUtility.UrlEncode(RPCLaunchData);
return deeplink;
}
public async Task<string> QueryServerLocation()
public async Task<string?> QueryServerLocation()
{
const string LOG_IDENT = "ActivityData::QueryServerLocation";
if (!MachineAddressValid)
throw new InvalidOperationException($"Machine address is invalid ({MachineAddress})");
if (GlobalCache.PendingTasks.TryGetValue(MachineAddress, out Task? task))
await task;
await serverQuerySemaphore.WaitAsync();
if (GlobalCache.ServerLocation.TryGetValue(MachineAddress, out string? location))
{
serverQuerySemaphore.Release();
return location;
}
try
{
location = "";
var ipInfoTask = Http.GetJson<IPInfoResponse>($"https://ipinfo.io/{MachineAddress}/json");
var ipInfo = await Http.GetJson<IPInfoResponse>($"https://ipinfo.io/{MachineAddress}/json");
GlobalCache.PendingTasks.Add(MachineAddress, ipInfoTask);
var ipInfo = await ipInfoTask;
GlobalCache.PendingTasks.Remove(MachineAddress);
if (String.IsNullOrEmpty(ipInfo.City))
if (string.IsNullOrEmpty(ipInfo.City))
throw new InvalidHTTPResponseException("Reported city was blank");
if (ipInfo.City == ipInfo.Region)
@ -121,25 +118,32 @@ namespace Bloxstrap.Models
location = $"{ipInfo.City}, {ipInfo.Region}, {ipInfo.Country}";
GlobalCache.ServerLocation[MachineAddress] = location;
return location;
serverQuerySemaphore.Release();
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to get server location for {MachineAddress}");
App.Logger.WriteException(LOG_IDENT, ex);
Frontend.ShowMessageBox($"{Strings.ActivityWatcher_LocationQueryFailed}\n\n{ex.Message}", MessageBoxImage.Warning);
GlobalCache.ServerLocation[MachineAddress] = location;
serverQuerySemaphore.Release();
return "?";
Frontend.ShowConnectivityDialog(
string.Format(Strings.Dialog_Connectivity_UnableToConnect, "ipinfo.io"),
Strings.ActivityWatcher_LocationQueryFailed,
MessageBoxImage.Warning,
ex
);
}
return location;
}
public override string ToString() => $"{PlaceId}/{JobId}";
private void RejoinServer()
{
string playerPath = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid, "RobloxPlayerBeta.exe");
string playerPath = Path.Combine(Paths.Roblox, "Player", "RobloxPlayerBeta.exe");
Process.Start(playerPath, GetInviteDeeplink(false));
}

View File

@ -1,7 +1,7 @@
using System.Security.Cryptography;
using System.Windows.Markup;
namespace Bloxstrap.Models
namespace Bloxstrap.Models.Entities
{
public class ModPresetFileData
{

View File

@ -1,4 +1,6 @@
namespace Bloxstrap.Models
using Bloxstrap.Models.APIs.Roblox;
namespace Bloxstrap.Models.Entities
{
public class UniverseDetails
{
@ -13,9 +15,9 @@
public static UniverseDetails? LoadFromCache(long id)
{
var cacheQuery = _cache.Where(x => x.Data?.Id == id);
var cacheQuery = _cache.Where(x => x.Data?.Id == id);
if (cacheQuery.Any())
if (cacheQuery.Any())
return cacheQuery.First();
return null;

View File

@ -9,10 +9,15 @@ namespace Bloxstrap.Models.Manifest
public class Package
{
public string Name { get; set; } = "";
public string Signature { get; set; } = "";
public int PackedSize { get; set; }
public int Size { get; set; }
public string DownloadPath => Path.Combine(Paths.Downloads, Signature);
public override string ToString()
{
return $"[{Signature}] {Name}";

View File

@ -8,9 +8,9 @@ namespace Bloxstrap.Models.Manifest
{
public class PackageManifest : List<Package>
{
private PackageManifest(string data)
public PackageManifest(string data)
{
using StringReader reader = new StringReader(data);
using var reader = new StringReader(data);
string? version = reader.ReadLine();
if (version != "v0")
@ -46,13 +46,5 @@ namespace Bloxstrap.Models.Manifest
});
}
}
public static async Task<PackageManifest> Get(string versionGuid)
{
string pkgManifestUrl = RobloxDeployment.GetLocation($"/{versionGuid}-rbxPkgManifest.txt");
var pkgManifestData = await App.HttpClient.GetStringAsync(pkgManifestUrl);
return new PackageManifest(pkgManifestData);
}
}
}

View File

@ -0,0 +1,11 @@
namespace Bloxstrap.Models.Persistable
{
public class AppState
{
public string VersionGuid { get; set; } = string.Empty;
public Dictionary<string, string> PackageHashes { get; set; } = new();
public int Size { get; set; }
}
}

View File

@ -1,6 +1,6 @@
using System.Collections.ObjectModel;
namespace Bloxstrap.Models
namespace Bloxstrap.Models.Persistable
{
public class Settings
{
@ -26,6 +26,5 @@ namespace Bloxstrap.Models
// mod preset configuration
public bool UseDisableAppPatch { get; set; } = false;
public bool DisableFullscreenOptimizations { get; set; } = false;
}
}

View File

@ -0,0 +1,17 @@
namespace Bloxstrap.Models.Persistable
{
public class State
{
public bool ShowFFlagEditorWarning { get; set; } = true;
public bool PromptWebView2Install { get; set; } = true;
public AppState Player { get; set; } = new();
public AppState Studio { get; set; } = new();
public WindowState SettingsWindow { get; set; } = new();
public List<string> ModManifest { get; set; } = new();
}
}

View File

@ -0,0 +1,13 @@
namespace Bloxstrap.Models.Persistable
{
public class WindowState
{
public double Width { get; set; }
public double Height { get; set; }
public double Left { get; set; }
public double Top { get; set; }
}
}

View File

@ -52,9 +52,12 @@ namespace Bloxstrap.Models.SettingTasks
{
App.Logger.WriteException(LOG_IDENT, ex);
Frontend.ShowMessageBox(
String.Format(Strings.Menu_Mods_Presets_EmojiType_Error, ex.Message),
MessageBoxImage.Warning);
Frontend.ShowConnectivityDialog(
String.Format(Strings.Dialog_Connectivity_UnableToConnect, "GitHub"),
$"{Strings.Menu_Mods_Presets_EmojiType_Error}\n\n{Strings.Dialog_Connectivity_TryAgainLater}",
MessageBoxImage.Warning,
ex
);
}
}
else if (query is not null && query.Any())

View File

@ -1,4 +1,5 @@
using Bloxstrap.Models.SettingTasks.Base;
using Bloxstrap.Models.Entities;
using Bloxstrap.Models.SettingTasks.Base;
namespace Bloxstrap.Models.SettingTasks
{

View File

@ -1,4 +1,5 @@
using Bloxstrap.Models.SettingTasks.Base;
using Bloxstrap.Models.Entities;
using Bloxstrap.Models.SettingTasks.Base;
namespace Bloxstrap.Models.SettingTasks
{

View File

@ -1,17 +0,0 @@
namespace Bloxstrap.Models
{
public class State
{
public bool ShowFFlagEditorWarning { get; set; } = true;
[Obsolete("Use PlayerVersionGuid instead", true)]
public string VersionGuid { set { PlayerVersionGuid = value; } }
public string PlayerVersionGuid { get; set; } = "";
public string StudioVersionGuid { get; set; } = "";
public int PlayerSize { get; set; } = 0;
public int StudioSize { get; set; } = 0;
public List<string> ModManifest { get; set; } = new();
}
}

View File

@ -2,4 +2,4 @@
FlashWindow
GetWindowLong
SetWindowLong
EnumDisplaySettings
SHObjectProperties

View File

@ -20,8 +20,8 @@
public static string Downloads { get; private set; } = "";
public static string Logs { get; private set; } = "";
public static string Integrations { get; private set; } = "";
public static string Versions { get; private set; } = "";
public static string Modifications { get; private set; } = "";
public static string Roblox { get; private set; } = "";
public static string Application { get; private set; } = "";
@ -35,8 +35,8 @@
Downloads = Path.Combine(Base, "Downloads");
Logs = Path.Combine(Base, "Logs");
Integrations = Path.Combine(Base, "Integrations");
Versions = Path.Combine(Base, "Versions");
Modifications = Path.Combine(Base, "Modifications");
Roblox = Path.Combine(Base, "Roblox");
Application = Path.Combine(Base, $"{App.ProjectName}.exe");
}

View File

@ -106,7 +106,7 @@ namespace Bloxstrap.Resources {
}
/// <summary>
/// Looks up a localized string similar to Failed to query server location..
/// Looks up a localized string similar to The server location could not be queried. You may be joining games too quickly..
/// </summary>
public static string ActivityWatcher_LocationQueryFailed {
get {
@ -141,33 +141,6 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to It&apos;s possible that something is preventing Bloxstrap from connecting to the internet. Please check and try again..
/// </summary>
public static string Bootstrapper_Connectivity_Preventing {
get {
return ResourceManager.GetString("Bootstrapper.Connectivity.Preventing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Roblox may be down right now. See status.roblox.com for more information. Please try again later..
/// </summary>
public static string Bootstrapper_Connectivity_RobloxDown {
get {
return ResourceManager.GetString("Bootstrapper.Connectivity.RobloxDown", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bloxstrap timed out when trying to connect to three different Roblox deployment mirrors, indicating a poor internet connection. Please try again later..
/// </summary>
public static string Bootstrapper_Connectivity_TimedOut {
get {
return ResourceManager.GetString("Bootstrapper.Connectivity.TimedOut", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Could not apply the {0} emoji mod preset because of a network error. To try again, please reconfigure the option in the Bloxstrap Menu..
/// </summary>
@ -197,6 +170,15 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Failed to save {0}: {1}.
/// </summary>
public static string Bootstrapper_JsonManagerSaveFailed {
get {
return ResourceManager.GetString("Bootstrapper.JsonManagerSaveFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bloxstrap does not have enough disk space to download and install Roblox. Please free up some disk space and try again..
/// </summary>
@ -539,6 +521,15 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Not available.
/// </summary>
public static string Common_NotAvailable {
get {
return ResourceManager.GetString("Common.NotAvailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to OK.
/// </summary>
@ -575,6 +566,15 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Roblox has not yet been installed. Please launch Roblox using Bloxstrap at least once before trying to use this option..
/// </summary>
public static string Common_RobloxNotInstalled {
get {
return ResourceManager.GetString("Common.RobloxNotInstalled", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shortcuts.
/// </summary>
@ -828,6 +828,60 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Something is likely preventing Bloxstrap from connecting to the internet..
/// </summary>
public static string Dialog_Connectivity_Preventing {
get {
return ResourceManager.GetString("Dialog.Connectivity.Preventing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Roblox may be down right now. See {0} for more information..
/// </summary>
public static string Dialog_Connectivity_RobloxDown {
get {
return ResourceManager.GetString("Dialog.Connectivity.RobloxDown", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Because Roblox needs to be installed or upgraded, Bloxstrap cannot continue..
/// </summary>
public static string Dialog_Connectivity_RobloxUpgradeNeeded {
get {
return ResourceManager.GetString("Dialog.Connectivity.RobloxUpgradeNeeded", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to For this launch, Roblox will not be checked for upgrades, and changes to mods will not be applied..
/// </summary>
public static string Dialog_Connectivity_RobloxUpgradeSkip {
get {
return ResourceManager.GetString("Dialog.Connectivity.RobloxUpgradeSkip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} may be down right now..
/// </summary>
public static string Dialog_Connectivity_ServiceDown {
get {
return ResourceManager.GetString("Dialog.Connectivity.ServiceDown", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The connection timed out, which could indicate a poor internet connection or a firewall block..
/// </summary>
public static string Dialog_Connectivity_TimedOut {
get {
return ResourceManager.GetString("Dialog.Connectivity.TimedOut", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connectivity error.
/// </summary>
@ -838,7 +892,16 @@ namespace Bloxstrap.Resources {
}
/// <summary>
/// Looks up a localized string similar to Bloxstrap is unable to connect to Roblox.
/// Looks up a localized string similar to Please try again later..
/// </summary>
public static string Dialog_Connectivity_TryAgainLater {
get {
return ResourceManager.GetString("Dialog.Connectivity.TryAgainLater", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bloxstrap is unable to connect to {0}.
/// </summary>
public static string Dialog_Connectivity_UnableToConnect {
get {
@ -1410,6 +1473,17 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Bloxstrap has been installed to this location before and is still present, however the installer cannot overwrite the old executable.
///
///Please manually delete Bloxstrap.exe from the install location or try restarting your system, and then retry installation afterwards..
/// </summary>
public static string Installer_Install_CannotOverwrite {
get {
return ResourceManager.GetString("Installer.Install.CannotOverwrite", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Existing data found. Your mods and settings will be restored..
/// </summary>
@ -1455,6 +1529,15 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to cancel the installation?.
/// </summary>
public static string Installer_ShouldCancel {
get {
return ResourceManager.GetString("Installer.ShouldCancel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bloxstrap Installer.
/// </summary>
@ -2270,24 +2353,6 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Allows you to configure 21 different quality levels instead of 10..
/// </summary>
public static string Menu_FastFlags_Presets_AltGraphicsSelector_Description {
get {
return ResourceManager.GetString("Menu.FastFlags.Presets.AltGraphicsSelector.Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use advanced graphics quality selector.
/// </summary>
public static string Menu_FastFlags_Presets_AltGraphicsSelector_Title {
get {
return ResourceManager.GetString("Menu.FastFlags.Presets.AltGraphicsSelector.Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Rendering and Graphics.
/// </summary>
@ -2789,6 +2854,24 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Configure application parameters such as DPI scaling behaviour and [fullscreen optimizations]({0})..
/// </summary>
public static string Menu_Mods_Misc_CompatibilitySettings_Description {
get {
return ResourceManager.GetString("Menu.Mods.Misc.CompatibilitySettings.Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Manage compatibility settings.
/// </summary>
public static string Menu_Mods_Misc_CompatibilitySettings_Title {
get {
return ResourceManager.GetString("Menu.Mods.Misc.CompatibilitySettings.Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Choose font....
/// </summary>
@ -2880,9 +2963,7 @@ namespace Bloxstrap.Resources {
}
/// <summary>
/// Looks up a localized string similar to The emoji mod could not be applied because of a network error during download.
///
///{0}.
/// Looks up a localized string similar to The emoji mod can not be applied at this time..
/// </summary>
public static string Menu_Mods_Presets_EmojiType_Error {
get {

View File

@ -123,14 +123,14 @@
<data name="Bootstrapper.ConfirmLaunch" xml:space="preserve">
<value>Roblox is currently running, and launching another instance will close it. Are you sure you want to continue launching?</value>
</data>
<data name="Bootstrapper.Connectivity.Preventing" xml:space="preserve">
<value>It's possible that something is preventing Bloxstrap from connecting to the internet. Please check and try again.</value>
<data name="Dialog.Connectivity.Preventing" xml:space="preserve">
<value>Something is likely preventing Bloxstrap from connecting to the internet.</value>
</data>
<data name="Bootstrapper.Connectivity.RobloxDown" xml:space="preserve">
<value>Roblox may be down right now. See status.roblox.com for more information. Please try again later.</value>
<data name="Dialog.Connectivity.RobloxDown" xml:space="preserve">
<value>Roblox may be down right now. See {0} for more information.</value>
</data>
<data name="Bootstrapper.Connectivity.TimedOut" xml:space="preserve">
<value>Bloxstrap timed out when trying to connect to three different Roblox deployment mirrors, indicating a poor internet connection. Please try again later.</value>
<data name="Dialog.Connectivity.TimedOut" xml:space="preserve">
<value>The connection timed out, which could indicate a poor internet connection or a firewall block.</value>
</data>
<data name="Bootstrapper.EmojiPresetFetchFailed" xml:space="preserve">
<value>Could not apply the {0} emoji mod preset because of a network error. To try again, please reconfigure the option in the Bloxstrap Menu.</value>
@ -294,7 +294,7 @@ Click for more information</value>
<value>Connectivity error</value>
</data>
<data name="Dialog.Connectivity.UnableToConnect" xml:space="preserve">
<value>Bloxstrap is unable to connect to Roblox</value>
<value>Bloxstrap is unable to connect to {0}</value>
</data>
<data name="Dialog.Exception.CopyLogContents" xml:space="preserve">
<value>Copy log contents</value>
@ -637,12 +637,6 @@ Do NOT use this to import large "flag lists" made by other people that promise t
<value>Learn more about Fast Flags, what these presets do, and how to use them.</value>
<comment>Title is Common.Help</comment>
</data>
<data name="Menu.FastFlags.Presets.AltGraphicsSelector.Description" xml:space="preserve">
<value>Allows you to configure 21 different quality levels instead of 10.</value>
</data>
<data name="Menu.FastFlags.Presets.AltGraphicsSelector.Title" xml:space="preserve">
<value>Use advanced graphics quality selector</value>
</data>
<data name="Menu.FastFlags.Presets.D3DExclusiveFullscreenInfo" xml:space="preserve">
<value>Direct3D [exclusive fullscreen]({0}) using Alt+Enter is enabled by default.</value>
</data>
@ -1108,9 +1102,7 @@ If not, then please report this exception to the maintainers of this fork. Do NO
<value>Connected to reserved server</value>
</data>
<data name="Menu.Mods.Presets.EmojiType.Error" xml:space="preserve">
<value>The emoji mod could not be applied because of a network error during download.
{0}</value>
<value>The emoji mod can not be applied at this time.</value>
</data>
<data name="Dialog.AlreadyRunning.Installer" xml:space="preserve">
<value>Please wait for installation to finish.</value>
@ -1175,6 +1167,41 @@ Are you sure you want to continue?</value>
<value>Game history is only recorded for your current Roblox session. Games will appear here as you leave them or teleport within them.</value>
</data>
<data name="ActivityWatcher.LocationQueryFailed" xml:space="preserve">
<value>Failed to query server location.</value>
<value>The server location could not be queried. You may be joining games too quickly.</value>
</data>
<data name="Dialog.Connectivity.ServiceDown" xml:space="preserve">
<value>{0} may be down right now.</value>
</data>
<data name="Dialog.Connectivity.TryAgainLater" xml:space="preserve">
<value>Please try again later.</value>
</data>
<data name="Dialog.Connectivity.RobloxUpgradeSkip" xml:space="preserve">
<value>For this launch, Roblox will not be checked for upgrades, and changes to mods will not be applied.</value>
</data>
<data name="Dialog.Connectivity.RobloxUpgradeNeeded" xml:space="preserve">
<value>Because Roblox needs to be installed or upgraded, Bloxstrap cannot continue.</value>
</data>
<data name="Installer.Install.CannotOverwrite" xml:space="preserve">
<value>Bloxstrap has been installed to this location before and is still present, however the installer cannot overwrite the old executable.
Please manually delete Bloxstrap.exe from the install location or try restarting your system, and then retry installation afterwards.</value>
</data>
<data name="Common.NotAvailable" xml:space="preserve">
<value>Not available</value>
</data>
<data name="Menu.Mods.Misc.CompatibilitySettings.Title" xml:space="preserve">
<value>Manage compatibility settings</value>
</data>
<data name="Menu.Mods.Misc.CompatibilitySettings.Description" xml:space="preserve">
<value>Configure application parameters such as DPI scaling behaviour and [fullscreen optimizations]({0}).</value>
</data>
<data name="Common.RobloxNotInstalled" xml:space="preserve">
<value>Roblox has not yet been installed. Please launch Roblox using Bloxstrap at least once before trying to use this option.</value>
</data>
<data name="Installer.ShouldCancel" xml:space="preserve">
<value>Are you sure you want to cancel the installation?</value>
</data>
<data name="Bootstrapper.JsonManagerSaveFailed" xml:space="preserve">
<value>Failed to save {0}: {1}</value>
</data>
</root>

View File

@ -15,6 +15,7 @@
private static readonly Dictionary<string, int> BaseUrls = new()
{
{ "https://setup.rbxcdn.com", 0 },
{ "https://setup-aws.rbxcdn.com", 2 },
{ "https://setup-ak.rbxcdn.com", 2 },
{ "https://roblox-setup.cachefly.net", 2 },
{ "https://s3.amazonaws.com/setup.roblox.com", 4 }
@ -22,7 +23,7 @@
private static async Task<string?> TestConnection(string url, int priority, CancellationToken token)
{
string LOG_IDENT = $"RobloxDeployment::TestConnection.{url}";
string LOG_IDENT = $"RobloxDeployment::TestConnection<{url}>";
await Task.Delay(priority * 1000, token);
@ -32,14 +33,14 @@
{
var response = await App.HttpClient.GetAsync($"{url}/versionStudio", token);
if (!response.IsSuccessStatusCode)
throw new HttpResponseException(response);
response.EnsureSuccessStatusCode();
// versionStudio is the version hash for the last MFC studio to be deployed.
// the response body should always be "version-012732894899482c".
string content = await response.Content.ReadAsStringAsync(token);
if (content != VersionStudioHash)
throw new Exception($"versionStudio response does not match (expected \"{VersionStudioHash}\", got \"{content}\")");
throw new InvalidHTTPResponseException($"versionStudio response does not match (expected \"{VersionStudioHash}\", got \"{content}\")");
}
catch (TaskCanceledException)
{
@ -66,11 +67,10 @@
// returns null for success
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
var tokenSource = new CancellationTokenSource();
var exceptions = new List<Exception>();
var tasks = (from entry in BaseUrls select TestConnection(entry.Key, entry.Value, token)).ToList();
var tasks = (from entry in BaseUrls select TestConnection(entry.Key, entry.Value, tokenSource.Token)).ToList();
App.Logger.WriteLine(LOG_IDENT, "Testing connectivity...");
@ -127,7 +127,11 @@
App.Logger.WriteLine(LOG_IDENT, $"Getting deploy info for channel {channel}");
if (String.IsNullOrEmpty(channel))
channel = DefaultChannel;
string cacheKey = $"{channel}-{binaryType}";
ClientVersion clientVersion;
if (ClientVersionCache.ContainsKey(cacheKey))
@ -137,57 +141,37 @@
}
else
{
bool isDefaultChannel = String.Compare(channel, DefaultChannel, StringComparison.OrdinalIgnoreCase) == 0;
string path = $"/v2/client-version/{binaryType}";
if (String.Compare(channel, DefaultChannel, StringComparison.InvariantCultureIgnoreCase) != 0)
if (!isDefaultChannel)
path = $"/v2/client-version/{binaryType}/channel/{channel}";
HttpResponseMessage deployInfoResponse;
try
{
deployInfoResponse = await App.HttpClient.GetAsync("https://clientsettingscdn.roblox.com" + path);
clientVersion = await Http.GetJson<ClientVersion>("https://clientsettingscdn.roblox.com" + path);
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to contact clientsettingscdn! Falling back to clientsettings...");
App.Logger.WriteException(LOG_IDENT, ex);
deployInfoResponse = await App.HttpClient.GetAsync("https://clientsettings.roblox.com" + path);
clientVersion = await Http.GetJson<ClientVersion>("https://clientsettings.roblox.com" + path);
}
string rawResponse = await deployInfoResponse.Content.ReadAsStringAsync();
if (!deployInfoResponse.IsSuccessStatusCode)
// check if channel is behind LIVE
if (!isDefaultChannel)
{
// 400 = Invalid binaryType.
// 404 = Could not find version details for binaryType.
// 500 = Error while fetching version information.
// either way, we throw
var defaultClientVersion = await GetInfo(DefaultChannel);
App.Logger.WriteLine(LOG_IDENT,
"Failed to fetch deploy info!\r\n" +
$"\tStatus code: {deployInfoResponse.StatusCode}\r\n" +
$"\tResponse: {rawResponse}"
);
throw new HttpResponseException(deployInfoResponse);
if (Utilities.CompareVersions(clientVersion.Version, defaultClientVersion.Version) == VersionComparison.LessThan)
clientVersion.IsBehindDefaultChannel = true;
}
clientVersion = JsonSerializer.Deserialize<ClientVersion>(rawResponse)!;
ClientVersionCache[cacheKey] = clientVersion;
}
// check if channel is behind LIVE
if (channel != DefaultChannel)
{
var defaultClientVersion = await GetInfo(DefaultChannel);
if (Utilities.CompareVersions(clientVersion.Version, defaultClientVersion.Version) == VersionComparison.LessThan)
clientVersion.IsBehindDefaultChannel = true;
}
ClientVersionCache[cacheKey] = clientVersion;
return clientVersion;
}
}

View File

@ -52,16 +52,7 @@ namespace Bloxstrap
string rawResponse = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
App.Logger.WriteLine(logIndent,
"Failed to fetch client settings!\r\n" +
$"\tStatus code: {response.StatusCode}\r\n" +
$"\tResponse: {rawResponse}"
);
throw new HttpResponseException(response);
}
response.EnsureSuccessStatusCode();
var clientSettings = JsonSerializer.Deserialize<ClientFlagSettings>(rawResponse);

View File

@ -108,7 +108,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper.Base
public void Dialog_FormClosing(object sender, FormClosingEventArgs e)
{
if (!_isClosing)
Bootstrapper?.CancelInstall();
Bootstrapper?.Cancel();
}
#endregion

View File

@ -105,7 +105,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
private void Window_Closing(object sender, CancelEventArgs e)
{
if (!_isClosing)
Bootstrapper?.CancelInstall();
Bootstrapper?.Cancel();
}
#region IBootstrapperDialog Methods

View File

@ -88,7 +88,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
private void UiWindow_Closing(object sender, CancelEventArgs e)
{
if (!_isClosing)
Bootstrapper?.CancelInstall();
Bootstrapper?.Cancel();
}
#region IBootstrapperDialog Methods

View File

@ -102,7 +102,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
private void UiWindow_Closing(object sender, CancelEventArgs e)
{
if (!_isClosing)
Bootstrapper?.CancelInstall();
Bootstrapper?.Cancel();
}
#region IBootstrapperDialog Methods

View File

@ -49,7 +49,7 @@
</Grid>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="ServerDetailsMenuItem" Visibility="Collapsed" Click="ServerDetailsMenuItem_Click">
<MenuItem x:Name="ServerDetailsMenuItem" Visibility="Collapsed" Click="ServerDetailsMenuItem_Click">
<MenuItem.Header>
<Grid>
<Grid.ColumnDefinitions>
@ -61,7 +61,7 @@
</Grid>
</MenuItem.Header>
</MenuItem>
<MenuItem Click="JoinLastServerMenuItem_Click">
<MenuItem x:Name="GameHistoryMenuItem" Click="JoinLastServerMenuItem_Click" Visibility="Collapsed">
<MenuItem.Header>
<Grid>
<Grid.ColumnDefinitions>

View File

@ -2,16 +2,11 @@
using System.Windows.Controls;
using System.Windows.Interop;
using Wpf.Ui.Appearance;
using Wpf.Ui.Mvvm.Contracts;
using Wpf.Ui.Mvvm.Services;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using Bloxstrap.Integrations;
using Bloxstrap.Resources;
namespace Bloxstrap.UI.Elements.ContextMenu
{
@ -46,6 +41,9 @@ namespace Bloxstrap.UI.Elements.ContextMenu
if (_watcher.RichPresence is not null)
RichPresenceMenuItem.Visibility = Visibility.Visible;
if (!App.Settings.Prop.UseDisableAppPatch)
GameHistoryMenuItem.Visibility = Visibility.Visible;
VersionTextBlock.Text = $"{App.ProjectName} v{App.Version}";
}

View File

@ -12,6 +12,7 @@
Width="480"
MinHeight="0"
SizeToContent="Height"
Title="{x:Static resources:Strings.Dialog_Connectivity_Title}"
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
ExtendsContentIntoTitleBar="True"
WindowStartupLocation="CenterScreen">
@ -29,9 +30,9 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Width="32" Height="32" Margin="0,0,15,0" VerticalAlignment="Top" RenderOptions.BitmapScalingMode="HighQuality" Source="pack://application:,,,/Resources/MessageBox/Error.png" />
<Image x:Name="IconImage" Grid.Column="0" Width="32" Height="32" Margin="0,0,15,0" VerticalAlignment="Top" RenderOptions.BitmapScalingMode="HighQuality" Source="pack://application:,,,/Resources/MessageBox/Error.png" />
<StackPanel Grid.Column="1">
<TextBlock x:Name="TitleTextBlock" Text="? is unable to connect to ?" FontSize="18" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock x:Name="TitleTextBlock" Text="? is unable to connect to ?" FontSize="18" Foreground="{DynamicResource TextFillColorPrimaryBrush}" TextWrapping="Wrap" />
<controls:MarkdownTextBlock x:Name="DescriptionTextBlock" MarkdownText="?" Margin="0,16,0,0" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="{x:Static resources:Strings.Dialog_Connectivity_MoreInfo}" Margin="0,16,0,0" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<RichTextBox x:Name="ErrorRichTextBox" Padding="8" Margin="0,8,0,0" Block.LineHeight="2" FontFamily="Courier New" IsReadOnly="True" />

View File

@ -1,5 +1,7 @@
using System.Media;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Windows.Win32;
using Windows.Win32.Foundation;
@ -14,10 +16,41 @@ namespace Bloxstrap.UI.Elements.Dialogs
/// </summary>
public partial class ConnectivityDialog
{
public ConnectivityDialog(string title, string description, Exception exception)
public ConnectivityDialog(string title, string description, MessageBoxImage image, Exception exception)
{
InitializeComponent();
string? iconFilename = null;
SystemSound? sound = null;
switch (image)
{
case MessageBoxImage.Error:
iconFilename = "Error";
sound = SystemSounds.Hand;
break;
case MessageBoxImage.Question:
iconFilename = "Question";
sound = SystemSounds.Question;
break;
case MessageBoxImage.Warning:
iconFilename = "Warning";
sound = SystemSounds.Exclamation;
break;
case MessageBoxImage.Information:
iconFilename = "Information";
sound = SystemSounds.Asterisk;
break;
}
if (iconFilename is null)
IconImage.Visibility = Visibility.Collapsed;
else
IconImage.Source = new BitmapImage(new Uri($"pack://application:,,,/Resources/MessageBox/{iconFilename}.png"));
TitleTextBlock.Text = title;
DescriptionTextBlock.MarkdownText = description;
@ -28,7 +61,7 @@ namespace Bloxstrap.UI.Elements.Dialogs
Close();
};
SystemSounds.Hand.Play();
sound?.Play();
Loaded += delegate
{

View File

@ -33,7 +33,7 @@
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Width="32" Height="32" Margin="0,0,15,0" VerticalAlignment="Top" RenderOptions.BitmapScalingMode="HighQuality" Source="pack://application:,,,/Resources/MessageBox/Error.png" />
<StackPanel Grid.Column="1">
<TextBlock Text="{x:Static resources:Strings.Dialog_Exception_Info_1}" FontSize="18" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="{x:Static resources:Strings.Dialog_Exception_Info_1}" FontSize="18" Foreground="{DynamicResource TextFillColorPrimaryBrush}" TextWrapping="Wrap" />
<RichTextBox x:Name="ErrorRichTextBox" Padding="8" Margin="0,16,0,0" Block.LineHeight="2" FontFamily="Courier New" IsReadOnly="True" />
<controls:MarkdownTextBlock x:Name="HelpMessageMDTextBlock" Margin="0,16,0,0" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
</StackPanel>

View File

@ -9,6 +9,7 @@ using Bloxstrap.UI.Elements.Installer.Pages;
using Bloxstrap.UI.Elements.Base;
using System.Windows.Media.Animation;
using System.Reflection.Metadata.Ecma335;
using Bloxstrap.Resources;
namespace Bloxstrap.UI.Elements.Installer
{
@ -102,7 +103,7 @@ namespace Bloxstrap.UI.Elements.Installer
if (Finished)
return;
var result = Frontend.ShowMessageBox("Are you sure you want to cancel the installation?", MessageBoxImage.Warning, MessageBoxButton.YesNo);
var result = Frontend.ShowMessageBox(Strings.Installer_ShouldCancel, MessageBoxImage.Warning, MessageBoxButton.YesNo);
if (result != MessageBoxResult.Yes)
e.Cancel = true;

View File

@ -14,6 +14,8 @@ namespace Bloxstrap.UI.Elements.Settings
/// </summary>
public partial class MainWindow : INavigationWindow
{
private Models.Persistable.WindowState _state => App.State.Prop.SettingsWindow;
public MainWindow(bool showAlreadyRunningWarning)
{
var viewModel = new MainWindowViewModel();
@ -33,6 +35,30 @@ namespace Bloxstrap.UI.Elements.Settings
if (showAlreadyRunningWarning)
ShowAlreadyRunningSnackbar();
LoadState();
}
public void LoadState()
{
if (_state.Left > SystemParameters.VirtualScreenWidth)
_state.Left = 0;
if (_state.Top > SystemParameters.VirtualScreenHeight)
_state.Top = 0;
if (_state.Width > 0)
this.Width = _state.Width;
if (_state.Height > 0)
this.Height = _state.Height;
if (_state.Left > 0 && _state.Top > 0)
{
this.WindowStartupLocation = WindowStartupLocation.Manual;
this.Left = _state.Left;
this.Top = _state.Top;
}
}
private async void ShowAlreadyRunningSnackbar()
@ -67,6 +93,14 @@ namespace Bloxstrap.UI.Elements.Settings
e.Cancel = true;
}
_state.Width = this.Width;
_state.Height = this.Height;
_state.Top = this.Top;
_state.Left = this.Left;
App.State.Save();
if (!e.Cancel)
App.Terminate();
}

View File

@ -165,12 +165,6 @@
</ComboBox>
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_AltGraphicsSelector_Title}"
Description="{x:Static resources:Strings.Menu_FastFlags_Presets_AltGraphicsSelector_Description}">
<ui:ToggleSwitch IsChecked="{Binding AlternateGraphicsSelectorEnabled, Mode=TwoWay}" />
</controls:OptionControl>
<ui:CardAction Margin="0,24,0,0" Icon="EditSettings24" Command="{Binding OpenFastFlagEditorCommand}">
<StackPanel>
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Menu_FastFlagEditor_Title}" />

View File

@ -96,7 +96,7 @@
</Style>
</StackPanel.Style>
<TextBlock Text="{x:Static resources:Strings.Common_Name}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<ui:TextBox Margin="0,4,0,0" Text="{Binding SelectedCustomIntegration.Name}" />
<ui:TextBox Margin="0,4,0,0" Text="{Binding SelectedCustomIntegration.Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Margin="0,8,0,0" Text="{x:Static resources:Strings.Menu_Integrations_Custom_AppLocation}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>

View File

@ -25,18 +25,27 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:CardAction Grid.Row="0" Grid.Column="0" x:Name="OpenModFolderCardAction" Margin="0,0,4,0" Icon="Folder24" Command="{Binding OpenModsFolderCommand}">
<StackPanel>
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Menu_Mods_OpenModsFolder_Title}" TextWrapping="Wrap" />
<TextBlock Margin="0,2,0,0" FontSize="12" Text="{x:Static resources:Strings.Menu_Mods_OpenModsFolder_Description}" Foreground="{DynamicResource TextFillColorTertiaryBrush}" TextWrapping="Wrap" />
</StackPanel>
</ui:CardAction>
<ui:CardAction Grid.Row="0" Grid.Column="1" Margin="4,0,0,0" Icon="BookQuestionMark24" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/wiki/Adding-custom-mods">
<StackPanel>
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Common_Help}" />
<TextBlock Margin="0,2,0,0" FontSize="12" Text="{x:Static resources:Strings.Menu_Mods_Help_Description}" Padding="0,0,16,0" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
</StackPanel>
</ui:CardAction>
<ui:CardAction Grid.Row="1" Grid.ColumnSpan="2" Margin="0,8,0,0" Icon="WindowWrench24" Command="{Binding OpenCompatSettingsCommand}">
<StackPanel>
<TextBlock FontSize="14" Text="{x:Static resources:Strings.Menu_Mods_Misc_CompatibilitySettings_Title}" TextWrapping="Wrap" />
<controls:MarkdownTextBlock Margin="0,2,0,0" FontSize="12" MarkdownText="{Binding Source={x:Static resources:Strings.Menu_Mods_Misc_CompatibilitySettings_Description}, Converter={StaticResource StringFormatConverter}, ConverterParameter='https://devblogs.microsoft.com/directx/demystifying-full-screen-optimizations/'}" Foreground="{DynamicResource TextFillColorTertiaryBrush}" TextWrapping="Wrap" />
</StackPanel>
</ui:CardAction>
</Grid>
<TextBlock Text="{x:Static resources:Strings.Common_Presets}" FontSize="20" FontWeight="Medium" Margin="0,16,0,0" />
@ -93,13 +102,5 @@
<ui:Button Icon="Delete16" Content="{x:Static resources:Strings.Menu_Mods_Misc_CustomFont_Remove}" Appearance="Danger" Command="{Binding ManageCustomFontCommand}" Visibility="{Binding DeleteCustomFontVisibility, Mode=OneWay}" />
</StackPanel>
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_Mods_Misc_DisableFullscreenOptimisations_Title}"
Description="{x:Static resources:Strings.Menu_Mods_Misc_DisableFullscreenOptimisations_Description}"
HelpLink="https://devblogs.microsoft.com/directx/demystifying-full-screen-optimizations/"
x:Name="FullscreenOptimizationsToggle">
<ui:ToggleSwitch IsChecked="{Binding DisableFullscreenOptimizations, Mode=TwoWay}" />
</controls:OptionControl>
</StackPanel>
</ui:UiPage>

View File

@ -1,6 +1,4 @@
using System.Windows;
using Bloxstrap.UI.ViewModels.Settings;
using Bloxstrap.UI.ViewModels.Settings;
namespace Bloxstrap.UI.Elements.Settings.Pages
{
@ -13,10 +11,6 @@ namespace Bloxstrap.UI.Elements.Settings.Pages
{
DataContext = new ModsViewModel();
InitializeComponent();
// fullscreen optimizations were only added in windows 10 build 17093
if (Environment.OSVersion.Version.Build < 17093)
this.FullscreenOptimizationsToggle.Visibility = Visibility.Collapsed;
}
}
}

View File

@ -17,20 +17,7 @@ namespace Bloxstrap.UI
if (App.LaunchSettings.QuietFlag.Active)
return defaultResult;
if (App.LaunchSettings.RobloxLaunchMode != LaunchMode.None)
return ShowFluentMessageBox(message, icon, buttons);
switch (App.Settings.Prop.BootstrapperStyle)
{
case BootstrapperStyle.FluentDialog:
case BootstrapperStyle.ClassicFluentDialog:
case BootstrapperStyle.FluentAeroDialog:
case BootstrapperStyle.ByfronDialog:
return ShowFluentMessageBox(message, icon, buttons);
default:
return MessageBox.Show(message, App.ProjectName, buttons, icon);
}
return ShowFluentMessageBox(message, icon, buttons);
}
public static void ShowPlayerErrorDialog(bool crash = false)
@ -49,17 +36,23 @@ namespace Bloxstrap.UI
public static void ShowExceptionDialog(Exception exception)
{
if (App.LaunchSettings.QuietFlag.Active)
return;
Application.Current.Dispatcher.Invoke(() =>
{
new ExceptionDialog(exception).ShowDialog();
});
}
public static void ShowConnectivityDialog(string title, string description, Exception exception)
public static void ShowConnectivityDialog(string title, string description, MessageBoxImage image, Exception exception)
{
if (App.LaunchSettings.QuietFlag.Active)
return;
Application.Current.Dispatcher.Invoke(() =>
{
new ConnectivityDialog(title, description, exception).ShowDialog();
new ConnectivityDialog(title, description, image, exception).ShowDialog();
});
}

View File

@ -59,7 +59,7 @@ namespace Bloxstrap.UI
if (_activityWatcher is null)
return;
string serverLocation = await _activityWatcher.Data.QueryServerLocation();
string? serverLocation = await _activityWatcher.Data.QueryServerLocation();
if (string.IsNullOrEmpty(serverLocation))
return;

View File

@ -35,7 +35,7 @@ namespace Bloxstrap.UI.ViewModels.Bootstrapper
private void CancelInstall()
{
_dialog.Bootstrapper?.CancelInstall();
_dialog.Bootstrapper?.Cancel();
_dialog.CloseBootstrapper();
}
}

View File

@ -19,8 +19,6 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
public ICommand CopyInstanceIdCommand => new RelayCommand(CopyInstanceId);
public EventHandler? RequestCloseEvent;
public ServerInformationViewModel(Watcher watcher)
{
_activityWatcher = watcher.ActivityWatcher!;
@ -31,7 +29,13 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
public async void QueryServerLocation()
{
ServerLocation = await _activityWatcher.Data.QueryServerLocation();
string? location = await _activityWatcher.Data.QueryServerLocation();
if (String.IsNullOrEmpty(location))
ServerLocation = Strings.Common_NotAvailable;
else
ServerLocation = location;
OnPropertyChanged(nameof(ServerLocation));
}

View File

@ -2,12 +2,6 @@
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
using Bloxstrap.Resources;
using Microsoft.Win32;
using Wpf.Ui.Mvvm.Interfaces;
using System.ComponentModel;
namespace Bloxstrap.UI.ViewModels.Installer
{
public class InstallViewModel : NotifyPropertyChangedViewModel

View File

@ -27,20 +27,20 @@
{
// wouldnt it be better to check old version guids?
// what about fresh installs?
get => String.IsNullOrEmpty(App.State.Prop.PlayerVersionGuid) && String.IsNullOrEmpty(App.State.Prop.StudioVersionGuid);
get => String.IsNullOrEmpty(App.State.Prop.Player.VersionGuid) && String.IsNullOrEmpty(App.State.Prop.Studio.VersionGuid);
set
{
if (value)
{
_oldPlayerVersionGuid = App.State.Prop.PlayerVersionGuid;
_oldStudioVersionGuid = App.State.Prop.StudioVersionGuid;
App.State.Prop.PlayerVersionGuid = "";
App.State.Prop.StudioVersionGuid = "";
_oldPlayerVersionGuid = App.State.Prop.Player.VersionGuid;
_oldStudioVersionGuid = App.State.Prop.Studio.VersionGuid;
App.State.Prop.Player.VersionGuid = "";
App.State.Prop.Studio.VersionGuid = "";
}
else
{
App.State.Prop.PlayerVersionGuid = _oldPlayerVersionGuid;
App.State.Prop.StudioVersionGuid = _oldStudioVersionGuid;
App.State.Prop.Player.VersionGuid = _oldPlayerVersionGuid;
App.State.Prop.Studio.VersionGuid = _oldStudioVersionGuid;
}
}
}

View File

@ -96,12 +96,6 @@ namespace Bloxstrap.UI.ViewModels.Settings
set => App.FastFlags.SetPreset("Rendering.DisableScaling", value ? "True" : null);
}
public bool AlternateGraphicsSelectorEnabled
{
get => App.FastFlags.GetPreset("UI.Menu.GraphicsSlider") == "True";
set => App.FastFlags.SetPreset("UI.Menu.GraphicsSlider", value ? "True" : null);
}
public IReadOnlyDictionary<InGameMenuVersion, Dictionary<string, string?>> IGMenuVersions => FastFlagManager.IGMenuVersions;
public InGameMenuVersion SelectedIGMenuVersion

View File

@ -1,8 +1,6 @@
using System.Collections.ObjectModel;
using System.Windows.Input;
using Bloxstrap.Resources;
using Microsoft.Win32;
using CommunityToolkit.Mvvm.Input;
@ -12,7 +10,9 @@ namespace Bloxstrap.UI.ViewModels.Settings
public class IntegrationsViewModel : NotifyPropertyChangedViewModel
{
public ICommand AddIntegrationCommand => new RelayCommand(AddIntegration);
public ICommand DeleteIntegrationCommand => new RelayCommand(DeleteIntegration);
public ICommand BrowseIntegrationLocationCommand => new RelayCommand(BrowseIntegrationLocation);
private void AddIntegration()
@ -57,6 +57,7 @@ namespace Bloxstrap.UI.ViewModels.Settings
if (dialog.ShowDialog() != true)
return;
SelectedCustomIntegration.Name = dialog.SafeFileName;
SelectedCustomIntegration.Location = dialog.FileName;
OnPropertyChanged(nameof(SelectedCustomIntegration));
}

View File

@ -3,9 +3,14 @@ using System.Windows.Input;
using Microsoft.Win32;
using Windows.Win32;
using Windows.Win32.UI.Shell;
using Windows.Win32.Foundation;
using CommunityToolkit.Mvvm.Input;
using Bloxstrap.Models.SettingTasks;
using Bloxstrap.AppData;
namespace Bloxstrap.UI.ViewModels.Settings
{
@ -59,6 +64,8 @@ namespace Bloxstrap.UI.ViewModels.Settings
public ICommand ManageCustomFontCommand => new RelayCommand(ManageCustomFont);
public ICommand OpenCompatSettingsCommand => new RelayCommand(OpenCompatSettings);
public ModPresetTask OldDeathSoundTask { get; } = new("OldDeathSound", @"content\sounds\ouch.ogg", "Sounds.OldDeath.ogg");
public ModPresetTask OldAvatarBackgroundTask { get; } = new("OldAvatarBackground", @"ExtraContent\places\Mobile.rbxl", "OldAvatarBackground.rbxl");
@ -96,10 +103,15 @@ namespace Bloxstrap.UI.ViewModels.Settings
public FontModPresetTask TextFontTask { get; } = new();
public bool DisableFullscreenOptimizations
private void OpenCompatSettings()
{
get => App.Settings.Prop.DisableFullscreenOptimizations;
set => App.Settings.Prop.DisableFullscreenOptimizations = value;
string path = new RobloxPlayerData().ExecutablePath;
if (File.Exists(path))
PInvoke.SHObjectProperties(HWND.Null, SHOP_TYPE.SHOP_FILEPATH, path, "Compatibility");
else
Frontend.ShowMessageBox(Strings.Common_RobloxNotInstalled, MessageBoxImage.Error);
}
}
}

View File

@ -1,5 +1,4 @@
using System.ComponentModel;
using System.Security.Principal;
namespace Bloxstrap
{
@ -51,10 +50,9 @@ namespace Bloxstrap
public static string GetRobloxVersion(bool studio)
{
string versionGuid = studio ? App.State.Prop.StudioVersionGuid : App.State.Prop.PlayerVersionGuid;
string fileName = studio ? "RobloxStudioBeta.exe" : "RobloxPlayerBeta.exe";
string fileName = studio ? "Studio/RobloxStudioBeta.exe" : "Player/RobloxPlayerBeta.exe";
string playerLocation = Path.Combine(Paths.Versions, versionGuid, fileName);
string playerLocation = Path.Combine(Paths.Roblox, fileName);
if (!File.Exists(playerLocation))
return "";

View File

@ -1,20 +0,0 @@
namespace Bloxstrap.Utility
{
public static class AsyncHelpers
{
public static void ExceptionHandler(Task task, object? state)
{
const string LOG_IDENT = "AsyncHelpers::ExceptionHandler";
if (task.Exception is null)
return;
if (state is null)
App.Logger.WriteLine(LOG_IDENT, "An exception occurred while running the task");
else
App.Logger.WriteLine(LOG_IDENT, $"An exception occurred while running the task '{state}'");
App.FinalizeExceptionHandling(task.Exception);
}
}
}

View File

@ -1,15 +1,12 @@
using System.Web;
using System.Windows;
using Microsoft.Win32;
using Microsoft.Win32;
namespace Bloxstrap
namespace Bloxstrap.Utility
{
static class ProtocolHandler
static class WindowsRegistry
{
private const string RobloxPlaceKey = "Roblox.Place";
public static void Register(string key, string name, string handler, string handlerParam = "%1")
public static void RegisterProtocol(string key, string name, string handler, string handlerParam = "%1")
{
string handlerArgs = $"\"{handler}\" {handlerParam}";
@ -30,10 +27,56 @@ namespace Bloxstrap
}
}
public static void RegisterRobloxPlace(string handler)
/// <summary>
/// Registers Roblox Player protocols for Bloxstrap
/// </summary>
public static void RegisterPlayer() => RegisterPlayer(Paths.Application, "-player \"%1\"");
public static void RegisterPlayer(string handler, string handlerParam)
{
RegisterProtocol("roblox", "Roblox", handler, handlerParam);
RegisterProtocol("roblox-player", "Roblox", handler, handlerParam);
}
/// <summary>
/// Registers all Roblox Studio classes for Bloxstrap
/// </summary>
public static void RegisterStudio()
{
RegisterStudioProtocol(Paths.Application, "-studio \"%1\"");
RegisterStudioFileClass(Paths.Application, "-studio \"%1\"");
RegisterStudioFileTypes();
}
/// <summary>
/// Registers roblox-studio and roblox-studio-auth protocols
/// </summary>
/// <param name="handler"></param>
/// <param name="handlerParam"></param>
public static void RegisterStudioProtocol(string handler, string handlerParam)
{
RegisterProtocol("roblox-studio", "Roblox", handler, handlerParam);
RegisterProtocol("roblox-studio-auth", "Roblox", handler, handlerParam);
}
/// <summary>
/// Registers file associations for Roblox.Place class
/// </summary>
public static void RegisterStudioFileTypes()
{
RegisterStudioFileType(".rbxl");
RegisterStudioFileType(".rbxlx");
}
/// <summary>
/// Registers Roblox.Place class
/// </summary>
/// <param name="handler"></param>
/// <param name="handlerParam"></param>
public static void RegisterStudioFileClass(string handler, string handlerParam)
{
const string keyValue = "Roblox Place";
string handlerArgs = $"\"{handler}\" -ide \"%1\"";
string handlerArgs = $"\"{handler}\" {handlerParam}";
string iconValue = $"{handler},0";
using RegistryKey uriKey = Registry.CurrentUser.CreateSubKey(@"Software\Classes\" + RobloxPlaceKey);
@ -54,7 +97,7 @@ namespace Bloxstrap
uriIconKey.SetValue("", iconValue);
}
public static void RegisterExtension(string key)
public static void RegisterStudioFileType(string key)
{
using RegistryKey uriKey = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{key}");
uriKey.CreateSubKey(RobloxPlaceKey + @"\ShellNew");

View File

@ -31,7 +31,7 @@ namespace Bloxstrap
#if DEBUG
if (String.IsNullOrEmpty(watcherData))
{
string path = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid, "RobloxPlayerBeta.exe");
string path = Path.Combine(Paths.Roblox, "Player", "RobloxPlayerBeta.exe");
using var gameClientProcess = Process.Start(path);
_gameClientPid = gameClientProcess.Id;
}