Code as of September 18th 2024

This commit is contained in:
pizzaboxer 2024-11-29 20:43:01 +00:00
parent 0b20720fdb
commit 82d017570b
No known key found for this signature in database
GPG Key ID: 59D4A1DBAD0F2BA8
15 changed files with 683 additions and 475 deletions

View File

@ -34,6 +34,7 @@
</Setter>
</Style>
<converters:StringResourceConverter x:Key="StringResourceConverter" />
<converters:StringFormatConverter x:Key="StringFormatConverter" />
<converters:RangeConverter x:Key="RangeConverter" />
<converters:EnumNameConverter x:Key="EnumNameConverter" />

View File

@ -236,6 +236,36 @@ namespace Bloxstrap
State.Load();
FastFlags.Load();
//List<string> tests = new()
//{
// "UI.FullscreenTitlebarDelay == null || true",
// "UI.FullscreenTitlebarDelay == null && true",
// "UI.FullscreenTitlebarDelay == null || false",
// "UI.FullscreenTitlebarDelay == null && false",
// "UI.FullscreenTitlebarDelay == null && false || true",
// "UI.FullscreenTitlebarDelay == null && true || false",
// "false && false || true",
// "(false && false) || true",
// "false && (false || true)",
// "FFlagDisableNewIGMinDUA != True",
// "FStringDebugFlagState == 'hi'",
// "FStringDebugFlagState == 'wassup'",
// "FStringDebugFlagState == FStringDebugFlagState",
// "FStringDebugFlagState != FStringDebugFlagState",
// "FLogNetwork == 7",
// "FLogNetwork > 9",
// "FLogNetwork < 9"
//};
//foreach (string test in tests)
//{
// bool result = new FlexParser(test).Evaluate();
// App.Logger.WriteLine(LOG_IDENT, $"'{test}' evaluated to {result}");
//}
//Debugger.Break();
if (!Locale.SupportedLocales.ContainsKey(Settings.Prop.Locale))
{
Settings.Prop.Locale = "nil";

View File

@ -0,0 +1,28 @@
namespace Bloxstrap.Enums
{
public enum FlexTokenType
{
// data types
NULL,
NUMBER,
BOOL,
STRING,
FLAG,
// comparison operators
COMPARE_EQ,
COMPARE_NEQ,
COMPARE_GT,
COMPARE_LT,
COMPARE_GEQ,
COMPARE_LEQ,
// boolean logic operators
LOGIC_AND,
LOGIC_OR,
// yes, "bracket" is technically not the correct term, don't care
BRACKET_OPEN,
BRACKET_CLOSE
}
}

View File

@ -0,0 +1,14 @@
namespace Bloxstrap.Exceptions
{
internal class FlexParseException : Exception
{
public FlexParseException(string expression, string message, string? position = null)
: base($"Invalid syntax encountered when parsing '{expression}' at position {position ?? "EOF"} ({message})") { }
public FlexParseException(string expression, string message, int position)
: this(expression, message, position.ToString()) { }
public FlexParseException(string expression, string message, FlexToken? token)
: this(expression, message, token?.Position.ToString()) { }
}
}

View File

@ -1,6 +1,4 @@
using Bloxstrap.Enums.FlagPresets;
namespace Bloxstrap
namespace Bloxstrap
{
public class FastFlagManager : JsonManager<Dictionary<string, object>>
{
@ -12,146 +10,13 @@ namespace Bloxstrap
public bool Changed => !OriginalProp.SequenceEqual(Prop);
public static IReadOnlyDictionary<string, string> PresetFlags = new Dictionary<string, string>
public readonly FFlagPresets PresetConfig;
public FastFlagManager()
{
{ "Network.Log", "FLogNetwork" },
#if DEBUG
{ "HTTP.Log", "DFLogHttpTraceLight" },
{ "HTTP.Proxy.Enable", "DFFlagDebugEnableHttpProxy" },
{ "HTTP.Proxy.Address.1", "DFStringDebugPlayerHttpProxyUrl" },
{ "HTTP.Proxy.Address.2", "DFStringHttpCurlProxyHostAndPort" },
{ "HTTP.Proxy.Address.3", "DFStringHttpCurlProxyHostAndPortForExternalUrl" },
#endif
{ "Rendering.Framerate", "DFIntTaskSchedulerTargetFps" },
{ "Rendering.ManualFullscreen", "FFlagHandleAltEnterFullscreenManually" },
{ "Rendering.DisableScaling", "DFFlagDisableDPIScale" },
{ "Rendering.MSAA", "FIntDebugForceMSAASamples" },
{ "Rendering.DisablePostFX", "FFlagDisablePostFx" },
{ "Rendering.ShadowIntensity", "FIntRenderShadowIntensity" },
{ "Rendering.Mode.D3D11", "FFlagDebugGraphicsPreferD3D11" },
{ "Rendering.Mode.D3D10", "FFlagDebugGraphicsPreferD3D11FL10" },
{ "Rendering.Lighting.Voxel", "DFFlagDebugRenderForceTechnologyVoxel" },
{ "Rendering.Lighting.ShadowMap", "FFlagDebugForceFutureIsBrightPhase2" },
{ "Rendering.Lighting.Future", "FFlagDebugForceFutureIsBrightPhase3" },
{ "Rendering.TextureQuality.OverrideEnabled", "DFFlagTextureQualityOverrideEnabled" },
{ "Rendering.TextureQuality.Level", "DFIntTextureQualityOverride" },
{ "Rendering.TerrainTextureQuality", "FIntTerrainArraySliceSize" },
{ "UI.Hide", "DFIntCanHideGuiGroupId" },
{ "UI.FontSize", "FIntFontSizePadding" },
#if DEBUG
{ "UI.FlagState", "FStringDebugShowFlagState" },
#endif
{ "UI.FullscreenTitlebarDelay", "FIntFullscreenTitleBarTriggerDelayMillis" },
{ "UI.Menu.Style.V2Rollout", "FIntNewInGameMenuPercentRollout3" },
{ "UI.Menu.Style.EnableV4.1", "FFlagEnableInGameMenuControls" },
{ "UI.Menu.Style.EnableV4.2", "FFlagEnableInGameMenuModernization" },
{ "UI.Menu.Style.EnableV4Chrome", "FFlagEnableInGameMenuChrome" },
{ "UI.Menu.Style.ABTest.1", "FFlagEnableMenuControlsABTest" },
{ "UI.Menu.Style.ABTest.2", "FFlagEnableV3MenuABTest3" },
{ "UI.Menu.Style.ABTest.3", "FFlagEnableInGameMenuChromeABTest3" }
};
public static IReadOnlyDictionary<RenderingMode, string> RenderingModes => new Dictionary<RenderingMode, string>
{
{ RenderingMode.Default, "None" },
{ RenderingMode.D3D11, "D3D11" },
{ RenderingMode.D3D10, "D3D10" },
};
public static IReadOnlyDictionary<LightingMode, string> LightingModes => new Dictionary<LightingMode, string>
{
{ LightingMode.Default, "None" },
{ LightingMode.Voxel, "Voxel" },
{ LightingMode.ShadowMap, "ShadowMap" },
{ LightingMode.Future, "Future" }
};
public static IReadOnlyDictionary<MSAAMode, string?> MSAAModes => new Dictionary<MSAAMode, string?>
{
{ MSAAMode.Default, null },
{ MSAAMode.x1, "1" },
{ MSAAMode.x2, "2" },
{ MSAAMode.x4, "4" }
};
public static IReadOnlyDictionary<TextureQuality, string?> TextureQualityLevels => new Dictionary<TextureQuality, string?>
{
{ TextureQuality.Default, null },
{ TextureQuality.Level0, "0" },
{ TextureQuality.Level1, "1" },
{ TextureQuality.Level2, "2" },
{ TextureQuality.Level3, "3" },
};
// this is one hell of a dictionary definition lmao
// since these all set the same flags, wouldn't making this use bitwise operators be better?
public static IReadOnlyDictionary<InGameMenuVersion, Dictionary<string, string?>> IGMenuVersions => new Dictionary<InGameMenuVersion, Dictionary<string, string?>>
{
{
InGameMenuVersion.Default,
new Dictionary<string, string?>
{
{ "V2Rollout", null },
{ "EnableV4", null },
{ "EnableV4Chrome", null },
{ "ABTest", null }
}
},
{
InGameMenuVersion.V1,
new Dictionary<string, string?>
{
{ "V2Rollout", "0" },
{ "EnableV4", "False" },
{ "EnableV4Chrome", "False" },
{ "ABTest", "False" }
}
},
{
InGameMenuVersion.V2,
new Dictionary<string, string?>
{
{ "V2Rollout", "100" },
{ "EnableV4", "False" },
{ "EnableV4Chrome", "False" },
{ "ABTest", "False" }
}
},
{
InGameMenuVersion.V4,
new Dictionary<string, string?>
{
{ "V2Rollout", "0" },
{ "EnableV4", "True" },
{ "EnableV4Chrome", "False" },
{ "ABTest", "False" }
}
},
{
InGameMenuVersion.V4Chrome,
new Dictionary<string, string?>
{
{ "V2Rollout", "0" },
{ "EnableV4", "True" },
{ "EnableV4Chrome", "True" },
{ "ABTest", "False" }
}
}
};
PresetConfig = JsonSerializer.Deserialize<FFlagPresets>(File.ReadAllText("C:\\Users\\pizzaboxer\\Documents\\Projects\\Bloxstrap\\PrototypeSchema.json"));
Debug.WriteLine(PresetConfig);
}
// all fflags are stored as strings
// to delete a flag, set the value as null
@ -170,7 +35,7 @@ namespace Bloxstrap
{
if (Prop.ContainsKey(key))
{
if (key == Prop[key].ToString())
if (value.ToString() == Prop[key].ToString())
return;
App.Logger.WriteLine(LOG_IDENT, $"Changing of '{key}' from '{Prop[key]}' to '{value}' is pending");
@ -184,10 +49,16 @@ namespace Bloxstrap
}
}
// this returns null if the fflag doesn't exist
/// <summary>
/// Returns null if the flag has not been set
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string? GetValue(string key)
{
// check if we have an updated change for it pushed first
if (PresetConfig.Flags.ContainsKey(key))
key = PresetConfig.Flags[key];
if (Prop.TryGetValue(key, out object? value) && value is not null)
return value.ToString();
@ -196,37 +67,25 @@ namespace Bloxstrap
public void SetPreset(string prefix, object? value)
{
foreach (var pair in PresetFlags.Where(x => x.Key.StartsWith(prefix)))
foreach (var pair in PresetConfig.Flags.Where(x => x.Key.StartsWith(prefix)))
SetValue(pair.Value, value);
}
public void SetPresetEnum(string prefix, string target, object? value)
public bool CheckPresetValue(string prefix, string value)
{
foreach (var pair in PresetFlags.Where(x => x.Key.StartsWith(prefix)))
var presets = PresetConfig.Flags.Where(x => x.Key.StartsWith(prefix));
foreach (var preset in presets)
{
if (pair.Key.StartsWith($"{prefix}.{target}"))
SetValue(pair.Value, value);
else
SetValue(pair.Value, null);
}
}
public string? GetPreset(string name) => GetValue(PresetFlags[name]);
public T GetPresetEnum<T>(IReadOnlyDictionary<T, string> mapping, string prefix, string value) where T : Enum
{
foreach (var pair in mapping)
{
if (pair.Value == "None")
continue;
if (GetPreset($"{prefix}.{pair.Value}") == value)
return pair.Key;
if (GetValue(preset.Value) != value)
return false;
}
return mapping.First().Key;
return true;
}
public bool CheckPresetValue(KeyValuePair<string, string> entry) => CheckPresetValue(entry.Key, entry.Value);
public override void Save()
{
// convert all flag values to strings before saving
@ -248,7 +107,7 @@ namespace Bloxstrap
OriginalProp = new(Prop);
// TODO - remove when activity tracking has been revamped
if (GetPreset("Network.Log") != "7")
if (GetValue("Network.Log") != "7")
SetPreset("Network.Log", "7");
}
}

249
Bloxstrap/FlexParser.cs Normal file
View File

@ -0,0 +1,249 @@
namespace Bloxstrap
{
/// <summary>
/// FastFlag Expression Parser
/// </summary>
public class FlexParser
{
private readonly string _expression;
private readonly List<FlexToken> _tokens = new();
private int _tokenPos = 0;
private static readonly Dictionary<string, FlexTokenType> _staticTokenMap = new()
{
{ "null", FlexTokenType.NULL },
{ "true", FlexTokenType.BOOL },
{ "false", FlexTokenType.BOOL },
{ "==", FlexTokenType.COMPARE_EQ },
{ "!=", FlexTokenType.COMPARE_NEQ },
{ ">", FlexTokenType.COMPARE_GT },
{ "<", FlexTokenType.COMPARE_LT },
{ ">=", FlexTokenType.COMPARE_GEQ },
{ "<=", FlexTokenType.COMPARE_LEQ },
{ "&&", FlexTokenType.LOGIC_AND },
{ "||", FlexTokenType.LOGIC_OR },
{ "(", FlexTokenType.BRACKET_OPEN },
{ ")", FlexTokenType.BRACKET_CLOSE }
};
private static readonly Dictionary<string, FlexTokenType> _regexTokenMap = new()
{
{ @"^\d+", FlexTokenType.NUMBER },
{ @"^('[^']+')", FlexTokenType.STRING },
{ @"^([a-zA-Z0-9_\.]+)", FlexTokenType.FLAG }
};
public FlexParser(string expression)
{
_expression = expression;
Tokenize();
}
public bool Evaluate() => EvaluateExpression();
private void Tokenize()
{
int position = 0;
while (position < _expression.Length)
{
string exprSlice = _expression.Substring(position);
if (exprSlice[0] == ' ')
{
position++;
continue;
}
string exprSliceLower = exprSlice.ToLowerInvariant();
var mapMatch = _staticTokenMap.FirstOrDefault(x => exprSliceLower.StartsWith(x.Key));
if (mapMatch.Key is null)
{
bool matched = false;
foreach (var entry in _regexTokenMap)
{
var match = Regex.Match(exprSlice, entry.Key);
if (match.Success)
{
matched = true;
string phrase = match.Groups[match.Groups.Count > 1 ? 1 : 0].Value;
_tokens.Add(new(entry.Value, phrase, position));
position += phrase.Length;
break;
}
}
if (!matched)
throw new FlexParseException(_expression, "unknown identifier", position);
}
else
{
_tokens.Add(new(mapMatch.Value, mapMatch.Key, position));
position += mapMatch.Key.Length;
}
}
}
/// <summary>
/// The brackets in this example expression are instances of subexpressions: "[FLogNetwork == 7] || [false]"
/// </summary>
/// <returns></returns>
/// <exception cref="FlexParseException"></exception>
private bool EvaluateSubExpression()
{
var token = _tokens.ElementAtOrDefault(_tokenPos++);
if (token?.Type == FlexTokenType.FLAG)
{
var compToken = _tokens.ElementAtOrDefault(_tokenPos++);
if (compToken is null || compToken.Value is null || !compToken.IsComparisonOperator)
throw new FlexParseException(_expression, "expected comparison operator", compToken);
var valueToken = _tokens.ElementAtOrDefault(_tokenPos++);
if (valueToken is null || !valueToken.IsDataType)
throw new FlexParseException(_expression, "expected data", valueToken);
string? flagValue = token.GetActualValue();
if (compToken.IsInequalityOperator)
{
if (flagValue is null || valueToken.Value is null)
return false;
if (valueToken.Type != FlexTokenType.NUMBER)
throw new FlexParseException(_expression, "expected integer", valueToken);
if (!long.TryParse(flagValue, out long intFlagValue))
return false;
long intValue = long.Parse(valueToken.Value);
switch (compToken.Type)
{
case FlexTokenType.COMPARE_GT:
return intFlagValue > intValue;
case FlexTokenType.COMPARE_LT:
return intFlagValue < intValue;
case FlexTokenType.COMPARE_GEQ:
return intFlagValue >= intValue;
case FlexTokenType.COMPARE_LEQ:
return intFlagValue <= intValue;
}
}
else
{
if (valueToken.Type == FlexTokenType.NULL)
return flagValue is null;
bool result = string.Compare(flagValue, valueToken.GetActualValue(), StringComparison.InvariantCultureIgnoreCase) == 0;
if (compToken.Type == FlexTokenType.COMPARE_EQ)
return result;
else
return !result;
}
}
else if (token?.Type == FlexTokenType.BOOL)
{
return token.BoolValue;
}
return false;
}
private bool EvaluateExpression(int finalPos = 0)
{
bool result = false;
if (finalPos == 0)
finalPos = _tokens.Count;
while (_tokenPos < finalPos)
{
var token = _tokens.ElementAtOrDefault(_tokenPos);
if (token is null)
break;
if (token.Type == FlexTokenType.FLAG || token.Type == FlexTokenType.BOOL)
{
result = EvaluateSubExpression();
}
else if (token.Type == FlexTokenType.BRACKET_OPEN)
{
var closeBracketToken = _tokens.Find(x => x.Type == FlexTokenType.BRACKET_CLOSE);
if (closeBracketToken is null)
throw new FlexParseException(_expression, "expected closing bracket");
_tokenPos++;
result = EvaluateExpression(_tokens.IndexOf(closeBracketToken));
_tokenPos++;
}
else
{
throw new FlexParseException(_expression, "identifier was unexpected here", token);
}
var nextToken = _tokens.ElementAtOrDefault(_tokenPos++);
if (nextToken is null)
break;
if (!nextToken.IsLogicToken)
throw new FlexParseException(_expression, "expected boolean operator", nextToken);
if (nextToken.Type == FlexTokenType.LOGIC_AND)
{
if (result)
{
continue;
}
else
{
int bracketNesting = 0;
while (_tokenPos < finalPos)
{
token = _tokens[_tokenPos++];
if (token.Type == FlexTokenType.BRACKET_OPEN)
bracketNesting++;
else if (token.Type == FlexTokenType.BRACKET_CLOSE)
bracketNesting--;
else if (bracketNesting == 0 && token.Type == FlexTokenType.LOGIC_OR)
break;
}
if (bracketNesting != 0)
throw new FlexParseException(_expression, "unclosed bracket");
}
}
else if (nextToken.Type == FlexTokenType.LOGIC_OR && result)
{
break;
}
}
return result;
}
}
}

View File

@ -483,14 +483,14 @@ namespace Bloxstrap
{
App.FastFlags.SetValue("FIntDebugForceMSAASamples", null);
if (App.FastFlags.GetPreset("UI.Menu.Style.DisableV2") is not null)
if (App.FastFlags.GetValue("UI.Menu.Style.DisableV2") is not null)
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)
string? val = App.FastFlags.GetValue("UI.Menu.Style.EnableV4.1");
if (App.FastFlags.GetValue("UI.Menu.Style.EnableV4.2") != val)
App.FastFlags.SetPreset("UI.Menu.Style.EnableV4.2", val);
}
@ -513,7 +513,7 @@ namespace Bloxstrap
if (App.Settings.Prop.BootstrapperStyle == BootstrapperStyle.ClassicFluentDialog)
App.Settings.Prop.BootstrapperStyle = BootstrapperStyle.FluentDialog;
_ = int.TryParse(App.FastFlags.GetPreset("Rendering.Framerate"), out int x);
_ = int.TryParse(App.FastFlags.GetValue("Rendering.Framerate"), out int x);
if (x == 0)
App.FastFlags.SetPreset("Rendering.Framerate", null);
}

