Merge branch 'main' into user-pfp-discord-rpc

This commit is contained in:
axell 2024-09-03 07:24:13 +02:00 committed by GitHub
commit 77262dc6e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 712 additions and 155 deletions

View File

@ -17,7 +17,7 @@ namespace Bloxstrap
public const string ProjectName = "Bloxstrap";
public const string ProjectOwner = "pizzaboxer";
public const string ProjectRepository = "pizzaboxer/bloxstrap";
public const string ProjectDownloadLink = "https://bloxstrap.pizzaboxer.xyz";
public const string ProjectDownloadLink = "https://bloxstraplabs.com";
public const string ProjectHelpLink = "https://github.com/pizzaboxer/bloxstrap/wiki";
public const string ProjectSupportLink = "https://github.com/pizzaboxer/bloxstrap/issues/new";

View File

@ -209,12 +209,9 @@ namespace Bloxstrap
if (_latestVersionGuid != _versionGuid || !File.Exists(_playerLocation))
await InstallLatestVersion();
MigrateIntegrations();
if (_installWebView2)
await InstallWebView2();
App.FastFlags.Save();
await ApplyModifications();
// TODO: move this to install/upgrade flow
@ -224,7 +221,6 @@ namespace Bloxstrap
CheckInstall();
// at this point we've finished updating our configs
App.Settings.Save();
App.State.Save();
await mutex.ReleaseAsync();
@ -328,18 +324,26 @@ namespace Bloxstrap
return;
}
using var startEvent = new EventWaitHandle(false, EventResetMode.ManualReset, AppData.StartEvent);
int gameClientPid;
bool startEventSignalled;
// TODO: figure out why this is causing roblox to block for some users
using (var startEvent = new EventWaitHandle(false, EventResetMode.ManualReset, AppData.StartEvent))
{
startEvent.Reset();
// v2.2.0 - byfron will trip if we keep a process handle open for over a minute, so we're doing this now
int gameClientPid;
using (var gameClient = Process.Start(startInfo)!)
using (var process = Process.Start(startInfo)!)
{
gameClientPid = gameClient.Id;
gameClientPid = process.Id;
}
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid}), waiting for start event");
if (!startEvent.WaitOne(TimeSpan.FromSeconds(10)))
startEventSignalled = startEvent.WaitOne(TimeSpan.FromSeconds(10));
}
if (!startEventSignalled)
{
Frontend.ShowPlayerErrorDialog();
return;
@ -382,8 +386,10 @@ namespace Bloxstrap
if (autoclosePids.Any())
args += $";{String.Join(',', autoclosePids)}";
using (var ipl = new InterProcessLock("Watcher"))
if (App.Settings.Prop.EnableActivityTracking || autoclosePids.Any())
{
using var ipl = new InterProcessLock("Watcher", TimeSpan.FromSeconds(5));
if (ipl.IsAcquired)
Process.Start(Paths.Process, $"-watcher \"{args}\"");
}
@ -766,32 +772,6 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, "Finished installing runtime");
}
public static void MigrateIntegrations()
{
// v2.2.0 - remove rbxfpsunlocker
string rbxfpsunlocker = Path.Combine(Paths.Integrations, "rbxfpsunlocker");
if (Directory.Exists(rbxfpsunlocker))
Directory.Delete(rbxfpsunlocker, true);
// v2.3.0 - remove reshade
string injectorLocation = Path.Combine(Paths.Modifications, "dxgi.dll");
string configLocation = Path.Combine(Paths.Modifications, "ReShade.ini");
if (File.Exists(injectorLocation))
{
Frontend.ShowMessageBox(
Strings.Bootstrapper_HyperionUpdateInfo,
MessageBoxImage.Warning
);
File.Delete(injectorLocation);
}
if (File.Exists(configLocation))
File.Delete(configLocation);
}
private async Task ApplyModifications()
{
const string LOG_IDENT = "Bootstrapper::ApplyModifications";

View File

@ -4,9 +4,9 @@
{
public static string ToTranslatedString(this ServerType value) => value switch
{
ServerType.Public => Resources.Strings.Enums_ServerType_Public,
ServerType.Private => Resources.Strings.Enums_ServerType_Private,
ServerType.Reserved => Resources.Strings.Enums_ServerType_Reserved,
ServerType.Public => Strings.Enums_ServerType_Public,
ServerType.Private => Strings.Enums_ServerType_Private,
ServerType.Reserved => Strings.Enums_ServerType_Reserved,
_ => "?"
};
}

View File

@ -4,6 +4,10 @@ namespace Bloxstrap
{
public class FastFlagManager : JsonManager<Dictionary<string, object>>
{
public override string ClassName => nameof(FastFlagManager);
public override string LOG_IDENT_CLASS => ClassName;
public override string FileLocation => Path.Combine(Paths.Modifications, "ClientSettings\\ClientAppSettings.json");
public bool Changed => !OriginalProp.SequenceEqual(Prop);
@ -48,7 +52,7 @@ namespace Bloxstrap
{ "UI.Menu.GraphicsSlider", "FFlagFixGraphicsQuality" },
{ "UI.FullscreenTitlebarDelay", "FIntFullscreenTitleBarTriggerDelayMillis" },
{ "UI.Menu.Style.DisableV2", "FFlagDisableNewIGMinDUA" },
{ "UI.Menu.Style.V2Rollout", "FIntNewInGameMenuPercentRollout3" },
{ "UI.Menu.Style.EnableV4.1", "FFlagEnableInGameMenuControls" },
{ "UI.Menu.Style.EnableV4.2", "FFlagEnableInGameMenuModernization" },
{ "UI.Menu.Style.EnableV4Chrome", "FFlagEnableInGameMenuChrome" },
@ -99,7 +103,7 @@ namespace Bloxstrap
InGameMenuVersion.Default,
new Dictionary<string, string?>
{
{ "DisableV2", null },
{ "V2Rollout", null },
{ "EnableV4", null },
{ "EnableV4Chrome", null },
{ "ABTest", null }
@ -110,7 +114,7 @@ namespace Bloxstrap
InGameMenuVersion.V1,
new Dictionary<string, string?>
{
{ "DisableV2", "True" },
{ "V2Rollout", "0" },
{ "EnableV4", "False" },
{ "EnableV4Chrome", "False" },
{ "ABTest", "False" }
@ -121,7 +125,7 @@ namespace Bloxstrap
InGameMenuVersion.V2,
new Dictionary<string, string?>
{
{ "DisableV2", "False" },
{ "V2Rollout", "100" },
{ "EnableV4", "False" },
{ "EnableV4Chrome", "False" },
{ "ABTest", "False" }
@ -132,7 +136,7 @@ namespace Bloxstrap
InGameMenuVersion.V4,
new Dictionary<string, string?>
{
{ "DisableV2", "True" },
{ "V2Rollout", "0" },
{ "EnableV4", "True" },
{ "EnableV4Chrome", "False" },
{ "ABTest", "False" }
@ -143,7 +147,7 @@ namespace Bloxstrap
InGameMenuVersion.V4Chrome,
new Dictionary<string, string?>
{
{ "DisableV2", "True" },
{ "V2Rollout", "0" },
{ "EnableV4", "True" },
{ "EnableV4Chrome", "True" },
{ "ABTest", "False" }
@ -238,9 +242,9 @@ namespace Bloxstrap
OriginalProp = new(Prop);
}
public override void Load()
public override void Load(bool alertFailure = true)
{
base.Load();
base.Load(alertFailure);
// clone the dictionary
OriginalProp = new(Prop);
@ -248,10 +252,6 @@ namespace Bloxstrap
// TODO - remove when activity tracking has been revamped
if (GetPreset("Network.Log") != "7")
SetPreset("Network.Log", "7");
string? val = GetPreset("UI.Menu.Style.EnableV4.1");
if (GetPreset("UI.Menu.Style.EnableV4.2") != val)
SetPreset("UI.Menu.Style.EnableV4.2", val);
}
}
}

View File

@ -11,6 +11,8 @@ namespace Bloxstrap
public string InstallLocation = Path.Combine(Paths.LocalAppData, "Bloxstrap");
public bool ExistingDataPresent => File.Exists(Path.Combine(InstallLocation, "Settings.json"));
public bool CreateDesktopShortcuts = true;
public bool CreateStartMenuShortcuts = true;
@ -21,6 +23,10 @@ namespace Bloxstrap
public void DoInstall()
{
const string LOG_IDENT = "Installer::DoInstall";
App.Logger.WriteLine(LOG_IDENT, "Beginning installation");
// should've been created earlier from the write test anyway
Directory.CreateDirectory(InstallLocation);
@ -69,9 +75,11 @@ namespace Bloxstrap
Shortcut.Create(Paths.Application, "", StartMenuShortcut);
// existing configuration persisting from an earlier install
App.Settings.Load();
App.State.Load();
App.FastFlags.Load();
App.Settings.Load(false);
App.State.Load(false);
App.FastFlags.Load(false);
App.Logger.WriteLine(LOG_IDENT, "Installation finished");
}
private bool ValidateLocation()
@ -400,6 +408,41 @@ namespace Bloxstrap
if (existingVer is not null)
{
if (Utilities.CompareVersions(existingVer, "2.2.0") == VersionComparison.LessThan)
{
string path = Path.Combine(Paths.Integrations, "rbxfpsunlocker");
try
{
if (Directory.Exists(path))
Directory.Delete(path, true);
}
catch (Exception ex)
{
App.Logger.WriteException(LOG_IDENT, ex);
}
}
if (Utilities.CompareVersions(existingVer, "2.3.0") == VersionComparison.LessThan)
{
string injectorLocation = Path.Combine(Paths.Modifications, "dxgi.dll");
string configLocation = Path.Combine(Paths.Modifications, "ReShade.ini");
if (File.Exists(injectorLocation))
{
Frontend.ShowMessageBox(
Strings.Bootstrapper_HyperionUpdateInfo,
MessageBoxImage.Warning
);
File.Delete(injectorLocation);
}
if (File.Exists(configLocation))
File.Delete(configLocation);
}
if (Utilities.CompareVersions(existingVer, "2.5.0") == VersionComparison.LessThan)
{
App.FastFlags.SetValue("DFFlagDisableDPIScale", null);
@ -414,6 +457,13 @@ namespace Bloxstrap
App.FastFlags.SetPreset("UI.Menu.Style.ABTest", false);
}
if (Utilities.CompareVersions(existingVer, "2.5.3") == VersionComparison.LessThan)
{
string? val = App.FastFlags.GetPreset("UI.Menu.Style.EnableV4.1");
if (App.FastFlags.GetPreset("UI.Menu.Style.EnableV4.2") != val)
App.FastFlags.SetPreset("UI.Menu.Style.EnableV4.2", val);
}
if (Utilities.CompareVersions(existingVer, "2.6.0") == VersionComparison.LessThan)
{
if (App.Settings.Prop.UseDisableAppPatch)
@ -435,10 +485,8 @@ namespace Bloxstrap
_ = int.TryParse(App.FastFlags.GetPreset("Rendering.Framerate"), out int x);
if (x == 0)
{
App.FastFlags.SetPreset("Rendering.Framerate", null);
}
}
if (Utilities.CompareVersions(existingVer, "2.8.0") == VersionComparison.LessThan)
{
@ -466,6 +514,18 @@ namespace Bloxstrap
ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\"");
ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\"");
string? oldV2Val = App.FastFlags.GetValue("FFlagDisableNewIGMinDUA");
if (oldV2Val is not null)
{
if (oldV2Val == "True")
App.FastFlags.SetPreset("UI.Menu.Style.V2Rollout", "0");
else
App.FastFlags.SetPreset("UI.Menu.Style.V2Rollout", "100");
App.FastFlags.SetValue("FFlagDisableNewIGMinDUA", null);
}
}
App.Settings.Save();

View File

@ -9,6 +9,7 @@ namespace Bloxstrap.Integrations
private const string GameJoiningEntry = "[FLog::Output] ! Joining game";
private const string GameJoiningPrivateServerEntry = "[FLog::GameJoinUtil] GameJoinUtil::joinGamePostPrivateServer";
private const string GameJoiningReservedServerEntry = "[FLog::GameJoinUtil] GameJoinUtil::initiateTeleportToReservedServer";
private const string GameJoiningUniverseEntry = "[FLog::GameJoinLoadTime] Report game_join_loadtime:";
private const string GameJoiningUDMUXEntry = "[FLog::Network] UDMUX Address = ";
private const string GameJoinedEntry = "[FLog::Network] serverId:";
private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
@ -19,6 +20,7 @@ namespace Bloxstrap.Integrations
private const string GameJoinLoadTimeEntryPattern = ", userid:([0-9]+)";
private const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)";
private const string GameJoiningUniversePattern = @"universeid:([0-9]+)";
private const string GameJoiningUDMUXPattern = @"UDMUX Address = ([0-9\.]+), Port = [0-9]+ \| RCC Server Address = ([0-9\.]+), Port = [0-9]+";
private const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|[0-9]+";
private const string GameMessageEntryPattern = @"\[BloxstrapRPC\] (.*)";
@ -41,8 +43,10 @@ namespace Bloxstrap.Integrations
// these are values to use assuming the player isn't currently in a game
// hmm... do i move this to a model?
public DateTime ActivityTimeJoined;
public bool ActivityInGame = false;
public long ActivityPlaceId = 0;
public long ActivityUniverseId = 0;
public string ActivityJobId = "";
public string ActivityUserId = "";
public string ActivityMachineAddress = "";
@ -51,6 +55,8 @@ namespace Bloxstrap.Integrations
public string ActivityLaunchData = "";
public ServerType ActivityServerType = ServerType.Public;
public List<ActivityHistoryEntry> ActivityHistory = new();
public bool IsDisposed = false;
public async void Start()
@ -92,7 +98,6 @@ namespace Bloxstrap.Integrations
if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now)
break;
// TODO: report failure after 10 seconds of no log file
App.Logger.WriteLine(LOG_IDENT, $"Could not find recent enough log file, waiting... (newest is {logFileInfo.Name})");
await Task.Delay(1000);
}
@ -135,6 +140,7 @@ namespace Bloxstrap.Integrations
return deeplink;
}
// TODO: i need to double check how this handles failed game joins (connection error, invalid permissions, etc)
private void ReadLogEntry(string entry)
{
const string LOG_IDENT = "ActivityWatcher::ReadLogEntry";
@ -168,6 +174,8 @@ namespace Bloxstrap.Integrations
}
if (!ActivityInGame && ActivityPlaceId == 0)
{
// We are not in a game, nor are in the process of joining one
if (entry.Contains(GameJoiningPrivateServerEntry))
{
// we only expect to be joining a private server if we're not already in a game
@ -206,13 +214,28 @@ namespace Bloxstrap.Integrations
}
else if (!ActivityInGame && ActivityPlaceId != 0)
{
if (entry.Contains(GameJoiningUDMUXEntry))
// We are not confirmed to be in a game, but we are in the process of joining one
if (entry.Contains(GameJoiningUniverseEntry))
{
var match = Regex.Match(entry, GameJoiningUniversePattern);
if (match.Groups.Count != 2)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to assert format for game join universe entry");
App.Logger.WriteLine(LOG_IDENT, entry);
return;
}
ActivityUniverseId = long.Parse(match.Groups[1].Value);
}
else if (entry.Contains(GameJoiningUDMUXEntry))
{
Match match = Regex.Match(entry, GameJoiningUDMUXPattern);
if (match.Groups.Count != 3 || match.Groups[2].Value != ActivityMachineAddress)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to assert format for game join UDMUX entry");
App.Logger.WriteLine(LOG_IDENT, "Failed to assert format for game join UDMUX entry");
App.Logger.WriteLine(LOG_IDENT, entry);
return;
}
@ -236,17 +259,35 @@ namespace Bloxstrap.Integrations
App.Logger.WriteLine(LOG_IDENT, $"Joined Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
ActivityInGame = true;
ActivityTimeJoined = DateTime.Now;
OnGameJoin?.Invoke(this, new EventArgs());
}
}
else if (ActivityInGame && ActivityPlaceId != 0)
{
// We are confirmed to be in a game
if (entry.Contains(GameDisconnectedEntry))
{
App.Logger.WriteLine(LOG_IDENT, $"Disconnected from Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
// TODO: should this be including launchdata?
if (ActivityServerType != ServerType.Reserved)
{
ActivityHistory.Insert(0, new ActivityHistoryEntry
{
PlaceId = ActivityPlaceId,
UniverseId = ActivityUniverseId,
JobId = ActivityJobId,
TimeJoined = ActivityTimeJoined,
TimeLeft = DateTime.Now
});
}
ActivityInGame = false;
ActivityPlaceId = 0;
ActivityUniverseId = 0;
ActivityJobId = "";
ActivityMachineAddress = "";
ActivityMachineUDMUX = false;

View File

@ -207,15 +207,8 @@ namespace Bloxstrap.Integrations
// TODO: move this to its own function under the activity watcher?
// TODO: show error if information cannot be queried instead of silently failing
var universeIdResponse = await Http.GetJson<UniverseIdResponse>($"https://apis.roblox.com/universes/v1/places/{placeId}/universe");
if (universeIdResponse is null)
{
App.Logger.WriteLine(LOG_IDENT, "Could not get Universe ID!");
return false;
}
long universeId = universeIdResponse.UniverseId;
App.Logger.WriteLine(LOG_IDENT, $"Got Universe ID as {universeId}");
long universeId = _activityWatcher.ActivityUniverseId;
// preserve time spent playing if we're teleporting between places in the same universe
if (_timeStartedUniverse is null || !_activityWatcher.ActivityIsTeleport || universeId != _currentUniverseId)
@ -277,7 +270,7 @@ namespace Bloxstrap.Integrations
buttons.Add(new Button
{
Label = "Join server",
Url = $"roblox://experiences/start?placeId={placeId}&gameInstanceId={_activityWatcher.ActivityJobId}"
Url = _activityWatcher.GetActivityDeeplink()
});
}

View File

@ -8,11 +8,13 @@ namespace Bloxstrap
public T Prop { get; set; } = new();
public virtual string FileLocation => Path.Combine(Paths.Base, $"{typeof(T).Name}.json");
public virtual string ClassName => typeof(T).Name;
private string LOG_IDENT_CLASS => $"JsonManager<{typeof(T).Name}>";
public virtual string FileLocation => Path.Combine(Paths.Base, $"{ClassName}.json");
public virtual void Load()
public virtual string LOG_IDENT_CLASS => $"JsonManager<{ClassName}>";
public virtual void Load(bool alertFailure = true)
{
string LOG_IDENT = $"{LOG_IDENT_CLASS}::Load";
@ -32,7 +34,22 @@ namespace Bloxstrap
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to load!");
App.Logger.WriteLine(LOG_IDENT, $"{ex.Message}");
App.Logger.WriteException(LOG_IDENT, ex);
if (alertFailure)
{
string message = "";
if (ClassName == nameof(Settings))
message = Strings.JsonManager_SettingsLoadFailed;
else if (ClassName == nameof(FastFlagManager))
message = Strings.JsonManager_FastFlagsLoadFailed;
if (!String.IsNullOrEmpty(message))
Frontend.ShowMessageBox($"{message}\n\n{ex.GetType()}: {ex.Message}", System.Windows.MessageBoxImage.Warning);
}
Save();
}
}

View File

@ -215,7 +215,7 @@ namespace Bloxstrap
App.Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
if (t.Exception is not null)
App.FinalizeExceptionHandling(t.Exception, false);
App.FinalizeExceptionHandling(t.Exception);
}
App.Terminate();

