Decompiled source of InteractiveMapCompanion v2026.718.0
plugins/InteractiveMapCompanion/InteractiveMapCompanion.dll
Decompiled 2 weeks 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.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<KeyCode> 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<KeyCode> 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<MapOverlay>(); _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<int> _port; private readonly ConfigEntry<int> _updateInterval; private readonly ConfigEntry<InteractiveMapCompanion.Config.LogLevel> _webSocketLogLevel; private readonly ConfigEntry<InteractiveMapCompanion.Config.LogLevel> _modLogLevel; private readonly ConfigEntry<bool> _enableOverlay; private readonly ConfigEntry<KeyCode> _toggleKey; private readonly ConfigEntry<float> _anchorX; private readonly ConfigEntry<float> _anchorY; private readonly ConfigEntry<int> _overlayWidth; private readonly ConfigEntry<int> _overlayHeight; private readonly ConfigEntry<bool> _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<int>("Server", "Port", 18585, "WebSocket server port. Clients connect to ws://localhost:{port}"); _updateInterval = config.Bind<int>("Server", "UpdateInterval", 100, "Interval in milliseconds between state broadcasts to clients"); _webSocketLogLevel = config.Bind<InteractiveMapCompanion.Config.LogLevel>("Logging", "WebSocketLogLevel", InteractiveMapCompanion.Config.LogLevel.Warning, "Log level for WebSocket library. Debug shows all messages (verbose), Warning shows only issues (recommended)."); _modLogLevel = config.Bind<InteractiveMapCompanion.Config.LogLevel>("Logging", "ModLogLevel", InteractiveMapCompanion.Config.LogLevel.Info, "Log level for the mod itself. Debug shows detailed diagnostics, Info shows important events (recommended)."); _enableOverlay = config.Bind<bool>("Overlay", "EnableOverlay", true, "Show the interactive map as an in-game overlay panel (requires Steam)"); _toggleKey = config.Bind<KeyCode>("Overlay", "ToggleKey", (KeyCode)109, "Key to show/hide the in-game map overlay"); _anchorX = config.Bind<float>("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<float>("Overlay", "AnchorY", -1f, "Normalized vertical anchor for the overlay panel (0 = bottom, 1 = top). -1 = auto (centred, computed on first run)"); _overlayWidth = config.Bind<int>("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<int>("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<bool>("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<string>? _log; private float _elapsed; private string _currentZone = ""; private bool _stopped; public BroadcastLoop(IEntityTracker entityTracker, InteractiveMapCompanion.Server.IWebSocketServer server, IModConfig config, Action<string>? 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<EntityData> 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<Guid, IWebSocketConnection> _clients = new ConcurrentDictionary<Guid, IWebSocketConnection>(); private readonly object _lifecycleGate = new object(); private Fleck.WebSocketServer? _server; private Action<Fleck.LogLevel, string, Exception>? _previousFleckLogAction; private Action<Fleck.LogLevel, string, Exception>? _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<Guid, IWebSocketConnection> 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>(T message) { return JsonConvert.SerializeObject(message, Settings); } public static T? Deserialize<T>(string json) { return JsonConvert.DeserializeObject<T>(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<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(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<HTML_NeedsPaint_t> _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<HTML_NeedsPaint_t>? _paintCallback; private Callback<HTML_StartRequest_t>? _startRequestCallback; private Callback<HTML_OpenLinkInNewTab_t>? _openLinkCallback; private Callback<HTML_NewWindow_t>? _newWindowCallback; private Callback<HTML_FinishedRequest_t>? _finishedRequestCallback; private Callback<HTML_JSAlert_t>? _jsAlertCallback; private Callback<HTML_JSConfirm_t>? _jsConfirmCallback; private Callback<HTML_FileOpenDialog_t>? _fileOpenDialogCallback; private Callback<HTML_CanGoBackAndForward_t>? _historyCallback; private CallResult<HTML_BrowserReady_t>? _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<HTML_NeedsPaint_t> 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<HTML_NeedsPaint_t>.Create((DispatchDelegate<HTML_NeedsPaint_t>)OnNeedsPaint); _startRequestCallback = Callback<HTML_StartRequest_t>.Create((DispatchDelegate<HTML_StartRequest_t>)OnStartRequest); _openLinkCallback = Callback<HTML_OpenLinkInNewTab_t>.Create((DispatchDelegate<HTML_OpenLinkInNewTab_t>)OnOpenLinkInNewTab); _newWindowCallback = Callback<HTML_NewWindow_t>.Create((DispatchDelegate<HTML_NewWindow_t>)OnNewWindow); _finishedRequestCallback = Callback<HTML_FinishedRequest_t>.Create((DispatchDelegate<HTML_FinishedRequest_t>)OnFinishedRequest); _jsAlertCallback = Callback<HTML_JSAlert_t>.Create((DispatchDelegate<HTML_JSAlert_t>)OnJSAlert); _jsConfirmCallback = Callback<HTML_JSConfirm_t>.Create((DispatchDelegate<HTML_JSConfirm_t>)OnJSConfirm); _fileOpenDialogCallback = Callback<HTML_FileOpenDialog_t>.Create((DispatchDelegate<HTML_FileOpenDialog_t>)OnFileOpenDialog); _historyCallback = Callback<HTML_CanGoBackAndForward_t>.Create((DispatchDelegate<HTML_CanGoBackAndForward_t>)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<HTML_BrowserReady_t>.Create((APIDispatchDelegate<HTML_BrowserReady_t>)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<HTML_BrowserReady_t>)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<RectTransform>(); 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<Image>(); ((Graphic)val2).color = ToolbarBorder; ((Graphic)val2).raycastTarget = true; GameObject val3 = new GameObject("Background"); val3.transform.SetParent((Transform)(object)RootRect, false); RectTransform val4 = val3.AddComponent<RectTransform>(); val4.anchorMin = Vector2.zero; val4.anchorMax = Vector2.one; val4.offsetMin = new Vector2(1f, 1f); val4.offsetMax = new Vector2(-1f, -1f); Image val5 = val3.AddComponent<Image>(); ((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<RectTransform>(); 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<Image>(); ((Graphic)val3).color = normalColor; Button val4 = val.AddComponent<Button>(); ((Selectable)val4).targetGraphic = (Graphic)(object)val3; ((Selectable)val4).transition = (Transition)1; ColorBlock colors = default(ColorBlock); ((ColorBlock)(ref colors)).normalColor = normalColor; ((ColorBlock)(ref colors)).highlightedColor = ButtonHover; ((ColorBlock)(ref colors)).pressedColor = ButtonPressed; ((ColorBlock)(ref colors)).selectedColor = ButtonFocused; ((ColorBlock)(ref colors)).disabledColor = ButtonDisabled; ((ColorBlock)(ref colors)).colorMultiplier = 1f; ((ColorBlock)(ref colors)).fadeDuration = 0.15f; ((Selectable)val4).colors = colors; ((UnityEvent)val4.onClick).AddListener((UnityAction)delegate { action(); }); GameObject val5 = new GameObject("Label"); val5.transform.SetParent(val.transform, false); RectTransform val6 = val5.AddComponent<RectTransform>(); val6.anchorMin = Vector2.zero; val6.anchorMax = Vector2.one; val6.sizeDelta = Vector2.zero; val6.anchoredPosition = Vector2.zero; text = val5.AddComponent<Text>(); text.font = Resources.GetBuiltinResource<Font>("Arial.ttf"); text.fontSize = 13; text.fontStyle = (FontStyle)1; text.alignment = (TextAnchor)4; ((Graphic)text).color = LabelNormal; ((Graphic)text).raycastTarget = false; text.supportRichText = false; text.text = label; return val4; } private static void SetInteractable(Button button, Text label, bool interactable) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) ((Selectable)button).interactable = interactable; ((Graphic)label).color = (interactable ? LabelNormal : LabelDisabled); } } internal sealed class BrowserRenderer : IDisposable { private Texture2D? _texture; private readonly RawImage _rawImage; private int _width; private int _height; private bool _textureDirty; private bool _disposed; internal BrowserRenderer(RawImage rawImage, int width, int height) { _rawImage = rawImage; _width = width; _height = height; CreateTexture(width, height); } internal void OnPaint(HTML_NeedsPaint_t param) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) int unWide = (int)param.unWide; int unTall = (int)param.unTall; if (unWide != _width || unTall != _height) { _width = unWide; _height = unTall; CreateTexture(unWide, unTall); } if (!((Object)(object)_texture == (Object)null)) { _texture.LoadRawTextureData(param.pBGRA, unWide * unTall * 4); _textureDirty = true; } } internal void Update() { if (_textureDirty && !((Object)(object)_texture == (Object)null)) { _textureDirty = false; _texture.Apply(false, false); } } private void CreateTexture(int width, int height) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_texture != (Object)null) { Object.Destroy((Object)(object)_texture); } _texture = new Texture2D(width, height, (TextureFormat)14, false); _rawImage.texture = (Texture)(object)_texture; _rawImage.uvRect = new Rect(0f, 1f, 1f, -1f); } public void Dispose() { if (!_disposed) { _disposed = true; if ((Object)(object)_texture != (Object)null) { Object.Destroy((Object)(object)_texture); _texture = null; } } } } internal sealed class InputForwarder { private readonly RectTransform _contentRect; private readonly RectTransform _excludedRect; private readonly Action _goBack; private readonly Action _goForward; private int _browserWidth; private int _browserHeight; private int _lastMouseX; private int _lastMouseY; private bool _hasMousePosition; private bool _focused; private readonly bool[] _buttonsDown = new bool[3]; private static readonly EHTMLMouseButton[] ButtonMap = (EHTMLMouseButton[])(object)new EHTMLMouseButton[3] { default(EHTMLMouseButton), (EHTMLMouseButton)1, (EHTMLMouseButton)2 }; private bool AnyButtonDown { get { bool[] buttonsDown = _buttonsDown; for (int i = 0; i < buttonsDown.Length; i++) { if (buttonsDown[i]) { return true; } } return false; } } internal InputForwarder(RectTransform contentRect, RectTransform excludedRect, int browserWidth, int browserHeight, Action goBack, Action goForward) { _contentRect = contentRect; _excludedRect = excludedRect; _browserWidth = browserWidth; _browserHeight = browserHeight; _goBack = goBack; _goForward = goForward; } internal void Tick(HHTMLBrowser browser) { //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_0015: 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_0027: Unknown result type (might be due to invalid IL or missing references) Vector2 browserPos; bool mouseOver = IsMouseOverPanel(out browserPos); ForwardMouseMove(browser, browserPos, mouseOver); ForwardMouseButtons(browser, mouseOver); ForwardMouseWheel(browser, mouseOver); ForwardKeyboard(browser); } private bool IsMouseOverPanel(out Vector2 browserPos) { //IL_0007: 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_0034: 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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0054: 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_005b: 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_00d2: 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) if (RectTransformUtility.RectangleContainsScreenPoint(_excludedRect, Vector2.op_Implicit(Input.mousePosition), (Camera)null)) { browserPos = Vector2.zero; return false; } Vector2 val = default(Vector2); if (RectTransformUtility.ScreenPointToLocalPointInRectangle(_contentRect, Vector2.op_Implicit(Input.mousePosition), (Camera)null, ref val)) { Rect rect = _contentRect.rect; float num = (val.x - ((Rect)(ref rect)).xMin) / ((Rect)(ref rect)).width; float num2 = (val.y - ((Rect)(ref rect)).yMin) / ((Rect)(ref rect)).height; bool result = num >= 0f && num <= 1f && num2 >= 0f && num2 <= 1f; browserPos = new Vector2(num * (float)_browserWidth, (1f - num2) * (float)_browserHeight); return result; } browserPos = Vector2.zero; return false; } private void ForwardMouseMove(HHTMLBrowser browser, Vector2 browserPos, bool mouseOver) { //IL_001e: 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_0077: Unknown result type (might be due to invalid IL or missing references) if (!mouseOver && !AnyButtonDown) { _hasMousePosition = false; return; } int num = (int)Mathf.Clamp(browserPos.x, 0f, (float)(_browserWidth - 1)); int num2 = (int)Mathf.Clamp(browserPos.y, 0f, (float)(_browserHeight - 1)); if (!_hasMousePosition || num != _lastMouseX || num2 != _lastMouseY) { SteamHTMLSurface.MouseMove(browser, num, num2); _lastMouseX = num; _lastMouseY = num2; _hasMousePosition = true; } } private void ForwardMouseButtons(HHTMLBrowser browser, bool mouseOver) { //IL_0030: 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) for (int i = 0; i < ButtonMap.Length; i++) { if (Input.GetMouseButtonDown(i)) { if (mouseOver) { _focused = true; if (!_buttonsDown[i]) { SteamHTMLSurface.MouseDown(browser, ButtonMap[i]); } _buttonsDown[i] = true; } else { _focused = false; } } if (Input.GetMouseButtonUp(i) && _buttonsDown[i]) { SteamHTMLSurface.MouseUp(browser, ButtonMap[i]); _buttonsDown[i] = false; } } if (mouseOver && Input.GetMouseButtonDown(3)) { _goBack(); } if (mouseOver && Input.GetMouseButtonDown(4)) { _goForward(); } } private void ForwardMouseWheel(HHTMLBrowser browser, bool mouseOver) { //IL_000b: 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) if (mouseOver) { float y = Input.mouseScrollDelta.y; if (y != 0f) { int num = (int)(Mathf.Clamp(y, -3f, 3f) * 120f); SteamHTMLSurface.MouseWheel(browser, num); } } } private void ForwardKeyboard(HHTMLBrowser browser) { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) if (!_focused) { return; } bool flag = Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307) || Input.GetKey((KeyCode)310) || Input.GetKey((KeyCode)309); if (flag && Input.GetKeyDown((KeyCode)276)) { _goBack(); } if (flag && Input.GetKeyDown((KeyCode)275)) { _goForward(); } string inputString = Input.inputString; foreach (char c in inputString) { if (c != '\b' && c != 0) { SteamHTMLSurface.KeyChar(browser, (uint)c, (EHTMLKeyModifiers)0); } } } internal void ResetMouseState(HHTMLBrowser browser) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < ButtonMap.Length; i++) { if (_buttonsDown[i]) { SteamHTMLSurface.MouseUp(browser, ButtonMap[i]); _buttonsDown[i] = false; } } _hasMousePosition = false; _focused = false; } } [DefaultExecutionOrder(-100)] internal sealed class MapOverlay : MonoBehaviour { private Canvas? _canvas; private RawImage? _rawImage; private BrowserManager? _browser; private BrowserRenderer? _renderer; private InputForwarder? _input; private BrowserNavigationToolbar? _toolbar; private bool _visible; private bool _ready; private bool _stopped; private bool _applicationQuitting; internal IModLogger? Log { get; set; } internal IModConfig? Config { get; set; } private void Start() { if (_stopped) { return; } if (_applicationQuitting) { Stop(); Object.Destroy((Object)(object)this); return; } if (Config == null || Log == null) { Debug.LogError((object)"[InteractiveMapCompanion] Overlay started without config/logger."); Stop(); Object.Destroy((Object)(object)this); return; } if (!Config.EnableOverlay) { Log.LogInfo("[Overlay] Overlay disabled via config."); Stop(); Object.Destroy((Object)(object)this); return; } try { BuildUI(); StartBrowser(); if (_browser != null) { _ready = true; } } catch (Exception arg) { Log.LogError($"[Overlay] Failed to initialise: {arg}"); Stop(); Object.Destroy((Object)(object)this); } } private void BuildUI() { //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Expected O, but got Unknown //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Expected O, but got Unknown //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) IModConfig config = Config; IModLogger log = Log; if (config.ResetToDefaults) { config.OverlayWidth = 0; config.OverlayHeight = 0; config.AnchorX = -1f; config.AnchorY = -1f; config.ResetToDefaults = false; log.LogInfo("[Overlay] Reset size/position to auto-computed defaults."); } if (config.OverlayWidth <= 0) { config.OverlayWidth = Mathf.RoundToInt((float)Screen.width * 0.8f); config.OverlayHeight = Mathf.RoundToInt((float)Screen.height * 0.8f); log.LogInfo($"[Overlay] Auto-sized to {config.OverlayWidth}x{config.OverlayHeight} (screen: {Screen.width}x{Screen.height})"); } if (config.AnchorX < 0f) { config.AnchorX = 0.5f; config.AnchorY = 0.5f; } int overlayWidth = config.OverlayWidth; int overlayHeight = config.OverlayHeight; float num = Mathf.Clamp01(config.AnchorX); float num2 = Mathf.Clamp01(config.AnchorY); GameObject val = new GameObject("MapOverlayCanvas"); Object.DontDestroyOnLoad((Object)(object)val); _canvas = val.AddComponent<Canvas>(); _canvas.renderMode = (RenderMode)0; _canvas.sortingOrder = 100; val.AddComponent<CanvasScaler>(); val.AddComponent<GraphicRaycaster>(); GameObject val2 = new GameObject("MapOverlayPanel"); val2.transform.SetParent(val.transform, false); RectTransform val3 = val2.AddComponent<RectTransform>(); Vector2 val4 = default(Vector2); ((Vector2)(ref val4))..ctor(num, num2); val3.anchorMin = val4; val3.anchorMax = val4; val3.pivot = val4; val3.anchoredPosition = Vector2.zero; val3.sizeDelta = new Vector2((float)overlayWidth, (float)overlayHeight); GameObject val5 = new GameObject("MapOverlayImage"); val5.transform.SetParent(val2.transform, false); _rawImage = val5.AddComponent<RawImage>(); RectTransform component = val5.GetComponent<RectTransform>(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; _toolbar = new BrowserNavigationToolbar(val3, delegate { _browser?.GoBack(); }, delegate { _browser?.GoForward(); }, delegate { _browser?.LoadMap(); }); _input = new InputForwarder(component, _toolbar.RootRect, overlayWidth, overlayHeight, delegate { _browser?.GoBack(); }, delegate { _browser?.GoForward(); }); val.SetActive(false); } private void StartBrowser() { if (!((Object)(object)_rawImage == (Object)null)) { int overlayWidth = Config.OverlayWidth; int overlayHeight = Config.OverlayHeight; _renderer = new BrowserRenderer(_rawImage, overlayWidth, overlayHeight); _browser = new BrowserManager(Log, _renderer.OnPaint); _browser.NavigationStateChanged += UpdateNavigationState; UpdateNavigationState(); if (!_browser.Initialize(overlayWidth, overlayHeight, "https://erenshor.compendiums.org/map")) { Log.LogWarning("[Overlay] Browser initialisation failed — overlay will not be shown."); _browser.NavigationStateChanged -= UpdateNavigationState; _browser.Dispose(); _browser = null; UpdateNavigationState(); } } } private void UpdateNavigationState() { _toolbar?.SetState(_browser?.IsReady ?? false, _browser?.CanGoBack ?? false, _browser?.CanGoForward ?? false); } internal void HandleShortcut(bool wasPressed) { //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_0078: Unknown result type (might be due to invalid IL or missing references) if (_stopped || !_ready || !wasPressed) { return; } int num; if (GameData.InCharSelect) { EventSystem current = EventSystem.current; object obj; if (current == null) { obj = null; } else { GameObject currentSelectedGameObject = current.currentSelectedGameObject; obj = ((currentSelectedGameObject != null) ? ((Object)currentSelectedGameObject).name : null); } num = (((string?)obj == "InputField (TMP)") ? 1 : 0); } else { num = 0; } bool flag = (byte)num != 0; IModConfig config = Config; if (GameData.PlayerTyping || flag) { Log?.LogDebug($"[Overlay] Ignored {config.ToggleKey} while text input was active."); return; } SetVisible(!_visible); if (config.ToggleKey == InputManager.Map) { MapKeyPatches.SuppressMapKey = true; } } private void Update() { //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (!_stopped && _ready && _browser != null) { _browser.ProcessPendingNavigation(); _renderer?.Update(); if (_visible && _browser.IsReady) { _input?.Tick(_browser.BrowserHandle); } } } private void LateUpdate() { if (!_stopped) { MapKeyPatches.SuppressMapKey = false; } } private void OnApplicationFocus(bool hasFocus) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (!_stopped && _ready) { BrowserManager? browser = _browser; if (browser != null && browser.IsReady && (!hasFocus || _visible)) { _input?.ResetMouseState(_browser.BrowserHandle); } } } internal void NotifyApplicationQuitting() { _applicationQuitting = true; _browser?.NotifyAppIsQuitting(); } private void SetVisible(bool visible) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (!_stopped) { _visible = visible; _browser?.SetVisible(visible); if ((Object)(object)_canvas != (Object)null) { ((Component)_canvas).gameObject.SetActive(visible); } BrowserManager? browser = _browser; if (browser != null && browser.IsReady) { _input?.ResetMouseState(_browser.BrowserHandle); } Log?.LogDebug("[Overlay] " + (visible ? "Shown" : "Hidden") + "."); } } internal void Stop() { if (!_stopped) { _stopped = true; _ready = false; MapKeyPatches.SuppressMapKey = false; if (_browser != null) { _browser.NavigationStateChanged -= UpdateNavigationState; _browser.Dispose(); _browser = null; } _renderer?.Dispose(); _renderer = null; _input = null; _toolbar = null; if ((Object)(object)_canvas != (Object)null) { Object.Destroy((Object)(object)((Component)_canvas).gameObject); _canvas = null; } } } private void OnDestroy() { Stop(); } } } namespace InteractiveMapCompanion.Entities { public sealed class EntityClassifier : IEntityClassifier { public EntityType? Classify(Character character) { if ((Object)(object)character == (Object)null) { return null; } if (character.MiningNode) { return null; } NPC myNPC = character.MyNPC; if (myNPC != null && myNPC.TreasureChest) { return null; } if ((Object)(object)GameData.PlayerControl?.Myself == (Object)(object)character) { return EntityType.Player; } if ((Object)(object)character.Master != (Object)null) { return EntityType.Pet; } if ((Object)(object)((Component)character).GetComponent<SimPlayer>() != (Object)null) { return EntityType.SimPlayer; } if (IsHostileToPlayer(character)) { return EntityType.NpcEnemy; } return EntityType.NpcFriendly; } private static bool IsHostileToPlayer(Character character) { if (character.AggressiveTowards != null && (character.AggressiveTowards.Contains((Faction)0) || character.AggressiveTowards.Contains((Faction)11))) { return true; } if ((Object)(object)character.MyWorldFaction != (Object)null && character.MyWorldFaction.FactionValue <= 0f) { return true; } return false; } } public sealed class EntityData { public int Id { get; } public string EntityType { get; } public string Name { get; } public float[] Position { get; } public float Rotation { get; } public int? Level { get; } public string? Rarity { get; } public string? CharacterClass { get; } public string? Owner { get; } public EntityData(int Id, string EntityType, string Name, float[] Position, float Rotation, int? Level = null, string? Rarity = null, string? CharacterClass = null, string? Owner = null) { this.Id = Id; this.EntityType = EntityType; this.Name = Name; this.Position = Position; this.Rotation = Rotation; this.Level = Level; this.Rarity = Rarity; this.CharacterClass = CharacterClass; this.Owner = Owner; } } public sealed class EntityExtractor : IEntityExtractor { public EntityData Extract(Character character, EntityType entityType) { //IL_003a: 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) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) Transform transform = ((Component)character).transform; Stats myStats = character.MyStats; return new EntityData(((Object)character).GetInstanceID(), EntityTypeToString(entityType), myStats?.MyName ?? ((Object)character).name, new float[3] { transform.position.x, transform.position.y, transform.position.z }, NormalizeRotation(transform.eulerAngles.y), GetLevel(myStats, entityType), GetRarity(character, entityType), GetCharacterClass(myStats, entityType), GetOwner(character, entityType)); } private static string EntityTypeToString(EntityType type) { if (1 == 0) { } string result = type switch { EntityType.Player => "player", EntityType.SimPlayer => "simplayer", EntityType.Pet => "pet", EntityType.NpcFriendly => "npc_friendly", EntityType.NpcEnemy => "npc_enemy", _ => "unknown", }; if (1 == 0) { } return result; } private static float NormalizeRotation(float degrees) { degrees %= 360f; if (degrees < 0f) { degrees += 360f; } return degrees; } private static int? GetLevel(Stats? stats, EntityType type) { return stats?.Level; } private static string? GetRarity(Character character, EntityType type) { if (type != EntityType.NpcEnemy) { return null; } if (character.BossXp > 1f) { return "boss"; } return "common"; } private static string? GetCharacterClass(Stats? stats, EntityType type) { if ((uint)type > 1u) { return null; } return stats?.CharacterClass?.DisplayName; } private static string? GetOwner(Character character, EntityType type) { if (type != EntityType.Pet) { return null; } return character.Master?.MyStats?.MyName ?? "Unknown"; } } public sealed class EntityFinder : IEntityFinder { public IEnumerable<Character> FindAll() { return Object.FindObjectsOfType<Character>(); } } public sealed class EntityTracker<TCharacter> where TCharacter : class { private readonly Func<IEnumerable<TCharacter>> _findEntities; private readonly Func<TCharacter, EntityType?> _classify; private readonly Func<TCharacter, EntityType, EntityData> _extract; private readonly Func<EntityType, bool> _shouldTrack; public EntityTracker(Func<IEnumerable<TCharacter>> findEntities, Func<TCharacter, EntityType?> classify, Func<TCharacter, EntityType, EntityData> extract, Func<EntityType, bool> shouldTrack) { _findEntities = findEntities; _classify = classify; _extract = extract; _shouldTrack = shouldTrack; } public IReadOnlyList<EntityData> GetTrackedEntities() { List<EntityData> list = new List<EntityData>(); foreach (TCharacter item2 in _findEntities()) { EntityType? entityType = _classify(item2); if (entityType.HasValue && _shouldTrack(entityType.Value)) { EntityData item = _extract(item2, entityType.Value); list.Add(item); } } return list; } } public sealed class EntityTrackerAdapter : IEntityTracker { private readonly EntityTracker<Character> _inner; public EntityTrackerAdapter(IEntityFinder finder, IEntityClassifier classifier, IEntityExtractor extractor, Func<EntityType, bool> entityFilter) { _inner = new EntityTracker<Character>(finder.FindAll, classifier.Classify, extractor.Extract, entityFilter); } public IReadOnlyList<EntityData> GetTrackedEntities() { return _inner.GetTrackedEntities(); } } public enum EntityType { Player, SimPlayer, Pet, NpcFriendly, NpcEnemy } public interface IEntityClassifier { EntityType? Classify(Character character); } public interface IEntityExtractor { EntityData Extract(Character character, EntityType entityType); } public interface IEntityFinder { IEnumerable<Character> FindAll(); } public interface IEntityTracker { IReadOnlyList<EntityData> GetTrackedEntities(); } } namespace InteractiveMapCompanion.Config { public enum LogLevel { Debug, Info, Warning, Error } public interface IModConfig { int Port { get; } int UpdateInterval { get; } LogLevel WebSocketLogLevel { get; } LogLevel ModLogLevel { get; } bool EnableOverlay { get; } KeyCode ToggleKey { get; } float AnchorX { get; set; } float AnchorY { get; set; } int OverlayWidth { get; set; } int OverlayHeight { get; set; } bool ResetToDefaults { get; set; } string[] GetCapabilities(); } public abstract class ModConfigBase : IModConfig { public abstract int Port { get; } public abstract int UpdateInterval { get; } public abstract LogLevel WebSocketLogLevel { get; } public abstract LogLevel ModLogLevel { get; } public abstract bool EnableOverlay { get; } public abstract KeyCode ToggleKey { get; } public abstract float AnchorX { get; set; } public abstract float AnchorY { get; set; } public abstract int OverlayWidth { get; set; } public abstract int OverlayHeight { get; set; } public abstract bool ResetToDefaults { get; set; } public string[] GetCapabilities() { return new string[1] { "entities" }; } } } namespace Fleck { internal class ConnectionNotAvailableException : Exception { public ConnectionNotAvailableException() { } public ConnectionNotAvailableException(string message) : base(message) { } public ConnectionNotAvailableException(string message, Exception innerException) : base(message, innerException) { } } internal enum LogLevel { Debug, Info, Warn, Error } internal class FleckLog { public static LogLevel Level = LogLevel.Info; public static Action<LogLevel, string, Exception> LogAction = delegate(LogLevel level, string message, Exception ex) { if (level >= Level) { Console.WriteLine("{0} [{1}] {2} {3}", DateTime.Now, level, message, ex); } }; public static void Warn(string message, Exception ex = null) { LogAction(LogLevel.Warn, message, ex); } public static void Error(string message, Exception ex = null) { LogAction(LogLevel.Error, message, ex); } public static void Debug(string message, Exception ex = null) { LogAction(LogLevel.Debug, message, ex); } public static void Info(string message, Exception ex = null) { LogAction(LogLevel.Info, message, ex); } } internal enum FrameType : byte { Continuation = 0, Text = 1, Binary = 2, Close = 8, Ping = 9, Pong = 10 } internal class HandlerFactory { public static IHandler BuildHandler(WebSocketHttpRequest request, Action<string> onMessage, Action onClose, Action<byte[]> onBinary, Action<byte[]> onPing, Action<byte[]> onPong) { switch (GetVersion(request)) { case "76": return Draft76Handler.Create(request, onMessage); case "7": case "8": case "13": return Hybi13Handler.Create(request, onMessage, onClose, onBinary, onPing, onPong); case "policy-file-request": return FlashSocketPolicyRequestHandler.Create(request); default: throw new WebSocketException(1003); } } public static string GetVersion(WebSocketHttpRequest request) { if (request.Headers.TryGetValue("Sec-WebSocket-Version", out var value)) { return value; } if (request.Headers.TryGetValue("Sec-WebSocket-Draft", out value)) { return value; } if (request.Headers.ContainsKey("Sec-WebSocket-Key1")) { return "76"; } if (request.Body != null && request.Body.ToLower().Contains("policy-file-request")) { return "policy-file-request"; } return "75"; } } internal interface IHandler { byte[] CreateHandshake(string subProtocol = null); void Receive(IEnumerable<byte> data); byte[] FrameText(string text); byte[] FrameBinary(byte[] bytes); byte[] FramePing(byte[] bytes); byte[] FramePong(byte[] bytes); byte[] FrameClose(int code); } internal interface ISocket { bool Connected { get; } string RemoteIpAddress { get; } int RemotePort { get; } Stream Stream { get; } bool NoDelay { get; set; } EndPoint LocalEndPoint { get; } Task<ISocket> Accept(Action<ISocket> callback, Action<Exception> error); Task Send(byte[] buffer, Action callback, Action<Exception> error); Task<int> Receive(byte[] buffer, Action<int> callback, Action<Exception> error, int offset = 0); Task Authenticate(X509Certificate2 certificate, SslProtocols enabledSslProtocols, Action callback, Action<Exception> error); void Dispose(); void Close(); void Bind(EndPoint ipLocal); void Listen(int backlog); } internal interface IWebSocketConnection { Action OnOpen { get; set; } Action OnClose { get; set; } Action<string> OnMessage { get; set; } Action<byte[]> OnBinary { get; set; } Action<byte[]> OnPing { get; set; } Action<byte[]> OnPong { get; set; } Action<Exception> OnError { get; set; } IWebSocketConnectionInfo ConnectionInfo { get; } bool IsAvailable { get; } Task Send(string message); Task Send(byte[] message); Task SendPing(byte[] message); Task SendPong(byte[] message); void Close(); void Close(int code); } internal interface IWebSocketConnectionInfo { string SubProtocol { get; } string Origin { get; } string Host { get; } string Path { get; } string ClientIpAddress { get; } int ClientPort { get; } IDictionary<string, string> Cookies { get; } IDictionary<string, string> Headers { get; } Guid Id { get; } string NegotiatedSubProtocol { get; } } internal interface IWebSocketServer : IDisposable { void Start(Action<IWebSocketConnection> config); } internal static class IntExtensions { public static byte[] ToBigEndianBytes<T>(this int source) { Type typeFromHandle = typeof(T); byte[] bytes; if (typeFromHandle == typeof(ushort)) { bytes = BitConverter.GetBytes((ushort)source); } else if (typeFromHandle == typeof(ulong)) { bytes = BitConverter.GetBytes((ulong)source); } else { if (!(typeFromHandle == typeof(int))) { throw new InvalidCastException("Cannot be cast to T"); } bytes = BitConverter.GetBytes(source); } if (BitConverter.IsLittleEndian) { Array.Reverse((Array)bytes); } return bytes; } public static int ToLittleEndianInt(this byte[] source) { if (BitConverter.IsLittleEndian) { Array.Reverse((Array)source); } if (source.Length == 2) { return BitConverter.ToUInt16(source, 0); } if (source.Length == 8) { return (int)BitConverter.ToUInt64(source, 0); } throw new ArgumentException("Unsupported Size"); } } internal class QueuedStream : Stream { private class WriteData { public readonly byte[] Buffer; public readonly int Offset; public readonly int Count; public readonly AsyncCallback Callback; public readonly object State; public readonly QueuedWriteResult AsyncResult; public WriteData(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { Buffer = buffer; Offset = offset; Count = count; Callback = callback; State = state; AsyncResult = new QueuedWriteResult(state); } } private class QueuedWriteResult : IAsyncResult { private readonly object _state; public Exception Exception { get; set; } public IAsyncResult ActualResult { get; set; } public object AsyncState => _state; public WaitHandle AsyncWaitHandle { get { throw new NotSupportedException("Queued write operations do not support wait handle."); } } public bool CompletedSynchronously => false; public bool IsCompleted { get { if (ActualResult != null) { return ActualResult.IsCompleted; } return false; } } public QueuedWriteResult(object state) { _state = state; } } private readonly Stream _stream; private readonly Queue<WriteData> _queue = new Queue<WriteData>(); private int _pendingWrite; private bool _disposed; public override bool CanRead => _stream.CanRead; public override bool CanSeek => _stream.CanSeek; public override bool CanWrite => _stream.CanWrite; public override long Length => _stream.Length; public override long Position { get { return _stream.Position; } set { _stream.Position = value; } } public QueuedStream(Stream stream) { _stream = stream; } public override int Read(byte[] buffer, int offset, int count) { return _stream.Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { return _stream.Seek(offset, origin); } public override void SetLength(long value) { _stream.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException("QueuedStream does not support synchronous write operations yet."); } public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return _stream.BeginRead(buffer, offset, count, callback, state); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { lock (_queue) { WriteData writeData = new WriteData(buffer, offset, count, callback, state); if (_pendingWrite > 0) { _queue.Enqueue(writeData); return writeData.AsyncResult; } return BeginWriteInternal(buffer, offset, count, callback, state, writeData); } } public override int EndRead(IAsyncResult asyncResult) { return _stream.EndRead(asyncResult); } public override void EndWrite(IAsyncResult asyncResult) { if (asyncResult is QueuedWriteResult) { QueuedWriteResult queuedWriteResult = asyncResult as QueuedWriteResult; if (queuedWriteResult.Exception != null) { throw queuedWriteResult.Exception; } if (queuedWriteResult.ActualResult == null) { throw new NotSupportedException("QueuedStream does not support synchronous write operations. Please wait for callback to be invoked before calling EndWrite."); } return; } throw new ArgumentException(); } public override void Flush() { _stream.Flush(); } public override void Close() { _stream.Close(); } protected override void Dispose(bool disposing) { if (!_disposed) { if (disposing) { _stream.Dispose(); } _disposed = true; } base.Dispose(disposing); } private IAsyncResult BeginWriteInternal(byte[] buffer, int offset, int count, AsyncCallback callback, object state, WriteData queued) { _pendingWrite++; IAsyncResult actualResult = _stream.BeginWrite(buffer, offset, count, delegate(IAsyncResult ar) { queued.AsyncResult.ActualResult = ar; try { _stream.EndWrite(ar); } catch (Exception exception) { queued.AsyncResult.Exception = exception; } lock (_queue) { _pendingWrite--; while (_queue.Count > 0) { WriteData writeData = _queue.Dequeue(); try { writeData.AsyncResult.ActualResult = BeginWriteInternal(writeData.Buffer, writeData.Offset, writeData.Count, writeData.Callback, writeData.State, writeData); } catch (Exception exception2) { _pendingWrite--; writeData.AsyncResult.Exception = exception2; writeData.Callback(writeData.AsyncResult); continue; } break; } callback(queued.AsyncResult); } }, state); queued.AsyncResult.ActualResult = actualResult; return queued.AsyncResult; } } internal class ReadState { public List<byte> Data { get; private set; } public FrameType? FrameType { get; set; } public ReadState() { Data = new List<byte>(); } public void Clear() { Data.Clear(); FrameType = null; } } internal class RequestParser { private const string pattern = "^(?<method>[^\\s]+)\\s(?<path>[^\\s]+)\\sHTTP\\/1\\.1\\r\\n((?<field_name>[^:\\r\\n]+):(?([^\\r\\n])\\s)*(?<field_value>[^\\r\\n]*)\\r\\n)+\\r\\n(?<body>.+)?"; private const string FlashSocketPolicyRequestPattern = "^[<]policy-file-request\\s*[/][>]"; private static readonly Regex _regex = new Regex("^(?<method>[^\\s]+)\\s(?<path>[^\\s]+)\\sHTTP\\/1\\.1\\r\\n((?<field_name>[^:\\r\\n]+):(?([^\\r\\n])\\s)*(?<field_value>[^\\r\\n]*)\\r\\n)+\\r\\n(?<body>.+)?", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex _FlashSocketPolicyRequestRegex = new Regex("^[<]policy-file-request\\s*[/][>]", RegexOptions.IgnoreCase | RegexOptions.Compiled); public static WebSocketHttpRequest Parse(byte[] bytes) { return Parse(bytes, "ws"); } public static WebSocketHttpRequest Parse(byte[] bytes, string scheme) { string text = Encoding.UTF8.GetString(bytes); Match match = _regex.Match(text); if (!match.Success) { match = _FlashSocketPolicyRequestRegex.Match(text); if (match.Success) { return new WebSocketHttpRequest { Body = text, Bytes = bytes }; } return null; } WebSocketHttpRequest webSocketHttpRequest = new WebSocketHttpRequest { Method = match.Groups["method"].Value, Path = match.Groups["path"].Value, Body = match.Groups["body"].Value, Bytes = bytes, Scheme = scheme }; CaptureCollection captures = match.Groups["field_name"].Captures; CaptureCollection captures2 = match.Groups["field_value"].Captures; for (int i = 0; i < captures.Count; i++) { string key = captures[i].ToString(); string value = captures2[i].ToString(); webSocketHttpRequest.Headers[key] = value; } return webSocketHttpRequest; } } internal class SocketWrapper : ISocket { public const uint KeepAliveInterval = 60000u; public const uint RetryInterval = 10000u; private readonly Socket _socket; private Stream _stream; private CancellationTokenSource _tokenSource; private TaskFactory _taskFactory; public string RemoteIpAddress { get { if (!(_socket.RemoteEndPoint is IPEndPoint iPEndPoint)) { return null; } return iPEndPoint.Address.ToString(); } } public int RemotePort { get { if (!(_socket.RemoteEndPoint is IPEndPoint iPEndPoint)) { return -1; } return iPEndPoint.Port; } } public bool Connected => _socket.Connected; public Stream Stream => _stream; public bool NoDelay { get { return _socket.NoDelay; } set { _socket.NoDelay = value; } } public EndPoint LocalEndPoint => _socket.LocalEndPoint; public void SetKeepAlive(Socket socket, uint keepAliveInterval, uint retryInterval) { int num = 4; byte[] array = new byte[num * 3]; Array.Copy(BitConverter.GetBytes(1u), 0, array, 0, num); Array.Copy(BitConverter.GetBytes(keepAliveInterval), 0, array, num, num); Array.Copy(BitConverter.GetBytes(retryInterval), 0, array, num * 2, num); socket.IOControl(IOControlCode.KeepAliveValues, array, null); } public SocketWrapper(Socket socket) { _tokenSource = new CancellationTokenSource(); _taskFactory = new TaskFactory(_tokenSource.Token); _socket = socket; if (_socket.Connected) { _stream = new NetworkStream(_socket); } if (FleckRuntime.IsRunningOnWindows()) { SetKeepAlive(socket, 60000u, 10000u); } } public Task Authenticate(X509Certificate2 certificate, SslProtocols enabledSslProtocols, Action callback, Action<Exception> error) { SslStream ssl = new SslStream(_stream, leaveInnerStreamOpen: false); _stream = new QueuedStream(ssl); Func<AsyncCallback, object, IAsyncResult> beginMethod = (AsyncCallback cb, object s) => ssl.BeginAuthenticateAsServer(certificate, clientCertificateRequired: false, enabledSslProtocols, checkCertificateRevocation: false, cb, s); Task task = Task.Factory.FromAsync(beginMethod, ssl.EndAuthenticateAsServer, null); task.ContinueWith(delegate { callback(); }, TaskContinuationOptions.NotOnFaulted).ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); task.ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); return task; } public void Listen(int backlog) { _socket.Listen(backlog); } public void Bind(EndPoint endPoint) { _socket.Bind(endPoint); } public Task<int> Receive(byte[] buffer, Action<int> callback, Action<Exception> error, int offset) { try { Func<AsyncCallback, object, IAsyncResult> beginMethod = (AsyncCallback cb, object s) => _stream.BeginRead(buffer, offset, buffer.Length, cb, s); Task<int> task = Task.Factory.FromAsync(beginMethod, (Func<IAsyncResult, int>)_stream.EndRead, (object?)null); task.ContinueWith(delegate(Task<int> t) { callback(t.Result); }, TaskContinuationOptions.NotOnFaulted).ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); task.ContinueWith(delegate(Task<int> t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); return task; } catch (Exception obj) { error(obj); return null; } } public Task<ISocket> Accept(Action<ISocket> callback, Action<Exception> error) { Func<IAsyncResult, ISocket> endMethod = (IAsyncResult r) => (!_tokenSource.Token.IsCancellationRequested) ? new SocketWrapper(_socket.EndAccept(r)) : null; Task<ISocket> task = _taskFactory.FromAsync(_socket.BeginAccept, endMethod, null); task.ContinueWith(delegate(Task<ISocket> t) { callback(t.Result); }, TaskContinuationOptions.OnlyOnRanToCompletion).ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); task.ContinueWith(delegate(Task<ISocket> t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); return task; } public void Dispose() { _tokenSource.Cancel(); if (_stream != null) { _stream.Dispose(); } if (_socket != null) { _socket.Dispose(); } } public void Close() { _tokenSource.Cancel(); if (_stream != null) { _stream.Close(); } if (_socket != null) { _socket.Close(); } } public int EndSend(IAsyncResult asyncResult) { _stream.EndWrite(asyncResult); return 0; } public Task Send(byte[] buffer, Action callback, Action<Exception> error) { if (_tokenSource.IsCancellationRequested) { return null; } try { Func<AsyncCallback, object, IAsyncResult> beginMethod = (AsyncCallback cb, object s) => _stream.BeginWrite(buffer, 0, buffer.Length, cb, s); Task task = Task.Factory.FromAsync(beginMethod, _stream.EndWrite, null); task.ContinueWith(delegate { callback(); }, TaskContinuationOptions.NotOnFaulted).ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); task.ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); return task; } catch (Exception obj) { error(obj); return null; } } } internal class SubProtocolNegotiationFailureException : Exception { public SubProtocolNegotiationFailureException() { } public SubProtocolNegotiationFailureException(string message) : base(message) { } public SubProtocolNegotiationFailureException(string message, Exception innerException) : base(message, innerException) { } } internal static class SubProtocolNegotiator { public static string Negotiate(IEnumerable<string> server, IEnumerable<string> client) { if (!server.Any() || !client.Any()) { return null; } IEnumerable<string> source = client.Intersect(server); if (!source.Any()) { throw new SubProtocolNegotiationFailureException("Unable to negotiate a subprotocol"); } return source.First(); } } internal class WebSocketConnection : IWebSocketConnection { private readonly Action<IWebSocketConnection> _initialize; private readonly Func<WebSocketHttpRequest, IHandler> _handlerFactory; private readonly Func<IEnumerable<string>, string> _negotiateSubProtocol; private readonly Func<byte[], WebSocketHttpRequest> _parseRequest; private bool _closing; private bool _closed; private const int ReadSize = 4096; public ISocket Socket { get; set; } public IHandler Handler { get; set; } public Action OnOpen { get; set; } public Action OnClose { get; set; } public Action<string> OnMessage { get; set; } public Action<byte[]> OnBinary { get; set; } public Action<byte[]> OnPing { get; set; } public Action<byte[]> OnPong { get; set; } public Action<Exception> OnError { get; set; } public IWebSocketConnectionInfo Con