View File

@ -0,0 +1,139 @@
namespace Bloxstrap.Models.APIs.Config
{
// technically an entity, whatever
public class FFlagPreset
{
public string Title { get; set; } = null!;
public string? Description { get; set; }
public string? HelpLink { get; set; }
public string Type { get; set; } = null!;
/// <summary>
/// Specific to TextBox and Toggle
/// </summary>
public Dictionary<string, string>? Apply { get; set; }
#region ComboBox
// Data
public Dictionary<string, Dictionary<string, string>>? Options { get; set; }
// Frontend
public List<string>? ComboBoxEntries => Options?.Keys.Prepend("Common.Default").ToList();
public string ComboBoxSelection
{
get
{
if (Options is null || ComboBoxEntries is null)
return "";
foreach (var optionEntry in Options)
{
bool matches = true;
foreach (var flagEntry in optionEntry.Value)
{
if (matches && !App.FastFlags.CheckPresetValue(flagEntry))
matches = false;
}
if (matches)
return optionEntry.Key;
}
return ComboBoxEntries[0];
}
set
{
if (Options is null || ComboBoxEntries is null)
throw new InvalidOperationException();
if (value == ComboBoxEntries[0])
{
// get all flags that this preset sets
var flags = Options.Values.SelectMany(x => x.Keys).Distinct().ToList();
foreach (string flag in flags)
App.FastFlags.SetPreset(flag, null);
}
else
{
foreach (var entry in Options[value])
App.FastFlags.SetPreset(entry.Key, entry.Value);
}
}
}
#endregion
#region TextBox
// TODO: filtering (i dont know how tf thats gonna work)
// Data
public string? InputFilter { get; set; }
public string? Subject { get; set; }
public string? DefaultValue { get; set; }
// Frontend
public string TextBoxValue
{
get
{
if (Subject is null || DefaultValue is null)
return "";
return App.FastFlags.GetValue(Subject) ?? DefaultValue;
}
set
{
if (Apply is null || DefaultValue is null)
throw new InvalidOperationException();
if (InputFilter is not null && !Regex.IsMatch(value, InputFilter))
{
value = TextBoxValue;
return;
}
foreach (var entry in Apply)
{
if (value == DefaultValue)
App.FastFlags.SetPreset(entry.Key, null);
else
App.FastFlags.SetPreset(entry.Key, String.Format(entry.Value, value));
}
}
}
#endregion
#region Toggle
public string? EnabledIf { get; set; }
public bool ToggleEnabled
{
get
{
if (EnabledIf is null)
return false;
return new FlexParser(EnabledIf).Evaluate();
}
set
{
if (Apply is null)
throw new InvalidOperationException();
foreach (var entry in Apply)
App.FastFlags.SetPreset(entry.Key, value ? entry.Value : null);
}
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
namespace Bloxstrap.Models.APIs.Config
{
public class FFlagPresets
{
public string Version { get; set; } = null!;
public Dictionary<string, string> Flags { get; set; } = null!;
public Dictionary<string, List<FFlagPreset>> Presets { get; set; } = null!;
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.Models.APIs.Config
{
internal class TranslationPatch
{
}
}

View File

@ -0,0 +1,56 @@
namespace Bloxstrap.Models
{
public class FlexToken
{
public FlexTokenType Type;
public string? Value;
public int Position;
public bool IsDataType =>
Type == FlexTokenType.FLAG
|| Type == FlexTokenType.NUMBER
|| Type == FlexTokenType.BOOL
|| Type == FlexTokenType.STRING
|| Type == FlexTokenType.NULL;
public bool IsLogicToken =>
Type == FlexTokenType.LOGIC_AND
|| Type == FlexTokenType.LOGIC_OR;
public bool IsInequalityOperator =>
Type == FlexTokenType.COMPARE_GT
|| Type == FlexTokenType.COMPARE_LT
|| Type == FlexTokenType.COMPARE_GEQ
|| Type == FlexTokenType.COMPARE_LEQ;
public bool IsComparisonOperator =>
IsInequalityOperator
|| Type == FlexTokenType.COMPARE_EQ
|| Type == FlexTokenType.COMPARE_NEQ;
public bool BoolValue => Type == FlexTokenType.BOOL && Value == "true";
public FlexToken(FlexTokenType type, string? value, int position)
{
Type = type;
Value = value;
Position = position;
}
public string? GetActualValue()
{
if (Value is null)
return null;
if (Type == FlexTokenType.STRING)
return Value.Substring(1, Value.Length - 2);
if (Type == FlexTokenType.FLAG)
return App.FastFlags.GetValue(Value);
return Value;
}
public override string ToString() => $"{Type}: {Value}";
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Xml.Linq;
namespace Bloxstrap.UI.Converters
{
class StringResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Strings.ResourceManager.GetStringSafe((string)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View File

@ -45,7 +45,7 @@ namespace Bloxstrap.UI.Elements.Settings.Pages
_fastFlagList.Clear();
var presetFlags = FastFlagManager.PresetFlags.Values;
var presetFlags = App.FastFlags.PresetConfig.Flags.Values;
foreach (var pair in App.FastFlags.Prop.OrderBy(x => x.Key))
{
@ -141,7 +141,7 @@ namespace Bloxstrap.UI.Elements.Settings.Pages
bool refresh = false;
if (!_showPresets && FastFlagManager.PresetFlags.Values.Contains(name))
if (!_showPresets && App.FastFlags.PresetConfig.Flags.Values.Contains(name))
{
TogglePresetsButton.IsChecked = true;
_showPresets = true;

View File

@ -21,149 +21,87 @@
<TextBlock Margin="0,2,0,0" FontSize="12" Text="{x:Static resources:Strings.Menu_FastFlags_Help_Description}" Padding="0,0,16,0" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
</StackPanel>
</ui:CardAction>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_ManagerEnabled_Title}"
Description="{x:Static resources:Strings.Menu_FastFlags_ManagerEnabled_Description}">
<ui:ToggleSwitch IsChecked="{Binding UseFastFlagManager, Mode=TwoWay}" />
</controls:OptionControl>
<StackPanel Visibility="{Binding ShowDebugFlags, Mode=OneTime}">
<TextBlock Text="Debug" FontSize="20" FontWeight="Medium" Margin="0,16,0,0" />
<controls:OptionControl
Header="HTTP request logging"
Description="Enables logging of HTTP requests (DFLogHttpTraceLight=12).">
<ui:ToggleSwitch IsChecked="{Binding HttpRequestLogging, Mode=TwoWay}" />
</controls:OptionControl>
<controls:OptionControl
Header="HTTP proxy address"
Description="Set blank if not using a proxy. Don't forget to add cacert.pem as a mod.">
<ui:TextBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" Text="{Binding HttpRequestProxy, Mode=TwoWay}" />
</controls:OptionControl>
<controls:OptionControl
Header="Flag state overlay"
Description="Show values of specified flags during runtime. Each flag is comma separated.">
<ui:TextBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" Text="{Binding StateOverlayFlags, Mode=TwoWay}" />
</controls:OptionControl>
</StackPanel>
<TextBlock Text="{x:Static resources:Strings.Common_Presets}" FontSize="20" FontWeight="Medium" Margin="0,16,0,0" />
<TextBlock Text="{x:Static resources:Strings.Menu_FastFlags_Presets_Categories_Rendering}" FontSize="16" FontWeight="Medium" Margin="0,16,0,0" />
<controls:MarkdownTextBlock MarkdownText="{Binding Source={x:Static resources:Strings.Menu_FastFlags_Presets_D3DExclusiveFullscreenInfo}, Converter={StaticResource StringFormatConverter}, ConverterParameter='https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#exclusive-fullscreen'}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_MSAA_Title}">
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding MSAALevels.Keys, Mode=OneTime}" Text="{Binding SelectedMSAALevel, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={StaticResource EnumNameConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:OptionControl>
<!-- first ListView is for each category of presets -->
<ListView ItemsSource="{Binding Presets, Mode=OneWay}" Margin="-4">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="Focusable" Value="False" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Key}" FontSize="16" FontWeight="Medium" Margin="0,16,0,0" />
<!-- second ListView is for each preset in the category -->
<ListView ItemsSource="{Binding Value}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="Focusable" Value="False" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<controls:OptionControl Header="{Binding Title}" Description="{Binding Description}" HelpLink="{Binding HelpLink}" Margin="-4">
<StackPanel>
<!-- Toggle type -->
<ui:ToggleSwitch IsChecked="{Binding ToggleEnabled, Mode=TwoWay}">
<ui:ToggleSwitch.Style>
<Style TargetType="ui:ToggleSwitch" BasedOn="{StaticResource {x:Type ui:ToggleSwitch}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Toggle">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</ui:ToggleSwitch.Style>
</ui:ToggleSwitch>
<!-- ComboBox type -->
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding ComboBoxEntries, Mode=OneTime}" Text="{Binding ComboBoxSelection, Mode=TwoWay}">
<ComboBox.Style>
<Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="ComboBox">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</ComboBox.Style>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={StaticResource StringResourceConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_DisablePlayerShadows_Title}">
<ui:ToggleSwitch IsChecked="{Binding DisablePlayerShadows, Mode=TwoWay}" />
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_DisablePostFX_Title}"
HelpLink="https://create.roblox.com/docs/environment/post-processing-effects">
<ui:ToggleSwitch IsChecked="{Binding DisablePostFX, Mode=TwoWay}" />
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_DisableTerrainTextures_Title}">
<ui:ToggleSwitch IsChecked="{Binding DisableTerrainTextures, Mode=TwoWay}" />
</controls:OptionControl>
<controls:OptionControl
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="ValidateUInt32" />
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_LightingTechnology_Title}"
Description="{Binding Source={x:Static resources:Strings.Menu_FastFlags_Presets_LightingTechnology_Description}, Converter={StaticResource StringFormatConverter}, ConverterParameter='https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#preferred-lighting-technology'}" Foreground="{DynamicResource TextFillColorSecondaryBrush}"
HelpLink="https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#preferred-lighting-technology">
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding LightingModes.Keys, Mode=OneTime}" Text="{Binding SelectedLightingMode, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={StaticResource EnumNameConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_FixDisplayScaling_Title}"
Description="{x:Static resources:Strings.Menu_FastFlags_Presets_FixDisplayScaling_Description}"
HelpLink="https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#dpi-scaling-fixes">
<ui:ToggleSwitch IsChecked="{Binding FixDisplayScaling, Mode=TwoWay}" />
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_RenderingMode_Title}">
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding RenderingModes.Keys, Mode=OneTime}" Text="{Binding SelectedRenderingMode, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={StaticResource EnumNameConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_TextureQuality_Title}">
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding TextureQualities.Keys, Mode=OneTime}" Text="{Binding SelectedTextureQuality, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={StaticResource EnumNameConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:OptionControl>
<TextBlock Text="{x:Static resources:Strings.Menu_FastFlags_Presets_Categories_UserInterface}" FontSize="16" FontWeight="Medium" Margin="0,16,0,0" />
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_FullscreenTitlebar_Title}"
Description="{x:Static resources:Strings.Menu_FastFlags_Presets_FullscreenTitlebar_Description}">
<ui:ToggleSwitch IsChecked="{Binding FullscreenTitlebarDisabled, Mode=TwoWay}" />
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_HideGuis_Title}"
Description="{Binding Source={x:Static resources:Strings.Menu_FastFlags_Presets_HideGuis_Description}, Converter={StaticResource StringFormatConverter}, ConverterParameter='https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#gui-hiding|https://www.roblox.com/groups/32380007/Bloxstrap'}"
HelpLink="https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#gui-hiding">
<ui:ToggleSwitch IsChecked="{Binding GuiHidingEnabled, Mode=TwoWay}" />
</controls:OptionControl>
<controls:OptionControl
x:Name="FontPaddingOption"
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_FontPadding_Title}"
Description="{Binding Source={x:Static resources:Strings.Menu_FastFlags_Presets_FontPadding_Description}, Converter={StaticResource StringFormatConverter}, ConverterParameter='https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#gui-hiding|https://www.roblox.com/groups/32380007/Bloxstrap'}">
<ui:TextBox Width="200" Padding="10,5,10,5" Text="{Binding FontSize, Mode=TwoWay}" PreviewTextInput="ValidateInt32" />
</controls:OptionControl>
<controls:OptionControl
Header="{x:Static resources:Strings.Menu_FastFlags_Presets_EscapeMenuVersion_Title}"
HelpLink="https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#escape-menu-version">
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding IGMenuVersions.Keys, Mode=OneTime}" Text="{Binding SelectedIGMenuVersion, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={StaticResource EnumNameConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:OptionControl>
<!-- TextBox type -->
<ui:TextBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" Text="{Binding TextBoxValue, Mode=TwoWay}">
<ui:TextBox.Style>
<Style TargetType="ui:TextBox" BasedOn="{StaticResource {x:Type ui:TextBox}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="TextBox">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</ui:TextBox.Style>
</ui:TextBox>
</StackPanel>
</controls:OptionControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ui:CardAction Margin="0,24,0,0" Icon="EditSettings24" Command="{Binding OpenFastFlagEditorCommand}">
<StackPanel>

View File

@ -7,7 +7,6 @@ using Wpf.Ui.Mvvm.Contracts;
using CommunityToolkit.Mvvm.Input;
using Bloxstrap.UI.Elements.Settings.Pages;
using Bloxstrap.Enums.FlagPresets;
namespace Bloxstrap.UI.ViewModels.Settings
{
@ -15,6 +14,16 @@ namespace Bloxstrap.UI.ViewModels.Settings
{
private readonly Page _page;
public ICommand OpenFastFlagEditorCommand => new RelayCommand(OpenFastFlagEditor);
public bool UseFastFlagManager
{
get => App.Settings.Prop.UseFastFlagManager;
set => App.Settings.Prop.UseFastFlagManager = value;
}
public Dictionary<string, List<FFlagPreset>> Presets => App.FastFlags.PresetConfig.Presets;
public FastFlagsViewModel(Page page)
{
_page = page;
@ -30,167 +39,5 @@ namespace Bloxstrap.UI.ViewModels.Settings
window.Navigate(typeof(FastFlagEditorPage));
}
}
public ICommand OpenFastFlagEditorCommand => new RelayCommand(OpenFastFlagEditor);
#if DEBUG
public Visibility ShowDebugFlags => Visibility.Visible;
#else
public Visibility ShowDebugFlags => Visibility.Collapsed;
#endif
public bool UseFastFlagManager
{
get => App.Settings.Prop.UseFastFlagManager;
set => App.Settings.Prop.UseFastFlagManager = value;
}
public bool HttpRequestLogging
{
get => App.FastFlags.GetPreset("HTTP.Log") is not null;
set => App.FastFlags.SetPreset("HTTP.Log", value ? 12 : null);
}
public string HttpRequestProxy
{
get => App.FastFlags.GetPreset("HTTP.Proxy.Address.1") ?? "";
set
{
App.FastFlags.SetPreset("HTTP.Proxy.Enable", String.IsNullOrEmpty(value) ? null : true);
App.FastFlags.SetPreset("HTTP.Proxy.Address", String.IsNullOrEmpty(value) ? null : value);
}
}
public string StateOverlayFlags
{
get => App.FastFlags.GetPreset("UI.FlagState") ?? "";
set => App.FastFlags.SetPreset("UI.FlagState", String.IsNullOrEmpty(value) ? null : value);
}
public int FramerateLimit
{
get => int.TryParse(App.FastFlags.GetPreset("Rendering.Framerate"), out int x) ? x : 0;
set => App.FastFlags.SetPreset("Rendering.Framerate", value == 0 ? null : value);
}
public IReadOnlyDictionary<MSAAMode, string?> MSAALevels => FastFlagManager.MSAAModes;
public MSAAMode SelectedMSAALevel
{
get => MSAALevels.FirstOrDefault(x => x.Value == App.FastFlags.GetPreset("Rendering.MSAA")).Key;
set => App.FastFlags.SetPreset("Rendering.MSAA", MSAALevels[value]);
}
public IReadOnlyDictionary<RenderingMode, string> RenderingModes => FastFlagManager.RenderingModes;
public RenderingMode SelectedRenderingMode
{
get => App.FastFlags.GetPresetEnum(RenderingModes, "Rendering.Mode", "True");
set => App.FastFlags.SetPresetEnum("Rendering.Mode", RenderingModes[value], "True");
}
public bool FixDisplayScaling
{
get => App.FastFlags.GetPreset("Rendering.DisableScaling") == "True";
set => App.FastFlags.SetPreset("Rendering.DisableScaling", value ? "True" : null);
}
public IReadOnlyDictionary<InGameMenuVersion, Dictionary<string, string?>> IGMenuVersions => FastFlagManager.IGMenuVersions;
public InGameMenuVersion SelectedIGMenuVersion
{
get
{
// yeah this kinda sucks
foreach (var version in IGMenuVersions)
{
bool flagsMatch = true;
foreach (var flag in version.Value)
{
foreach (var presetFlag in FastFlagManager.PresetFlags.Where(x => x.Key.StartsWith($"UI.Menu.Style.{flag.Key}")))
{
if (App.FastFlags.GetValue(presetFlag.Value) != flag.Value)
flagsMatch = false;
}
}
if (flagsMatch)
return version.Key;
}
return IGMenuVersions.First().Key;
}
set
{
foreach (var flag in IGMenuVersions[value])
App.FastFlags.SetPreset($"UI.Menu.Style.{flag.Key}", flag.Value);
}
}
public IReadOnlyDictionary<LightingMode, string> LightingModes => FastFlagManager.LightingModes;
public LightingMode SelectedLightingMode
{
get => App.FastFlags.GetPresetEnum(LightingModes, "Rendering.Lighting", "True");
set => App.FastFlags.SetPresetEnum("Rendering.Lighting", LightingModes[value], "True");
}
public bool FullscreenTitlebarDisabled
{
get => int.TryParse(App.FastFlags.GetPreset("UI.FullscreenTitlebarDelay"), out int x) && x > 5000;
set => App.FastFlags.SetPreset("UI.FullscreenTitlebarDelay", value ? "3600000" : null);
}
public bool GuiHidingEnabled
{
get => App.FastFlags.GetPreset("UI.Hide") == "32380007";
set => App.FastFlags.SetPreset("UI.Hide", value ? "32380007" : null);
}
public IReadOnlyDictionary<TextureQuality, string?> TextureQualities => FastFlagManager.TextureQualityLevels;
public TextureQuality SelectedTextureQuality
{
get => TextureQualities.Where(x => x.Value == App.FastFlags.GetPreset("Rendering.TextureQuality.Level")).FirstOrDefault().Key;
set
{
if (value == TextureQuality.Default)
{
App.FastFlags.SetPreset("Rendering.TextureQuality", null);
}
else
{
App.FastFlags.SetPreset("Rendering.TextureQuality.OverrideEnabled", "True");
App.FastFlags.SetPreset("Rendering.TextureQuality.Level", TextureQualities[value]);
}
}
}
public bool DisablePostFX
{
get => App.FastFlags.GetPreset("Rendering.DisablePostFX") == "True";
set => App.FastFlags.SetPreset("Rendering.DisablePostFX", value ? "True" : null);
}
public bool DisablePlayerShadows
{
get => App.FastFlags.GetPreset("Rendering.ShadowIntensity") == "0";
set => App.FastFlags.SetPreset("Rendering.ShadowIntensity", value ? "0" : null);
}
public int? FontSize
{
get => int.TryParse(App.FastFlags.GetPreset("UI.FontSize"), out int x) ? x : 1;
set => App.FastFlags.SetPreset("UI.FontSize", value == 1 ? null : value);
}
public bool DisableTerrainTextures
{
get => App.FastFlags.GetPreset("Rendering.TerrainTextureQuality") == "0";
set => App.FastFlags.SetPreset("Rendering.TerrainTextureQuality", value ? "0" : null);
}
}
}