using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Data; using System.Data.SqlTypes; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Dynamic; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Numerics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters; using System.Runtime.Versioning; using System.Security; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ErenshorMods.Input; using Fleck; using Fleck.Handlers; using Fleck.Helpers; using HarmonyLib; using InteractiveMapCompanion.Config; using InteractiveMapCompanion.Entities; using InteractiveMapCompanion.Overlay; using InteractiveMapCompanion.Patches; using InteractiveMapCompanion.Protocol; using InteractiveMapCompanion.Server; using InteractiveMapCompanion.State; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Bson; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq.JsonPath; using Newtonsoft.Json.Schema; using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Utilities; using Steamworks; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("InteractiveMapCompanion")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+a826b99152e16b11b7c8914bcf0596f0d0d7876a")] [assembly: AssemblyProduct("InteractiveMapCompanion")] [assembly: AssemblyTitle("InteractiveMapCompanion")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ErenshorMods.Input { public interface IKeyboardInput { bool IsHeld(KeyCode key); bool WasPressed(KeyCode key); } public sealed class UnityKeyboardInput : IKeyboardInput { public static UnityKeyboardInput Instance { get; } = new UnityKeyboardInput(); private UnityKeyboardInput() { } public bool IsHeld(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return Input.GetKey(key); } public bool WasPressed(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return Input.GetKeyDown(key); } } public static class KeyboardShortcuts { public static bool WasPressed(KeyCode key, IKeyboardInput keyboard) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) return (int)key != 0 && keyboard.WasPressed(key); } public static bool IsHeld(KeyCode key, IKeyboardInput keyboard) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) return (int)key != 0 && keyboard.IsHeld(key); } public static bool IsHeld(IReadOnlyList keys, IKeyboardInput keyboard) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (keys.Count == 0) { return false; } for (int i = 0; i < keys.Count; i++) { if ((int)keys[i] == 0 || !keyboard.IsHeld(keys[i])) { return false; } } return true; } public static bool IsHeld(KeyCode mainKey, IReadOnlyList modifiers, IKeyboardInput keyboard) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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) if ((int)mainKey == 0 || !keyboard.IsHeld(mainKey)) { return false; } for (int i = 0; i < modifiers.Count; i++) { if ((int)modifiers[i] == 0 || !keyboard.IsHeld(modifiers[i])) { return false; } } return true; } } } namespace InteractiveMapCompanion { public interface IModLogger { void LogDebug(string message); void LogInfo(string message); void LogWarning(string message); void LogError(string message); } public sealed class InteractiveMapRuntime { private readonly GameObject _owner; private readonly IModConfig _config; private readonly IModLogger _log; private Harmony? _harmony; private InteractiveMapCompanion.Server.IWebSocketServer? _server; private IBroadcastLoop? _broadcastLoop; private MapOverlay? _overlay; private bool _started; private bool _stopped; private bool _applicationQuitting; public InteractiveMapRuntime(GameObject owner, IModConfig config, IModLogger log) { _owner = owner; _config = config; _log = log; } public void Start() { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown if (_started || _stopped) { return; } _started = true; try { EntityFinder finder = new EntityFinder(); EntityClassifier classifier = new EntityClassifier(); EntityExtractor extractor = new EntityExtractor(); EntityTrackerAdapter entityTracker = new EntityTrackerAdapter(finder, classifier, extractor, (EntityType _) => true); _server = new InteractiveMapCompanion.Server.WebSocketServer(_config, _log); _server.Start(); _broadcastLoop = new BroadcastLoop(entityTracker, _server, _config, delegate(string message) { if (_config.ModLogLevel == InteractiveMapCompanion.Config.LogLevel.Debug) { _log.LogDebug(message); } }); SceneManager.sceneLoaded += OnSceneLoaded; IBroadcastLoop? broadcastLoop = _broadcastLoop; Scene activeScene = SceneManager.GetActiveScene(); broadcastLoop.OnSceneLoaded(((Scene)(ref activeScene)).name); _overlay = _owner.AddComponent(); _overlay.Config = _config; _overlay.Log = _log; _harmony = new Harmony("wow-much.interactive-map-companion"); _harmony.PatchAll(); _log.LogInfo("Interactive Map Companion v2026.718.0 loaded"); } catch (Exception arg) { _log.LogError(string.Format("Failed to start {0}: {1}", "Interactive Map Companion", arg)); Stop(); } } public void Tick(float deltaTime, bool togglePressed) { if (_started && !_stopped) { _overlay?.HandleShortcut(togglePressed); _broadcastLoop?.Tick(deltaTime); } } public void NotifyApplicationQuitting() { if (!_applicationQuitting) { _applicationQuitting = true; _overlay?.NotifyApplicationQuitting(); } } public void Stop() { if (!_stopped) { _stopped = true; _broadcastLoop?.Stop(); _server?.Stop(); _overlay?.Stop(); if ((Object)(object)_overlay != (Object)null) { Object.Destroy((Object)(object)_overlay); } SceneManager.sceneLoaded -= OnSceneLoaded; Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } MapKeyPatches.SuppressMapKey = false; CharSelectManagerPatch.ResetPlayerTyping(); _overlay = null; _broadcastLoop = null; _server = null; _harmony = null; } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!_stopped) { _broadcastLoop?.OnSceneLoaded(((Scene)(ref scene)).name); if (((Scene)(ref scene)).name != "LoadScene") { CharSelectManagerPatch.ResetPlayerTyping(); } } } } internal static class PluginInfo { public const string GUID = "wow-much.interactive-map-companion"; public const string Name = "Interactive Map Companion"; public const string Version = "2026.718.0"; } [BepInPlugin("wow-much.interactive-map-companion", "Interactive Map Companion", "2026.718.0")] public sealed class Plugin : BaseUnityPlugin { private sealed class BepModLogger : IModLogger { private readonly ManualLogSource _logger; internal BepModLogger(ManualLogSource logger) { _logger = logger; } public void LogDebug(string message) { _logger.LogDebug((object)message); } public void LogInfo(string message) { _logger.LogInfo((object)message); } public void LogWarning(string message) { _logger.LogWarning((object)message); } public void LogError(string message) { _logger.LogError((object)message); } } private sealed class BepModConfig : ModConfigBase { private readonly ConfigEntry _port; private readonly ConfigEntry _updateInterval; private readonly ConfigEntry _webSocketLogLevel; private readonly ConfigEntry _modLogLevel; private readonly ConfigEntry _enableOverlay; private readonly ConfigEntry _toggleKey; private readonly ConfigEntry _anchorX; private readonly ConfigEntry _anchorY; private readonly ConfigEntry _overlayWidth; private readonly ConfigEntry _overlayHeight; private readonly ConfigEntry _resetToDefaults; public override int Port => _port.Value; public override int UpdateInterval => _updateInterval.Value; public override InteractiveMapCompanion.Config.LogLevel WebSocketLogLevel => _webSocketLogLevel.Value; public override InteractiveMapCompanion.Config.LogLevel ModLogLevel => _modLogLevel.Value; public override bool EnableOverlay => _enableOverlay.Value; public override KeyCode ToggleKey => _toggleKey.Value; public override float AnchorX { get { return _anchorX.Value; } set { _anchorX.Value = value; } } public override float AnchorY { get { return _anchorY.Value; } set { _anchorY.Value = value; } } public override int OverlayWidth { get { return _overlayWidth.Value; } set { _overlayWidth.Value = value; } } public override int OverlayHeight { get { return _overlayHeight.Value; } set { _overlayHeight.Value = value; } } public override bool ResetToDefaults { get { return _resetToDefaults.Value; } set { _resetToDefaults.Value = value; } } internal BepModConfig(ConfigFile config) { _port = config.Bind("Server", "Port", 18585, "WebSocket server port. Clients connect to ws://localhost:{port}"); _updateInterval = config.Bind("Server", "UpdateInterval", 100, "Interval in milliseconds between state broadcasts to clients"); _webSocketLogLevel = config.Bind("Logging", "WebSocketLogLevel", InteractiveMapCompanion.Config.LogLevel.Warning, "Log level for WebSocket library. Debug shows all messages (verbose), Warning shows only issues (recommended)."); _modLogLevel = config.Bind("Logging", "ModLogLevel", InteractiveMapCompanion.Config.LogLevel.Info, "Log level for the mod itself. Debug shows detailed diagnostics, Info shows important events (recommended)."); _enableOverlay = config.Bind("Overlay", "EnableOverlay", true, "Show the interactive map as an in-game overlay panel (requires Steam)"); _toggleKey = config.Bind("Overlay", "ToggleKey", (KeyCode)109, "Key to show/hide the in-game map overlay"); _anchorX = config.Bind("Overlay", "AnchorX", -1f, "Normalized horizontal anchor for the overlay panel (0 = left edge, 1 = right edge). -1 = auto (centred, computed on first run)"); _anchorY = config.Bind("Overlay", "AnchorY", -1f, "Normalized vertical anchor for the overlay panel (0 = bottom, 1 = top). -1 = auto (centred, computed on first run)"); _overlayWidth = config.Bind("Overlay", "Width", 0, "Width of the in-game map overlay in pixels. 0 = auto (80% of screen width, computed on first run)"); _overlayHeight = config.Bind("Overlay", "Height", 0, "Height of the in-game map overlay in pixels. 0 = auto (80% of screen height, computed on first run)"); _resetToDefaults = config.Bind("Overlay", "ResetToDefaults", false, "Set to true to reset size and position to auto-computed defaults on next game launch. Resets itself to false automatically."); } } private InteractiveMapRuntime? _runtime; private BepModConfig? _config; private void Awake() { ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; _config = new BepModConfig(((BaseUnityPlugin)this).Config); BepModLogger log = new BepModLogger(((BaseUnityPlugin)this).Logger); _runtime = new InteractiveMapRuntime(((Component)this).gameObject, _config, log); _runtime.Start(); } private void Update() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) bool togglePressed = _config != null && KeyboardShortcuts.WasPressed(_config.ToggleKey, UnityKeyboardInput.Instance); _runtime?.Tick(Time.deltaTime, togglePressed); } private void OnApplicationQuit() { _runtime?.NotifyApplicationQuitting(); } private void OnDestroy() { _runtime?.Stop(); _runtime = null; _config = null; } } } namespace InteractiveMapCompanion.State { public sealed class BroadcastLoop : IBroadcastLoop { private readonly IEntityTracker _entityTracker; private readonly InteractiveMapCompanion.Server.IWebSocketServer _server; private readonly IModConfig _config; private readonly Action? _log; private float _elapsed; private string _currentZone = ""; private bool _stopped; public BroadcastLoop(IEntityTracker entityTracker, InteractiveMapCompanion.Server.IWebSocketServer server, IModConfig config, Action? log = null) { _entityTracker = entityTracker; _server = server; _config = config; _log = log; } public void Tick(float deltaTime) { if (!_stopped) { _elapsed += deltaTime; float num = (float)_config.UpdateInterval / 1000f; if (!(_elapsed < num)) { _elapsed = 0f; BroadcastState(); } } } public void OnSceneLoaded(string newZone) { if (!_stopped) { string currentZone = _currentZone; _currentZone = newZone; if (!string.IsNullOrEmpty(currentZone) && currentZone != newZone) { SendZoneChange(currentZone, newZone); } BroadcastState(); } } public void Stop() { if (!_stopped) { _stopped = true; _elapsed = 0f; _currentZone = ""; } } private void BroadcastState() { if (_stopped || _server.ClientCount == 0) { return; } try { IReadOnlyList trackedEntities = _entityTracker.GetTrackedEntities(); StateUpdateMessage message = StateUpdateMessage.Create(_currentZone, trackedEntities.ToArray()); string message2 = MessageSerializer.Serialize(message); _server.Broadcast(message2); } catch (Exception ex) { _log?.Invoke("Error broadcasting state: " + ex.Message); } } private void SendZoneChange(string previousZone, string newZone) { if (_stopped || _server.ClientCount == 0) { return; } try { ZoneChangeMessage message = ZoneChangeMessage.Create(previousZone, newZone); string message2 = MessageSerializer.Serialize(message); _server.Broadcast(message2); _log?.Invoke("Zone changed: " + previousZone + " -> " + newZone); } catch (Exception ex) { _log?.Invoke("Error sending zone change: " + ex.Message); } } } public interface IBroadcastLoop { void Tick(float deltaTime); void Stop(); void OnSceneLoaded(string newZone); } } namespace InteractiveMapCompanion.Server { public interface IWebSocketServer : IDisposable { int ClientCount { get; } void Start(); void Stop(); void Broadcast(string message); } public sealed class WebSocketServer : IWebSocketServer, IDisposable { private readonly IModConfig _config; private readonly IModLogger _logger; private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); private readonly object _lifecycleGate = new object(); private Fleck.WebSocketServer? _server; private Action? _previousFleckLogAction; private Action? _fleckLogAction; private bool _stopped; private bool _disposed; public int ClientCount => _clients.Count; public WebSocketServer(IModConfig config, IModLogger logger) { _config = config; _logger = logger; ConfigureFleckLogging(); } public void Start() { int port = _config.Port; string text = $"ws://0.0.0.0:{port}"; lock (_lifecycleGate) { if (_disposed || _server != null) { return; } try { Fleck.WebSocketServer webSocketServer = new Fleck.WebSocketServer(text); _stopped = false; _server = webSocketServer; webSocketServer.Start(ConfigureSocket); _logger.LogInfo("WebSocket server started on " + text); } catch (Exception ex) { _server = null; _stopped = true; _logger.LogError($"Failed to start WebSocket server on port {port}: {ex.Message}"); _logger.LogDebug(ex.ToString()); } } } public void Stop() { Fleck.WebSocketServer server; IWebSocketConnection[] array; lock (_lifecycleGate) { if (_stopped && _server == null && _clients.IsEmpty) { return; } _stopped = true; server = _server; _server = null; array = _clients.Values.ToArray(); _clients.Clear(); } IWebSocketConnection[] array2 = array; foreach (IWebSocketConnection webSocketConnection in array2) { try { webSocketConnection.Close(); } catch { } } try { server?.Dispose(); } catch (Exception ex) { _logger.LogDebug("WebSocket server disposal failed: " + ex.Message); } if (server != null) { _logger.LogInfo("WebSocket server stopped"); } } public void Broadcast(string message) { if (_disposed || _stopped) { return; } foreach (KeyValuePair client in _clients) { client.Deconstruct(out var key, out var value); Guid guid = key; IWebSocketConnection webSocketConnection = value; try { if (webSocketConnection.IsAvailable && !_stopped && !_disposed) { webSocketConnection.Send(message); } else { _clients.TryRemove(guid, out value); } } catch (Exception ex) { _logger.LogWarning($"Failed to send to client {guid}: {ex.Message}"); _clients.TryRemove(guid, out value); } } } public void Dispose() { lock (_lifecycleGate) { if (_disposed) { return; } _disposed = true; } Stop(); RestoreFleckLogging(); } private void ConfigureSocket(IWebSocketConnection socket) { socket.OnOpen = delegate { OnClientConnected(socket); }; socket.OnClose = delegate { OnClientDisconnected(socket); }; socket.OnError = delegate(Exception ex) { OnClientError(socket, ex); }; socket.OnMessage = delegate(string message) { OnClientMessage(socket, message); }; } private void OnClientConnected(IWebSocketConnection socket) { bool flag; lock (_lifecycleGate) { flag = _disposed || _stopped; if (!flag) { _clients[socket.ConnectionInfo.Id] = socket; } } if (flag) { try { socket.Close(); return; } catch { return; } } _logger.LogInfo($"Client connected: {socket.ConnectionInfo.ClientIpAddress} (total: {ClientCount})"); SendHandshake(socket); } private void OnClientDisconnected(IWebSocketConnection socket) { _clients.TryRemove(socket.ConnectionInfo.Id, out IWebSocketConnection _); if (!_disposed) { _logger.LogInfo($"Client disconnected: {socket.ConnectionInfo.ClientIpAddress} (total: {ClientCount})"); } } private void OnClientError(IWebSocketConnection socket, Exception ex) { if (!_disposed) { _logger.LogWarning("Client error (" + socket.ConnectionInfo.ClientIpAddress + "): " + ex.Message); } _clients.TryRemove(socket.ConnectionInfo.Id, out IWebSocketConnection _); } private void OnClientMessage(IWebSocketConnection socket, string message) { if (!_disposed && !_stopped) { _logger.LogDebug("Received message from " + socket.ConnectionInfo.ClientIpAddress + ": " + message); } } private void SendHandshake(IWebSocketConnection socket) { string currentZone = GetCurrentZone(); string[] capabilities = _config.GetCapabilities(); HandshakeMessage message = HandshakeMessage.Create(currentZone, capabilities); string message2 = MessageSerializer.Serialize(message); try { if (!_disposed && !_stopped) { socket.Send(message2); } } catch (Exception ex) { _logger.LogWarning("Failed to send handshake: " + ex.Message); } } private static string GetCurrentZone() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) try { Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name; } catch { return ""; } } private void ConfigureFleckLogging() { _previousFleckLogAction = FleckLog.LogAction; _fleckLogAction = delegate(Fleck.LogLevel level, string message, Exception ex) { InteractiveMapCompanion.Config.LogLevel webSocketLogLevel = _config.WebSocketLogLevel; if (1 == 0) { } bool flag = level switch { Fleck.LogLevel.Debug => webSocketLogLevel == InteractiveMapCompanion.Config.LogLevel.Debug, Fleck.LogLevel.Info => webSocketLogLevel == InteractiveMapCompanion.Config.LogLevel.Debug || webSocketLogLevel == InteractiveMapCompanion.Config.LogLevel.Info, Fleck.LogLevel.Warn => webSocketLogLevel == InteractiveMapCompanion.Config.LogLevel.Debug || webSocketLogLevel == InteractiveMapCompanion.Config.LogLevel.Info || webSocketLogLevel == InteractiveMapCompanion.Config.LogLevel.Warning, Fleck.LogLevel.Error => true, _ => false, }; if (1 == 0) { } if (flag) { switch (level) { case Fleck.LogLevel.Debug: _logger.LogDebug("[Fleck] " + message); break; case Fleck.LogLevel.Info: _logger.LogInfo("[Fleck] " + message); break; case Fleck.LogLevel.Warn: _logger.LogWarning("[Fleck] " + message); break; case Fleck.LogLevel.Error: _logger.LogError("[Fleck] " + message); if (ex != null) { _logger.LogDebug(ex.ToString()); } break; } } }; FleckLog.LogAction = _fleckLogAction; } private void RestoreFleckLogging() { if (_fleckLogAction != null && (object)FleckLog.LogAction == _fleckLogAction) { FleckLog.LogAction = _previousFleckLogAction; } _fleckLogAction = null; _previousFleckLogAction = null; } } } namespace InteractiveMapCompanion.Protocol { public sealed class HandshakeMessage { public string Type { get; } public string ProtocolVersion { get; } public string ModVersion { get; } public string Zone { get; } public string[] Capabilities { get; } public HandshakeMessage(string Type, string ProtocolVersion, string ModVersion, string Zone, string[] Capabilities) { this.Type = Type; this.ProtocolVersion = ProtocolVersion; this.ModVersion = ModVersion; this.Zone = Zone; this.Capabilities = Capabilities; } public static HandshakeMessage Create(string zone, string[] capabilities) { return new HandshakeMessage("handshake", "0.2.0", "2026.718.0", zone, capabilities); } } public sealed class StateUpdateMessage { public string Type { get; } public string Zone { get; } public long Timestamp { get; } public EntityData[] Entities { get; } public StateUpdateMessage(string Type, string Zone, long Timestamp, EntityData[] Entities) { this.Type = Type; this.Zone = Zone; this.Timestamp = Timestamp; this.Entities = Entities; } public static StateUpdateMessage Create(string zone, EntityData[] entities) { return new StateUpdateMessage("stateUpdate", zone, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), entities); } } public sealed class ZoneChangeMessage { public string Type { get; } public string PreviousZone { get; } public string Zone { get; } public long Timestamp { get; } public ZoneChangeMessage(string Type, string PreviousZone, string Zone, long Timestamp) { this.Type = Type; this.PreviousZone = PreviousZone; this.Zone = Zone; this.Timestamp = Timestamp; } public static ZoneChangeMessage Create(string previousZone, string zone) { return new ZoneChangeMessage("zoneChange", previousZone, zone, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); } } public static class MessageSerializer { private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() }, NullValueHandling = NullValueHandling.Ignore, Formatting = Newtonsoft.Json.Formatting.None }; public static string Serialize(T message) { return JsonConvert.SerializeObject(message, Settings); } public static T? Deserialize(string json) { return JsonConvert.DeserializeObject(json, Settings); } } public static class ProtocolVersion { public const string Current = "0.2.0"; } } namespace InteractiveMapCompanion.Patches { [HarmonyPatch(typeof(CharSelectManager), "Update")] internal static class CharSelectManagerPatch { internal static bool _weSetPlayerTyping; internal static void ResetPlayerTyping() { if (_weSetPlayerTyping) { GameData.PlayerTyping = false; _weSetPlayerTyping = false; } } [HarmonyPostfix] private static void Postfix(CharSelectManager __instance) { if (!((Object)(object)EventSystem.current == (Object)null)) { int num; if (__instance.CharCreate.activeSelf) { GameObject currentSelectedGameObject = EventSystem.current.currentSelectedGameObject; num = ((((currentSelectedGameObject != null) ? ((Object)currentSelectedGameObject).name : null) == "InputField (TMP)") ? 1 : 0); } else { num = 0; } bool flag = (byte)num != 0; if (flag && !_weSetPlayerTyping) { GameData.PlayerTyping = true; _weSetPlayerTyping = true; } else if (!flag && _weSetPlayerTyping) { GameData.PlayerTyping = false; _weSetPlayerTyping = false; } } } } internal static class MapKeyPatches { internal static bool SuppressMapKey; internal static bool GetKeyDownUnlessSuppressed(KeyCode key) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) return !SuppressMapKey && Input.GetKeyDown(key); } } [HarmonyPatch(typeof(HotkeyManager), "OpenCloseMap")] internal static class OpenCloseMapPatch { [HarmonyPrefix] private static bool Prefix() { return !MapKeyPatches.SuppressMapKey; } } [HarmonyPatch(typeof(Minimap), "Update")] internal static class MinimapUpdatePatch { private static readonly MethodInfo _getKeyDown = typeof(Input).GetMethod("GetKeyDown", new Type[1] { typeof(KeyCode) }); private static readonly FieldInfo _inputManagerMap = typeof(InputManager).GetField("Map"); private static readonly MethodInfo _helper = typeof(MapKeyPatches).GetMethod("GetKeyDownUnlessSuppressed", BindingFlags.Static | BindingFlags.NonPublic); [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown List list = new List(instructions); bool flag = false; for (int i = 0; i < list.Count - 1; i++) { bool flag2 = list[i].opcode == OpCodes.Ldsfld && list[i].operand is FieldInfo fieldInfo && fieldInfo == _inputManagerMap; bool flag3 = list[i + 1].opcode == OpCodes.Call && list[i + 1].operand is MethodInfo methodInfo && methodInfo == _getKeyDown; if (flag2 && flag3) { list[i + 1] = new CodeInstruction(OpCodes.Call, (object)_helper); flag = true; break; } } if (!flag) { throw new InvalidOperationException("[InteractiveMapCompanion] MinimapUpdatePatch: could not find Input.GetKeyDown(InputManager.Map) in Minimap.Update(). The game may have been updated — please update the transpiler."); } return list; } } } namespace InteractiveMapCompanion.Overlay { internal sealed class BrowserManager : IDisposable { internal const string MapUrl = "https://erenshor.compendiums.org/map"; private const string SameTabNavigationScript = "(function () {\n if (window.__erenshorSameTab) return;\n window.__erenshorSameTab = true;\n window.open = function (url) {\n if (url) window.location.href = url;\n return null;\n };\n document.addEventListener('click', function (e) {\n if (e.defaultPrevented || e.button !== 0) return;\n var link = e.target && e.target.closest ? e.target.closest('a[href]') : null;\n if (!link) return;\n var target = (link.getAttribute('target') || '').toLowerCase();\n if (target !== '_blank' && target !== '_new') return;\n e.preventDefault();\n window.location.href = link.href;\n });\n })();"; private readonly IModLogger _log; private readonly Action _onPaint; private HHTMLBrowser _browser; private bool _browserReady; private bool _initialized; private bool _visible; private bool _disposed; private bool _appIsQuitting; private bool _canGoBack; private bool _canGoForward; private string? _pendingNavigationUrl; private Callback? _paintCallback; private Callback? _startRequestCallback; private Callback? _openLinkCallback; private Callback? _newWindowCallback; private Callback? _finishedRequestCallback; private Callback? _jsAlertCallback; private Callback? _jsConfirmCallback; private Callback? _fileOpenDialogCallback; private Callback? _historyCallback; private CallResult? _browserReadyResult; internal bool IsReady => _browserReady; internal bool CanGoBack => _browserReady && _canGoBack; internal bool CanGoForward => _browserReady && _canGoForward; internal HHTMLBrowser BrowserHandle => _browser; internal event Action? NavigationStateChanged; internal BrowserManager(IModLogger log, Action onPaint) { _log = log; _onPaint = onPaint; } internal void NotifyAppIsQuitting() { _appIsQuitting = true; } internal bool Initialize(int width, int height, string url) { if (_disposed || _appIsQuitting) { return false; } if (_initialized) { return true; } if (!SteamHTMLSurface.Init()) { _log.LogWarning("[Overlay] SteamHTMLSurface.Init() failed — map overlay disabled."); return false; } _initialized = true; RegisterCallbacks(); CreateBrowser(width, height, url); return true; } internal void SetVisible(bool visible) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) _visible = visible; if (!_disposed && !_appIsQuitting && _browserReady) { SteamHTMLSurface.SetBackgroundMode(_browser, !visible); } } internal void SetSize(int width, int height) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting && _browserReady) { SteamHTMLSurface.SetSize(_browser, (uint)width, (uint)height); } } internal void LoadUrl(string url) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting && _browserReady) { SteamHTMLSurface.LoadURL(_browser, url, (string)null); } } internal void GoBack() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting && CanGoBack) { SteamHTMLSurface.GoBack(_browser); } } internal void ProcessPendingNavigation() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting && _browserReady && _pendingNavigationUrl != null) { string pendingNavigationUrl = _pendingNavigationUrl; _pendingNavigationUrl = null; _log.LogInfo("[Overlay] Opening external link: " + pendingNavigationUrl); SteamHTMLSurface.LoadURL(_browser, pendingNavigationUrl, (string)null); } } internal void GoForward() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting && CanGoForward) { SteamHTMLSurface.GoForward(_browser); } } internal void LoadMap() { LoadUrl("https://erenshor.compendiums.org/map"); } private void RegisterCallbacks() { _paintCallback = Callback.Create((DispatchDelegate)OnNeedsPaint); _startRequestCallback = Callback.Create((DispatchDelegate)OnStartRequest); _openLinkCallback = Callback.Create((DispatchDelegate)OnOpenLinkInNewTab); _newWindowCallback = Callback.Create((DispatchDelegate)OnNewWindow); _finishedRequestCallback = Callback.Create((DispatchDelegate)OnFinishedRequest); _jsAlertCallback = Callback.Create((DispatchDelegate)OnJSAlert); _jsConfirmCallback = Callback.Create((DispatchDelegate)OnJSConfirm); _fileOpenDialogCallback = Callback.Create((DispatchDelegate)OnFileOpenDialog); _historyCallback = Callback.Create((DispatchDelegate)OnHistoryChanged); } private void CreateBrowser(int width, int height, string url) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) SteamAPICall_t val = SteamHTMLSurface.CreateBrowser((string)null, (string)null); _browserReadyResult = CallResult.Create((APIDispatchDelegate)delegate(HTML_BrowserReady_t param, bool ioFailure) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) OnBrowserReady(param, ioFailure, width, height, url); }); _browserReadyResult.Set(val, (APIDispatchDelegate)null); _log.LogInfo("[Overlay] Browser creation requested, waiting for ready callback..."); } private void OnBrowserReady(HTML_BrowserReady_t param, bool ioFailure, int width, int height, string url) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting) { if (ioFailure) { _log.LogWarning("[Overlay] Browser creation failed (IO failure) — map overlay disabled."); return; } _browser = param.unBrowserHandle; _browserReady = true; _canGoBack = false; _canGoForward = false; SteamHTMLSurface.SetSize(_browser, (uint)width, (uint)height); SteamHTMLSurface.LoadURL(_browser, url, (string)null); SteamHTMLSurface.SetBackgroundMode(_browser, !_visible); this.NavigationStateChanged?.Invoke(); _log.LogInfo($"[Overlay] Browser ready (handle={_browser}), surface={width}x{height}, loading {url}"); } } private void OnNeedsPaint(HTML_NeedsPaint_t param) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting && _visible && !(param.unBrowserHandle != _browser)) { _onPaint(param); } } private void OnStartRequest(HTML_StartRequest_t param) { //IL_0018: 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) if (!_disposed && !_appIsQuitting) { SteamHTMLSurface.AllowStartRequest(param.unBrowserHandle, true); } } private void OnOpenLinkInNewTab(HTML_OpenLinkInNewTab_t param) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting && !(param.unBrowserHandle != _browser)) { QueueExternalNavigation(param.pchURL); } } private void OnNewWindow(HTML_NewWindow_t param) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting && !(param.unBrowserHandle != _browser)) { QueueExternalNavigation(param.pchURL); } } private void QueueExternalNavigation(string url) { if (!string.IsNullOrWhiteSpace(url)) { _pendingNavigationUrl = url; } } private void OnFinishedRequest(HTML_FinishedRequest_t param) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting && !(param.unBrowserHandle != _browser)) { SteamHTMLSurface.ExecuteJavascript(_browser, "(function () {\n if (window.__erenshorSameTab) return;\n window.__erenshorSameTab = true;\n window.open = function (url) {\n if (url) window.location.href = url;\n return null;\n };\n document.addEventListener('click', function (e) {\n if (e.defaultPrevented || e.button !== 0) return;\n var link = e.target && e.target.closest ? e.target.closest('a[href]') : null;\n if (!link) return;\n var target = (link.getAttribute('target') || '').toLowerCase();\n if (target !== '_blank' && target !== '_new') return;\n e.preventDefault();\n window.location.href = link.href;\n });\n })();"); } } private void OnHistoryChanged(HTML_CanGoBackAndForward_t param) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting && !(param.unBrowserHandle != _browser) && (_canGoBack != param.bCanGoBack || _canGoForward != param.bCanGoForward)) { _canGoBack = param.bCanGoBack; _canGoForward = param.bCanGoForward; this.NavigationStateChanged?.Invoke(); } } private void OnJSAlert(HTML_JSAlert_t param) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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_0038: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting) { if (param.unBrowserHandle == _browser) { _log.LogDebug("[Overlay] JS alert: " + param.pchMessage); } SteamHTMLSurface.JSDialogResponse(param.unBrowserHandle, true); } } private void OnJSConfirm(HTML_JSConfirm_t param) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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_0038: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !_appIsQuitting) { if (param.unBrowserHandle == _browser) { _log.LogDebug("[Overlay] JS confirm: " + param.pchMessage); } SteamHTMLSurface.JSDialogResponse(param.unBrowserHandle, true); } } private void OnFileOpenDialog(HTML_FileOpenDialog_t param) { //IL_0018: 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) if (!_disposed && !_appIsQuitting) { SteamHTMLSurface.FileLoadDialogResponse(param.unBrowserHandle, IntPtr.Zero); } } public void Dispose() { //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) if (_disposed) { return; } _disposed = true; _paintCallback?.Dispose(); _startRequestCallback?.Dispose(); _openLinkCallback?.Dispose(); _newWindowCallback?.Dispose(); _finishedRequestCallback?.Dispose(); _jsAlertCallback?.Dispose(); _jsConfirmCallback?.Dispose(); _fileOpenDialogCallback?.Dispose(); _historyCallback?.Dispose(); _browserReadyResult?.Dispose(); if (!_appIsQuitting) { if (_browserReady) { SteamHTMLSurface.RemoveBrowser(_browser); } if (_initialized) { SteamHTMLSurface.Shutdown(); } } _browserReady = false; _initialized = false; _canGoBack = false; _canGoForward = false; _pendingNavigationUrl = null; _browser = default(HHTMLBrowser); } } internal sealed class BrowserNavigationToolbar { private const int ControlWidth = 240; private const int ControlHeight = 32; private const int TopInset = 10; private const int BorderThickness = 1; private const int HorizontalPadding = 4; private const int ButtonGap = 4; private const int BackButtonWidth = 72; private const int ForwardButtonWidth = 88; private const int MapButtonWidth = 62; private const int LabelSize = 13; private static readonly Color ToolbarBorder = Color32.op_Implicit(new Color32((byte)67, (byte)81, (byte)95, (byte)170)); private static readonly Color ToolbarBackground = Color32.op_Implicit(new Color32((byte)8, (byte)12, (byte)18, (byte)220)); private static readonly Color ButtonNormal = Color32.op_Implicit(new Color32((byte)25, (byte)33, (byte)43, (byte)230)); private static readonly Color MapButtonNormal = Color32.op_Implicit(new Color32((byte)25, (byte)53, (byte)64, (byte)240)); private static readonly Color ButtonHover = Color32.op_Implicit(new Color32((byte)26, (byte)73, (byte)86, byte.MaxValue)); private static readonly Color ButtonPressed = Color32.op_Implicit(new Color32((byte)15, (byte)47, (byte)58, byte.MaxValue)); private static readonly Color ButtonFocused = Color32.op_Implicit(new Color32((byte)24, (byte)58, (byte)70, byte.MaxValue)); private static readonly Color ButtonDisabled = Color32.op_Implicit(new Color32((byte)15, (byte)21, (byte)28, (byte)175)); private static readonly Color LabelNormal = Color32.op_Implicit(new Color32((byte)232, (byte)239, (byte)246, byte.MaxValue)); private static readonly Color LabelDisabled = Color32.op_Implicit(new Color32((byte)135, (byte)148, (byte)161, byte.MaxValue)); private readonly Button _backButton; private readonly Button _forwardButton; private readonly Button _mapButton; private readonly Text _backLabel; private readonly Text _forwardLabel; private readonly Text _mapLabel; internal RectTransform RootRect { get; } internal BrowserNavigationToolbar(RectTransform parent, Action goBack, Action goForward, Action loadMap) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_008e: 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) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Expected O, but got Unknown //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("MapBrowserToolbar"); val.transform.SetParent((Transform)(object)parent, false); RootRect = val.AddComponent(); RootRect.anchorMin = new Vector2(0.5f, 1f); RootRect.anchorMax = new Vector2(0.5f, 1f); RootRect.pivot = new Vector2(0.5f, 1f); RootRect.anchoredPosition = new Vector2(0f, -10f); RootRect.sizeDelta = new Vector2(240f, 32f); Image val2 = val.AddComponent(); ((Graphic)val2).color = ToolbarBorder; ((Graphic)val2).raycastTarget = true; GameObject val3 = new GameObject("Background"); val3.transform.SetParent((Transform)(object)RootRect, false); RectTransform val4 = val3.AddComponent(); val4.anchorMin = Vector2.zero; val4.anchorMax = Vector2.one; val4.offsetMin = new Vector2(1f, 1f); val4.offsetMax = new Vector2(-1f, -1f); Image val5 = val3.AddComponent(); ((Graphic)val5).color = ToolbarBackground; ((Graphic)val5).raycastTarget = false; float num = 5f; _backButton = CreateButton(RootRect, "Back", "< Back", num, 72f, ButtonNormal, goBack, out _backLabel); num += 76f; _forwardButton = CreateButton(RootRect, "Forward", "Forward >", num, 88f, ButtonNormal, goForward, out _forwardLabel); num += 92f; _mapButton = CreateButton(RootRect, "Map", "Map", num, 62f, MapButtonNormal, loadMap, out _mapLabel); SetState(browserReady: false, canGoBack: false, canGoForward: false); } internal void SetState(bool browserReady, bool canGoBack, bool canGoForward) { SetInteractable(_backButton, _backLabel, browserReady && canGoBack); SetInteractable(_forwardButton, _forwardLabel, browserReady && canGoForward); SetInteractable(_mapButton, _mapLabel, browserReady); } private static Button CreateButton(RectTransform parent, string name, string label, float x, float width, Color normalColor, Action action, out Text text) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Expected O, but got Unknown //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Expected O, but got Unknown //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("MapBrowser" + name + "Button"); val.transform.SetParent((Transform)(object)parent, false); RectTransform val2 = val.AddComponent(); val2.anchorMin = new Vector2(0f, 0.5f); val2.anchorMax = new Vector2(0f, 0.5f); val2.pivot = new Vector2(0f, 0.5f); val2.anchoredPosition = new Vector2(x, 0f); val2.sizeDelta = new Vector2(width, 30f); Image val3 = val.AddComponent(); ((Graphic)val3).color = normalColor; Button val4 = val.AddComponent