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 RadialHub v1.0.3
plugins/RadialHub/RadialHub.dll
Decompiled 3 hours 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.IO; using System.Linq; using System.Reflection; 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 HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Unity.Collections; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.InputSystem.LowLevel; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("RadialHub")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("RadialHub")] [assembly: AssemblyTitle("RadialHub")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 RadialHub { internal static class Diagnostics { private const BindingFlags All = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly string[] Keywords = new string[9] { "radial", "info", "armor", "armour", "weight", "tooltip", "wheel", "emote", "stat" }; public static void Dump() { try { StringBuilder stringBuilder = new StringBuilder(); Assembly assembly = typeof(Player).Assembly; DumpType(stringBuilder, typeof(ZInput)); DumpType(stringBuilder, typeof(Console)); Type[] source; try { source = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { source = ex.Types.Where((Type t) => t != null).ToArray(); } foreach (Type item in source.Where((Type t) => t.Name.IndexOf("radial", StringComparison.OrdinalIgnoreCase) >= 0)) { DumpType(stringBuilder, item); } stringBuilder.AppendLine(); stringBuilder.AppendLine("=== Hud fields containing 'radial'"); foreach (FieldInfo item2 in from f in typeof(Hud).GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where f.Name.IndexOf("radial", StringComparison.OrdinalIgnoreCase) >= 0 select f) { stringBuilder.AppendLine(" " + item2.FieldType.FullName + " " + item2.Name); DumpType(stringBuilder, item2.FieldType); } string text = Path.Combine(Paths.ConfigPath, "RadialHub"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, "dump.txt"); File.WriteAllText(text2, stringBuilder.ToString()); Plugin.Log.LogInfo((object)("Wrote reflection dump: " + text2)); } catch (Exception ex2) { Plugin.Log.LogError((object)("Dump failed: " + ex2)); } } public static void DumpUi() { try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("RadialHub UI dump, " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); stringBuilder.AppendLine("Lines marked with <<< contain one of: " + string.Join(", ", Keywords)); object obj = NativeRadial.CurrentRadial(); DumpState(stringBuilder, "RadialBase (Hud.m_radialMenu)", obj, null); if (obj != null) { object obj2 = null; try { obj2 = Traverse.Create(obj).Property("Selected", (object[])null).GetValue(); } catch (Exception) { } DumpState(stringBuilder, "RadialBase.Selected (the tile under the cursor)", obj2, null); } DumpState(stringBuilder, "RadialData.SO", NativeRadial.CurrentData(), null); if ((Object)(object)Hud.instance != (Object)null) { DumpState(stringBuilder, "Hud fields that look related", Hud.instance, Keywords); } stringBuilder.AppendLine(); stringBuilder.AppendLine("=== Visible UI objects: indent = depth, name [components] \"text\" (size @ position)"); int lines = 0; Canvas[] array = Object.FindObjectsOfType<Canvas>(); foreach (Canvas val in array) { if (!((Object)(object)val == (Object)null) && val.isRootCanvas && ((Component)val).gameObject.activeInHierarchy) { stringBuilder.AppendLine(); stringBuilder.AppendLine("--- canvas '" + ((Object)val).name + "'"); Walk(stringBuilder, ((Component)val).transform, 0, ref lines); } } string text = Path.Combine(Paths.ConfigPath, "RadialHub"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, "ui_dump.txt"); File.WriteAllText(text2, stringBuilder.ToString()); Plugin.Log.LogInfo((object)("Wrote UI dump: " + text2 + " (" + lines + " objects)")); Plugin.Warn("UI dump written: " + text2, showInGame: true); } catch (Exception ex2) { Plugin.Log.LogError((object)("UI dump failed: " + ex2)); } } private static bool HasKeyword(string text) { if (string.IsNullOrEmpty(text)) { return false; } string[] keywords = Keywords; foreach (string value in keywords) { if (text.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static void Walk(StringBuilder sb, Transform t, int depth, ref int lines) { //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)t == (Object)null || !((Component)t).gameObject.activeInHierarchy || depth > 16 || lines > 8000) { return; } lines++; List<string> list = new List<string>(); string text = null; Component[] components = ((Component)t).GetComponents<Component>(); foreach (Component val in components) { if ((Object)(object)val == (Object)null || val is Transform || val is CanvasRenderer) { continue; } list.Add(((object)val).GetType().Name); if (text != null || ((object)val).GetType().Name.IndexOf("Text", StringComparison.OrdinalIgnoreCase) < 0) { continue; } try { PropertyInfo property = ((object)val).GetType().GetProperty("text"); if (property != null && property.PropertyType == typeof(string)) { text = (string)property.GetValue(val, null); } } catch (Exception) { } } if (text != null) { text = text.Replace("\r", " ").Replace("\n", " / "); } if (text != null && text.Length > 70) { text = text.Substring(0, 70) + "..."; } string text2 = ""; RectTransform val2 = (RectTransform)(object)((t is RectTransform) ? t : null); if ((Object)(object)val2 != (Object)null) { string[] obj = new string[9] { " (", null, null, null, null, null, null, null, null }; Rect rect = val2.rect; obj[1] = Mathf.RoundToInt(((Rect)(ref rect)).width).ToString(); obj[2] = "x"; rect = val2.rect; obj[3] = Mathf.RoundToInt(((Rect)(ref rect)).height).ToString(); obj[4] = " @ "; obj[5] = Mathf.RoundToInt(val2.anchoredPosition.x).ToString(); obj[6] = ","; obj[7] = Mathf.RoundToInt(val2.anchoredPosition.y).ToString(); obj[8] = ")"; text2 = string.Concat(obj); } bool flag = HasKeyword(((Object)t).name) || HasKeyword(text); sb.AppendLine(new string(' ', depth * 2) + ((Object)t).name + ((list.Count > 0) ? (" [" + string.Join(", ", list.ToArray()) + "]") : "") + (string.IsNullOrEmpty(text) ? "" : (" \"" + text + "\"")) + text2 + (flag ? " <<<" : "")); for (int j = 0; j < t.childCount; j++) { Walk(sb, t.GetChild(j), depth + 1, ref lines); } } private static void DumpState(StringBuilder sb, string title, object obj, string[] nameFilter) { sb.AppendLine(); if (obj == null) { sb.AppendLine("=== " + title + ": null"); return; } sb.AppendLine("=== " + title + " : " + obj.GetType().FullName); Type type = obj.GetType(); while (type != null && type != typeof(object) && type != typeof(MonoBehaviour)) { FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (Wanted(fieldInfo.Name, nameFilter)) { string text; try { text = Describe(fieldInfo.GetValue(obj)); } catch (Exception ex) { text = "(error: " + ex.Message + ")"; } sb.AppendLine(" field " + fieldInfo.FieldType.Name + " " + fieldInfo.Name + " = " + text); } } PropertyInfo[] properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (Wanted(propertyInfo.Name, nameFilter) && propertyInfo.GetIndexParameters().Length == 0 && propertyInfo.CanRead) { string text2; try { text2 = Describe(propertyInfo.GetValue(obj, null)); } catch (Exception ex2) { text2 = "(error: " + ex2.Message + ")"; } sb.AppendLine(" property " + propertyInfo.PropertyType.Name + " " + propertyInfo.Name + " = " + text2); } } type = type.BaseType; } } private static bool Wanted(string name, string[] filter) { if (filter == null) { return true; } foreach (string value in filter) { if (name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static string PathOf(Transform t) { List<string> list = new List<string>(); while ((Object)(object)t != (Object)null) { list.Insert(0, ((Object)t).name); t = t.parent; } return string.Join("/", list.ToArray()); } private static string Describe(object v) { if (v == null) { return "null"; } Type type = v.GetType(); if (v is string) { return "\"" + v?.ToString() + "\""; } if (type.IsPrimitive || type.IsEnum || v is Vector2 || v is Vector3 || v is Color) { return v.ToString(); } Object val = (Object)((v is Object) ? v : null); if (val != (Object)null) { Component val2 = (Component)(object)((val is Component) ? val : null); GameObject val3 = (GameObject)(object)((val is GameObject) ? val : null); string text = (((Object)(object)val2 != (Object)null) ? (" at " + PathOf(val2.transform)) : (((Object)(object)val3 != (Object)null) ? (" at " + PathOf(val3.transform)) : "")); return type.Name + " '" + val.name + "'" + text; } if (v is ICollection collection) { return type.Name + " (" + collection.Count + " items)"; } return type.FullName; } private static void DumpType(StringBuilder sb, Type t) { sb.AppendLine(); sb.AppendLine("=== " + t.FullName + " : " + ((t.BaseType != null) ? t.BaseType.FullName : "-")); FieldInfo[] fields = t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { sb.AppendLine(" field " + (fieldInfo.IsStatic ? "static " : "") + fieldInfo.FieldType.Name + " " + fieldInfo.Name); } PropertyInfo[] properties = t.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { sb.AppendLine(" property " + propertyInfo.PropertyType.Name + " " + propertyInfo.Name); } MethodInfo[] methods = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!methodInfo.IsSpecialName) { string text = string.Join(", ", (from x in methodInfo.GetParameters() select x.ParameterType.Name + " " + x.Name).ToArray()); sb.AppendLine(" method " + (methodInfo.IsStatic ? "static " : "") + methodInfo.ReturnType.Name + " " + methodInfo.Name + "(" + text + ")"); } } } } internal static class GameActions { private static Type emoteClass; private static Type emotesEnum; private static Type FindGameType(string simpleName) { Type[] types; try { types = typeof(Player).Assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } Type[] array = types; foreach (Type type in array) { if (type != null && type.Name == simpleName) { return type; } } return null; } private static bool Init() { if (emoteClass == null) { emoteClass = FindGameType("Emote"); } if (emotesEnum == null) { emotesEnum = FindGameType("Emotes"); } if (emoteClass != null && emotesEnum != null) { return emotesEnum.IsEnum; } return false; } private static string ValidNames() { return string.Join(", ", Array.FindAll(Enum.GetNames(emotesEnum), (string n) => n != "Count")); } public static bool CheckEmote(string name, out string error) { error = null; try { if (!Init()) { error = "the game's emote system was not found"; return false; } if (!TryParse(name, out var _)) { error = "unknown emote '" + name + "'. Valid emotes: " + ValidNames(); return false; } return true; } catch (Exception ex) { error = ex.Message; return false; } } private static bool TryParse(string name, out object value) { value = null; try { value = Enum.Parse(emotesEnum, name.Trim(), ignoreCase: true); return true; } catch (ArgumentException) { return false; } } public static bool TryEmote(string name, out string error) { error = null; try { if (!Init()) { error = "the game's emote system was not found"; return false; } if (!TryParse(name, out var value)) { error = "unknown emote '" + name + "'. Valid emotes: " + ValidNames(); return false; } MethodInfo methodInfo = AccessTools.Method(emoteClass, "DoEmote", new Type[1] { emotesEnum }, (Type[])null); if (methodInfo == null) { error = "Emote.DoEmote not found"; return false; } methodInfo.Invoke(null, new object[1] { value }); return true; } catch (Exception ex) { error = ex.Message; return false; } } } internal static class GameIcons { public class Icon { public string id; public string label; public Sprite sprite; } private static bool locSearched; private static Type locType; private static MethodInfo locMethod; private static string Localize(string text) { try { if (!locSearched) { locSearched = true; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (!assembly.GetName().Name.StartsWith("assembly_")) { continue; } Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } Type[] array = types; foreach (Type type in array) { if (type != null && type.Name == "Localization") { locType = type; break; } } if (locType != null) { break; } } if (locType != null) { locMethod = AccessTools.Method(locType, "Localize", new Type[1] { typeof(string) }, (Type[])null); } } if (locType == null || locMethod == null) { return text; } Traverse val = Traverse.Create(locType); object obj = val.Field("instance").GetValue() ?? val.Property("instance", (object[])null).GetValue(); return (obj == null) ? text : ((string)locMethod.Invoke(obj, new object[1] { text })); } catch (Exception) { return text; } } public static Sprite Get(string prefabName) { try { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return null; } return FromPrefab(instance.GetItemPrefab(prefabName)); } catch (Exception) { return null; } } private static Sprite FromPrefab(GameObject prefab) { if ((Object)(object)prefab == (Object)null) { return null; } ItemDrop component = prefab.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null) { return null; } Sprite[] icons = component.m_itemData.m_shared.m_icons; if (icons == null || icons.Length == 0) { return null; } return icons[0]; } public static List<Icon> All() { List<Icon> list = new List<Icon>(); try { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || instance.m_items == null) { return list; } foreach (GameObject item in instance.m_items) { Sprite val = FromPrefab(item); if (!((Object)(object)val == (Object)null)) { string label = ((Object)item).name; try { label = Localize(item.GetComponent<ItemDrop>().m_itemData.m_shared.m_name); } catch (Exception) { } list.Add(new Icon { id = ((Object)item).name, label = label, sprite = val }); } } list.Sort((Icon a, Icon b) => string.Compare(a.label, b.label, StringComparison.OrdinalIgnoreCase)); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Could not list the game's item icons: " + ex2.Message)); } return list; } } internal static class GameUiBlocker { private static readonly List<BaseInputModule> disabled = new List<BaseInputModule>(); private static EventSystem blockedSystem; public static bool Blocking => (Object)(object)blockedSystem != (Object)null; public static void Set(bool block) { try { if (block) { EventSystem current = EventSystem.current; if ((Object)(object)current == (Object)null || current == blockedSystem) { return; } if ((Object)(object)blockedSystem != (Object)null) { Restore(); } BaseInputModule[] components = ((Component)current).GetComponents<BaseInputModule>(); foreach (BaseInputModule val in components) { if (!((Object)(object)val == (Object)null) && ((Behaviour)val).enabled) { ((Behaviour)val).enabled = false; disabled.Add(val); } } blockedSystem = current; } else if ((Object)(object)blockedSystem != (Object)null || disabled.Count > 0) { Restore(); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not switch the game's UI input: " + ex.Message)); disabled.Clear(); blockedSystem = null; } } private static void Restore() { foreach (BaseInputModule item in disabled) { if ((Object)(object)item != (Object)null) { ((Behaviour)item).enabled = true; } } disabled.Clear(); blockedSystem = null; } } internal static class InputInjector { public static readonly HashSet<KeyCode> Down = new HashSet<KeyCode>(); public static readonly HashSet<KeyCode> Held = new HashSet<KeyCode>(); public static readonly HashSet<KeyCode> Up = new HashSet<KeyCode>(); private static readonly List<KeyCode> queue = new List<KeyCode>(); private static readonly List<KeyCode> release = new List<KeyCode>(); private static readonly List<KeyCode> holdStart = new List<KeyCode>(); private static readonly List<KeyCode> holdEnd = new List<KeyCode>(); private static readonly HashSet<KeyCode> sustained = new HashSet<KeyCode>(); public static void Tap(IEnumerable<KeyCode> keys) { queue.AddRange(keys); } public static void HoldStart(IEnumerable<KeyCode> keys) { holdStart.AddRange(keys); } public static void HoldEnd(IEnumerable<KeyCode> keys) { holdEnd.AddRange(keys); } public static void BeginFrame() { //IL_0023: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) Down.Clear(); Up.Clear(); foreach (KeyCode item in release) { Held.Remove(item); Up.Add(item); } release.Clear(); foreach (KeyCode item2 in queue) { Down.Add(item2); Held.Add(item2); release.Add(item2); VirtualKeyboard.Press(item2); } queue.Clear(); foreach (KeyCode item3 in holdStart) { if (sustained.Add(item3)) { Down.Add(item3); Held.Add(item3); VirtualKeyboard.Press(item3, sticky: true); } } holdStart.Clear(); foreach (KeyCode item4 in holdEnd) { if (sustained.Remove(item4)) { Held.Remove(item4); Up.Add(item4); VirtualKeyboard.Release(item4); } } holdEnd.Clear(); VirtualKeyboard.Tick(); } } internal static class VirtualKeyboard { private class Hold { public Key key; public int framesLeft; public bool sticky; } private const int HoldFrames = 3; private static readonly List<Hold> holds = new List<Hold>(); private static MethodInfo gameMapper; public unsafe static void Press(KeyCode kc, bool sticky = false) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_0053: Unknown result type (might be due to invalid IL or missing references) if (!TryMap(kc, out var key)) { Plugin.Warn("Cannot send " + ((object)(*(KeyCode*)(&kc))/*cast due to .constrained prefix*/).ToString() + ": no matching keyboard key (mouse buttons are not supported).", showInGame: true); return; } if (Keyboard.current == null) { Plugin.Warn("No keyboard device found, cannot send shortcuts.", showInGame: true); return; } Send(key, pressed: true); holds.Add(new Hold { key = key, framesLeft = 3, sticky = sticky }); } private unsafe static bool TryMap(KeyCode kc, out Key key) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected I4, but got Unknown key = (Key)0; try { if (gameMapper == null) { gameMapper = AccessTools.Method(typeof(ZInput), "TryKeyCodeToKey", new Type[2] { typeof(KeyCode), typeof(Key).MakeByRefType() }, (Type[])null); } if (gameMapper != null) { object[] array = new object[2] { kc, (object)(Key)0 }; if ((bool)gameMapper.Invoke(null, array)) { key = (Key)(int)(Key)array[1]; if (key) { return true; } } } } catch (Exception) { } string text = ((object)(*(KeyCode*)(&kc))/*cast due to .constrained prefix*/).ToString(); switch (text) { case "LeftControl": text = "LeftCtrl"; break; case "RightControl": text = "RightCtrl"; break; case "Return": text = "Enter"; break; default: if (text.StartsWith("Alpha")) { text = "Digit" + text.Substring(5); } break; } if (Enum.TryParse<Key>(text, out key)) { return (int)key != 0; } return false; } public static void Tick() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) for (int num = holds.Count - 1; num >= 0; num--) { if (!holds[num].sticky && --holds[num].framesLeft <= 0) { Send(holds[num].key, pressed: false); holds.RemoveAt(num); } } } public static void Release(KeyCode kc) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (!TryMap(kc, out var key)) { return; } for (int num = holds.Count - 1; num >= 0; num--) { if (holds[num].sticky && holds[num].key == key) { Send(key, pressed: false); holds.RemoveAt(num); } } } private static void Send(Key key, bool pressed) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) Keyboard current = Keyboard.current; if (current == null) { return; } InputEventPtr val2 = default(InputEventPtr); NativeArray<byte> val = StateEvent.From((InputDevice)(object)current, ref val2, (Allocator)2); try { InputControlExtensions.WriteValueIntoEvent<float>((InputControl<float>)(object)current[key], pressed ? 1f : 0f, val2); InputSystem.QueueEvent(val2); } finally { ((IDisposable)val/*cast due to .constrained prefix*/).Dispose(); } } } internal class InjectionProbe : MonoBehaviour { private unsafe void Update() { //IL_0021: 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_0027: Unknown result type (might be due to invalid IL or missing references) if (InputInjector.Down.Count == 0) { return; } foreach (KeyCode item in new List<KeyCode>(InputInjector.Down)) { if (Input.GetKeyDown(item)) { Plugin.Verbose("Probe: Input.GetKeyDown(" + ((object)(*(KeyCode*)(&item))/*cast due to .constrained prefix*/).ToString() + ") sees the injected key"); } else { Plugin.Log.LogWarning((object)("Probe: Input.GetKeyDown(" + ((object)(*(KeyCode*)(&item))/*cast due to .constrained prefix*/).ToString() + ") does NOT see the injected key (patch ineffective)")); } } } } internal static class KeyParser { private static readonly Dictionary<string, string> Alias = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "ctrl", "LeftControl" }, { "control", "LeftControl" }, { "lctrl", "LeftControl" }, { "rctrl", "RightControl" }, { "alt", "LeftAlt" }, { "lalt", "LeftAlt" }, { "ralt", "RightAlt" }, { "shift", "LeftShift" }, { "lshift", "LeftShift" }, { "rshift", "RightShift" }, { "esc", "Escape" }, { "enter", "Return" }, { "del", "Delete" }, { "ins", "Insert" }, { "pgup", "PageUp" }, { "pgdn", "PageDown" }, { "pgdown", "PageDown" } }; public static bool TryParse(string text, out List<KeyCode> keys, out string error) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) keys = new List<KeyCode>(); error = null; if (string.IsNullOrWhiteSpace(text)) { error = "no keys given"; return false; } string[] array = text.Split('+'); foreach (string text2 in array) { string text3 = text2.Trim(); if (text3.Length == 0) { error = "empty part (check the '+' signs)"; return false; } if (Alias.TryGetValue(text3, out var value)) { text3 = value; } else if (text3.Length == 1 && char.IsDigit(text3[0])) { text3 = "Alpha" + text3; } if (!Enum.TryParse<KeyCode>(text3, ignoreCase: true, out KeyCode result) || (int)result == 0) { error = "unknown key '" + text2.Trim() + "'"; return false; } keys.Add(result); } return true; } } internal class ModShortcut { public string modId; public string modName; public string section; public string key; public string description; public List<KeyCode> keys = new List<KeyCode>(); public bool isMouse; public string Current { get { if (keys.Count != 0) { return ModShortcuts.Join(keys); } return "not set"; } } public string Action { get { string text = (description ?? "").Trim(); int num = text.IndexOf('\n'); if (num >= 0) { text = text.Substring(0, num).Trim(); } if (text.Length != 0) { return text; } return section; } } } internal static class ModShortcuts { private class Cached { public string text; public float time; } private static readonly Dictionary<string, Cached> cache = new Dictionary<string, Cached>(); public unsafe static string Join(List<KeyCode> keys) { return string.Join(" + ", keys.ConvertAll((KeyCode k) => ((object)(*(KeyCode*)(&k))/*cast due to .constrained prefix*/).ToString()).ToArray()); } public static bool HasMouse(List<KeyCode> keys) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_0017: Invalid comparison between Unknown and I4 //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 foreach (KeyCode key in keys) { if ((int)key >= 323 && (int)key <= 329) { return true; } } return false; } private static bool TryGetKeys(ConfigEntryBase entry, out List<KeyCode> keys) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0080: Unknown result type (might be due to invalid IL or missing references) keys = null; Type settingType = entry.SettingType; if (settingType == typeof(KeyboardShortcut)) { KeyboardShortcut val = (KeyboardShortcut)entry.BoxedValue; keys = new List<KeyCode>(); if ((int)((KeyboardShortcut)(ref val)).MainKey != 0) { keys.AddRange(((KeyboardShortcut)(ref val)).Modifiers); keys.Add(((KeyboardShortcut)(ref val)).MainKey); } return true; } if (settingType == typeof(KeyCode)) { KeyCode val2 = (KeyCode)entry.BoxedValue; keys = new List<KeyCode>(); if ((int)val2 != 0) { keys.Add(val2); } return true; } return false; } public static List<ModShortcut> ReadAll() { List<ModShortcut> list = new List<ModShortcut>(); foreach (PluginInfo value in Chainloader.PluginInfos.Values) { try { BaseUnityPlugin instance = value.Instance; if ((Object)(object)instance == (Object)null) { continue; } ConfigFile config = instance.Config; foreach (ConfigDefinition key in config.Keys) { ConfigEntryBase val = config[key]; if (TryGetKeys(val, out var keys)) { list.Add(new ModShortcut { modId = value.Metadata.GUID, modName = value.Metadata.Name, section = key.Section, key = key.Key, description = ((val.Description != null) ? val.Description.Description : ""), keys = keys, isMouse = HasMouse(keys) }); } } } catch (Exception ex) { Plugin.Verbose("Could not read the settings of a mod: " + ex.Message); } } list.Sort(delegate(ModShortcut a, ModShortcut b) { int num = string.Compare(a.modName, b.modName, StringComparison.OrdinalIgnoreCase); return (num == 0) ? string.Compare(a.key, b.key, StringComparison.OrdinalIgnoreCase) : num; }); return list; } public static bool TryRead(EntryLink link, out List<KeyCode> keys, out string modName) { keys = null; modName = null; if (link == null || string.IsNullOrEmpty(link.mod)) { return false; } if (!Chainloader.PluginInfos.TryGetValue(link.mod, out var value)) { return false; } modName = value.Metadata.Name; try { BaseUnityPlugin instance = value.Instance; if ((Object)(object)instance == (Object)null) { return false; } ConfigFile config = instance.Config; foreach (ConfigDefinition key in config.Keys) { if (!(key.Section != link.section) && !(key.Key != link.key)) { return TryGetKeys(config[key], out keys); } } } catch (Exception) { } return false; } public static bool ResolveKeys(Entry e, out List<KeyCode> keys, out string error) { if (e.link != null && TryRead(e.link, out var keys2, out var _) && keys2.Count > 0 && !HasMouse(keys2)) { keys = keys2; error = null; return true; } return KeyParser.TryParse(e.keys, out keys, out error); } public static string DisplayFor(Entry e) { if (e.link == null) { return ""; } string key = e.link.mod + "|" + e.link.section + "|" + e.link.key + "|" + e.keys; float unscaledTime = Time.unscaledTime; if (cache.TryGetValue(key, out var value) && unscaledTime - value.time < 1f) { return value.text; } List<KeyCode> keys; string modName; string text = ((!TryRead(e.link, out keys, out modName)) ? (string.IsNullOrWhiteSpace(e.keys) ? "link not found" : (e.keys + " - link not found")) : ((keys.Count == 0) ? "not set" : (Join(keys) + (HasMouse(keys) ? " - mouse, not supported" : "")))); cache[key] = new Cached { text = text, time = unscaledTime }; return text; } public static string LinkInfo(Entry e) { if (TryRead(e.link, out var keys, out var modName)) { return "Linked to " + modName + " > " + e.link.key + ". Its key now: " + ((keys.Count == 0) ? "not set" : Join(keys)) + ". Fallback keys: " + (string.IsNullOrWhiteSpace(e.keys) ? "none" : e.keys) + "."; } return "Linked to " + (modName ?? e.link.mod) + " > " + e.link.key + ", but that setting was not found. Using the fallback keys: " + (string.IsNullOrWhiteSpace(e.keys) ? "none" : e.keys) + "."; } public static void RefreshFallbacks(List<Entry> list) { foreach (Entry item in list) { List<KeyCode> keys; string modName; if (item.IsGroup()) { RefreshFallbacks(item.children); } else if (item.link != null && TryRead(item.link, out keys, out modName) && keys.Count > 0 && !HasMouse(keys)) { item.keys = Join(keys); } } } } internal static class NativeRadial { private class Pending { public string label; public List<KeyCode> keys; public float chosenAt; public bool early; } private static readonly Type RadialType = AccessTools.TypeByName("Valheim.UI.RadialBase"); private static readonly Type ElementType = AccessTools.TypeByName("Valheim.UI.RadialMenuElement"); private static readonly Type DataType = AccessTools.TypeByName("Valheim.UI.RadialData"); private static object radial; private static object prevLastUsed; private static bool built; private static int framesWaiting; private static Sprite dotSprite; private static readonly List<Pending> pending = new List<Pending>(); private static int settleFrames; private static float pressTime; private static int openFrame; private static bool forcedClose; private static Wheel wheel; private static string editorWheelName; private static readonly Dictionary<Object, Entry> elementEntries = new Dictionary<Object, Entry>(); private static List<KeyCode> heldKeys; private static int heldSince; private static readonly Dictionary<object, List<Entry>> groups = new Dictionary<object, List<Entry>>(); private static readonly List<Object> clones = new List<Object>(); private static Transform scaledTransform; private static Vector3 originalScale; private static bool loggedTintInfo; private static bool openEditorAfterClose; private static Sprite gearSprite; public static bool SessionActive { get; private set; } private static T Prop<T>(object o, string name) { return Traverse.Create(o).Property(name, (object[])null).GetValue<T>(); } private static void SetProp(object o, string name, object value) { Traverse.Create(o).Property(name, (object[])null).SetValue(value); } internal static object CurrentRadial() { return GetRadial(); } internal static object CurrentData() { return GetData(); } private static object GetRadial() { if (!((Object)(object)Hud.instance == (Object)null)) { return Traverse.Create((object)Hud.instance).Field("m_radialMenu").GetValue(); } return null; } private static object GetData() { if (!(DataType == null)) { return Traverse.Create(DataType).Field("SO").GetValue(); } return null; } private static object InitData() { try { Type type = AccessTools.TypeByName("Valheim.UI.RadialDataInitializer"); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(DataType, "Init", (Type[])null, (Type[])null)); if (type == null || methodInfo == null) { return null; } Object[] array = Resources.FindObjectsOfTypeAll(type); for (int i = 0; i < array.Length; i++) { object value = Traverse.Create((object)array[i]).Field("_dataObject").GetValue(); if (value != null) { methodInfo.Invoke(null, new object[1] { value }); Plugin.Verbose("Native wheel: initialised RadialData.SO manually."); return GetData(); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Native wheel: RadialData init failed: " + ex.Message)); } return null; } private static bool IsActive() { if (radial != null) { return Prop<bool>(radial, "Active"); } return false; } private static Wheel CurrentWheel() { return wheel ?? Wheels.Main; } public static bool TryOpen(Wheel w) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) try { if (RadialType == null || ElementType == null || DataType == null) { Plugin.Log.LogWarning((object)"Native wheel: game radial types not found."); return false; } radial = GetRadial(); object obj = GetData() ?? InitData(); if (radial == null || obj == null) { Plugin.Log.LogWarning((object)("Native wheel: Hud.m_radialMenu is " + ((radial == null) ? "null" : "ok") + ", RadialData.SO is " + ((obj == null) ? "null" : "ok") + ".")); return false; } if (IsActive()) { return true; } object obj2 = radial; GameObject val = (((Object)((obj2 is Component) ? obj2 : null) != (Object)null) ? ((Component)radial).gameObject : null); if ((Object)(object)val != (Object)null && !val.activeInHierarchy) { Plugin.Verbose("Native wheel: radial GameObject '" + ((Object)val).name + "' was inactive, activating it."); val.SetActive(true); } object value = Traverse.Create(obj).Field("OpenConfig").GetValue(); if (value == null) { Plugin.Log.LogWarning((object)"Native wheel: RadialData.SO.OpenConfig is null."); return false; } wheel = w; RadialMenu.Instance.PrepareWheel(w); prevLastUsed = Prop<object>(radial, "LastUsed"); built = false; framesWaiting = 0; forcedClose = false; SessionActive = true; bool flag = Prop<bool>(radial, "CanOpen"); if (!flag) { SetProp(radial, "CanOpen", true); } AccessTools.Method(RadialType, "Open", (Type[])null, (Type[])null).Invoke(radial, new object[2] { value, null }); if (!IsActive()) { Plugin.Log.LogWarning((object)("Native wheel did not open. State: CanOpen before=" + flag + ", CanOpen now=" + Prop<bool>(radial, "CanOpen") + ", InRadialBlockingPlaceMode=" + Prop<bool>(radial, "InRadialBlockingPlaceMode") + ", localPlayer=" + ((Object)(object)Player.m_localPlayer != (Object)null) + ". Falling back to the built-in wheel.")); SessionActive = false; return false; } pressTime = Time.unscaledTime; openFrame = Time.frameCount; OverrideControls(); ApplyScale(); Plugin.Verbose("Native wheel opened (elements built immediately: " + built + "). Timings: game input lock (InteractionDelay) = " + GameInputLock().ToString("0.000") + " s, EnableToggleAnimation = " + Traverse.Create(obj).Field("EnableToggleAnimation").GetValue<bool>() + ", ToggleAnimDuration = " + Traverse.Create(obj).Field("ToggleAnimDuration").GetValue<float>() .ToString("0.000") + " s"); return true; } catch (Exception ex) { Plugin.Log.LogError((object)("Native wheel failed: " + ex)); SessionActive = false; return false; } } public static void Tick() { if (!SessionActive) { return; } try { if (!IsActive()) { End(); return; } HoldTick(); if (pending.Count > 0 && Plugin.InstantClose.Value && !forcedClose) { forcedClose = true; AccessTools.Method(RadialType, "Close", new Type[1] { typeof(bool) }, (Type[])null)?.Invoke(radial, new object[1] { true }); if (!IsActive()) { End(); return; } } if (!built && ++framesWaiting == 10) { Plugin.Log.LogWarning((object)"Native wheel: vanilla config route not hit, building elements directly."); BuildInto(radial, null); } } catch (Exception ex) { Plugin.Log.LogError((object)("Native wheel tick failed: " + ex)); End(); } } private static void End() { //IL_0145: Unknown result type (might be due to invalid IL or missing references) try { if (radial != null) { AccessTools.Method("Valheim.UI.RadialConfigHelper:SetItemInteractionControls", (Type[])null, (Type[])null)?.Invoke(null, new object[1] { radial }); object obj = prevLastUsed; Object val = (Object)((obj is Object) ? obj : null); SetProp(radial, "LastUsed", (prevLastUsed != null && val == (Object)null) ? null : prevLastUsed); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Native wheel cleanup: " + ex.Message)); } try { foreach (Object clone in clones) { if (clone != (Object)null) { Object.Destroy(clone); } } if (radial != null) { Traverse.Create(radial).Field("m_backConfigs").Method("Clear", Array.Empty<object>()) .GetValue(); } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Native wheel group cleanup: " + ex2.Message)); } clones.Clear(); groups.Clear(); StopHold(); SessionActive = false; built = false; if ((Object)(object)scaledTransform != (Object)null) { scaledTransform.localScale = originalScale; scaledTransform = null; } forcedClose = false; settleFrames = Plugin.SendDelayFrames.Value; if (openEditorAfterClose) { openEditorAfterClose = false; RadialEditor.Open(editorWheelName); } } private static bool ReleaseToUseEnabled() { return Traverse.Create(GetData()).Field("EnableReleaseToUseMode").GetValue<bool>(); } private static float HoldDelay() { return Traverse.Create(GetData()).Field("HoldCloseDelay").GetValue<float>(); } private static bool HeldLongEnough() { return Time.unscaledTime - pressTime > HoldDelay(); } private static void OverrideControls() { Func<bool> origClose = Prop<Func<bool>>(radial, "GetClose"); SetProp(radial, "GetClose", (Func<bool>)delegate { if (origClose != null && origClose()) { return true; } if (Time.frameCount <= openFrame) { return false; } if (OpenKeyUtil.IsDown(wheel)) { return true; } if (Mode() == CloseMode.Toggle) { if (!ReleaseToUseEnabled() && HeldLongEnough()) { return OpenKeyUtil.IsUp(wheel); } return false; } Object val = Prop<Object>(radial, "Selected"); return !OpenKeyUtil.IsPressed(wheel) && val == (Object)null; }); SetProp(radial, "GetReleaseToUse", (Func<bool>)delegate { if (Time.frameCount <= openFrame) { return false; } return (Mode() == CloseMode.Toggle) ? (ReleaseToUseEnabled() && HeldLongEnough() && OpenKeyUtil.IsUp(wheel)) : (!OpenKeyUtil.IsPressed(wheel)); }); } private static CloseMode Mode() { return Wheels.EffectiveClose(CurrentWheel()); } private static bool PointerPressed() { if (!Input.GetMouseButton(0)) { return Input.GetKey((KeyCode)330); } return true; } private static bool PointerDown() { if (!Input.GetMouseButtonDown(0)) { return Input.GetKeyDown((KeyCode)330); } return true; } private static void OnTileInteract(Entry e) { if (Mode() != CloseMode.HoldOpen || !OpenKeyUtil.IsPressed(wheel)) { Run(e); } } private static bool TileCloses() { if (Mode() == CloseMode.HoldOpen) { return !OpenKeyUtil.IsPressed(wheel); } return true; } private unsafe static void HoldTick() { if (heldKeys != null) { if (!PointerPressed() && Time.frameCount - heldSince >= 2) { StopHold(); } } else { if (Mode() != CloseMode.HoldOpen || !PointerDown() || !OpenKeyUtil.IsPressed(wheel)) { return; } Object val = Prop<Object>(radial, "Selected"); if (val == (Object)null || !elementEntries.TryGetValue(val, out var value)) { return; } if (!ModShortcuts.ResolveKeys(value, out var keys, out var error)) { Plugin.Warn("Entry '" + value.label + "' has invalid keys \"" + value.keys + "\": " + error, showInGame: true); } else { InputInjector.HoldStart(keys); heldKeys = keys; heldSince = Time.frameCount; Plugin.Verbose("Entry '" + value.label + "' clicked in hold-open mode, keys held: " + string.Join(" + ", keys.ConvertAll((KeyCode k) => ((object)(*(KeyCode*)(&k))/*cast due to .constrained prefix*/).ToString()).ToArray())); } } } private static void StopHold() { if (heldKeys != null) { InputInjector.HoldEnd(heldKeys); heldKeys = null; } } public static void BuildInto(object r, object config) { object data = GetData(); object value = Traverse.Create(data).Field("EmoteElement").GetValue(); Component val = (Component)((value is Component) ? value : null); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("RadialData.SO.EmoteElement not found"); } elementEntries.Clear(); List<Entry> value2 = null; bool flag = config != null && groups.TryGetValue(config, out value2); if (!flag || value2 == null) { flag = false; value2 = CurrentWheel().entries; } object parentConfig = config ?? Traverse.Create(data).Field("MainGroupConfig").GetValue(); int num = ((!flag && Plugin.ShowEditorTile.Value) ? 1 : 0); int num2 = ((flag || CurrentWheel().groupStyle) ? Mathf.Max(1, value2.Count + num) : Mathf.Max(Plugin.Slots.Value, value2.Count + num)); IList list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(ElementType)); for (int i = 0; i < num2; i++) { if (i < num) { Component val2 = MakeEditTile(val); ApplyTint(val2); list.Add(val2); continue; } int num3 = i - num; Entry entry = ((num3 < value2.Count) ? value2[num3] : null); Component val3 = null; if (entry != null && entry.IsGroup()) { val3 = MakeGroup(entry, parentConfig, r); } if ((Object)(object)val3 == (Object)null) { val3 = Object.Instantiate<Component>(val); Setup(val3, (entry != null && entry.IsGroup()) ? null : entry); } ApplyTint(val3); list.Add(val3); } AccessTools.Method(RadialType, "ConstructRadial", (Type[])null, (Type[])null).Invoke(r, new object[1] { list }); built = true; Plugin.Verbose("Native wheel: built " + value2.Count + " entries (" + num2 + (flag ? " tiles + back tile, inside a group)" : " slots)")); } private static Component MakeGroup(Entry e, object parentConfig, object r) { try { object data = GetData(); object value = Traverse.Create(data).Field("GroupElement").GetValue(); Component val = (Component)((value is Component) ? value : null); object value2 = Traverse.Create(data).Field("MainGroupConfig").GetValue(); Object val2 = (Object)((value2 is Object) ? value2 : null); if ((Object)(object)val == (Object)null || val2 == (Object)null) { Plugin.Warn("Cannot create group '" + e.label + "': the game's group template was not found."); return null; } Object val3 = Object.Instantiate(val2); groups[val3] = e.children; clones.Add(val3); Component val4 = Object.Instantiate<Component>(val); MethodInfo methodInfo = null; MethodInfo[] methods = ((object)val4).GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (methodInfo2.Name == "Init" && methodInfo2.GetParameters().Length == 3) { methodInfo = methodInfo2; break; } } if (methodInfo == null) { throw new InvalidOperationException("GroupElement.Init not found"); } methodInfo.Invoke(val4, new object[3] { val3, parentConfig, r }); Traverse obj = Traverse.Create((object)val4); obj.Property("Name", (object[])null).SetValue((object)(e.label ?? "")); Image value3 = obj.Property("Icon", (object[])null).GetValue<Image>(); if ((Object)(object)value3 != (Object)null) { value3.sprite = RadialMenu.Instance.LoadIcon(e.icon) ?? Dot(); ((Component)value3).gameObject.SetActive(true); } return val4; } catch (Exception ex) { Plugin.Log.LogError((object)("Cannot create group '" + e.label + "': " + ex)); return null; } } private static void Setup(Component el, Entry e) { Traverse obj = Traverse.Create((object)el); obj.Property("Name", (object[])null).SetValue((object)((e != null) ? (e.label ?? "") : "")); obj.Property("Interact", (object[])null).SetValue((object)((e == null) ? null : ((Func<bool>)delegate { OnTileInteract(e); return true; }))); obj.Property("CloseOnInteract", (object[])null).SetValue((object)(Func<bool>)(() => TileCloses())); if (e != null) { elementEntries[(Object)(object)el] = e; } Image value = obj.Property("Icon", (object[])null).GetValue<Image>(); if ((Object)(object)value != (Object)null) { Sprite val = null; if (e != null) { val = RadialMenu.Instance.LoadIcon(e.icon) ?? Dot(); } value.sprite = val; ((Component)value).gameObject.SetActive((Object)(object)val != (Object)null); } } private static void Run(Entry e) { if (!ModShortcuts.ResolveKeys(e, out var keys, out var error)) { Plugin.Warn("Entry '" + e.label + "' has invalid keys \"" + e.keys + "\": " + error, showInGame: true); } else { bool flag = e.early ?? Plugin.SendDuringCloseAnimation.Value; pending.Add(new Pending { label = e.label, keys = keys, chosenAt = Time.unscaledTime, early = flag }); Plugin.Verbose("Entry '" + e.label + "' chosen, keys will be sent " + (flag ? "while the wheel is closing." : "once the wheel has closed.")); } } private static float GameInputLock() { try { return Mathf.Max(0f, Traverse.Create(GetData()).Field("InteractionDelay").GetValue<float>()); } catch (Exception) { return 0f; } } private static float InteractionDelay() { return Plugin.SendDelaySeconds.Value; } public unsafe static void Flush() { if (NativeRadial.pending.Count == 0) { return; } bool flag = !SessionActive; if (flag && settleFrames > 0) { settleFrames--; return; } int num = 0; while (num < NativeRadial.pending.Count) { Pending pending = NativeRadial.pending[num]; if (!flag && !pending.early) { num++; continue; } float num2 = (pending.early ? InteractionDelay() : 0f); if (Time.unscaledTime - pending.chosenAt < num2) { num++; continue; } InputInjector.Tap(pending.keys); Plugin.Verbose("Entry '" + pending.label + "' -> sending keys: " + string.Join(" + ", pending.keys.ConvertAll((KeyCode k) => ((object)(*(KeyCode*)(&k))/*cast due to .constrained prefix*/).ToString()).ToArray()) + " (" + (Time.unscaledTime - pending.chosenAt).ToString("0.000") + " s after it was picked, wheel " + (flag ? "closed" : "still closing") + ", early=" + pending.early + ", wait used " + num2.ToString("0.000") + " s)"); NativeRadial.pending.RemoveAt(num); } } private static void ApplyScale() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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) float value = Plugin.WheelScale.Value; object obj = radial; Component val = (Component)((obj is Component) ? obj : null); if (!((Object)(object)val == (Object)null) && !Mathf.Approximately(value, 1f)) { scaledTransform = val.transform; originalScale = scaledTransform.localScale; scaledTransform.localScale = originalScale * value; } } private static void ApplyTint(Component el) { //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: 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) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)el == (Object)null || !Plugin.TryGetWheelColor(out var color)) { return; } Traverse val = Traverse.Create((object)el); Material value = val.Property("BackgroundMaterial", (object[])null).GetValue<Material>(); if ((Object)(object)value != (Object)null && (Object)(object)value.shader != (Object)null) { Shader shader = value.shader; StringBuilder stringBuilder = new StringBuilder(); int num = 0; for (int i = 0; i < shader.GetPropertyCount(); i++) { if ((int)shader.GetPropertyType(i) == 0) { string propertyName = shader.GetPropertyName(i); Color color2 = value.GetColor(propertyName); stringBuilder.Append(propertyName).Append('=').Append(color2) .Append("; "); if (TryShade(propertyName, color, out var shade)) { value.SetColor(propertyName, new Color(shade.r, shade.g, shade.b, color2.a * color.a)); num++; } } } if (!loggedTintInfo) { loggedTintInfo = true; Plugin.Verbose("Native wheel tint: shader '" + ((Object)shader).name + "', tinted " + num + " color propert" + ((num == 1) ? "y" : "ies") + ". Color properties found: " + stringBuilder); } if (num > 0) { return; } } Image value2 = val.Property("Background", (object[])null).GetValue<Image>(); if ((Object)(object)value2 != (Object)null) { ((Graphic)value2).color = new Color(color.r, color.g, color.b, ((Graphic)value2).color.a * color.a); } } private static bool TryShade(string property, Color baseColor, out Color shade) { //IL_0008: 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_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) string text = property.ToLowerInvariant(); shade = baseColor; if (text.Contains("unselected") || text == "_color" || text.Contains("base") || text.Contains("background") || text.Contains("tint")) { return true; } if (text.Contains("activated") || text.Contains("select") || text.Contains("hover") || text.Contains("highlight")) { shade = Color.Lerp(baseColor, new Color(1f, 0.72f, 0.18f), 0.45f); return true; } if (text.Contains("queued")) { shade = Color.Lerp(baseColor, Color.white, 0.3f); return true; } return false; } private static Component MakeEditTile(Component prefab) { Component obj = Object.Instantiate<Component>(prefab); Traverse obj2 = Traverse.Create((object)obj); obj2.Property("Name", (object[])null).SetValue((object)"Edit wheel"); obj2.Property("Interact", (object[])null).SetValue((object)(Func<bool>)delegate { openEditorAfterClose = true; editorWheelName = CurrentWheel().name; return true; }); obj2.Property("CloseOnInteract", (object[])null).SetValue((object)(Func<bool>)(() => true)); Image value = obj2.Property("Icon", (object[])null).GetValue<Image>(); if ((Object)(object)value != (Object)null) { value.sprite = WheelIcon() ?? Gear(); ((Component)value).gameObject.SetActive(true); } return obj; } private static Sprite WheelIcon() { Wheel wheel = CurrentWheel(); return ((wheel == null || string.IsNullOrEmpty(wheel.icon)) ? null : RadialMenu.Instance.LoadIcon(wheel.icon)) ?? RadialMenu.Instance.EditorDefaultIcon(); } internal static Sprite DefaultEditorIcon() { return RadialMenu.Instance.EditorDefaultIcon() ?? Gear(); } private static Sprite Gear() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gearSprite != (Object)null) { return gearSprite; } Texture2D val = new Texture2D(96, 96, (TextureFormat)4, false) { filterMode = (FilterMode)1 }; Color[] array = (Color[])(object)new Color[9216]; float num = 47.5f; for (int i = 0; i < 96; i++) { for (int j = 0; j < 96; j++) { float num2 = (float)j - num; float num3 = (float)i - num; float num4 = Mathf.Sqrt(num2 * num2 + num3 * num3) / num; bool flag = (Mathf.Atan2(num3, num2) * 57.29578f + 360f) % 45f < 24f; float num5 = 0f; if (num4 > 0.3f && num4 < 0.58f) { num5 = 1f; } else if (flag && num4 >= 0.58f && num4 < 0.8f) { num5 = 1f; } array[i * 96 + j] = new Color(1f, 0.72f, 0.18f, num5); } } val.SetPixels(array); val.Apply(); return gearSprite = Sprite.Create(val, new Rect(0f, 0f, 96f, 96f), new Vector2(0.5f, 0.5f), 100f); } private static Sprite Dot() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)dotSprite != (Object)null) { return dotSprite; } Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false) { filterMode = (FilterMode)1 }; Color[] array = (Color[])(object)new Color[4096]; float num = 31.5f; for (int i = 0; i < 64; i++) { for (int j = 0; j < 64; j++) { float num2 = Mathf.Sqrt(((float)j - num) * ((float)j - num) + ((float)i - num) * ((float)i - num)) / num; array[i * 64 + j] = new Color(1f, 0.72f, 0.18f, Mathf.Clamp01((0.8f - num2) * num / 1.5f)); } } val.SetPixels(array); val.Apply(); return dotSprite = Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f), 100f); } } [HarmonyPatch] internal static class Patch_ValheimRadialConfigInit { private static MethodBase TargetMethod() { return AccessTools.Method("Valheim.UI.ValheimRadialConfig:InitRadialConfig", (Type[])null, (Type[])null); } private static bool Prefix(object __instance, object[] __args) { if (!NativeRadial.SessionActive) { return true; } try { NativeRadial.BuildInto(__args[0], __instance); return false; } catch (Exception ex) { Plugin.Log.LogError((object)("Native wheel build failed, showing vanilla: " + ex)); return true; } } } [HarmonyPatch] internal static class Patch_TileAlpha { private static IEnumerable<MethodBase> TargetMethods() { Type type = AccessTools.TypeByName("Valheim.UI.RadialMenuElement"); string[] array = new string[3] { "UnselectedColorAlpha", "ActivatedColorAlpha", "QueuedColorAlpha" }; foreach (string text in array) { MethodInfo methodInfo = ((type == null) ? null : AccessTools.PropertySetter(type, text)); if (methodInfo != null) { yield return methodInfo; } } } private static void Prefix(ref float value) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (NativeRadial.SessionActive && Plugin.TryGetWheelColor(out var color)) { value *= color.a; } } } internal static class OpenKeyUtil { private static bool ModifiersHeld(Wheel w) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) foreach (KeyCode modifier in w.Modifiers) { if (!Input.GetKey(modifier)) { return false; } } return true; } public static bool IsDown(Wheel w) { //IL_0006: 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_000c: 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) if (w == null) { return false; } KeyCode mainKey = w.MainKey; if ((int)mainKey != 0 && Input.GetKeyDown(mainKey)) { return ModifiersHeld(w); } return false; } public static bool IsPressed(Wheel w) { //IL_0006: 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_000c: 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) if (w == null) { return false; } KeyCode mainKey = w.MainKey; if ((int)mainKey != 0) { return Input.GetKey(mainKey); } return false; } public static bool IsUp(Wheel w) { //IL_0006: 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_000c: 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) if (w == null) { return false; } KeyCode mainKey = w.MainKey; if ((int)mainKey != 0) { return Input.GetKeyUp(mainKey); } return false; } } [BepInPlugin("akarian.radialhub", "RadialHub", "1.0.3")] public class Plugin : BaseUnityPlugin { public const string Guid = "akarian.radialhub"; private const string OldGuid = "yourname.radialhub"; public const string Name = "RadialHub"; public const string Version = "1.0.3"; internal static ConfigEntry<KeyboardShortcut> OpenKey; internal static ConfigEntry<float> Deadzone; internal static ConfigEntry<float> Sensitivity; internal static ConfigEntry<bool> UseNativeWheel; internal static ConfigEntry<int> Slots; internal static ConfigEntry<CloseMode> WheelCloseMode; internal static ConfigEntry<bool> InstantClose; internal static ConfigEntry<bool> SendDuringCloseAnimation; internal static ConfigEntry<float> WheelScale; internal static ConfigEntry<bool> UseCustomColor; internal static ConfigEntry<Color> WheelColor; internal static ConfigEntry<int> SendDelayFrames; internal static ConfigEntry<float> SendDelaySeconds; internal static ConfigEntry<bool> VerboseLogging; internal static ConfigEntry<bool> ShowWarningsInGame; internal static ConfigEntry<bool> ShowEditorTile; internal static ConfigEntry<KeyboardShortcut> EditorKey; internal static ConfigEntry<KeyboardShortcut> DumpUiKey; internal static ConfigEntry<int> EditorFontSize; internal static ConfigEntry<float> EditorScale; internal static ConfigEntry<Color> EditorWindowColor; internal static ConfigEntry<Color> EditorPanelColor; internal static ConfigEntry<Color> EditorTextColor; internal static ConfigEntry<Color> EditorButtonColor; internal static ConfigEntry<Color> EditorButtonHoverColor; internal static ConfigEntry<Color> EditorButtonTextColor; internal static ConfigEntry<Color> EditorSelectedColor; internal static ConfigEntry<Color> EditorInputColor; internal static ConfigEntry<Color> EditorInputTextColor; internal static ConfigEntry<Color> EditorAccentColor; internal static ConfigEntry<Color> EditorErrorColor; internal static ConfigEntry<Color> EditorTitleBarColor; internal static ConfigEntry<Color> EditorTitleTextColor; internal static ManualLogSource Log; private Dictionary<string, string> rawBefore = new Dictionary<string, string>(); internal static void Verbose(string msg) { if (VerboseLogging != null && VerboseLogging.Value) { Log.LogInfo((object)msg); } } internal static void Warn(string msg, bool showInGame = false) { Log.LogWarning((object)msg); if (!showInGame) { return; } if (RadialEditor.IsOpen) { RadialEditor.Notify("Warning: " + msg); } else { if (ShowWarningsInGame == null || !ShowWarningsInGame.Value) { return; } try { if ((Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)1, "RadialHub: " + msg, 0, (Sprite)null, false); } } catch (Exception) { } } } internal static bool TryGetWheelColor(out Color color) { //IL_0014: 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_0019: Unknown result type (might be due to invalid IL or missing references) color = ((WheelColor != null) ? WheelColor.Value : Color.white); if (UseCustomColor != null) { return UseCustomColor.Value; } return false; } private static Dictionary<string, string> ReadRawConfig(string path) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); try { if (!File.Exists(path)) { return dictionary; } string text = ""; string[] array = File.ReadAllLines(path); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0 || text2.StartsWith("#")) { continue; } if (text2.StartsWith("[") && text2.EndsWith("]")) { text = text2.Substring(1, text2.Length - 2).Trim(); continue; } int num = text2.IndexOf('='); if (num > 0) { dictionary[text + "/" + text2.Substring(0, num).Trim()] = text2.Substring(num + 1).Trim(); } } } catch (Exception) { } return dictionary; } private void Migrate<T>(ConfigEntry<T> entry, string oldSection) { try { if (!rawBefore.ContainsKey(((ConfigEntryBase)entry).Definition.Section + "/" + ((ConfigEntryBase)entry).Definition.Key) && rawBefore.TryGetValue(oldSection + "/" + ((ConfigEntryBase)entry).Definition.Key, out var value)) { ((ConfigEntryBase)entry).SetSerializedValue(value); } } catch (Exception) { } } private void MigrateToggleMode() { try { if (!rawBefore.ContainsKey("General/CloseMode") && rawBefore.TryGetValue("General/ToggleMode", out var value)) { WheelCloseMode.Value = (string.Equals(value, "false", StringComparison.OrdinalIgnoreCase) ? CloseMode.ReleaseToUse : CloseMode.Toggle); } } catch (Exception) { } } private void MigrateOldConfigFile() { try { string text = Path.Combine(Paths.ConfigPath, "yourname.radialhub.cfg"); string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; if (!File.Exists(configFilePath) && File.Exists(text)) { File.Copy(text, configFilePath); ((BaseUnityPlugin)this).Config.Reload(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Copied the settings of the old config file " + Path.GetFileName(text) + " to " + Path.GetFileName(configFilePath))); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not copy the old config file: " + ex.Message)); } } private void Awake() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Expected O, but got Unknown //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Expected O, but got Unknown //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Expected O, but got Unknown //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Expected O, but got Unknown //IL_033b: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Expected O, but got Unknown //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_03a6: Unknown result type (might be due to invalid IL or missing references) //IL_03de: Unknown result type (might be due to invalid IL or missing references) //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_044e: Unknown result type (might be due to invalid IL or missing references) //IL_0486: Unknown result type (might be due to invalid IL or missing references) //IL_04be: Unknown result type (might be due to invalid IL or missing references) //IL_04f6: Unknown result type (might be due to invalid IL or missing references) //IL_052e: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_059e: Unknown result type (might be due to invalid IL or missing references) //IL_05d6: Unknown result type (might be due to invalid IL or missing references) //IL_060e: Unknown result type (might be due to invalid IL or missing references) //IL_067c: Unknown result type (might be due to invalid IL or missing references) //IL_06a5: Unknown result type (might be due to invalid IL or missing references) //IL_06ab: Expected O, but got Unknown //IL_06e1: Unknown result type (might be due to invalid IL or missing references) //IL_06e6: Unknown result type (might be due to invalid IL or missing references) //IL_0714: Unknown result type (might be due to invalid IL or missing references) //IL_071a: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; MigrateOldConfigFile(); rawBefore = ReadRawConfig(((BaseUnityPlugin)this).Config.ConfigFilePath); OpenKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "OpenKey", new KeyboardShortcut((KeyCode)102, Array.Empty<KeyCode>()), "Key used to open the MAIN radial menu (other wheels have their own keys, set in the editor). Accepts a key name from Unity's KeyCode list (e.g. F, G, Mouse3, Tab), optionally with modifiers, e.g. 'LeftAlt + F'."); WheelCloseMode = ((BaseUnityPlugin)this).Config.Bind<CloseMode>("General", "CloseMode", CloseMode.Toggle, "How the native wheel opens and closes. Applies to the main wheel; every other wheel can use its own mode (set in the editor).\nToggle: behaves like the G wheel. A short tap opens it and it stays open (click an entry, press the key again or Esc to close). If 'Release to use' is on in the game's settings, holding the key and releasing picks the hovered entry.\nReleaseToUse: hold the key to keep the wheel open; releasing the key uses the entry under the cursor.\nHoldOpen: the wheel stays open while the key is held. Clicking an entry sends its shortcut but does not close the wheel (hold the click to keep the shortcut pressed). Releasing the key closes the wheel and uses the entry under the cursor."); ShowEditorTile = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowEditorTile", true, "Show the 'Edit wheel' tile as the first tile of the native wheel's top level. It opens the in-game editor for your shortcuts. Turn it off when you are done editing (the editor key below still works)."); EditorKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "EditorKey", new KeyboardShortcut((KeyCode)0, Array.Empty<KeyCode>()), "Optional key that opens the in-game editor directly (also works with the built-in wheel). None = not used."); Deadzone = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Deadzone", 0.25f, new ConfigDescription("Built-in wheel only: fraction of the wheel radius where nothing is selected.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.9f), Array.Empty<object>())); Sensitivity = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Sensitivity", 0.05f, "Built-in wheel only: mouse movement to wheel movement scale."); SendDuringCloseAnimation = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "SendDuringCloseAnimation", false, "Native wheel only, experimental: send shortcuts while the wheel is still fading out instead of waiting until it has completely closed (after SendDelaySeconds). Works for keys read by other mods (and the console), but the game ignores gameplay keys such as sitting (X) until its wheel has closed (about 0.3 s). Single entries can override this with \"early\": true/false in entries.json."); SendDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SendDelaySeconds", 0f, new ConfigDescription("Native wheel, used when SendDuringCloseAnimation is true: seconds to wait after you pick a tile before its shortcut is sent. 0 = right away.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); InstantClose = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "InstantClose", false, "Native wheel only: close the wheel immediately after you pick an entry (no closing animation)."); SendDelayFrames = ((BaseUnityPlugin)this).Config.Bind<int>("General", "SendDelayFrames", 1, new ConfigDescription("Native wheel only: frames to wait after the wheel has fully closed before the shortcut is sent. Lower is faster; raise it if shortcuts that need free player control get ignored.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), Array.Empty<object>())); UseNativeWheel = ((BaseUnityPlugin)this).Config.Bind<bool>("Appearance", "UseNativeWheel", true, "Show the entries inside Valheim's own radial menu (same look as the G wheel). Set to false to use RadialHub's simpler built-in wheel instead."); Slots = ((BaseUnityPlugin)this).Config.Bind<int>("Appearance", "Slots", 8, new ConfigDescription("Number of slots on the first page of the native wheel (for the main wheel and for every wheel set to 'Main wheel style'). Unused slots stay empty. If you have more entries than slots, every entry still gets its own slot. Groups always show exactly as many tiles as they have entries (plus the back tile).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 24), Array.Empty<object>())); WheelScale = ((BaseUnityPlugin)this).Config.Bind<float>("Appearance", "WheelScale", 1f, new ConfigDescription("Size of the wheel as a multiplier (1 = normal size). Applies to the native and the built-in wheel.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), Array.Empty<object>())); UseCustomColor = ((BaseUnityPlugin)this).Config.Bind<bool>("Appearance", "UseCustomColor", false, "Turn on to use WheelColor below. Off = the original colors."); WheelColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Appearance", "WheelColor", new Color(0.23f, 0.16f, 0.06f, 1f), "Color of the wheel tiles (used when UseCustomColor is on). The A (alpha) value sets how see-through the tiles are compared with the original look: 100% = original transparency, lower = more transparent."); Migrate<bool>(UseNativeWheel, "General"); Migrate<int>(Slots, "General"); MigrateToggleMode(); EditorFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("Editor", "FontSize", 15, new ConfigDescription("Text size in the editor window.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 30), Array.Empty<object>())); EditorScale = ((BaseUnityPlugin)this).Config.Bind<float>("Editor", "WindowScale", 1f, new ConfigDescription("Size of the whole editor window as a multiplier (1 = normal). Reopen the editor after changing it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.7f, 2f), Array.Empty<object>())); EditorWindowColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "WindowColor", new Color(0.11f, 0.1f, 0.09f, 0.98f), "Background of the editor window."); EditorPanelColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "PanelColor", new Color(0.17f, 0.15f, 0.13f, 1f), "Background of boxes, icon previews and scrollbar tracks."); EditorTextColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "TextColor", new Color(0.95f, 0.92f, 0.85f, 1f), "Color of normal text."); EditorAccentColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "AccentColor", new Color(1f, 0.78f, 0.25f, 1f), "Window title, headings, group names and scrollbar handles."); EditorButtonColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "ButtonColor", new Color(0.3f, 0.24f, 0.14f, 1f), "Background of buttons and list rows."); EditorButtonHoverColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "ButtonHoverColor", new Color(0.45f, 0.35f, 0.16f, 1f), "Background of a button while the mouse is over it."); EditorButtonTextColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "ButtonTextColor", new Color(1f, 0.95f, 0.8f, 1f), "Text color on buttons and list rows."); EditorSelectedColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "SelectedColor", new Color(0.62f, 0.45f, 0.12f, 1f), "Background of the selected list row and of the chosen option in a toggle bar."); EditorInputColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "InputFieldColor", new Color(0.06f, 0.05f, 0.04f, 1f), "Background of text fields."); EditorInputTextColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "InputTextColor", new Color(1f, 1f, 1f, 1f), "Text color inside text fields."); EditorErrorColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "ErrorTextColor", new Color(1f, 0.45f, 0.4f, 1f), "Color of error messages (for example keys that are not valid)."); EditorTitleBarColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "TitleBarColor", new Color(0.24f, 0.18f, 0.08f, 1f), "Background of the title bar at the top of the editor and icon picker windows."); EditorTitleTextColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Editor", "TitleTextColor", new Color(1f, 0.78f, 0.25f, 1f), "Color of the window name in the title bar."); VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "VerboseLogging", false, "Write detailed diagnostic lines to the BepInEx log (every wheel open, every key sent, startup patches) and a reflection dump of the game's input/radial classes next to entries.json. Leave off for normal play; turn on when reporting a problem. Warnings and errors are always logged."); ShowWarningsInGame = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "ShowWarningsInGame", true, "Also show important warnings (for example an entry with invalid keys) in the top-left corner of the screen."); DumpUiKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Diagnostics", "DumpUiKey", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()), "Only with VerboseLogging on: press this key while a radial wheel (the game's or ours) is open, and the state of the wheel and the list of all visible UI objects is written to BepInEx/config/RadialHub/ui_dump.txt. Used to find the game's info windows (armor, weight, item info) that appear next to the wheel."); Presets.EnsureFolders(); ((BaseUnityPlugin)this).Config.Save(); GameObject val = new GameObject("RadialHub"); Object.DontDestroyOnLoad((Object)(object)val); val.AddComponent<RadialMenu>(); val.AddComponent<RadialEditor>(); if (VerboseLogging.Value) { val.AddComponent<InjectionProbe>(); } Log.LogInfo((object)("RadialHub loaded. Open key: " + ((object)OpenKey.Value/*cast due to .constrained prefix*/).ToString())); if (VerboseLogging.Value) { Diagnostics.Dump(); } Harmony val2 = new Harmony("akarian.radialhub"); int num = 0; Type[] types = typeof(Plugin).Assembly.GetTypes(); foreach (Type type in types) { if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0) { try { val2.CreateClassProcessor(type).Patch(); Verbose("Patched: " + type.Name); } catch (Exception ex) { num++; Log.LogError((object)("PATCH FAILED: " + type.Name + " -> " + ex.Message)); } } } if (num > 0) { Log.LogWarning((object)(num + " patch(es) failed, so the game may have changed. Some RadialHub features (shortcuts, camera lock, native wheel) may not work. Please report the PATCH FAILED lines above.")); } } } [HarmonyPatch(typeof(Input), "GetKeyDown", new Type[] { typeof(KeyCode) })] internal static class Patch_GetKeyDown { private static void Postfix(KeyCode key, ref bool __result) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (!__result && InputInjector.Down.Contains(key)) { __result = true; } } } [HarmonyPatch(typeof(Input), "GetKey", new Type[] { typeof(KeyCode) })] internal static class Patch_GetKey { private static void Postfix(KeyCode key, ref bool __result) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (!__result && InputInjector.Held.Contains(key)) { __result = true; } } } [HarmonyPatch(typeof(Input), "GetKeyUp", new Type[] { typeof(KeyCode) })] internal static class Patch_GetKeyUp { private static void Postfix(KeyCode key, ref bool __result) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (!__result && InputInjector.Up.Contains(key)) { __result = true; } } } [HarmonyPatch(typeof(Player), "TakeInput")] internal static class Patch_TakeInput { private static void Postfix(ref bool __result) { if (RadialMenu.IsOpen || RadialEditor.IsOpen) { __result = false; } } } [HarmonyPatch(typeof(ZInput), "GetMouseDelta")] internal static class Patch_MouseDelta { private static void Postfix(ref Vector2 __result) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (RadialMenu.IsOpen || RadialEditor.IsOpen) { __result = Vector2.zero; } } } [HarmonyPatch] internal static class Patch_MouseScroll { private static MethodInfo method; private static bool Prepare() { try { method = AccessTools.Method(typeof(ZInput), "GetMouseScrollWheel", (Type[])null, (Type[])null); } catch (Exception) { method = null; } if (method != null) { return method.ReturnType == typeof(float); } return false; } private static MethodBase TargetMethod() { return method; } private static void Postfix(ref float __result) { if (RadialEditor.IsOpen) { __result = 0f; } } } [HarmonyPatch] internal static class Patch_PlayerControllerInput { private static List<MethodBase> Find() { List<MethodBase> list = new List<MethodBase>(); try { Type type = AccessTools.TypeByName("PlayerController"); if (type == null) { return list; } MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "TakeInput" && methodInfo.ReturnType == typeof(bool)) { list.Add(methodInfo); } } } catch (Exception) { } return list; } private static bool Prepare() { return Find().Count > 0; } private static IEnumerable<MethodBase> TargetMethods() { return Find(); } private static void Postfix(ref bool __result) { if (RadialEditor.IsOpen) { __result = false; } } } [Serializable] internal class PresetFile { public string icon; public string mod; public List<Entry> entries = new List<Entry>(); } internal class Preset { public string name; public string icon = ""; public string mod = ""; public List<Entry> entries = new List<Entry>(); public bool bundled; public bool unsaved; public string path; } internal static class Presets { public static string PluginDir { get { try { string location = typeof(Plugin).Assembly.Location; if (!string.IsNullOrEmpty(location)) { return Path.GetDirectoryName(location); } } catch (Exception) { } return Paths.PluginPath; } } public static string ResourcesDir => Path.Combine(PluginDir, "resources"); public static string IconsDir => Path.Combine(ResourcesDir, "icons"); public static string BundledDir => Path.Combine(ResourcesDir, "presets"); public static string UserDir => Path.Combine(RadialMenu.Dir, "presets"); public static void EnsureFolders() { string[] array = new string[3] { IconsDir, BundledDir, UserDir }; foreach (string text in array) { try { Directory.CreateDirectory(text); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not create the folder " + text + ": " + ex.Message)); } } } public static List<Preset> LoadAll() { List<Preset> list = new List<Preset>(); HashSet<string> taken = new HashSet<string>(StringComparer.OrdinalIgnoreCase); Read(UserDir, bundled: false, list, taken); Read(BundledDir, bundled: true, list, taken); list.Sort((Preset a, Preset b) => string.Compare(a.name, b.name, StringComparison.OrdinalIgnoreCase)); return list; } private static void Read(string dir, bool bundled, List<Preset> into, HashSet<string> taken) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Invalid comparison between Unknown and I4 if (!Directory.Exists(dir)) { return; } string[] files = Directory.GetFiles(dir, "*.json"); foreach (string path in files) { try { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); if (taken.Add(fileNameWithoutExtension)) { JToken val = JToken.Parse(File.ReadAllText(path)); Preset preset = new Preset { name = fileNameWithoutExtension, bundled = bundled, path = path }; if ((int)val.Type == 2) { preset.entries = val.ToObject<List<Entry>>() ?? new List<Entry>(); } else { PresetFile presetFile = val.ToObject<PresetFile>(); preset.icon = presetFile.icon ?? ""; preset.mod = presetFile.mod ?? ""; preset.entries = presetFile.entries ?? new List<Entry>(); } into.Add(preset); } } catch (Exception ex) { Plugin.Warn("Preset file '" + Path.GetFileName(path) + "' could not be read: " + ex.Message); } } } public static bool Save(Preset p, out string error) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown error = null; try { Directory.CreateDirectory(UserDir); string path = Path.Combine(UserDir, SafeFileName(p.name) + ".json"); PresetFile presetFile = new PresetFile { icon = (p.icon ?? ""), mod = (p.mod ?? ""), entries = p.entries }; File.WriteAllText(path, JsonConvert.SerializeObject((object)presetFile, (Formatting)1, new JsonSerializerSettings { NullValueHandling = (NullValueHandling)1 })); p.path = path; p.unsaved = false; p.bundled = false; return true; } catch (Exception ex) { error = ex.Message; return false; } } public static void Delete(Preset p) { if (!p.bundled && !string.IsNullOrEmpty(p.path) && File.Exists(p.path)) { File.Delete(p.path); } } public static string SafeFileName(string name) { name = (name ?? "").Trim(); char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { name = name.Replace(oldChar, '_'); } if (name.Length != 0) { return name; } return "preset"; } public static List<Entry> Clone(List<Entry> list) { return JsonConvert.DeserializeObject<List<Entry>>(JsonConvert.SerializeObject((object)list)) ?? new List<Entry>(); } public static Entry CloneEntry(Entry e) { return JsonConvert.DeserializeObject<Entry>(JsonConvert.SerializeObject((object)e)); } public static Entry ToGroup(Preset p) { return new Entry { label = p.name, icon = (p.icon ?? ""), children = Clone(p.entries) }; } public static Preset FromGroup(Entry group, IEnumerable<Preset> existing) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (Preset item in existing) { hashSet.Add(item.name); } string text = SafeFileName(string.IsNullOrWhiteSpace(group.label) ? "New preset" : group.label); string text2 = text; int num = 2; while (hashSet.Contains(text2)) { text2 = text + " (" + num + ")"; num++; } return new Preset { name = text2, icon = (group.icon ?? ""), mod = "", entries = Clone(group.children), unsaved = true }; } public static bool HasMod(Preset p) { return !string.IsNullOrWhiteSpace(p.mod); } public static IEnumerable<string> ModIdList(string mod) { string[] array = (mod ?? "").Split(';', ','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { yield return text; } } } public static bool ModsActive(string mod) { bool result = false; string[] array = (mod ?? "").Split(';', ','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { result = true; if (!Chainloader.PluginInfos.ContainsKey(text)) { return false; } } } return result; } public static List<KeyValuePair<string, string>> LoadedMods() { List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); foreach (PluginInfo value in Chainloader.PluginInfos.Values) { list.Add(new KeyValuePair<string, string>(value.Metadata.GUID, value.Metadata.Name)); } list.Sort((KeyValuePair<string, string> a, KeyValuePair<string, string> b) => string.Compare(a.Value, b.Value, StringComparison.OrdinalIgnoreCase)); return list; } } internal static class Ui { public const float PresetsW = 316f; public const float W = 1316f; public const float H = 760f; public const float TitleH = 28f; public const float ScrollbarW = 18f; public const int ButtonRadius = 2; public const int InputRadius = 5; public const int HelpRadius = 5; public const int ThumbRadius = 5; public static readonly Vector2 TitlePos = new Vector2(12f, 5f); public static readonly Rect PresetHeading = new Rect(14f, 36f, 250f, 22f); public static readonly Rect PresetHelp = new Rect(272f, 34f, 20f, 20f); public static readonly Rect ViewAll = new Rect(14f, 64f, 84f, 28f); public static readonly Rect ViewUnsaved = new Rect(102f, 64f, 92f, 28f); public static readonly Rect ViewActive = new Rect(198f, 64f, 106f, 28f); public static readonly Rect PresetSearch = new Rect(14f, 98f, 290f, 30f); public static readonly Rect PresetTree = new Rect(14f, 136f, 290f, 500f); public static readonly Rect NewPreset = new Rect(14f, 700f, 140f, 44f); public static readonly Rect PresetSave = new Rect(162f, 700f, 142f, 44f); public static readonly Rect ListHeading = Sh(14f, 36f, 440f, 22f); public static readonly Rect ListHelp = Sh(462f, 34f, 20f, 20f); public static readonly Rect TabEntries = Sh(14f, 64f, 110f, 28f); public static readonly Rect TabWheels = Sh(128f, 64f, 110f, 28f); public static readonly Rect ListSearch = Sh(14f, 98f, 468f, 30f); public static readonly Rect ListView = Sh(14f, 136f, 468f, 500f); public const float RowH = 33f; public const float RowStep = 35f; public const float RowFirstY = 2f; public const float Indent = 28f; public const float RowTextGap = 4f; public const float RowRightGap = 18f; public const float RowDelBodyGap = 6f; public const float RowDelW = 28f; public static readonly Rect FormHeading = Sh(510f, 36f, 476f, 22f); public static readonly Rect NameLabel = Sh(510f, 70f, 60f, 22f); public static readonly Rect NameHelp = Sh(556f, 68f, 20f, 20f); public static readonly Rect NameBox = Sh(510f, 96f, 476f, 30f); public static readonly Rect IconLabel = Sh(510f, 144f, 60f, 22f); public static readonly Rect IconHelp = Sh(546f, 142f, 20f, 20f); public static readonly Rect IconPreview = Sh(510f, 172f, 72f, 72f); public static readonly Rect ChooseIcon = Sh(596f, 172f, 190f, 34f); public static readonly Rect ClearIcon = Sh(596f, 212f, 190f, 30f); public static readonly Rect GroupInfo = Sh(510f, 270f, 300f, 22f); public static readonly Rect KeysLabel = Sh(510f, 270f, 80f, 22f); public static readonly Rect KeysHelp = Sh(552f, 268f, 20f, 20f); public static readonly Rect KeysBox = Sh(510f, 296f, 216f, 30f); public static readonly Rect RecordKeys = Sh(736f, 296f, 250f, 30f); public static readonly Rect KeysInfo = Sh(510f, 332f, 476f, 38f); public static readonly Rect LinkButton = Sh(510f, 372f, 250f, 30f); public static readonly Rect Unlink = Sh(768f, 372f, 130f, 30f); public static readonly Rect LinkHelp = Sh(906f, 377f, 20f, 20f); public static readonly Rect WhenLabel = Sh(510f, 414f, 200f, 22f); public static readonly Rect WhenHelp = Sh(672f, 412f, 20f, 20f); public static readonly Rect ModeDefault = Sh(510f, 440f, 156f, 32f); public static readonly Rect ModeEarly = Sh(670f, 440f, 150f, 32f); public static readonly Rect ModeLate = Sh(824f, 440f, 163f, 32f); public static readonly Rect MoveButton = Sh(510f, 484f, 270f, 32f); public static readonly Rect CloseLabel = Sh(510f, 484f, 200f, 22f); public static readonly Rect CloseHelp = Sh(590f, 482f, 20f, 20f); public static readonly Rect Close0 = Sh(510f, 510f, 112f, 32f); public static readonly Rect Close1 = Sh(626f, 510f, 112f, 32f); public static readonly Rect Close2 = Sh(742f, 510f, 112f, 32f); public static readonly Rect Close3 = Sh(858f, 510f, 128f, 32f); public static readonly Rect WheelNote = Sh(510f, 552f, 476f, 78f); public static readonly Rect MovePanel = Sh(510f, 524f, 476f, 106f); public static readonly Rect MultiNote = Sh(510f, 70f, 476f, 330f); public static readonly Rect MultiDelete = Sh(510f, 440f, 230f, 32f); public static readonly Rect MultiClear = Sh(750f, 440f, 236f, 32f); public static readonly Rect AddShortcut = Sh(10f, 700f, 150f, 44f); public static readonly Rect AddGroup = Sh(168f, 700f, 130f, 44f); public s