View File

@ -16,8 +16,7 @@
{
const string LOG_IDENT = "Logger::Initialize";
// TODO: <Temp>/Bloxstrap/Logs/
string directory = useTempDir ? Path.Combine(Paths.LocalAppData, "Temp") : Path.Combine(Paths.Base, "Logs");
string directory = useTempDir ? Path.Combine(Paths.TempLogs) : Path.Combine(Paths.Base, "Logs");
string timestamp = DateTime.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'");
string filename = $"{App.ProjectName}_{timestamp}.log";
string location = Path.Combine(directory, filename);

View File

@ -0,0 +1,38 @@
using CommunityToolkit.Mvvm.Input;
using System.Windows.Input;
namespace Bloxstrap.Models
{
public class ActivityHistoryEntry
{
public long UniverseId { get; set; }
public long PlaceId { get; set; }
public string JobId { get; set; } = String.Empty;
public DateTime TimeJoined { get; set; }
public DateTime TimeLeft { get; set; }
public string TimeJoinedFriendly => String.Format("{0} - {1}", TimeJoined.ToString("h:mm tt"), TimeLeft.ToString("h:mm tt"));
public bool DetailsLoaded = false;
public string GameName { get; set; } = String.Empty;
public string GameThumbnail { get; set; } = String.Empty;
public ICommand RejoinServerCommand => new RelayCommand(RejoinServer);
private void RejoinServer()
{
string playerPath = Path.Combine(Paths.Versions, App.State.Prop.PlayerVersionGuid, "RobloxPlayerBeta.exe");
string deeplink = $"roblox://experiences/start?placeId={PlaceId}&gameInstanceId={JobId}";
// start RobloxPlayerBeta.exe directly since Roblox can reuse the existing window
// ideally, i'd like to find out how roblox is doing it
Process.Start(playerPath, deeplink);
}
}
}

View File

@ -0,0 +1,13 @@
namespace Bloxstrap.Models
{
public class Supporter
{
[JsonPropertyName("imageAsset")]
public string ImageAsset { get; set; } = null!;
[JsonPropertyName("name")]
public string Name { get; set; } = null!;
public string Image => $"https://raw.githubusercontent.com/bloxstraplabs/config/main/assets/{ImageAsset}";
}
}

View File

@ -0,0 +1,11 @@
namespace Bloxstrap.Models
{
public class SupporterData
{
[JsonPropertyName("columns")]
public int Columns { get; set; }
[JsonPropertyName("supporters")]
public List<Supporter> Supporters { get; set; } = null!;
}
}

View File

@ -69,6 +69,24 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to These are the people currently supporting Bloxstrap through [Ko-fi]({0}). A massive thank you to everyone here!.
/// </summary>
public static string About_Supporters_Description {
get {
return ResourceManager.GetString("About.Supporters.Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Supporters.
/// </summary>
public static string About_Supporters_Title {
get {
return ResourceManager.GetString("About.Supporters.Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to About Bloxstrap.
/// </summary>
@ -449,6 +467,15 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Loading, please wait....
/// </summary>
public static string Common_Loading {
get {
return ResourceManager.GetString("Common.Loading", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Miscellaneous.
/// </summary>
@ -485,6 +512,15 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Could not load data because of a network error..
/// </summary>
public static string Common_NetworkError {
get {
return ResourceManager.GetString("Common.NetworkError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to New.
/// </summary>
@ -629,6 +665,24 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Rejoin.
/// </summary>
public static string ContextMenu_GameHistory_Rejoin {
get {
return ResourceManager.GetString("ContextMenu.GameHistory.Rejoin", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Game history.
/// </summary>
public static string ContextMenu_GameHistory_Title {
get {
return ResourceManager.GetString("ContextMenu.GameHistory.Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Roblox is still launching. A log file will only be available once Roblox launches..
/// </summary>
@ -638,15 +692,6 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to See server details.
/// </summary>
public static string ContextMenu_SeeServerDetails {
get {
return ResourceManager.GetString("ContextMenu.SeeServerDetails", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy Instance ID.
/// </summary>
@ -1422,7 +1467,7 @@ namespace Bloxstrap.Resources {
/// <summary>
/// Looks up a localized string similar to Thank you for downloading Bloxstrap.
///
///You should have gotten it from either {0} or {1}. Those are the only official websites to get it from.
///You should have downloaded it from either {0} or {1}. Those are the only official websites to get it from. It is your responsibility to ensure you download from an official source.
///
///This installation process will be quick and simple, and you will be able to configure any of Bloxstrap&apos;s settings after installation..
/// </summary>
@ -1459,6 +1504,24 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Your Fast Flags could not be loaded. They have been reset to the default configuration..
/// </summary>
public static string JsonManager_FastFlagsLoadFailed {
get {
return ResourceManager.GetString("JsonManager.FastFlagsLoadFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your Settings could not be loaded. They have been reset to the default configuration..
/// </summary>
public static string JsonManager_SettingsLoadFailed {
get {
return ResourceManager.GetString("JsonManager.SettingsLoadFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Configure settings.
/// </summary>

View File

@ -268,9 +268,6 @@ Your ReShade configuration files will still be saved, and you can locate them by
<data name="ContextMenu.CopyDeeplinkInvite" xml:space="preserve">
<value>Copy invite deeplink</value>
</data>
<data name="ContextMenu.SeeServerDetails" xml:space="preserve">
<value>See server details</value>
</data>
<data name="ContextMenu.ServerInformation.CopyInstanceId" xml:space="preserve">
<value>Copy Instance ID</value>
</data>
@ -999,7 +996,7 @@ Selecting 'No' will ignore this warning and continue installation.</value>
<data name="Installer.Welcome.MainText" xml:space="preserve">
<value>Thank you for downloading Bloxstrap.
You should have gotten it from either {0} or {1}. Those are the only official websites to get it from.
You should have downloaded it from either {0} or {1}. Those are the only official websites to get it from. It is your responsibility to ensure you download from an official source.
This installation process will be quick and simple, and you will be able to configure any of Bloxstrap's settings after installation.</value>
</data>
@ -1156,4 +1153,28 @@ Are you sure you want to continue?</value>
<data name="Dialog.PlayerError.HelpInformation" xml:space="preserve">
<value>Please read the following help information, which will open in your web browser when you close this dialog.</value>
</data>
<data name="Common.NetworkError" xml:space="preserve">
<value>Could not load data because of a network error.</value>
</data>
<data name="Common.Loading" xml:space="preserve">
<value>Loading, please wait...</value>
</data>
<data name="About.Supporters.Title" xml:space="preserve">
<value>Supporters</value>
</data>
<data name="About.Supporters.Description" xml:space="preserve">
<value>These are the people currently supporting Bloxstrap through [Ko-fi]({0}). A massive thank you to everyone here!</value>
</data>
<data name="JsonManager.SettingsLoadFailed" xml:space="preserve">
<value>Your Settings could not be loaded. They have been reset to the default configuration.</value>
</data>
<data name="JsonManager.FastFlagsLoadFailed" xml:space="preserve">
<value>Your Fast Flags could not be loaded. They have been reset to the default configuration.</value>
</data>
<data name="ContextMenu.GameHistory.Title" xml:space="preserve">
<value>Game history</value>
</data>
<data name="ContextMenu.GameHistory.Rejoin" xml:space="preserve">
<value>Rejoin</value>
</data>
</root>

View File

@ -32,11 +32,11 @@ namespace Bloxstrap.UI.Converters
return attribute.StaticName;
if (attribute.FromTranslation is not null)
return Resources.Strings.ResourceManager.GetStringSafe(attribute.FromTranslation);
return Strings.ResourceManager.GetStringSafe(attribute.FromTranslation);
}
}
return Resources.Strings.ResourceManager.GetStringSafe(String.Format(
return Strings.ResourceManager.GetStringSafe(String.Format(
"{0}.{1}",
typeName.Substring(typeName.IndexOf('.', StringComparison.Ordinal) + 1),
stringVal

View File

@ -3,7 +3,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:enums="clr-namespace:Bloxstrap.Enums"
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels"
xmlns:dmodels="clr-namespace:Bloxstrap.UI.ViewModels.About"
xmlns:controls="clr-namespace:Bloxstrap.UI.Elements.Controls"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:resources="clr-namespace:Bloxstrap.Resources"
@ -11,7 +13,13 @@
d:DesignHeight="1500" d:DesignWidth="800"
Title="AboutPage"
Scrollable="True">
<!--d:DataContext="{d:DesignInstance dmodels:AboutViewModel, IsDesignTimeCreatable=True}"-->
<StackPanel Margin="0,0,14,14">
<StackPanel.Resources>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
</StackPanel.Resources>
<Grid Margin="0,0,0,24" HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -73,6 +81,85 @@
</Grid>
</StackPanel>
<TextBlock Text="{x:Static resources:Strings.About_Supporters_Title}" FontWeight="Medium" FontSize="20" Margin="0,16,0,0" />
<controls:MarkdownTextBlock MarkdownText="{Binding Source={x:Static resources:Strings.About_Supporters_Description}, Converter={StaticResource StringFormatConverter}, ConverterParameter='https://ko-fi.com/boxerpizza'}" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
<Grid Margin="0,8,0,0">
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding SupportersLoadedState, Mode=OneWay}" Value="{x:Static enums:GenericTriState.Unknown}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</Grid.Style>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:ProgressRing Grid.Column="0" IsIndeterminate="True" />
<TextBlock Grid.Column="1" Margin="16,0,0,0" Text="{x:Static resources:Strings.Common_Loading}" VerticalAlignment="Center" />
</Grid>
<Grid Margin="0,8,0,0">
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding SupportersLoadedState, Mode=OneWay}" Value="{x:Static enums:GenericTriState.Failed}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</Grid.Style>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="pack://application:,,,/Resources/MessageBox/Error.png" Width="60" Height="60" RenderOptions.BitmapScalingMode="HighQuality" />
<StackPanel Grid.Column="1" Margin="16,0,0,0" VerticalAlignment="Center">
<TextBlock Text="{x:Static resources:Strings.Common_NetworkError}" />
<TextBlock Text="{Binding SupportersLoadError, Mode=OneWay}" />
</StackPanel>
</Grid>
<ListView ItemsSource="{Binding Supporters, Mode=OneWay}" Margin="0,8,0,0" ScrollViewer.CanContentScroll="False" IsEnabled="False">
<ListView.Style>
<Style TargetType="ListView" BasedOn="{StaticResource {x:Type ListView}}">
<Style.Triggers>
<DataTrigger Binding="{Binding SupportersLoadedState, Mode=OneWay}" Value="{x:Static enums:GenericTriState.Successful}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</ListView.Style>
<ListView.ItemTemplate>
<DataTemplate>
<ui:Card Padding="8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Height="32" Width="32" VerticalAlignment="Center">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding Image, IsAsync=True}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" Margin="8,0,2,0" VerticalAlignment="Center" Text="{Binding Name}" />
</Grid>
</ui:Card>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding SupporterColumns}" Margin="-4" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<TextBlock Text="{x:Static resources:Strings.Menu_About_Contributors}" FontWeight="Medium" FontSize="20" Margin="0,16,0,0" />
<TextBlock Text="{x:Static resources:Strings.Menu_About_Contributors_Description}" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
<Grid Margin="0,8,0,0">

View File

@ -1,4 +1,4 @@
using Bloxstrap.UI.ViewModels.Settings;
using Bloxstrap.UI.ViewModels.About;
namespace Bloxstrap.UI.Elements.About.Pages
{

View File

@ -43,7 +43,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
{
InitializeComponent();
this.buttonCancel.Text = Resources.Strings.Common_Cancel;
this.buttonCancel.Text = Strings.Common_Cancel;
ScaleWindow();
SetupDialog();

View File

@ -43,7 +43,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
InitializeComponent();
this.IconBox.BackgroundImage = App.Settings.Prop.BootstrapperIcon.GetIcon().ToBitmap();
this.buttonCancel.Text = Resources.Strings.Common_Cancel;
this.buttonCancel.Text = Strings.Common_Cancel;
ScaleWindow();
SetupDialog();

View File

@ -52,8 +52,8 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
this.BackColor = Color.FromArgb(25, 27, 29);
}
this.labelMessage.Text = Resources.Strings.Bootstrapper_StylePreview_TextCancel;
this.buttonCancel.Text = Resources.Strings.Common_Cancel;
this.labelMessage.Text = Strings.Bootstrapper_StylePreview_TextCancel;
this.buttonCancel.Text = Strings.Common_Cancel;
this.IconBox.BackgroundImage = App.Settings.Prop.BootstrapperIcon.GetIcon().GetSized(128, 128).ToBitmap();
SetupDialog();

View File

@ -57,7 +57,19 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:SymbolIcon Grid.Column="0" Symbol="Info28"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="{x:Static resources:Strings.ContextMenu_SeeServerDetails}" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="{x:Static resources:Strings.ContextMenu_ServerInformation_Title}" />
</Grid>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="JoinLastServerMenuItem" Visibility="Collapsed" Click="JoinLastServerMenuItem_Click">
<MenuItem.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:SymbolIcon Grid.Column="0" Symbol="History24"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="{x:Static resources:Strings.ContextMenu_GameHistory_Title}" />
</Grid>
</MenuItem.Header>
</MenuItem>

View File

@ -80,6 +80,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
public void ActivityWatcher_OnGameLeave(object? sender, EventArgs e)
{
Dispatcher.Invoke(() => {
JoinLastServerMenuItem.Visibility = Visibility.Visible;
InviteDeeplinkMenuItem.Visibility = Visibility.Collapsed;
ServerDetailsMenuItem.Visibility = Visibility.Collapsed;
@ -129,5 +130,13 @@ namespace Bloxstrap.UI.Elements.ContextMenu
_watcher.KillRobloxProcess();
}
private void JoinLastServerMenuItem_Click(object sender, RoutedEventArgs e)
{
if (_activityWatcher is null)
throw new ArgumentNullException(nameof(_activityWatcher));
new ServerHistory(_activityWatcher).ShowDialog();
}
}
}

View File

@ -0,0 +1,89 @@
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.ContextMenu.ServerHistory"
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:local="clr-namespace:Bloxstrap.UI.Elements.ContextMenu"
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels.ContextMenu"
xmlns:resources="clr-namespace:Bloxstrap.Resources"
d:DataContext="{d:DesignInstance Type=models:ServerHistoryViewModel}"
mc:Ignorable="d"
Title="{x:Static resources:Strings.ContextMenu_GameHistory_Title}"
MinWidth="420"
MinHeight="420"
Width="580"
Height="420"
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" x:Name="RootTitleBar" Title="{x:Static resources:Strings.ContextMenu_GameHistory_Title}" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" KeyboardNavigation.TabNavigation="None" Icon="pack://application:,,,/Bloxstrap.ico" />
<Border Grid.Row="1">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ActivityHistory, Mode=OneWay}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</Border.Style>
<ui:ProgressRing Grid.Row="1" IsIndeterminate="True" />
</Border>
<ListView Grid.Row="1" ItemsSource="{Binding ActivityHistory, Mode=OneWay}" Margin="8">
<ListView.Style>
<Style TargetType="ListView" BasedOn="{StaticResource {x:Type ListView}}">
<Style.Triggers>
<DataTrigger Binding="{Binding ActivityHistory, Mode=OneWay}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Visible" />
</Style>
</ListView.Style>
<ListView.ItemTemplate>
<DataTemplate>
<ui:Card Padding="12">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Width="84" Height="84" CornerRadius="4">
<Border.Background>
<ImageBrush ImageSource="{Binding GameThumbnail, IsAsync=True}" />
</Border.Background>
</Border>
<StackPanel Grid.Column="1" Margin="16,0,0,0" VerticalAlignment="Center">
<TextBlock Text="{Binding GameName}" FontSize="18" FontWeight="Medium" />
<TextBlock Text="{Binding TimeJoinedFriendly}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<ui:Button Margin="0,8,0,0" Content="{x:Static resources:Strings.ContextMenu_GameHistory_Rejoin}" Command="{Binding RejoinServerCommand}" Appearance="Success" Icon="Play28" IconFilled="True" />
</StackPanel>
</Grid>
</ui:Card>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Border Grid.Row="2" Padding="15" Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
<StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Right">
<Button Margin="12,0,0,0" MinWidth="100" Content="{x:Static resources:Strings.Common_Close}" IsCancel="True" />
</StackPanel>
</Border>
</Grid>
</base:WpfUiWindow>

View File

@ -0,0 +1,21 @@
using Bloxstrap.Integrations;
using Bloxstrap.UI.ViewModels.ContextMenu;
namespace Bloxstrap.UI.Elements.ContextMenu
{
/// <summary>
/// Interaction logic for ServerInformation.xaml
/// </summary>
public partial class ServerHistory
{
public ServerHistory(ActivityWatcher watcher)
{
var viewModel = new ServerHistoryViewModel(watcher);
viewModel.RequestCloseEvent += (_, _) => Close();
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@ -121,13 +121,13 @@ namespace Bloxstrap.UI.Elements.Dialogs
switch (result)
{
case MessageBoxResult.OK:
return Bloxstrap.Resources.Strings.Common_OK;
return Strings.Common_OK;
case MessageBoxResult.Cancel:
return Bloxstrap.Resources.Strings.Common_Cancel;
return Strings.Common_Cancel;
case MessageBoxResult.Yes:
return Bloxstrap.Resources.Strings.Common_Yes;
return Strings.Common_Yes;
case MessageBoxResult.No:
return Bloxstrap.Resources.Strings.Common_No;
return Strings.Common_No;
default:
Debug.Assert(false);
return result.ToString();

View File

@ -24,7 +24,7 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ui:TitleBar Grid.Row="0" Grid.ColumnSpan="2" Padding="8" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" KeyboardNavigation.TabNavigation="None" />
<ui:TitleBar Grid.Row="0" Grid.ColumnSpan="2" Padding="8" ShowMinimize="False" ShowMaximize="False" Title="Bloxstrap" Icon="pack://application:,,,/Bloxstrap.ico" CanMaximize="False" KeyboardNavigation.TabNavigation="None" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
@ -53,7 +53,7 @@
<StackPanel Grid.Row="1" HorizontalAlignment="Center">
<ui:Hyperlink Icon="QuestionCircle48" Content="About Bloxstrap" Margin="0,0,0,8" Command="{Binding LaunchAboutCommand, Mode=OneTime}" />
<ui:Hyperlink Icon="WindowNew16" Content="Support us on Ko-fi!" NavigateUri="https://ko-fi.com/boxerpizza" />
<ui:Hyperlink Icon="Heart48" Content="Support us on Ko-fi!" NavigateUri="https://ko-fi.com/boxerpizza" />
</StackPanel>
</Grid>

View File

@ -307,12 +307,10 @@ namespace Bloxstrap.UI.Elements.Settings.Pages
private void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
int index = e.Row.GetIndex();
FastFlag entry = _fastFlagList[index];
if (e.Row.DataContext is not FastFlag entry)
return;
var textbox = e.EditingElement as TextBox;
if (textbox is null)
if (e.EditingElement is not TextBox textbox)
return;
switch (e.Column.Header)

View File

@ -86,7 +86,7 @@
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_FPSLimit_Title}"
Description="{x:Static resources:Strings.Menu_FastFlags_Presets_FPSLimit_Description}"
HelpLink="https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#framerate-limit">
<ui:TextBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" Text="{Binding FramerateLimit, Mode=TwoWay}" PreviewTextInput="ValidateInt32" />
<ui:TextBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" Text="{Binding FramerateLimit, Mode=TwoWay}" PreviewTextInput="ValidateUInt32" />
</controls:OptionControl>
<controls:OptionControl

View File

@ -31,6 +31,8 @@ namespace Bloxstrap.UI.Elements.Settings.Pages
DataContext = new FastFlagsViewModel(this);
}
private void ValidateInt32(object sender, TextCompositionEventArgs e) => e.Handled = !Int32.TryParse(e.Text, out int _);
private void ValidateInt32(object sender, TextCompositionEventArgs e) => e.Handled = e.Text != "-" && !Int32.TryParse(e.Text, out int _);
private void ValidateUInt32(object sender, TextCompositionEventArgs e) => e.Handled = !UInt32.TryParse(e.Text, out uint _);
}
}

View File

@ -0,0 +1,63 @@
using System.Windows;
namespace Bloxstrap.UI.ViewModels.About
{
public class AboutViewModel : NotifyPropertyChangedViewModel
{
private SupporterData? _supporterData;
public string Version => string.Format(Strings.Menu_About_Version, App.Version);
public BuildMetadataAttribute BuildMetadata => App.BuildMetadata;
public string BuildTimestamp => BuildMetadata.Timestamp.ToFriendlyString();
public string BuildCommitHashUrl => $"https://github.com/{App.ProjectRepository}/commit/{BuildMetadata.CommitHash}";
public Visibility BuildInformationVisibility => App.IsProductionBuild ? Visibility.Collapsed : Visibility.Visible;
public Visibility BuildCommitVisibility => App.IsActionBuild ? Visibility.Visible : Visibility.Collapsed;
public List<Supporter> Supporters => _supporterData?.Supporters ?? Enumerable.Empty<Supporter>().ToList();
public int SupporterColumns => _supporterData?.Columns ?? 0;
public GenericTriState SupportersLoadedState { get; set; } = GenericTriState.Unknown;
public string SupportersLoadError { get; set; } = "";
public AboutViewModel()
{
// this will cause momentary freezes only when ran under the debugger
LoadSupporterData();
}
public async void LoadSupporterData()
{
const string LOG_IDENT = "AboutViewModel::LoadSupporterData";
try
{
_supporterData = await Http.GetJson<SupporterData>("https://raw.githubusercontent.com/bloxstraplabs/config/main/supporters.json");
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, "Could not load supporter data");
App.Logger.WriteException(LOG_IDENT, ex);
SupportersLoadedState = GenericTriState.Failed;
SupportersLoadError = ex.Message;
OnPropertyChanged(nameof(SupportersLoadError));
}
if (_supporterData is not null)
{
SupportersLoadedState = GenericTriState.Successful;
OnPropertyChanged(nameof(Supporters));
OnPropertyChanged(nameof(SupporterColumns));
}
OnPropertyChanged(nameof(SupportersLoadedState));
}
}
}

View File

@ -0,0 +1,60 @@
using System.Collections.ObjectModel;
using System.Windows.Data;
using System.Windows.Input;
using Bloxstrap.Integrations;
using CommunityToolkit.Mvvm.Input;
namespace Bloxstrap.UI.ViewModels.ContextMenu
{
internal class ServerHistoryViewModel : NotifyPropertyChangedViewModel
{
private readonly ActivityWatcher _activityWatcher;
public List<ActivityHistoryEntry>? ActivityHistory { get; private set; }
public ICommand CloseWindowCommand => new RelayCommand(RequestClose);
public EventHandler? RequestCloseEvent;
public ServerHistoryViewModel(ActivityWatcher activityWatcher)
{
_activityWatcher = activityWatcher;
_activityWatcher.OnGameLeave += (_, _) => LoadData();
LoadData();
}
private async void LoadData()
{
var entries = _activityWatcher.ActivityHistory.Where(x => !x.DetailsLoaded);
if (entries.Any())
{
string universeIds = String.Join(',', entries.Select(x => x.UniverseId));
var gameDetailResponse = await Http.GetJson<ApiArrayResponse<GameDetailResponse>>($"https://games.roblox.com/v1/games?universeIds={universeIds}");
if (gameDetailResponse is null || !gameDetailResponse.Data.Any())
return;
var universeThumbnailResponse = await Http.GetJson<ApiArrayResponse<ThumbnailResponse>>($"https://thumbnails.roblox.com/v1/games/icons?universeIds={universeIds}&returnPolicy=PlaceHolder&size=128x128&format=Png&isCircular=false");
if (universeThumbnailResponse is null || !universeThumbnailResponse.Data.Any())
return;
foreach (var entry in entries)
{
entry.GameName = gameDetailResponse.Data.Where(x => x.Id == entry.UniverseId).Select(x => x.Name).First();
entry.GameThumbnail = universeThumbnailResponse.Data.Where(x => x.TargetId == entry.UniverseId).Select(x => x.ImageUrl).First();
entry.DetailsLoaded = true;
}
}
ActivityHistory = new(_activityWatcher.ActivityHistory);
OnPropertyChanged(nameof(ActivityHistory));
}
private void RequestClose() => RequestCloseEvent?.Invoke(this, EventArgs.Empty);
}
}

View File

@ -32,11 +32,11 @@ namespace Bloxstrap.UI.ViewModels.Installer
}
installer.InstallLocation = value;
CheckExistingData();
OnPropertyChanged(nameof(DataFoundMessageVisibility));
}
}
public Visibility DataFoundMessageVisibility { get; set; } = Visibility.Collapsed;
public Visibility DataFoundMessageVisibility => installer.ExistingDataPresent ? Visibility.Visible : Visibility.Collapsed;
public string ErrorMessage => installer.InstallLocationError;
@ -61,7 +61,6 @@ namespace Bloxstrap.UI.ViewModels.Installer
public InstallViewModel()
{
_originalInstallLocation = installer.InstallLocation;
CheckExistingData();
}
public bool DoInstall()
@ -79,16 +78,6 @@ namespace Bloxstrap.UI.ViewModels.Installer
return true;
}
public void CheckExistingData()
{
if (File.Exists(Path.Combine(InstallLocation, "Settings.json")))
DataFoundMessageVisibility = Visibility.Visible;
else
DataFoundMessageVisibility = Visibility.Collapsed;
OnPropertyChanged(nameof(DataFoundMessageVisibility));
}
private void BrowseInstallLocation()
{
using var dialog = new System.Windows.Forms.FolderBrowserDialog();

View File

@ -5,9 +5,9 @@
{
// formatting is done here instead of in xaml, it's just a bit easier
public string MainText => String.Format(
Resources.Strings.Installer_Welcome_MainText,
Strings.Installer_Welcome_MainText,
"[github.com/pizzaboxer/bloxstrap](https://github.com/pizzaboxer/bloxstrap)",
"[bloxstrap.pizzaboxer.xyz](https://bloxstrap.pizzaboxer.xyz)"
"[bloxstraplabs.com](https://bloxstraplabs.com)"
);
public string VersionNotice { get; private set; } = "";

View File

@ -1,17 +0,0 @@
using System.Windows;
namespace Bloxstrap.UI.ViewModels.Settings
{
public class AboutViewModel
{
public string Version => string.Format(Strings.Menu_About_Version, App.Version);
public BuildMetadataAttribute BuildMetadata => App.BuildMetadata;
public string BuildTimestamp => BuildMetadata.Timestamp.ToFriendlyString();
public string BuildCommitHashUrl => $"https://github.com/{App.ProjectRepository}/commit/{BuildMetadata.CommitHash}";
public Visibility BuildInformationVisibility => App.IsProductionBuild ? Visibility.Collapsed : Visibility.Visible;
public Visibility BuildCommitVisibility => App.IsActionBuild ? Visibility.Visible : Visibility.Collapsed;
}
}

View File

@ -23,9 +23,9 @@ namespace Bloxstrap.UI.ViewModels.Settings
IBootstrapperDialog dialog = App.Settings.Prop.BootstrapperStyle.GetNew();
if (App.Settings.Prop.BootstrapperStyle == BootstrapperStyle.ByfronDialog)
dialog.Message = Resources.Strings.Bootstrapper_StylePreview_ImageCancel;
dialog.Message = Strings.Bootstrapper_StylePreview_ImageCancel;
else
dialog.Message = Resources.Strings.Bootstrapper_StylePreview_TextCancel;
dialog.Message = Strings.Bootstrapper_StylePreview_TextCancel;
dialog.CancelEnabled = true;
dialog.ShowBootstrapper();
@ -35,7 +35,7 @@ namespace Bloxstrap.UI.ViewModels.Settings
{
var dialog = new OpenFileDialog
{
Filter = $"{Resources.Strings.Menu_IconFiles}|*.ico"
Filter = $"{Strings.Menu_IconFiles}|*.ico"
};
if (dialog.ShowDialog() != true)

View File

@ -31,13 +31,13 @@ namespace Bloxstrap.UI.ViewModels.Settings
{
for (int i = 10; i > 0; i--)
{
ContinueButtonText = $"({i}) {Resources.Strings.Menu_FastFlagEditor_Warning_Continue}";
ContinueButtonText = $"({i}) {Strings.Menu_FastFlagEditor_Warning_Continue}";
OnPropertyChanged(nameof(ContinueButtonText));
await Task.Delay(1000);
}
ContinueButtonText = Resources.Strings.Menu_FastFlagEditor_Warning_Continue;
ContinueButtonText = Strings.Menu_FastFlagEditor_Warning_Continue;
OnPropertyChanged(nameof(ContinueButtonText));
CanContinue = true;

View File

@ -19,7 +19,7 @@ namespace Bloxstrap.UI.ViewModels.Settings
{
CustomIntegrations.Add(new CustomIntegration()
{
Name = Resources.Strings.Menu_Integrations_Custom_NewIntegration
Name = Strings.Menu_Integrations_Custom_NewIntegration
});
SelectedCustomIntegrationIndex = CustomIntegrations.Count - 1;

View File

@ -17,8 +17,16 @@ namespace Bloxstrap.Utility
public InterProcessLock(string name, TimeSpan timeout)
{
Mutex = new Mutex(false, "Bloxstrap-" + name);
try
{
IsAcquired = Mutex.WaitOne(timeout);
}
catch (AbandonedMutexException)
{
IsAcquired = true;
}
}
public void Dispose()
{

View File

@ -1,5 +1,5 @@
> [!CAUTION]
> The only official places to download Bloxstrap are this GitHub repository and [bloxstrap.pizzaboxer.xyz](https://bloxstrap.pizzaboxer.xyz). Any other websites offering downloads or claiming to be us are not controlled by us.
> The only official places to download Bloxstrap are this GitHub repository and [bloxstraplabs.com](https://bloxstraplabs.com). Any other websites offering downloads or claiming to be us are not controlled by us.
# <img src="https://github.com/pizzaboxer/bloxstrap/raw/main/Images/Bloxstrap.png" width="48"/> Bloxstrap
@ -20,7 +20,7 @@ Bloxstrap is only supported for PCs running Windows.
**Q: Is this malware?**
**A:** No. The source code here is viewable to all, and it'd be impossible for us to slip anything malicious into the downloads without anyone noticing. Just be sure you're downloading it from an official source. The only two official sources are this GitHub repository and [bloxstrap.pizzaboxer.xyz](https://bloxstrap.pizzaboxer.xyz).
**A:** No. The source code here is viewable to all, and it'd be impossible for us to slip anything malicious into the downloads without anyone noticing. Just be sure you're downloading it from an official source. The only two official sources are this GitHub repository and [bloxstraplabs.com](https://bloxstraplabs.com).
**Q: Can using this get me banned?**