Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ControllerLayout v0.2.9
plugins/LethalControls/LethalControls.dll
Decompiled 4 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Lc.Common; using LethalCompanyInputUtils; using LethalCompanyInputUtils.Api; using LethalCompanyInputUtils.Components; using LethalCompanyInputUtils.Config; using LethalCompanyInputUtils.Glyphs; using LethalControls.Aim; using LethalControls.Bindings; using LethalControls.Core; using LethalControls.Core.Aim; using LethalControls.Core.Favorites; using LethalControls.Favorites; using LethalControls.UI; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.Utilities; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("LethalCompanyInputUtils")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("thatumi")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.8.0")] [assembly: AssemblyInformationalVersion("0.2.8+7b112dc5529fd329152c1ec62e374aa48dc435fc")] [assembly: AssemblyProduct("LethalControls")] [assembly: AssemblyTitle("LethalControls")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.2.8.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Lc.Common { internal static class FontProvider { private static TMP_FontAsset? _hudFont; internal static TMP_FontAsset? Get() { if ((Object)(object)_hudFont != (Object)null) { return _hudFont; } TMP_FontAsset val = ReflectionGuard.Run("FontProvider.HUD", delegate { HUDManager instance = HUDManager.Instance; return (!((Object)(object)instance != (Object)null) || !((Object)(object)instance.chatText != (Object)null)) ? null : ((TMP_Text)instance.chatText).font; }, null); if ((Object)(object)val != (Object)null) { _hudFont = val; return val; } TMP_FontAsset val2 = ReflectionGuard.Run("FontProvider.Default", () => TMP_Settings.defaultFontAsset, null); if ((Object)(object)val2 != (Object)null) { return val2; } TMP_FontAsset? obj = ReflectionGuard.Run("FontProvider.Any", () => Resources.FindObjectsOfTypeAll<TMP_FontAsset>().FirstOrDefault(), null); if ((Object)(object)obj == (Object)null) { Log.WarnOnce("FontProvider.none", "FontProvider: no TMP font available yet; text will use TMP defaults"); } return obj; } internal static void Invalidate() { _hudFont = null; } } internal enum LogKind { Debug, Info, Warning, Error } internal static class Log { private static readonly object Gate = new object(); private static readonly HashSet<string> OnceKeys = new HashSet<string>(StringComparer.Ordinal); private static Action<LogKind, string>? _sink; internal static bool DebugEnabled { get; set; } internal static void SetSink(Action<LogKind, string>? sink) { _sink = sink; } internal static void Debug(string message) { if (DebugEnabled) { Write(LogKind.Debug, message); } } internal static void Info(string message) { Write(LogKind.Info, message); } internal static void Warn(string message) { Write(LogKind.Warning, message); } internal static void Error(string message) { Write(LogKind.Error, message); } internal static bool WarnOnce(string key, string message) { if (!MarkOnce(key)) { return false; } Write(LogKind.Warning, message); return true; } internal static bool ErrorOnce(string key, string message) { if (!MarkOnce(key)) { return false; } Write(LogKind.Error, message); return true; } internal static bool MarkOnce(string key) { lock (Gate) { return OnceKeys.Add(key); } } internal static void ResetOnce() { lock (Gate) { OnceKeys.Clear(); } } private static void Write(LogKind kind, string message) { Action<LogKind, string> sink = _sink; try { if (sink != null) { sink(kind, message); } else { Console.WriteLine("[" + kind.ToString() + "] " + message); } } catch { } } } internal static class LogBepInEx { internal static void Init(ManualLogSource source) { Log.SetSink(delegate(LogKind kind, string message) { switch (kind) { case LogKind.Debug: source.LogDebug((object)message); break; case LogKind.Info: source.LogInfo((object)message); break; case LogKind.Warning: source.LogWarning((object)message); break; default: source.LogError((object)message); break; } }); } } internal static class ReflectionGuard { internal const BindingFlags AllMembers = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private const BindingFlags Declared = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly object Gate = new object(); private static readonly Dictionary<string, string> DisabledReasons = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly Dictionary<string, int> FailureCounts = new Dictionary<string, int>(StringComparer.Ordinal); private static readonly Dictionary<string, Type?> TypeCache = new Dictionary<string, Type>(StringComparer.Ordinal); internal static int MaxFailuresBeforeDisable { get; set; } = 25; internal static IReadOnlyDictionary<string, string> DisabledFeatures { get { lock (Gate) { return new Dictionary<string, string>(DisabledReasons, StringComparer.Ordinal); } } } internal static event Action<string, string>? FeatureDisabled; internal static bool IsEnabled(string feature) { lock (Gate) { return !DisabledReasons.ContainsKey(feature); } } internal static void Disable(string feature, string reason) { lock (Gate) { if (DisabledReasons.ContainsKey(feature)) { return; } DisabledReasons[feature] = reason; } Log.Warn("[" + feature + "] disabled: " + reason); try { ReflectionGuard.FeatureDisabled?.Invoke(feature, reason); } catch { } } internal static void Reset() { lock (Gate) { DisabledReasons.Clear(); FailureCounts.Clear(); TypeCache.Clear(); } } internal static Type? FindType(string fullName, string feature, string? assemblyName = null) { string key = (assemblyName ?? "*") + "|" + fullName; lock (Gate) { if (TypeCache.TryGetValue(key, out Type value) && value != null) { return value; } } Type type = null; try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (assemblyName == null || string.Equals(assembly.GetName().Name, assemblyName, StringComparison.Ordinal)) { type = assembly.GetType(fullName, throwOnError: false); if (type != null) { break; } } } } catch (Exception ex) { Disable(feature, "type lookup " + fullName + " threw " + ex.GetType().Name + ": " + ex.Message); return null; } if (type != null) { lock (Gate) { TypeCache[key] = type; } } else { Disable(feature, "type " + fullName + ((assemblyName != null) ? (" (in " + assemblyName + ")") : "") + " not found"); } return type; } internal static FieldInfo? Field(Type? type, string name, string feature) { if (type == null) { Disable(feature, "field " + name + ": owner type is null"); return null; } Type type2 = type; while (type2 != null) { FieldInfo field = type2.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field; } type2 = type2.BaseType; } Disable(feature, "field " + type.FullName + "." + name + " not found"); return null; } internal static MethodInfo? Method(Type? type, string name, Type[]? parameterTypes, string feature) { if (type == null) { Disable(feature, "method " + name + ": owner type is null"); return null; } Type type2 = type; while (type2 != null) { List<MethodInfo> list = (from m in type2.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == name select m).ToList(); if (list.Count != 0) { if (parameterTypes == null) { if (list.Count == 1) { return list[0]; } Disable(feature, "method " + type2.FullName + "." + name + " is ambiguous (" + list.Count + " overloads); pass parameter types"); return null; } foreach (MethodInfo item in list) { if (ParametersMatch(item.GetParameters(), parameterTypes)) { return item; } } } type2 = type2.BaseType; } Disable(feature, "method " + type.FullName + "." + name + Signature(parameterTypes) + " not found"); return null; } internal static PropertyInfo? Property(Type? type, string name, string feature) { if (type == null) { Disable(feature, "property " + name + ": owner type is null"); return null; } Type type2 = type; while (type2 != null) { PropertyInfo property = type2.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property; } type2 = type2.BaseType; } Disable(feature, "property " + type.FullName + "." + name + " not found"); return null; } internal static bool TryGet<T>(FieldInfo? field, object? instance, out T value, string feature) { value = default(T); if (field == null || !IsEnabled(feature)) { return false; } try { object value2 = field.GetValue(field.IsStatic ? null : instance); if (value2 is T val) { value = val; return true; } if (value2 == null && default(T) == null) { return true; } Log.WarnOnce(feature + "|type|" + field.Name, "[" + feature + "] field " + field.Name + " is " + (value2?.GetType().FullName ?? "null") + ", expected " + typeof(T).FullName); return false; } catch (Exception e) { CountFailure(feature, "get " + field.Name, e); return false; } } internal static bool TryGet<T>(PropertyInfo? property, object? instance, out T value, string feature) { value = default(T); MethodInfo methodInfo = property?.GetGetMethod(nonPublic: true); if (property == null || methodInfo == null || !IsEnabled(feature)) { return false; } try { object value2 = property.GetValue(methodInfo.IsStatic ? null : instance, null); if (value2 is T val) { value = val; return true; } if (value2 == null && default(T) == null) { return true; } Log.WarnOnce(feature + "|type|" + property.Name, "[" + feature + "] property " + property.Name + " is " + (value2?.GetType().FullName ?? "null") + ", expected " + typeof(T).FullName); return false; } catch (Exception e) { CountFailure(feature, "get " + property.Name, e); return false; } } internal static bool TrySet(FieldInfo? field, object? instance, object? value, string feature) { if (field == null || !IsEnabled(feature)) { return false; } try { field.SetValue(field.IsStatic ? null : instance, value); return true; } catch (Exception e) { CountFailure(feature, "set " + field.Name, e); return false; } } internal static bool TryInvoke(MethodInfo? method, object? instance, object?[]? args, out object? result, string feature) { result = null; if (method == null || !IsEnabled(feature)) { return false; } try { result = method.Invoke(method.IsStatic ? null : instance, args); return true; } catch (TargetInvocationException ex) when (ex.InnerException != null) { CountFailure(feature, "invoke " + method.Name, ex.InnerException); return false; } catch (Exception e) { CountFailure(feature, "invoke " + method.Name, e); return false; } } internal static void Run(string key, Action body) { if (!IsEnabled(key)) { return; } try { body(); } catch (Exception e) { CountFailure(key, "threw", e); } } internal static T Run<T>(string key, Func<T> body, T fallback) { if (!IsEnabled(key)) { return fallback; } try { return body(); } catch (Exception e) { CountFailure(key, "threw", e); return fallback; } } internal static bool Prefix(string key, Func<bool> body) { return Run(key, body, fallback: true); } internal static int FailureCount(string key) { lock (Gate) { int value; return FailureCounts.TryGetValue(key, out value) ? value : 0; } } private static void CountFailure(string key, string what, Exception e) { int value; lock (Gate) { FailureCounts.TryGetValue(key, out value); value++; FailureCounts[key] = value; } if (value == 1) { Log.Error("[" + key + "] " + what + ": " + e); } if (value >= MaxFailuresBeforeDisable) { Disable(key, value + " exceptions (first logged above)"); } } private static bool ParametersMatch(ParameterInfo[] actual, Type[] expected) { if (actual.Length != expected.Length) { return false; } for (int i = 0; i < actual.Length; i++) { if (actual[i].ParameterType != expected[i]) { return false; } } return true; } private static string Signature(Type[]? parameterTypes) { if (parameterTypes != null) { return "(" + string.Join(", ", parameterTypes.Select((Type t) => t.Name)) + ")"; } return ""; } } internal static class TexGen { private delegate float CoverageFn(float x, float y); private static readonly Dictionary<string, Sprite> Cache = new Dictionary<string, Sprite>(); internal static Sprite RoundedRect(int radius = 12, int size = 0) { if (size <= 0) { size = radius * 2 + 4; } string key = "rr|" + radius + "|" + size; return Get(key, delegate { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) Texture2D obj = NewTexture(size, size, key); Paint(obj, (float x, float y) => TexGenMath.FillCoverage(TexGenMath.RoundedRectDistance(x, y, size, size, radius))); return Sprite.Create(obj, new Rect(0f, 0f, (float)size, (float)size), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4((float)(radius + 1), (float)(radius + 1), (float)(radius + 1), (float)(radius + 1))); }); } internal static Sprite Outline(int radius = 12, int thickness = 2, int size = 0) { if (size <= 0) { size = radius * 2 + 4; } string key = "ol|" + radius + "|" + thickness + "|" + size; return Get(key, delegate { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) Texture2D obj = NewTexture(size, size, key); Paint(obj, (float x, float y) => TexGenMath.StrokeCoverage(TexGenMath.RoundedRectDistance(x, y, size, size, radius), thickness)); int num = Mathf.Max(radius, thickness) + 1; return Sprite.Create(obj, new Rect(0f, 0f, (float)size, (float)size), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4((float)num, (float)num, (float)num, (float)num)); }); } internal static Sprite Circle(int diameter = 64, int thickness = 0) { string key = "ci|" + diameter + "|" + thickness; return Get(key, delegate { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) Texture2D obj = NewTexture(diameter, diameter, key); float r = (float)diameter * 0.5f; Paint(obj, delegate(float x, float y) { float distance = TexGenMath.CircleDistance(x, y, r, r, r - 0.5f); return (thickness <= 0) ? TexGenMath.FillCoverage(distance) : TexGenMath.StrokeCoverage(distance, thickness); }); return Sprite.Create(obj, new Rect(0f, 0f, (float)diameter, (float)diameter), new Vector2(0.5f, 0.5f), 100f); }); } internal static Sprite White() { return Get("white", delegate { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) Texture2D obj = NewTexture(1, 1, "white"); obj.SetPixel(0, 0, Color.white); obj.Apply(false, false); return Sprite.Create(obj, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f), 100f); }); } private static Sprite Get(string key, Func<Sprite> create) { if (Cache.TryGetValue(key, out Sprite value) && (Object)(object)value != (Object)null) { return value; } value = create(); ((Object)value).name = "LcTexGen_" + key; ((Object)value).hideFlags = (HideFlags)52; Cache[key] = value; return value; } private static Texture2D NewTexture(int w, int h, string key) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown return new Texture2D(w, h, (TextureFormat)4, false) { name = "LcTexGen_" + key, wrapMode = (TextureWrapMode)1, filterMode = (FilterMode)1, hideFlags = (HideFlags)52 }; } private static void Paint(Texture2D tex, CoverageFn coverage) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) int width = ((Texture)tex).width; int height = ((Texture)tex).height; Color32[] array = (Color32[])(object)new Color32[width * height]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { float num = coverage((float)j + 0.5f, (float)i + 0.5f); array[i * width + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)Mathf.RoundToInt(num * 255f)); } } tex.SetPixels32(array); tex.Apply(false, false); } } internal static class TexGenMath { internal static float RoundedRectDistance(float px, float py, float w, float h, float r) { r = Math.Max(0f, Math.Min(r, Math.Min(w, h) * 0.5f)); float num = w * 0.5f; float num2 = h * 0.5f; float val = Math.Abs(px - num) - (num - r); float num3 = Math.Abs(py - num2) - (num2 - r); float num4 = Math.Max(val, 0f); float num5 = Math.Max(num3, 0f); float num6 = (float)Math.Sqrt(num4 * num4 + num5 * num5); float num7 = Math.Min(Math.Max(val, num3), 0f); return num6 + num7 - r; } internal static float CircleDistance(float px, float py, float cx, float cy, float r) { float num = px - cx; float num2 = py - cy; return (float)Math.Sqrt(num * num + num2 * num2) - r; } internal static float FillCoverage(float distance) { return Clamp01(0.5f - distance); } internal static float StrokeCoverage(float distance, float thickness) { float num = FillCoverage(distance); float num2 = FillCoverage(distance + thickness); return Clamp01(num - num2); } internal static float Clamp01(float v) { if (!(v < 0f)) { if (!(v > 1f)) { return v; } return 1f; } return 0f; } } internal static class UiFactory { internal static class Theme { internal static Color PanelBg = new Color(0.05f, 0.05f, 0.06f, 0.92f); internal static Color PanelBorder = new Color(1f, 0.45f, 0.1f, 0.9f); internal static Color Text = new Color(0.98f, 0.62f, 0.25f, 1f); internal static Color TextDim = new Color(0.75f, 0.6f, 0.45f, 1f); internal static Color ButtonBg = new Color(0.16f, 0.09f, 0.05f, 0.95f); internal static Color ButtonHighlight = new Color(0.45f, 0.22f, 0.07f, 1f); internal static Color ButtonPressed = new Color(0.7f, 0.33f, 0.08f, 1f); internal static Color ButtonDisabled = new Color(0.12f, 0.12f, 0.12f, 0.7f); internal static Color Danger = new Color(0.9f, 0.18f, 0.15f, 1f); internal static Color BackdropColor = new Color(0f, 0f, 0f, 0.72f); internal static int CornerRadius = 10; internal static float FontSize = 26f; } internal static readonly Vector2 ReferenceResolution = new Vector2(1920f, 1080f); internal static Canvas Canvas(string name, int sortingOrder, bool dontDestroyOnLoad = false) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.layer = 5; if (dontDestroyOnLoad) { Object.DontDestroyOnLoad((Object)(object)val); } Canvas obj = val.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = sortingOrder; obj.pixelPerfect = false; CanvasScaler obj2 = val.AddComponent<CanvasScaler>(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = ReferenceResolution; obj2.screenMatchMode = (ScreenMatchMode)0; obj2.matchWidthOrHeight = 0.5f; val.AddComponent<GraphicRaycaster>(); return obj; } internal static RectTransform Rect(Transform parent, string name) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown RectTransform val = (RectTransform)new GameObject(name, new Type[1] { typeof(RectTransform) }) { layer = 5 }.transform; ((Transform)val).SetParent(parent, false); return val; } internal static RectTransform Fill(RectTransform rt, float padding = 0f) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; rt.pivot = new Vector2(0.5f, 0.5f); rt.offsetMin = new Vector2(padding, padding); rt.offsetMax = new Vector2(0f - padding, 0f - padding); return rt; } internal static RectTransform Place(RectTransform rt, Vector2 anchor, Vector2 offset, Vector2 size, Vector2? pivot = null) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) rt.anchorMin = anchor; rt.anchorMax = anchor; rt.pivot = pivot ?? anchor; rt.anchoredPosition = offset; rt.sizeDelta = size; return rt; } internal static RectTransform PlaceTL(RectTransform rt, float cx, float cy, float w, float h) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) rt.anchorMin = new Vector2(0f, 1f); rt.anchorMax = new Vector2(0f, 1f); rt.pivot = new Vector2(0.5f, 0.5f); rt.anchoredPosition = new Vector2(cx, 0f - cy); rt.sizeDelta = new Vector2(w, h); return rt; } internal static LayoutElement Size(Component c, float width = -1f, float height = -1f, float flexibleWidth = -1f) { LayoutElement val = c.GetComponent<LayoutElement>(); if ((Object)(object)val == (Object)null) { val = c.gameObject.AddComponent<LayoutElement>(); } val.preferredWidth = width; val.preferredHeight = height; val.minHeight = height; val.flexibleWidth = flexibleWidth; return val; } internal static VerticalLayoutGroup Vertical(Component c, float spacing = 8f, int padding = 12) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown VerticalLayoutGroup obj = c.gameObject.AddComponent<VerticalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj).spacing = spacing; ((LayoutGroup)obj).padding = new RectOffset(padding, padding, padding, padding); ((LayoutGroup)obj).childAlignment = (TextAnchor)0; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; return obj; } internal static HorizontalLayoutGroup Horizontal(Component c, float spacing = 8f, int padding = 0) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown HorizontalLayoutGroup obj = c.gameObject.AddComponent<HorizontalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj).spacing = spacing; ((LayoutGroup)obj).padding = new RectOffset(padding, padding, padding, padding); ((LayoutGroup)obj).childAlignment = (TextAnchor)3; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; return obj; } internal static Image Panel(Transform parent, string name, Color? color = null, bool border = true) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) RectTransform val = Fill(Rect(parent, name)); Image obj = ((Component)val).gameObject.AddComponent<Image>(); obj.sprite = TexGen.RoundedRect(Theme.CornerRadius); obj.type = (Type)1; ((Graphic)obj).color = (Color)(((??)color) ?? Theme.PanelBg); ((Graphic)obj).raycastTarget = true; if (border) { Image obj2 = ((Component)Fill(Rect((Transform)(object)val, "Border"))).gameObject.AddComponent<Image>(); obj2.sprite = TexGen.Outline(Theme.CornerRadius); obj2.type = (Type)1; ((Graphic)obj2).color = Theme.PanelBorder; ((Graphic)obj2).raycastTarget = false; } return obj; } internal static TextMeshProUGUI Text(Transform parent, string text, float size = 0f, TextAlignmentOptions align = (TextAlignmentOptions)513, Color? color = null, string name = "Text") { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI val = ((Component)Rect(parent, name)).gameObject.AddComponent<TextMeshProUGUI>(); TMP_FontAsset val2 = FontProvider.Get(); if ((Object)(object)val2 != (Object)null) { ((TMP_Text)val).font = val2; } ((TMP_Text)val).text = text; ((TMP_Text)val).fontSize = ((size > 0f) ? size : Theme.FontSize); ((TMP_Text)val).alignment = align; ((Graphic)val).color = (Color)(((??)color) ?? Theme.Text); ((TMP_Text)val).enableWordWrapping = true; ((TMP_Text)val).overflowMode = (TextOverflowModes)1; ((Graphic)val).raycastTarget = false; return val; } internal static Button Button(Transform parent, string label, Action onClick, Vector2? size = null, string? name = null) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) RectTransform obj = Rect(parent, name ?? ("Button_" + label)); obj.sizeDelta = (Vector2)(((??)size) ?? new Vector2(260f, 52f)); Image val = ((Component)obj).gameObject.AddComponent<Image>(); val.sprite = TexGen.RoundedRect(Theme.CornerRadius); val.type = (Type)1; ((Graphic)val).color = Color.white; Button val2 = ((Component)obj).gameObject.AddComponent<Button>(); ((Selectable)val2).targetGraphic = (Graphic)(object)val; ((Selectable)val2).colors = Colors(); ((UnityEvent)val2.onClick).AddListener(Guarded("Button " + label, onClick)); Fill(((TMP_Text)Text((Transform)(object)obj, label, 0f, (TextAlignmentOptions)514, null, "Label")).rectTransform, 6f); if (size.HasValue) { Size((Component)(object)val2, size.Value.x, size.Value.y); } return val2; } internal static Button CycleButton(Transform parent, Func<string> label, Action onClick, Vector2? size = null, string? name = null) { TextMeshProUGUI text = null; Button val = Button(parent, label(), delegate { onClick(); if ((Object)(object)text != (Object)null) { ((TMP_Text)text).text = label(); } }, size, name); text = ((Component)val).GetComponentInChildren<TextMeshProUGUI>(true); return val; } internal static void SetLabel(Button button, string label) { TextMeshProUGUI componentInChildren = ((Component)button).GetComponentInChildren<TextMeshProUGUI>(true); if ((Object)(object)componentInChildren != (Object)null) { ((TMP_Text)componentInChildren).text = label; } } internal static Image Picture(Transform parent, string name, Sprite? sprite, Color color, bool raycast = false) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) Image obj = ((Component)Rect(parent, name)).gameObject.AddComponent<Image>(); obj.sprite = (Sprite)(((Object)(object)sprite != (Object)null) ? ((object)sprite) : ((object)TexGen.White())); ((Graphic)obj).color = color; ((Graphic)obj).raycastTarget = raycast; obj.preserveAspect = (Object)(object)sprite != (Object)null; return obj; } internal static Image Line(RectTransform parent, Vector2 from, Vector2 to, float thickness, Color color) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) Image obj = Picture((Transform)(object)parent, "Line", null, color); UiMath.Line(from.x, from.y, to.x, to.y, out var centreX, out var centreY, out var length, out var angleDeg); PlaceTL(((Graphic)obj).rectTransform, centreX, centreY, length, thickness); ((Transform)((Graphic)obj).rectTransform).localRotation = Quaternion.Euler(0f, 0f, angleDeg); return obj; } internal static RectTransform Backdrop(Transform parent, string name = "Backdrop", Color? color = null) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) RectTransform obj = Fill(Rect(parent, name)); Image obj2 = ((Component)obj).gameObject.AddComponent<Image>(); obj2.sprite = TexGen.White(); ((Graphic)obj2).color = (Color)(((??)color) ?? Theme.BackdropColor); ((Graphic)obj2).raycastTarget = true; return obj; } internal static Toggle Toggle(Transform parent, string label, bool value, Action<bool> onChanged, float height = 44f) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) RectTransform obj = Rect(parent, "Toggle_" + label); obj.sizeDelta = new Vector2(400f, height); Size((Component)(object)obj, -1f, height, 1f); Image val = ((Component)obj).gameObject.AddComponent<Image>(); val.sprite = TexGen.RoundedRect(Theme.CornerRadius); val.type = (Type)1; ((Graphic)val).color = Color.white; RectTransform obj2 = Place(Rect((Transform)(object)obj, "Box"), new Vector2(0f, 0.5f), new Vector2(8f, 0f), new Vector2(height - 12f, height - 12f)); Image obj3 = ((Component)obj2).gameObject.AddComponent<Image>(); obj3.sprite = TexGen.Outline(6); obj3.type = (Type)1; ((Graphic)obj3).color = Theme.Text; ((Graphic)obj3).raycastTarget = false; Image val2 = ((Component)Fill(Rect((Transform)(object)obj2, "Check"), 6f)).gameObject.AddComponent<Image>(); val2.sprite = TexGen.RoundedRect(4); val2.type = (Type)1; ((Graphic)val2).color = Theme.Text; ((Graphic)val2).raycastTarget = false; TextMeshProUGUI obj4 = Text((Transform)(object)obj, label, 0f, (TextAlignmentOptions)513, null, "Label"); ((TMP_Text)obj4).rectTransform.anchorMin = new Vector2(0f, 0f); ((TMP_Text)obj4).rectTransform.anchorMax = new Vector2(1f, 1f); ((TMP_Text)obj4).rectTransform.offsetMin = new Vector2(height + 4f, 0f); ((TMP_Text)obj4).rectTransform.offsetMax = new Vector2(-8f, 0f); Toggle obj5 = ((Component)obj).gameObject.AddComponent<Toggle>(); ((Selectable)obj5).targetGraphic = (Graphic)(object)val; obj5.graphic = (Graphic)(object)val2; ((Selectable)obj5).colors = Colors(transparentNormal: true); obj5.isOn = value; UnityAction<bool> val3 = Guarded("Toggle " + label, onChanged); ((UnityEvent<bool>)(object)obj5.onValueChanged).AddListener(val3); return obj5; } internal static ScrollRect ScrollList(Transform parent, string name, out RectTransform content, float spacing = 6f) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) RectTransform obj = Fill(Rect(parent, name)); ScrollRect val = ((Component)obj).gameObject.AddComponent<ScrollRect>(); val.horizontal = false; val.vertical = true; val.movementType = (MovementType)2; val.scrollSensitivity = 30f; RectTransform val2 = Fill(Rect((Transform)(object)obj, "Viewport")); ((Component)val2).gameObject.AddComponent<RectMask2D>(); ((Graphic)((Component)val2).gameObject.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0f); content = Rect((Transform)(object)val2, "Content"); content.anchorMin = new Vector2(0f, 1f); content.anchorMax = new Vector2(1f, 1f); content.pivot = new Vector2(0.5f, 1f); content.anchoredPosition = Vector2.zero; content.sizeDelta = Vector2.zero; ((LayoutGroup)Vertical((Component)(object)content, spacing, 4)).childAlignment = (TextAnchor)1; ContentSizeFitter obj2 = ((Component)content).gameObject.AddComponent<ContentSizeFitter>(); obj2.verticalFit = (FitMode)2; obj2.horizontalFit = (FitMode)0; val.viewport = val2; val.content = content; return val; } internal static void ScrollIntoView(ScrollRect scroll, RectTransform item) { ReflectionGuard.Run("UiFactory.ScrollIntoView", delegate { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) RectTransform content = scroll.content; RectTransform val = (RectTransform)(((Object)(object)scroll.viewport != (Object)null) ? ((object)scroll.viewport) : ((object)(RectTransform)((Component)scroll).transform)); if (!((Object)(object)content == (Object)null)) { Canvas.ForceUpdateCanvases(); Rect rect = content.rect; float height = ((Rect)(ref rect)).height; rect = val.rect; float height2 = ((Rect)(ref rect)).height; if (!(height <= height2)) { float y = ((Transform)content).InverseTransformPoint(((Transform)item).position).y; rect = item.rect; float num = 0f - (y + ((Rect)(ref rect)).height * (1f - item.pivot.y)); rect = item.rect; float num2 = num + ((Rect)(ref rect)).height; float num3 = content.anchoredPosition.y; if (num < num3) { num3 = num; } else if (num2 > num3 + height2) { num3 = num2 - height2; } num3 = Mathf.Clamp(num3, 0f, height - height2); content.anchoredPosition = new Vector2(content.anchoredPosition.x, num3); } } }); } internal static void LinkVertical(IList<Selectable> items, bool wrap = true, Selectable? left = null, Selectable? right = null) { Link(items, wrap, vertical: true, left, right); } internal static void LinkHorizontal(IList<Selectable> items, bool wrap = false, Selectable? up = null, Selectable? down = null) { Link(items, wrap, vertical: false, up, down); } internal static void Select(Selectable? target) { ReflectionGuard.Run("UiFactory.Select", delegate { EventSystem current = EventSystem.current; if (!((Object)(object)current == (Object)null) && !((Object)(object)target == (Object)null) && ((Component)target).gameObject.activeInHierarchy) { current.SetSelectedGameObject((GameObject)null); current.SetSelectedGameObject(((Component)target).gameObject); } }); } internal static void Destroy(Component? root) { if ((Object)(object)root != (Object)null) { Object.Destroy((Object)(object)root.gameObject); } } private static void Link(IList<Selectable> items, bool wrap, bool vertical, Selectable? sideA, Selectable? sideB) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) List<Selectable> list = new List<Selectable>(); foreach (Selectable item in items) { if ((Object)(object)item != (Object)null && ((Component)item).gameObject.activeInHierarchy && item.IsInteractable()) { list.Add(item); } } for (int i = 0; i < list.Count; i++) { Selectable val = ((i > 0) ? list[i - 1] : ((wrap && list.Count > 1) ? list[list.Count - 1] : null)); Selectable val2 = ((i < list.Count - 1) ? list[i + 1] : ((wrap && list.Count > 1) ? list[0] : null)); Navigation val3 = default(Navigation); ((Navigation)(ref val3)).mode = (Mode)4; Navigation navigation = val3; if (vertical) { ((Navigation)(ref navigation)).selectOnUp = val; ((Navigation)(ref navigation)).selectOnDown = val2; ((Navigation)(ref navigation)).selectOnLeft = sideA; ((Navigation)(ref navigation)).selectOnRight = sideB; } else { ((Navigation)(ref navigation)).selectOnLeft = val; ((Navigation)(ref navigation)).selectOnRight = val2; ((Navigation)(ref navigation)).selectOnUp = sideA; ((Navigation)(ref navigation)).selectOnDown = sideB; } list[i].navigation = navigation; } } private static ColorBlock Colors(bool transparentNormal = false) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) ColorBlock defaultColorBlock = ColorBlock.defaultColorBlock; ((ColorBlock)(ref defaultColorBlock)).normalColor = (Color)(transparentNormal ? new Color(0f, 0f, 0f, 0f) : Theme.ButtonBg); ((ColorBlock)(ref defaultColorBlock)).highlightedColor = Theme.ButtonHighlight; ((ColorBlock)(ref defaultColorBlock)).selectedColor = Theme.ButtonHighlight; ((ColorBlock)(ref defaultColorBlock)).pressedColor = Theme.ButtonPressed; ((ColorBlock)(ref defaultColorBlock)).disabledColor = Theme.ButtonDisabled; ((ColorBlock)(ref defaultColorBlock)).colorMultiplier = 1f; ((ColorBlock)(ref defaultColorBlock)).fadeDuration = 0.08f; return defaultColorBlock; } private static UnityAction Guarded(string key, Action action) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown return (UnityAction)delegate { ReflectionGuard.Run(key, action); }; } private static UnityAction<T> Guarded<T>(string key, Action<T> action) { return delegate(T v) { ReflectionGuard.Run(key, delegate { action(v); }); }; } } internal static class UiMath { internal static void Line(float fromX, float fromY, float toX, float toY, out float centreX, out float centreY, out float length, out float angleDeg) { float num = toX - fromX; float num2 = 0f - (toY - fromY); centreX = (fromX + toX) * 0.5f; centreY = (fromY + toY) * 0.5f; length = (float)Math.Sqrt(num * num + num2 * num2); angleDeg = ((length > 0f) ? ((float)(Math.Atan2(num2, num) * (180.0 / Math.PI))) : 0f); } } } namespace LethalControls { internal static class ContextDetector { internal static List<string> Detect(out string reason) { string why = "not in a game"; List<string> result = ReflectionGuard.Run("Controls.ContextDetect", delegate { PlayerControllerB val = OverlayHost.LocalPlayer(); if ((Object)(object)val == (Object)null) { return new List<string> { "OnFoot", "Emotes" }; } if (val.isPlayerDead) { why = "dead"; return new List<string> { "Spectating" }; } if (val.inTerminalMenu) { why = "using the terminal"; return new List<string> { "Terminal" }; } ShipBuildModeManager instance = ShipBuildModeManager.Instance; if ((Object)(object)instance != (Object)null && instance.InBuildMode) { why = "in build mode"; return new List<string> { "BuildMode" }; } if (Driving()) { why = "driving"; return new List<string> { "Cruiser" }; } why = "on foot"; return new List<string> { "OnFoot", "Emotes" }; }, new List<string> { "OnFoot" }); reason = why; return result; } internal static bool Driving() { return (Object)(object)DrivenVehicle() != (Object)null; } internal static VehicleController? DrivenVehicle() { VehicleController[] array = Object.FindObjectsOfType<VehicleController>(); foreach (VehicleController val in array) { if ((Object)(object)val != (Object)null && val.localPlayerInControl) { return val; } } return null; } } internal sealed class VehicleWatcher : MonoBehaviour { private float _next; private bool _wasDriving; private bool _pending; private bool _shownThisSession; private void Update() { if (Time.unscaledTime < _next) { return; } _next = Time.unscaledTime + 0.5f; if (Plugin.Cfg == null || !Plugin.Cfg.AutoOpenInVehicle.Value) { return; } ReflectionGuard.Run("Controls.VehicleWatcher", delegate { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Invalid comparison between Unknown and I4 //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if ((Object)(object)OverlayHost.LocalPlayer() == (Object)null) { _wasDriving = false; _pending = false; _shownThisSession = false; } else if (!_shownThisSession) { VehicleController val = ContextDetector.DrivenVehicle(); bool flag = (Object)(object)val != (Object)null; if (flag && !_wasDriving) { _pending = true; } if (!flag) { _pending = false; } _wasDriving = flag; if (_pending && (Object)(object)val != (Object)null && (int)val.gear != 1 && (int)val.gear != 2 && !OverlayHost.IsOpen) { _pending = false; _shownThisSession = true; OverlayHost.ToggleFromHotkey(OverlayKind.Layout); } } }); } } internal sealed class ControlsActions : LcInputActions { internal const string DefaultLayoutKey = "F5"; [InputAction("<Keyboard>/f5", Name = "Open controller layout")] public InputAction OpenLayout { get; set; } internal static string LayoutHotkeyText() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string>(); try { InputAction val = Plugin.Actions?.OpenLayout; if (val != null) { Enumerator<InputBinding> enumerator = val.bindings.GetEnumerator(); try { while (enumerator.MoveNext()) { InputBinding current = enumerator.Current; if (!((InputBinding)(ref current)).isComposite && !BindingPaths.IsUnbound(((InputBinding)(ref current)).effectivePath)) { list.Add(Humanize.Control(((InputBinding)(ref current)).effectivePath)); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } } catch (Exception) { } if (list.Count <= 0) { return "the \"Open controller layout\" hotkey"; } return string.Join(" / ", list.Distinct()); } } [BepInPlugin("dev.thatumi.LethalControls", "LethalControls", "0.2.8")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private Harmony? _harmony; private static readonly HashSet<Type> FeaturePatchClasses = new HashSet<Type> { typeof(LookInputPatch), typeof(TerminalPatches) }; internal static PluginConfig Cfg { get; private set; } = null; internal static ControlsActions? Actions { get; private set; } private void Awake() { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) LogBepInEx.Init(((BaseUnityPlugin)this).Logger); Cfg = new PluginConfig(((BaseUnityPlugin)this).Config); Log.DebugEnabled = Cfg.DebugLogging.Value; Cfg.DebugLogging.SettingChanged += delegate { Log.DebugEnabled = Cfg.DebugLogging.Value; }; try { Actions = new ControlsActions(); Actions.OpenLayout.performed += OnOpenLayout; } catch (Exception ex) { Log.Error("Could not register hotkeys with InputUtils (the menu buttons still work): " + ex); } ApplyPatches(); try { GameObject val = new GameObject("LethalControls.Runtime"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; val.AddComponent<OverlayHost>(); val.AddComponent<VehicleWatcher>(); val.AddComponent<FavoritesRadial>(); } catch (Exception ex2) { Log.Error("Could not create the overlay host: " + ex2); } Log.Info("LethalControls 0.2.8 loaded (" + CoreInfo.Describe() + ")"); } private void ApplyPatches() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown try { _harmony = new Harmony("dev.thatumi.LethalControls"); } catch (Exception ex) { Log.Error("Harmony is unavailable; menu buttons and overlay hooks are disabled: " + ex); return; } int num = 0; Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly); foreach (Type type in typesFromAssembly) { if (!(type == null) && type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0 && !FeaturePatchClasses.Contains(type)) { try { _harmony.CreateClassProcessor(type).Patch(); num++; } catch (Exception ex2) { Log.Error("Could not apply " + type.Name + " (" + PatchFeature(type.Name) + " disabled; everything else still works): " + ex2); } } } Log.Debug("Applied " + num + " patch classes"); AimFeature.Install(Cfg.Aim); FavoritesFeature.Install(Cfg.Terminal); } private static string PatchFeature(string patchClass) { return patchClass switch { "RemapPanelPatch" => "keybind-menu buttons", "QuickMenuPatches" => "closing our pages with the pause menu", "StartOfRoundPatches" => "cleanup when leaving a lobby", "PlayerControllerPatches" => "Escape/Start closing our page in a lobby", _ => "that patch", }; } private static void OnOpenLayout(CallbackContext ctx) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) ReflectionGuard.Run("Controls.Hotkey.Layout", delegate { if (((CallbackContext)(ref ctx)).performed) { OverlayHost.ToggleFromHotkey(OverlayKind.Layout); } }); } } internal enum ScopeChoice { Global, Local } internal enum GlyphChoice { Auto, Xbox, PlayStation, Text } internal enum ExportDevices { Both, Controller, KeyboardMouse } internal sealed class PluginConfig { internal AimConfig Aim { get; } internal TerminalConfig Terminal { get; } internal ConfigEntry<ScopeChoice> DefaultScope { get; } internal ConfigEntry<ImportMode> DefaultMode { get; } internal ConfigEntry<bool> ExportIncludeDefaults { get; } internal ConfigEntry<ExportDevices> ExportDeviceChoice { get; } internal ConfigEntry<GlyphChoice> Glyphs { get; } internal ConfigEntry<bool> AutoContext { get; } internal ConfigEntry<bool> AutoOpenInVehicle { get; } internal ConfigEntry<bool> InjectButtons { get; } internal ConfigEntry<bool> DebugLogging { get; } internal DeviceSet ExportDeviceSet => ExportDeviceChoice.Value switch { ExportDevices.Controller => DeviceSet.Pad, ExportDevices.KeyboardMouse => DeviceSet.Kbm, _ => DeviceSet.Both, }; internal PluginConfig(ConfigFile cfg) { DefaultScope = cfg.Bind<ScopeChoice>("Import", "DefaultScope", ScopeChoice.Global, "Where imported binds are saved by default. Global = InputUtils' global controls (all profiles, the default InputUtils priority GlobalThenLocal makes them win). Local = this profile's BepInEx/config/controls."); DefaultMode = cfg.Bind<ImportMode>("Import", "DefaultMode", ImportMode.Merge, "Merge = only change the binds in the file. Replace = also reset every other changed bind of the imported sets (for the devices and situations the file covers)."); ExportIncludeDefaults = cfg.Bind<bool>("Export", "IncludeDefaults", false, "Also export binds that are still at their default (a full snapshot instead of only your changes)."); ExportDeviceChoice = cfg.Bind<ExportDevices>("Export", "Devices", ExportDevices.Both, "Which devices an export covers by default."); Glyphs = cfg.Bind<GlyphChoice>("Layout", "GlyphStyle", GlyphChoice.Auto, "Button icons on the layout page. Auto = InputUtils' best match for the connected pad (respects the CorrectControllerIcon choice). Text = no icons, text badges only."); AutoContext = cfg.Bind<bool>("Layout", "AutoContext", true, "When the layout page opens, tick the contexts that match what you are doing (driving, dead, build mode...)."); AutoOpenInVehicle = cfg.Bind<bool>("Layout", "AutoOpenInVehicle", false, "Show the layout page (Cruiser context) the first time you take the wheel in each lobby. It waits until the vehicle is in Park, and the vehicle's controls are paused while the page is open."); InjectButtons = cfg.Bind<bool>("Menu", "InjectButtons", true, "Add 'Controller layout' and 'Import / Export' buttons to the InputUtils keybind menu."); DebugLogging = cfg.Bind<bool>("Debug", "VerboseLogging", false, "Log extra detail (binding enumeration, apply steps)."); Aim = new AimConfig(cfg); Terminal = new TerminalConfig(cfg); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "dev.thatumi.LethalControls"; public const string PLUGIN_NAME = "LethalControls"; public const string PLUGIN_VERSION = "0.2.8"; } } namespace LethalControls.Core { public sealed class OverrideEntry { public string? Action { get; } public string? OrigPath { get; } public string? Path { get; } public string? Groups { get; } public string MatchKey { get { if (!string.IsNullOrEmpty(Groups)) { return Action + "\u0001group\u0001" + Groups; } return Action + "\u0001path\u0001" + OrigPath; } } public DeviceKind Device => BindingPaths.InferDevice(Groups, OrigPath, Path); public OverrideEntry(string? action, string? origPath, string? path, string? groups) { Action = action; OrigPath = origPath; Path = path; Groups = groups; } public override string ToString() { return Action + " " + OrigPath + " -> " + Path + " [" + Groups + "]"; } } public static class BindingOverridesJson { public static string Serialize(IEnumerable<OverrideEntry> entries) { StringBuilder stringBuilder = new StringBuilder("{\"overrides\":["); bool flag = true; foreach (OverrideEntry entry in entries) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append("{\"action\":"); WriteNullable(stringBuilder, entry.Action); stringBuilder.Append(",\"origPath\":"); WriteNullable(stringBuilder, entry.OrigPath); stringBuilder.Append(",\"path\":"); WriteNullable(stringBuilder, entry.Path); stringBuilder.Append(",\"groups\":"); WriteNullable(stringBuilder, entry.Groups); stringBuilder.Append('}'); } return stringBuilder.Append("]}").ToString(); } public static List<OverrideEntry> Parse(string json) { JsonValue jsonValue = JsonReader.Parse(json); if (jsonValue.Kind != JsonKind.Object) { throw new JsonParseException("override file is not a JSON object"); } JsonValue jsonValue2 = jsonValue.Get("overrides"); if (jsonValue2 == null || jsonValue2.Kind != JsonKind.Array) { throw new JsonParseException("override file has no \"overrides\" array"); } List<OverrideEntry> list = new List<OverrideEntry>(jsonValue2.Items.Count); foreach (JsonValue item in jsonValue2.Items) { if (item.Kind != JsonKind.Object) { throw new JsonParseException("override entry is not an object"); } list.Add(new OverrideEntry(Field(item, "action"), Field(item, "origPath"), Field(item, "path"), Field(item, "groups"))); } return list; } public static List<OverrideEntry> ParseOrEmpty(string? json, out string? error) { error = null; if (string.IsNullOrWhiteSpace(json)) { return new List<OverrideEntry>(); } try { return Parse(json); } catch (JsonParseException ex) { error = ex.Message; return new List<OverrideEntry>(); } } private static string? Field(JsonValue obj, string key) { JsonValue jsonValue = obj.Get(key); if (jsonValue == null || jsonValue.IsNull) { return null; } if (jsonValue.Kind != JsonKind.String) { throw new JsonParseException("override field \"" + key + "\" must be a string or null"); } return jsonValue.AsString; } private static void WriteNullable(StringBuilder sb, string? s) { if (s == null) { sb.Append("null"); } else { JsonWriter.WriteString(sb, s); } } } public static class BindsConvert { public static string ExportPath(string? path) { if (!BindingPaths.IsUnbound(path)) { return path; } return ""; } public static string ImportPath(string path, bool vanilla, DeviceKind device) { if (!BindingPaths.IsUnbound(path)) { return path; } if (!vanilla) { return BindingPaths.UnboundSentinel(device); } return ""; } public static List<BindEntry> FromOverrides(IEnumerable<OverrideEntry> overrides) { List<BindEntry> list = new List<BindEntry>(); foreach (OverrideEntry @override in overrides) { if (!string.IsNullOrEmpty(@override.Action)) { list.Add(new BindEntry(@override.Action, "", @override.OrigPath ?? "", ExportPath(@override.Path), @override.Groups ?? "")); } } return list; } public static List<OverrideEntry> ToOverrides(IEnumerable<BindEntry> entries, bool vanilla) { List<OverrideEntry> list = new List<OverrideEntry>(); foreach (BindEntry entry in entries) { DeviceKind device = BindingPaths.InferDevice(entry.G, entry.Orig, entry.Path); list.Add(new OverrideEntry(entry.A, entry.Orig, ImportPath(entry.Path, vanilla, device), entry.G)); } return list; } } public sealed class OverrideChange { public string Action { get; } public string OrigPath { get; } public string Groups { get; } public string Path { get; } public bool Reset { get; } public OverrideChange(string action, string origPath, string groups, string path, bool reset) { Action = action; OrigPath = origPath; Groups = groups; Path = path; Reset = reset; } internal OverrideEntry ToEntry() { return new OverrideEntry(Action, OrigPath, Path, Groups); } } public static class OverrideMerger { public static List<OverrideEntry> Apply(IReadOnlyList<OverrideEntry> existing, IEnumerable<OverrideChange> changes, ImportMode mode, DeviceSet clearDevices) { List<OverrideEntry> list = new List<OverrideEntry>(); foreach (OverrideEntry item in existing) { if (mode != ImportMode.Replace || !clearDevices.Contains(item.Device)) { list.Add(item); } } foreach (OverrideChange change in changes) { OverrideEntry entry = change.ToEntry(); int num = list.FindIndex((OverrideEntry x) => string.Equals(x.MatchKey, entry.MatchKey, StringComparison.Ordinal)); int num2 = list.Count - 1; while (num2 > num && num >= 0) { if (string.Equals(list[num2].MatchKey, entry.MatchKey, StringComparison.Ordinal)) { list.RemoveAt(num2); } num2--; } if (change.Reset) { if (num >= 0) { list.RemoveAt(num); } } else if (num >= 0) { list[num] = entry; } else { list.Add(entry); } } return list; } } public enum DeviceKind { Kbm, Pad } [Flags] public enum DeviceSet { None = 0, Kbm = 1, Pad = 2, Both = 3 } public static class DeviceSets { public static bool Contains(this DeviceSet set, DeviceKind device) { return ((uint)set & (uint)((device == DeviceKind.Kbm) ? 1 : 2)) != 0; } public static DeviceSet Of(DeviceKind device) { if (device != DeviceKind.Kbm) { return DeviceSet.Pad; } return DeviceSet.Kbm; } } public static class BindingPaths { public const string KbmUnbound = "<InputUtils-Kbm-Not-Bound>"; public const string PadUnbound = "<InputUtils-Gamepad-Not-Bound>"; public const string KbmGroup = "KeyboardAndMouse"; public const string PadGroup = "Gamepad"; private static readonly Dictionary<string, string> PadAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["a"] = "buttonSouth", ["cross"] = "buttonSouth", ["b"] = "buttonEast", ["circle"] = "buttonEast", ["x"] = "buttonWest", ["square"] = "buttonWest", ["y"] = "buttonNorth", ["triangle"] = "buttonNorth", ["leftBumper"] = "leftShoulder", ["L1"] = "leftShoulder", ["rightBumper"] = "rightShoulder", ["R1"] = "rightShoulder", ["L2"] = "leftTrigger", ["R2"] = "rightTrigger", ["L3"] = "leftStickPress", ["R3"] = "rightStickPress", ["leftStickButton"] = "leftStickPress", ["rightStickButton"] = "rightStickPress", ["options"] = "start", ["menu"] = "start", ["share"] = "select", ["create"] = "select", ["view"] = "select", ["back"] = "select" }; public static bool IsUnbound(string? path) { if (!string.IsNullOrEmpty(path) && !(path == "<InputUtils-Kbm-Not-Bound>")) { return path == "<InputUtils-Gamepad-Not-Bound>"; } return true; } public static DeviceKind? DeviceOfPath(string? path) { if (string.IsNullOrEmpty(path)) { return null; } if (path == "<InputUtils-Kbm-Not-Bound>") { return DeviceKind.Kbm; } if (path == "<InputUtils-Gamepad-Not-Bound>") { return DeviceKind.Pad; } string text = LayoutOf(path); if (text.Length == 0) { return null; } return (!IsKbmLayout(text)) ? DeviceKind.Pad : DeviceKind.Kbm; } public static DeviceKind? DeviceOfGroup(string? groups) { if (string.IsNullOrEmpty(groups)) { return null; } string[] array = groups.Split(';'); foreach (string a in array) { if (string.Equals(a, "KeyboardAndMouse", StringComparison.Ordinal)) { return DeviceKind.Kbm; } if (string.Equals(a, "Gamepad", StringComparison.Ordinal)) { return DeviceKind.Pad; } } return null; } public static DeviceKind InferDevice(string? groups, string? origPath, string? effectivePath, IEnumerable<DeviceKind>? siblingDevices = null) { DeviceKind? deviceKind = DeviceOfGroup(groups) ?? DeviceOfPath(origPath) ?? DeviceOfPath(effectivePath); if (deviceKind.HasValue) { return deviceKind.Value; } if (siblingDevices != null) { bool flag = false; bool flag2 = false; foreach (DeviceKind siblingDevice in siblingDevices) { if (siblingDevice == DeviceKind.Kbm) { flag = true; } else { flag2 = true; } } if (flag && !flag2) { return DeviceKind.Pad; } if (flag2 && !flag) { return DeviceKind.Kbm; } } return DeviceKind.Pad; } public static string UnboundSentinel(DeviceKind device) { if (device != DeviceKind.Kbm) { return "<InputUtils-Gamepad-Not-Bound>"; } return "<InputUtils-Kbm-Not-Bound>"; } public static string LayoutOf(string path) { if (path.Length < 2 || path[0] != '<') { return ""; } int num = path.IndexOf('>'); if (num <= 1) { return ""; } return path.Substring(1, num - 1); } public static string ControlOf(string? path) { if (string.IsNullOrEmpty(path)) { return ""; } int num = path.IndexOf('>'); return ((num >= 0) ? path.Substring(num + 1) : path).TrimStart('/'); } public static string Canonical(string? path) { if (IsUnbound(path)) { return ""; } DeviceKind? deviceKind = DeviceOfPath(path); string text = ControlOf(path); if (!deviceKind.HasValue) { return path; } if (deviceKind == DeviceKind.Pad) { return "<Gamepad>/" + CanonicalPadControl(text); } string text2 = LayoutOf(path); return ((text2.IndexOf("Mouse", StringComparison.OrdinalIgnoreCase) >= 0 || text2.Equals("Pointer", StringComparison.OrdinalIgnoreCase)) ? "<Mouse>/" : "<Keyboard>/") + text; } public static bool Overlaps(string a, string b) { if (a.Length == 0 || b.Length == 0) { return false; } if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase)) { return true; } if (!IsParent(a, b) && !IsParent(b, a) && !AxisContains(a, b)) { return AxisContains(b, a); } return true; } private static bool IsParent(string parent, string child) { if (child.Length > parent.Length + 1 && child[parent.Length] == '/') { return child.StartsWith(parent, StringComparison.OrdinalIgnoreCase); } return false; } private static bool AxisContains(string axis, string half) { int num = axis.LastIndexOf('/'); int num2 = half.LastIndexOf('/'); if (num <= 0 || num2 <= 0 || num != num2 || string.Compare(axis, 0, half, 0, num, StringComparison.OrdinalIgnoreCase) != 0) { return false; } string text = axis.Substring(num + 1); string text2 = half.Substring(num2 + 1); if (text.Equals("x", StringComparison.OrdinalIgnoreCase)) { if (!text2.Equals("left", StringComparison.OrdinalIgnoreCase)) { return text2.Equals("right", StringComparison.OrdinalIgnoreCase); } return true; } if (text.Equals("y", StringComparison.OrdinalIgnoreCase)) { if (!text2.Equals("up", StringComparison.OrdinalIgnoreCase)) { return text2.Equals("down", StringComparison.OrdinalIgnoreCase); } return true; } return false; } private static bool IsKbmLayout(string layout) { if (layout.IndexOf("Keyboard", StringComparison.OrdinalIgnoreCase) < 0 && layout.IndexOf("Mouse", StringComparison.OrdinalIgnoreCase) < 0) { return layout.Equals("Pointer", StringComparison.OrdinalIgnoreCase); } return true; } private static string CanonicalPadControl(string control) { if (control.Length == 0) { return control; } int num = control.IndexOf('/'); string text = ((num >= 0) ? control.Substring(0, num) : control); string text2 = ((num >= 0) ? control.Substring(num) : ""); if (PadAliases.TryGetValue(text, out string value)) { text = value; } return text + text2; } } public sealed class BindRow { public const string VanillaSetId = "vanilla"; public string SetId { get; } public string Action { get; } public string Part { get; } public int Index { get; } public string OrigPath { get; } public string Path { get; } public string Group { get; } public string Label { get; } public DeviceKind Device { get; } public IReadOnlyList<ContextUse> Contexts { get; set; } = Array.Empty<ContextUse>(); public bool IsVanilla => SetId == "vanilla"; public bool IsChanged { get { if (!string.Equals(Path, OrigPath, StringComparison.Ordinal)) { if (BindingPaths.IsUnbound(Path)) { return !BindingPaths.IsUnbound(OrigPath); } return true; } return false; } } public bool IsBound => !BindingPaths.IsUnbound(Path); public BindKey Key => new BindKey(SetId, Action, Part, OrigPath, Group); public string DisplayLabel { get { if (Part.Length != 0) { return Label + " (" + Part + ")"; } return Label; } } public BindRow(string setId, string action, string part, int index, string origPath, string path, string group, string label, DeviceKind device) { SetId = setId ?? throw new ArgumentNullException("setId"); Action = action ?? throw new ArgumentNullException("action"); Part = part ?? ""; Index = index; OrigPath = origPath ?? ""; Path = path ?? ""; Group = group ?? ""; Label = label ?? action; Device = device; } public BindRow WithPath(string path) { return new BindRow(SetId, Action, Part, Index, OrigPath, path, Group, Label, Device) { Contexts = Contexts }; } public override string ToString() { return SetId + "/" + Action + ((Part.Length > 0) ? ("/" + Part) : "") + " " + OrigPath + " -> " + Path; } } public readonly struct BindKey : IEquatable<BindKey> { public string SetId { get; } public string Action { get; } public string Part { get; } public string OrigPath { get; } public string Group { get; } public BindKey(string setId, string action, string part, string origPath, string group) { SetId = setId ?? ""; Action = action ?? ""; Part = part ?? ""; OrigPath = origPath ?? ""; Group = group ?? ""; } public bool Equals(BindKey other) { if (string.Equals(SetId, other.SetId, StringComparison.Ordinal) && string.Equals(Action, other.Action, StringComparison.Ordinal) && string.Equals(Part, other.Part, StringComparison.Ordinal) && string.Equals(OrigPath, other.OrigPath, StringComparison.Ordinal)) { return string.Equals(Group, other.Group, StringComparison.Ordinal); } return false; } public override bool Equals(object? obj) { if (obj is BindKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (((StringComparer.Ordinal.GetHashCode(SetId) * 31 + StringComparer.Ordinal.GetHashCode(Action)) * 31 + StringComparer.Ordinal.GetHashCode(Part)) * 31 + StringComparer.Ordinal.GetHashCode(OrigPath)) * 31 + StringComparer.Ordinal.GetHashCode(Group); } public override string ToString() { return SetId + "/" + Action + ((Part.Length > 0) ? ("/" + Part) : "") + " [" + OrigPath + "|" + Group + "]"; } } public sealed class BindSet { public string Id { get; } public string DisplayName { get; } public IReadOnlyList<BindRow> Rows { get; } public ModInfo? Mod { get; } public bool IsVanilla => Id == "vanilla"; public BindSet(string id, string displayName, IReadOnlyList<BindRow> rows, ModInfo? mod = null) { Id = id; DisplayName = displayName; Rows = rows; Mod = mod; } } public sealed class ModInfo { public string Guid { get; } public string Name { get; } public string Version { get; } public ModInfo(string guid, string name, string version) { Guid = guid ?? ""; Name = name ?? ""; Version = version ?? ""; } } public sealed class BindsFormatException : FormatException { public BindsFormatException(string message) : base(message) { } } public sealed class BindEntry : IEquatable<BindEntry> { public string A { get; } public string Part { get; } public string Orig { get; } public string Path { get; } public string G { get; } public DeviceKind? Device => BindingPaths.DeviceOfGroup(G) ?? BindingPaths.DeviceOfPath(Orig) ?? BindingPaths.DeviceOfPath(Path); public BindEntry(string action, string part, string orig, string path, string group) { A = action ?? ""; Part = part ?? ""; Orig = orig ?? ""; Path = path ?? ""; G = group ?? ""; } public bool SameIdentity(BindEntry other) { if (A == other.A && Part == other.Part && Orig == other.Orig) { return G == other.G; } return false; } public bool Equals(BindEntry? other) { if (other != null && SameIdentity(other)) { return Path == other.Path; } return false; } public override bool Equals(object? obj) { return Equals(obj as BindEntry); } public override int GetHashCode() { return (A + "\u0001" + Part + "\u0001" + Orig + "\u0001" + G + "\u0001" + Path).GetHashCode(); } public override string ToString() { return A + ((Part.Length > 0) ? ("/" + Part) : "") + " " + Orig + " -> " + Path + " [" + G + "]"; } } public sealed class BindsDocument { public const string FormatName = "lc-binds"; public const int CurrentVersion = 1; public const int MaxEntries = 20000; public const int MaxStringLength = 1024; public Dictionary<string, List<BindEntry>> Sets { get; } = new Dictionary<string, List<BindEntry>>(StringComparer.Ordinal); public Dictionary<string, ModInfo> Mods { get; } = new Dictionary<string, ModInfo>(StringComparer.Ordinal); public string Created { get; set; } = ""; public string Game { get; set; } = ""; public string InputUtils { get; set; } = ""; public string Generator { get; set; } = ""; public DeviceSet Include { get; set; } = DeviceSet.Both; public List<string> Contexts { get; } = new List<string>(); public List<string> Warnings { get; } = new List<string>(); public JsonValue? Aim { get; set; } public int EntryCount => Sets.Values.Sum((List<BindEntry> s) => s.Count); public List<BindEntry> GetOrAddSet(string setId) { if (!Sets.TryGetValue(setId, out List<BindEntry> value)) { value = new List<BindEntry>(); Sets[setId] = value; } return value; } public static string FormatTimestamp(DateTime utc) { return utc.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture); } public IEnumerable<string> OrderedSetIds() { return Sets.Keys.OrderBy((string k) => (!(k == "vanilla")) ? 1 : 0).ThenBy<string, string>((string k) => k, StringComparer.Ordinal); } public JsonValue ToJson() { JsonValue jsonValue = JsonValue.NewObject().Set("format", "lc-binds").Set("v", JsonValue.Num(1L)); if (Created.Length > 0) { jsonValue.Set("created", Created); } if (Generator.Length > 0) { jsonValue.Set("generator", Generator); } if (Game.Length > 0) { jsonValue.Set("game", Game); } if (InputUtils.Length > 0) { jsonValue.Set("inpututils", InputUtils); } JsonValue jsonValue2 = JsonValue.NewArray(); if (Include.Contains(DeviceKind.Kbm)) { jsonValue2.Add(JsonValue.Str("kbm")); } if (Include.Contains(DeviceKind.Pad)) { jsonValue2.Add(JsonValue.Str("gamepad")); } jsonValue.Set("include", jsonValue2); if (Contexts.Count > 0) { JsonValue jsonValue3 = JsonValue.NewArray(); foreach (string context in Contexts) { jsonValue3.Add(JsonValue.Str(context)); } jsonValue.Set("contexts", jsonValue3); } if (Aim != null) { jsonValue.Set("aim", Aim); } JsonValue jsonValue4 = JsonValue.NewObject(); foreach (string item in OrderedSetIds()) { JsonValue jsonValue5 = JsonValue.NewArray(); foreach (BindEntry item2 in Sets[item]) { JsonValue jsonValue6 = JsonValue.NewObject().Set("a", item2.A); if (item2.Part.Length > 0) { jsonValue6.Set("part", item2.Part); } jsonValue6.Set("orig", item2.Orig).Set("path", item2.Path).Set("g", item2.G); jsonValue5.Add(jsonValue6); } jsonValue4.Set(item, jsonValue5); } jsonValue.Set("sets", jsonValue4); JsonValue jsonValue7 = JsonValue.NewObject(); foreach (KeyValuePair<string, ModInfo> item3 in Mods.OrderBy<KeyValuePair<string, ModInfo>, string>((KeyValuePair<string, ModInfo> k) => k.Key, StringComparer.Ordinal)) { if (Sets.ContainsKey(item3.Key)) { jsonValue7.Set(item3.Key, JsonValue.NewObject().Set("guid", item3.Value.Guid).Set("name", item3.Value.Name) .Set("ver", item3.Value.Version)); } } if (jsonValue7.Properties.Count > 0) { jsonValue.Set("mods", jsonValue7); } return jsonValue; } public string Serialize(bool pretty = true) { return JsonWriter.Write(ToJson(), pretty) + (pretty ? "\n" : ""); } public static BindsDocument Parse(string text) { if (string.IsNullOrWhiteSpace(text)) { throw new BindsFormatException("The binds file is empty."); } JsonValue jsonValue; try { jsonValue = JsonReader.Parse(text); } catch (JsonParseException ex) { throw new BindsFormatException("Not valid JSON: " + ex.Message); } if (jsonValue.Kind != JsonKind.Object) { throw new BindsFormatException("Not an lc-binds document (expected a JSON object)."); } if (jsonValue.Get("format")?.AsString != "lc-binds") { throw new BindsFormatException("Not an lc-binds document (\"format\" must be \"lc-binds\")."); } long? num = jsonValue.Get("v")?.AsLong; if (!num.HasValue || num < 1) { throw new BindsFormatException("Invalid lc-binds version (\"v\" must be a positive integer)."); } if (num > 1) { long? num2 = num; throw new BindsFormatException("This binds file uses format v" + num2 + ", made by a newer LethalControls. Update the mod to import it."); } BindsDocument bindsDocument = new BindsDocument { Created = Str(jsonValue, "created"), Generator = Str(jsonValue, "generator"), Game = Str(jsonValue, "game"), InputUtils = Str(jsonValue, "inpututils") }; JsonValue jsonValue2 = jsonValue.Get("include"); if (jsonValue2 != null && jsonValue2.Kind == JsonKind.Array) { DeviceSet deviceSet = DeviceSet.None; foreach (JsonValue item in jsonValue2.Items) { string asString = item.AsString; if (!(asString == "kbm")) { if (asString == "gamepad") { deviceSet |= DeviceSet.Pad; } else { bindsDocument.Warnings.Add("Unknown \"include\" value " + item?.ToString() + " ignored."); } } else { deviceSet |= DeviceSet.Kbm; } } bindsDocument.Include = ((deviceSet == DeviceSet.None) ? DeviceSet.Both : deviceSet); } JsonValue jsonValue3 = jsonValue.Get("contexts"); if (jsonValue3 != null && jsonValue3.Kind == JsonKind.Array) { foreach (JsonValue item2 in jsonValue3.Items) { if (item2.AsString != null) { bindsDocument.Contexts.Add(item2.AsString); } } } JsonValue jsonValue4 = jsonValue.Get("aim"); if (jsonValue4 != null) { if (jsonValue4.Kind == JsonKind.Object) { bindsDocument.Aim = jsonValue4; } else if (!jsonValue4.IsNull) { bindsDocument.Warnings.Add("The \"aim\" section is not an object; ignored."); } } JsonValue jsonValue5 = jsonValue.Get("sets"); if (jsonValue5 == null || jsonValue5.Kind != JsonKind.Object) { throw new BindsFormatException("The binds file has no \"sets\" object."); } int num3 = 0; foreach (KeyValuePair<string, JsonValue> property in jsonValue5.Properties) { string key = property.Key; if (key.Length == 0 || key.Length > 1024) { throw new BindsFormatException("Invalid set id \"" + Clip(key) + "\"."); } if (property.Value.Kind != JsonKind.Array) { throw new BindsFormatException("Set \"" + Clip(key) + "\" must be an array."); } List<BindEntry> orAddSet = bindsDocument.GetOrAddSet(key); int num4 = 0; foreach (JsonValue item3 in property.Value.Items) { num4++; if (++num3 > 20000) { throw new BindsFormatException("The binds file has more than " + 20000 + " entries."); } if (item3.Kind != JsonKind.Object) { throw new BindsFormatException("Entry " + num4 + " of set \"" + Clip(key) + "\" is not an object."); } string text2 = item3.Get("a")?.AsString; if (string.IsNullOrEmpty(text2)) { throw new BindsFormatException("Entry " + num4 + " of set \"" + Clip(key) + "\" has no action name (\"a\")."); } JsonValue jsonValue6 = item3.Get("orig"); if (jsonValue6 == null || (jsonValue6.Kind != JsonKind.String && !jsonValue6.IsNull)) { throw new BindsFormatException("Entry " + num4 + " (" + Clip(text2) + ") of set \"" + Clip(key) + "\" has no \"orig\" path."); } JsonValue jsonValue7 = item3.Get("path"); if (jsonValue7 != null && jsonValue7.Kind != JsonKind.String && !jsonValue7.IsNull) { throw new BindsFormatException("Entry " + num4 + " (" + Clip(text2) + ") of set \"" + Clip(key) + "\": \"path\" must be a string."); } BindEntry entry = new BindEntry(text2, item3.Get("part")?.AsString ?? "", jsonValue6.AsString ?? "", jsonValue7?.AsString ?? "", item3.Get("g")?.AsString ?? ""); if (entry.A.Length > 1024 || entry.Orig.Length > 1024 || entry.Path.Length > 1024 || entry.Part.Length > 1024 || entry.G.Length > 1024) { throw new BindsFormatException("Entry " + num4 + " of set \"" + Clip(key) + "\" has an over-long value."); } int num5 = orAddSet.FindIndex((BindEntry x) => x.SameIdentity(entry)); if (num5 >= 0) { bindsDocument.Warnings.Add("Duplicate entry " + entry.A + " in \"" + key + "\": the later one wins."); orAddSet[num5] = entry; } else { orAddSet.Add(entry); } } } JsonValue jsonValue8 = jsonValue.Get("mods"); if (jsonValue8 != null && jsonValue8.Kind == JsonKind.Object) { foreach (KeyValuePair<string, JsonValue> property2 in jsonValue8.Properties) { if (property2.Value.Kind == JsonKind.Object) { bindsDocument.Mods[property2.Key] = new ModInfo(Str(property2.Value, "guid"), Str(property2.Value, "name"), Str(property2.Value, "ver")); } } } return bindsDocument; } private static string Str(JsonValue obj, string key) { return obj.Get(key)?.AsString ?? ""; } private static string Clip(string s) { if (s.Length > 60) { return s.Substring(0, 57) + "..."; } return s; } } public sealed class LayoutItem { public BindRow Row { get; } public string Context { get; } public string Label { get; } public bool Guessed { get; } public bool Conflict { get; internal set; } public bool Possible { get; internal set; } public LayoutItem(BindRow row, string context, string label, bool guessed = false) { Row = row; Context = context; Label = label; Guessed = guessed; } public string DisplayText(Func<string, string>? setName = null) { string text = ((Row.Part.Length > 0) ? (Label + " (" + Row.Part + ")") : Label); if (Row.IsVanilla || Row.SetId == "fixed" || setName == null) { return text; } return setName(Row.SetId) + ": " + text; } } public sealed class ControlGroup { public string Control { get; } public DeviceKind Device { get; } public List<LayoutItem> Items { get; } = new List<LayoutItem>(); public bool HasConflict => Items.Any((LayoutItem i) => i.Conflict); public bool HasPossibleOnly { get { if (!HasConflict) { return Items.Any((LayoutItem i) => i.Possible); } return false; } } public IEnumerable<string> ConflictContexts => (from i in Items where i.Conflict select i.Context).Distinct(); public ControlGroup(string control, DeviceKind device) { Control = control; Device = device; } } public sealed class LayoutModel { public List<ControlGroup> Pad { get; } = new List<ControlGroup>(); public List<ControlGroup> Kbm { get; } = new List<ControlGroup>(); public int ConflictCount => Pad.Count((ControlGroup g) => g.HasConflict) + Kbm.Count((ControlGroup g) => g.HasConflict); public int PossibleCount => Pad.Count((ControlGroup g) => g.HasPossibleOnly) + Kbm.Count((ControlGroup g) => g.HasPossibleOnly); public ControlGroup? Find(string canonicalControl) { return Pad.Concat(Kbm).FirstOrDefault((ControlGroup g) => string.Equals(g.Control, canonicalControl, StringComparison.OrdinalIgnoreCase)); } } public static class ConflictDetector { public static LayoutModel Build(IEnumerable<BindRow> annotatedRows, ICollection<string> selectedContexts, ContextTable table, Func<string, bool>? setVisible = null) { List<(LayoutItem, string, string)> list = new List<(LayoutItem, string, string)>(); foreach (BindRow item4 in annotatedRows.Concat(table.FixedRows)) { if (!item4.IsBound || (setVisible != null && !item4.IsVanilla && item4.SetId != "fixed" && !setVisible(item4.SetId))) { continue; } string text = BindingPaths.Canonical(item4.Path); if (text.Length == 0) { continue; } foreach (ContextUse context in item4.Contexts) { if (selectedContexts.Contains(context.Context)) { string item = ((context.OverlapControl != null && context.OverlapControl.Length > 0) ? context.OverlapControl : text); list.Add((new LayoutItem(item4, context.Context, context.Label, context.Guessed), text, item)); } } } foreach (IGrouping<string, (LayoutItem, string, string)> item5 in from i in list group i by i.Item.Context) { List<(LayoutItem, string, string)> list2 = item5.ToList(); for (int num = 0; num < list2.Count; num++) { for (int num2 = num + 1; num2 < list2.Count; num2++) { LayoutItem item2 = list2[num].Item1; LayoutItem item3 = list2[num2].Item1; if (BindingPaths.Overlaps(list2[num].Item3, list2[num2].Item3) && !table.Compatible(item2.Row, item3.Row, item5.Key)) { if (item2.Guessed || item3.Guessed) { item2.Possible = true; item3.Possible = true; } else { item2.Conflict = true; item3.Conflict = true; } } } } } LayoutModel layoutModel = new LayoutModel(); Dictionary<string, int> contextOrder = ContextIds.All.Select((string c, int i) => (c: c, i: i)).ToDictionary(((string c, int i) x) => x.c, ((string c, int i) x) => x.i); foreach (IGrouping<string, (LayoutItem, string, string)> item6 in list.GroupBy<(LayoutItem, string, string), string>(((LayoutItem Item, string Control, string Overlap) i) => i.Control, StringComparer.OrdinalIgnoreCase)) { (LayoutItem, string, string) tuple = item6.First(); ControlGroup controlGroup = new ControlGroup(tuple.Item2, tuple.Item1.Row.Device); controlGroup.Items.AddRange((from x in item6 select x.Item into x orderby (!contextOrder.TryGetValue(x.Context, out var value)) ? 99 : value, (!x.Row.IsVanilla) ? 1 : 0 select x).ThenBy<LayoutItem, string>((LayoutItem x) => x.Label, StringComparer.OrdinalIgnoreCase)); ((controlGroup.Device == DeviceKind.Pad) ? layoutModel.Pad : layoutModel.Kbm).Add(controlGroup); } layoutModel.Kbm.Sort((ControlGroup a, ControlGroup b) => string.Compare(Humanize.Control(a.Control), Humanize.Control(b.Control), StringComparison.OrdinalIgnoreCase)); layoutModel.Pad.Sort((ControlGroup a, ControlGroup b) => string.Compare(a.Control, b.Control, StringComparison.OrdinalIgnoreCase)); return layoutModel; } } public static class ContextIds { public const string OnFoot = "OnFoot"; public const string Cruiser = "Cruiser"; public const string Spectating = "Spectating"; public const string BuildMode = "BuildMode"; public const string Terminal = "Terminal"; public const string Emotes = "Emotes"; public static readonly IReadOnlyList<string> All = new string[6] { "OnFoot", "Cruiser", "Spectating", "BuildMode", "Terminal", "Emotes" }; private static readonly Dictionary<string, string> Aliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["onfoot"] = "OnFoot", ["foot"] = "OnFoot", ["on foot"] = "OnFoot", ["walking"] = "OnFoot", ["player"] = "OnFoot", ["cruiser"] = "Cruiser", ["vehicle"] = "Cruiser", ["car"] = "Cruiser", ["driving"] = "Cruiser", ["drive"] = "Cruiser", ["spectating"] = "Spectating", ["spectate"] = "Spectating", ["dead"] = "Spectating", ["buildmode"] = "BuildMode", ["build"] = "BuildMode", ["build mode"] = "BuildMode", ["ship build mode"] = "BuildMode", ["terminal"] = "Terminal", ["emotes"] = "Emotes", ["emote"] = "Emotes" }; public static string DisplayName(string id) { return id switch { "OnFoot" => "On foot", "Cruiser" => "Cruiser (driving)", "Spectating" => "Spectating", "BuildMode" => "Ship build mode", "Terminal" => "Terminal", "Emotes" => "Emotes", _ => id, }; } public static string? Resolve(string? name) { if (name == null || !Aliases.TryGetValue(name.Trim(), out string value)) { return null; } return value; } } public readonly struct ContextUse : IEquatable<ContextUse> { public string Context { get; } public string Label { get; } public string? OverlapControl { get; } public bool Guessed { get; } public ContextUse(string context, string label, string? overlapControl = null, bool guessed = false) { Context = context; Label = label; OverlapControl = overlapControl; Guessed = guessed; } public bool Equals(ContextUse other) { if (Context == other.Context && Label == other.Label && OverlapControl == other.OverlapControl) { return Guessed == other.Guessed; } return false; } public override bool Equals(object? obj) { if (obj is ContextUse other) { return Equals(other); } return false; } public override int GetHashCode() { return (Context + "|" + Label + "|" + OverlapControl + "|" + Guessed).GetHashCode(); } public override string ToString() { return Context + ":" + Label + ((OverlapControl != null) ? (" @" + OverlapControl) : "") + (Guessed ? " (guessed)" : ""); } } public sealed class ContextTable { private sealed class VanillaRule { internal string Context { get; } internal string Label { get; } internal bool OnlyWithoutBvc { get; } internal bool BvcLabel { get; } internal VanillaRule(string context, string label, bool onlyWithoutBvc = false, bool bvcLabel = false) { Context = context; Label = label; OnlyWithoutBvc = onlyWithoutBvc; BvcLabel = bvcLabel; } } public const string BetterVehicleControlsGuid = "uk.1a3.bettervehiclecontrols"; public const string FixedSetId = "fixed"; private static readonly Dictionary<string, VanillaRule[]> Vanilla = new Dictionary<string, VanillaRule[]>(StringComparer.Ordinal) { ["Look"] = new VanillaRule[4] { new VanillaRule("OnFoot", "Look"), new VanillaRule("Cruiser", "Look"), new VanillaRule("Spectating", "Look around"), new VanillaRule("BuildMode", "Aim placement") }, ["Move"] = new VanillaRule[4] { new VanillaRule("OnFoot", "Move"), new VanillaRule("Cruiser", "Steer / gas / brake"), new VanillaRule("Cruiser", "Steer", onlyWithoutBvc: false, bvcLabel: true), new VanillaRule("BuildMode", "Move") }, ["Jump"] = new VanillaRule[2] { new VanillaRule("OnFoot", "Jump"), new VanillaRule("Cruiser", "Turbo boost", onlyWithoutBvc: true) }, ["Sprint"] = new VanillaRule[1] { new VanillaRule("OnFoot", "Sprint") }, ["OpenMenu"] = new VanillaRule[4] { new VanillaRule("OnFoot", "Pause menu"), new VanillaRule("Cruiser", "Pause menu"), new VanillaRule("Spectating", "Pause menu"), new VanillaRule("BuildMode", "Pause menu") }, ["Interact"] = new VanillaRule[3] { new VanillaRule("OnFoot", "Interact"), new VanillaRule("Cruiser", "Interact (key, gear, horn)"), new VanillaRule("Spectating", "Next player") }, ["Crouch"] = new VanillaRule[1] { new VanillaRule("OnFoot", "Crouch") }, ["ActivateItem"] = new VanillaRule[2] { new VanillaRule("OnFoot", "Use item"), new VanillaRule("Spectating", "Next player") }, ["Discard"] = new VanillaRule[1] { new VanillaRule("OnFoot", "Drop item") }, ["SwitchItem"] = new VanillaRule[1] { new VanillaRule("OnFoot", "Switch item") }, ["EnableChat"] = new VanillaRule[2] { new VanillaRule("OnFoot", "Open chat"), new VanillaRule("Spectating", "Open chat") }, ["SubmitChat"] = new VanillaRule[2] { new VanillaRule("OnFoot", "Send chat"), new VanillaRule("Spectating", "Send chat") }, ["ReloadBatteries"] = new VanillaRule[1] { new VanillaRule("BuildMode", "Rotate object") }, ["InspectItem"] = new VanillaRule[1] { new VanillaRule("OnFoot", "Inspect item") }, ["SpeedCheat"] = new VanillaRule[1] { new VanillaRule("OnFoot", "Error console (with Sprint)") }, ["PingScan"] = new VanillaRule[1] { new VanillaRule("OnFoot", "Scan") }, ["VoiceButton"] = new VanillaRule[5] { new VanillaRule("OnFoot", "Push to talk"), new VanillaRule("Cruiser", "Push to talk"), new VanillaRule("Spectating", "Push to talk"), new VanillaRule("BuildMode", "Push to talk"), new VanillaRule("Terminal", "Push to talk") }, ["Emote1"] = new VanillaRule[1] { new VanillaRule("Emotes", "Emote: dance") }, ["Emote2"] = new VanillaRule[1] { new VanillaRule("Emotes", "Emote: point") }, ["BuildMode"] = new VanillaRule[2] { new VanillaRule("OnFoot", "Build mode"), new VanillaRule("BuildMode", "Place object") }, ["Delete"] = new VanillaRule[1] { new VanillaRule("BuildMode", "Store object") }, ["ItemSecondaryUse"] = new VanillaRule[1] { new VanillaRule("OnFoot", "Item secondary use") }, ["ItemTertiaryUse"] = new VanillaRule[1] { new VanillaRule("OnFoot", "Item tertiary use") }, ["UseUtilitySlot"] = new VanillaRule[1] { new VanillaRule("OnFoot", "Use utility slot") } }; public static readonly IReadOnlyDictionary<string, string> ExcludedVanilla = new Dictionary<string, string>(StringComparer.Ordinal) { ["Use"] = "only referenced by the stale PlayerActions wrapper; nothing subscribes it", ["QEItemInteract"] = "QEItemInteract_performed exists but is never subscribed", ["ConfirmBuildMode"] = "ConfirmBuildMode_performed is never subscribed; BuildMode confirms placement", ["SetFreeCamera"] = "SetFreeCamera_performed only reads allPlayersDead (no effect)" }; private static readonly (string Context, string Path, string Label)[] Fixed = new(string, string, string)[4] { ("BuildMode", "<Gamepad>/leftShoulder", "Rotate object (fixed)"), ("Terminal", "<Keyboard>/escape", "Leave terminal (fixed)"), ("Terminal", "<Keyboard>/tab", "Leave terminal (fixed)"), ("Terminal", "<Gamepad>/start", "Leave terminal (fixed)") }; private readonly HashSet<string> _designedOverlaps; private readonly ContextOverrides _user; private readonly HashSet<string> _vehicleSets = new HashSet<string>(StringComparer.Ordinal); private static readonly HashSet<string> Vector2Controls = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "leftStick", "rightStick", "dpad" }; private static readonly (string Context, string[] Substrings, string[] Words)[] Keywords = new(string, string[], string[])[4] { ("Cruiser", new string[4] { "vehicle", "cruiser", "hauler", "driving" }, new string[4] { "car", "van", "truck", "drive" }), ("Terminal", new string[1] { "terminal" }, Array.Empty<string>()), ("Emotes", new string[1] { "emote" }, Array.Empty<string>()), ("Spectating", new string[3] { "spectat", "ghost", "afterlife" }, Array.Empty<string>()) }; public VanillaData VanillaData { get; } public bool BvcLoaded { get; } public IReadOnlyList<BindRow> FixedRows { get; } public IEnumerable<string> UserWarnings => _user.Warnings; public IEnumerable<(string, string)> DesignedOverlaps => _designedOverlaps.Select(delegate(string k) { string[] array = k.Split('|'); return (array[0], array[1]); }); public static string VanillaLabel(string action) { if (!Vanilla.TryGetValue(action, out VanillaRule[] value)) { return Humanize.Action(action); } return value[0].Label; } private ContextTable(VanillaData vanilla, bool bvcLoaded, ContextOverrides user) { VanillaData = vanilla; BvcLoaded = bvcLoaded; _user = user; _designedOverlaps = ComputeDesignedOverlaps(vanilla); FixedRows = Fixed.Select(delegate((string Context, string Path, string Label) f, int i) { DeviceKind device = BindingPaths.DeviceOfPath(f.Path) ?? DeviceKind.Pad; return new BindRow("fixed", "fixed" + i, "", -1, f.Path, f.Path, "", f.Label, device) { Contexts = new ContextUse[1] { new ContextUse(f.Context, f.Label) } }; }).ToList(); } public static ContextTable Create(VanillaData vanilla, IEnumerable<string> loadedPluginGuids, ContextOverrides? user = null) { bool bvcLoaded = loadedPluginGuids.Any((string g)