Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of Hotline v1.0.0
Hotline.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using HarmonyLib; using Hotline; using Hotline.Bridge; using Hotline.Config; using Hotline.Hotkeys; using Hotline.Logging; using Hotline.Panels; using Hotline.UI; using Il2CppScheduleOne; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "Hotline", "1.0.0", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-Hotline")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonOptionalDependencies(new string[] { "ModManager&PhoneApp" })] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("Hotline")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+fdf6b5934424a816a61101d2e2f3cce9caa6a542")] [assembly: AssemblyProduct("Hotline")] [assembly: AssemblyTitle("Hotline")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Hotline { public sealed class Core : MelonMod { private bool _inWorld; public static Core Instance { get; private set; } public static Instance Log { get; private set; } internal static Harmony HarmonyInst { get; private set; } public override void OnInitializeMelon() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((MelonBase)this).LoggerInstance; HarmonyInst = ((MelonBase)this).HarmonyInstance; Preferences.Initialize(); LogHub.Install(); BridgeHost.Install(); try { ((MelonBase)this).HarmonyInstance.PatchAll(); } catch (Exception ex) { Log.Warning("[Hotline] Harmony patch failed: " + ex.Message); } Log.Msg("Hotline v1.0.0 - mod HUD & hotkey hub. Press " + ((object)Preferences.MasterKey/*cast due to .constrained prefix*/).ToString() + " in-world to open the overlay ('hotline help' for commands)."); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { _inWorld = sceneName == "Main"; if (_inWorld) { Interceptor.Install(((MelonBase)this).HarmonyInstance); } } public override void OnSceneWasUnloaded(int buildIndex, string sceneName) { _inWorld = false; } public override void OnUpdate() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (!_inWorld || !Preferences.Enabled) { return; } try { if (Input.GetKeyDown(Preferences.MasterKey)) { if (!Preferences.ShowHud || !WindowLayout.IsVisible("overview")) { Preferences.SetShowHud(v: true); WindowLayout.SetVisible("overview", v: true); foreach (string item in Interceptor.ModsForKey(Preferences.MasterKey)) { WindowLayout.SetVisible(item, v: true); } } else { Preferences.SetShowHud(v: false); } } HotkeyRegistry.Poll(); if (Preferences.ShowHud) { WindowManager.HandleInput(); } } catch { } } public override void OnGUI() { if (_inWorld && Preferences.Enabled && Preferences.ShowHud) { WindowManager.Draw(); } } public override void OnApplicationQuit() { LogHub.Uninstall(); } public override void OnDeinitializeMelon() { LogHub.Uninstall(); } } internal static class HotlineConsole { private static int _lastFrame = -1; private static string _lastSig = ""; internal static bool TryHandle(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return false; } return Dispatch(raw.Trim().Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries)); } internal static bool TryHandle(List<string> args) { if (args == null || args.Count == 0) { return false; } string[] array = new string[args.Count]; for (int i = 0; i < args.Count; i++) { array[i] = args[i]; } return Dispatch(array); } private static bool Dispatch(string[] p) { if (p.Length == 0 || !p[0].Equals("hotline", StringComparison.OrdinalIgnoreCase)) { return false; } string text = string.Join(" ", p); int frameCount = Time.frameCount; if (frameCount == _lastFrame && text == _lastSig) { return true; } _lastFrame = frameCount; _lastSig = text; LogHub.Write("Console", 0, text); string text2 = ((p.Length > 1) ? p[1].ToLowerInvariant() : "panels"); try { switch (text2) { case "hud": Hud(p); break; case "panels": PanelsList(); break; case "panel": PanelCmd(p); break; case "act": ActCmd(p); break; case "toggle": ToggleCmd(p); break; case "log": LogCmd(p); break; case "keys": KeysList(); break; case "intercept": Intercept(p); break; case "key": KeyCmd(p); break; case "help": Help(); break; default: Log("unknown '" + text2 + "'. Try 'hotline help'."); break; } } catch (Exception ex) { Log("error: " + ex.Message); } return true; } private static void Help() { Log("commands: hud [on|off|move <x> <y>|font <n>|reset] | panels | panel <id> [on|off|move <x> <y>|size <w> <h>|reset] | act <actionId> | toggle <toggleId> [on|off] | log [<channel>|all] [n] | keys | intercept [on|off|status|suppress on|off] | key press <KeyCode> [mod] | key master <KeyCode>"); } private static void Intercept(string[] p) { //IL_016e: Unknown result type (might be due to invalid IL or missing references) string text = ((p.Length > 2) ? p[2].ToLowerInvariant() : "status"); switch (text) { case "on": case "off": Preferences.SetInterceptFunctionKeys(text == "on"); MelonPreferences.Save(); Log("auto-interception = " + Preferences.InterceptFunctionKeys); return; case "suppress": Preferences.SetSuppressRawKeys(BoolArg(p, 3, !Preferences.SuppressRawKeys)); MelonPreferences.Save(); Log("suppress raw keys = " + Preferences.SuppressRawKeys); return; } Log($"auto-interception: pref={Preferences.InterceptFunctionKeys} suppressRaw={Preferences.SuppressRawKeys} patch={(Interceptor.Installed ? "installed" : "NOT installed")} postfixCalls={Interceptor.PostfixCalls}"); IReadOnlyDictionary<KeyCode, HashSet<string>> owners = Interceptor.Owners; if (owners.Count == 0) { Log(" no mod function keys discovered yet."); return; } foreach (KeyValuePair<KeyCode, HashSet<string>> item in owners) { Log($" {item.Key} <- {string.Join(", ", item.Value)}"); } } private unsafe static void KeyCmd(string[] p) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: 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_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) string text = ((p.Length > 2) ? p[2].ToLowerInvariant() : null); if (text == "press" && p.Length > 3) { if (Enum.TryParse<KeyCode>(p[3], ignoreCase: true, out KeyCode result) && (int)result != 0) { if (p.Length > 4) { Interceptor.RequestPress(result, p[4]); Log($"queued synthetic press: {result} -> {p[4]}"); return; } int num = 0; foreach (string item in Interceptor.ModsForKey(result)) { Interceptor.RequestPress(result, item); num++; } Log((num > 0) ? $"queued synthetic press: {result} -> {num} mod(s)" : $"no mod polls {result} yet (nothing to press)."); } else { Log("unknown key '" + p[3] + "' (use a UnityEngine.KeyCode name, e.g. F9)."); } } else if (text == "master" && p.Length > 3) { if (Enum.TryParse<KeyCode>(p[3], ignoreCase: true, out KeyCode result2) && (int)result2 != 0) { Preferences.SetMasterKey(((object)(*(KeyCode*)(&result2))/*cast due to .constrained prefix*/).ToString()); MelonPreferences.Save(); Log("master overlay key = " + ((object)Preferences.MasterKey/*cast due to .constrained prefix*/).ToString()); } else { Log("unknown key '" + p[3] + "' (use a UnityEngine.KeyCode name, e.g. F6)."); } } else { Log("usage: hotline key press <KeyCode> | hotline key master <KeyCode>"); } } private static void Hud(string[] p) { switch ((p.Length > 2) ? p[2].ToLowerInvariant() : null) { case "move": Preferences.SetHudPos(FloatArg(p, 3, Preferences.HudX), FloatArg(p, 4, Preferences.HudY)); WindowLayout.Get("overview", 8f, 8f, 320f, 300f, dvis: true); WindowLayout.SetPos("overview", Preferences.HudX, Preferences.HudY); WindowLayout.Save(); Log($"overview pos = {Preferences.HudX:F0},{Preferences.HudY:F0}"); break; case "font": Preferences.SetHudFontSize((int)FloatArg(p, 3, Preferences.HudFontSize)); MelonPreferences.Save(); Log("overlay font = " + Preferences.HudFontSize); break; case "reset": Preferences.SetHudPos(8f, 8f); Preferences.SetHudFontSize(12); WindowLayout.Reset("overview"); MelonPreferences.Save(); Log("overlay reset (pos 8,8 font 12)"); break; default: { bool num = BoolArg(p, 2, !Preferences.ShowHud); Preferences.SetShowHud(num); if (num) { WindowLayout.SetVisible("overview", v: true); } MelonPreferences.Save(); Log("overlay = " + Preferences.ShowHud); break; } } } private static void PanelsList() { Log($"overview [{(WindowLayout.IsVisible("overview") ? "on" : "off")}] timeline [{(WindowLayout.IsVisible("timeline") ? "on" : "off")}]"); IReadOnlyList<PanelModel> all = PanelRegistry.All; if (all.Count == 0) { Log("no mod panels registered yet (enter the world; mods register on load)."); return; } for (int i = 0; i < all.Count; i++) { PanelModel panelModel = all[i]; Log($" {panelModel.Id,-16} [{(WindowLayout.IsVisible(panelModel.Id) ? "on" : "off")}] actions={panelModel.Actions.Count} toggles={panelModel.Toggles.Count} title=\"{panelModel.Title}\""); } } private static void PanelCmd(string[] p) { if (p.Length <= 2) { Log("usage: hotline panel <id> [on|off|move <x> <y>|size <w> <h>|reset]. 'hotline panels' lists ids."); return; } string text = p[2]; switch ((p.Length > 3) ? p[3].ToLowerInvariant() : "on") { case "on": WindowLayout.SetVisible(text, v: true); Log("panel " + text + " = on"); break; case "off": WindowLayout.SetVisible(text, v: false); Log("panel " + text + " = off"); break; case "move": WindowLayout.Get(text, 8f, 8f, 320f, 240f, dvis: true); WindowLayout.SetPos(text, FloatArg(p, 4, 8f), FloatArg(p, 5, 8f)); WindowLayout.Save(); Log("panel " + text + " moved"); break; case "size": WindowLayout.Get(text, 8f, 8f, 320f, 240f, dvis: true); WindowLayout.SetSize(text, FloatArg(p, 4, 320f), FloatArg(p, 5, 240f)); WindowLayout.Save(); Log("panel " + text + " resized"); break; case "reset": WindowLayout.Reset(text); Log("panel " + text + " reset"); break; default: Log("usage: hotline panel <id> [on|off|move <x> <y>|size <w> <h>|reset]"); break; } } private static void ActCmd(string[] p) { if (p.Length <= 2) { Log("usage: hotline act <actionId> (see the panel; ids look like 'Siesta:force-cosmetic')."); } else { Log(PanelRegistry.Invoke(p[2]) ? ("ran " + p[2]) : ("no action '" + p[2] + "'")); } } private static void ToggleCmd(string[] p) { if (p.Length <= 2) { Log("usage: hotline toggle <toggleId> [on|off] (omit to flip)."); return; } string text = p[2]; bool value = BoolArg(p, 3, !PanelRegistry.GetToggle(text)); Log(PanelRegistry.SetToggle(text, value) ? $"{text} = {value}" : ("no toggle '" + text + "'")); } private static void LogCmd(string[] p) { string text = ((p.Length > 2) ? p[2] : "all"); int n = IntArg(p, 3, 25); List<LogEntry> list = (text.Equals("all", StringComparison.OrdinalIgnoreCase) ? LogHub.Timeline(n) : LogHub.Channel(text, n)); if (list.Count == 0) { Log($"log '{text}': no entries (channels: {string.Join(", ", LogHub.Channels())})."); return; } Log($"log '{text}' (last {list.Count}):"); foreach (LogEntry item in list) { string value = ((item.Lvl == 2) ? "E" : ((item.Lvl == 1) ? "W" : "I")); Log($" {item.Time} {value} [{item.Ch}] {item.Msg}"); } } private static void KeysList() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList<HotkeyBinding> all = HotkeyRegistry.All; if (all.Count == 0) { Log("no central hotkeys registered. Master overlay key = " + ((object)Preferences.MasterKey/*cast due to .constrained prefix*/).ToString() + "."); return; } Log($"central hotkeys (master overlay key = {Preferences.MasterKey}):"); for (int i = 0; i < all.Count; i++) { HotkeyBinding hotkeyBinding = all[i]; Log($" {hotkeyBinding.Owner}:{hotkeyBinding.Label} = {(((int)hotkeyBinding.Key == 0) ? "(button only)" : ((object)Unsafe.As<KeyCode, KeyCode>(ref hotkeyBinding.Key)/*cast due to .constrained prefix*/).ToString())}"); } } private static int IntArg(string[] p, int idx, int def) { if (p.Length > idx && int.TryParse(p[idx], out var result)) { return result; } return def; } private static float FloatArg(string[] p, int idx, float def) { if (p.Length > idx && float.TryParse(p[idx], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } return def; } private static bool BoolArg(string[] p, int idx, bool toggleDefault) { if (p.Length <= idx) { return toggleDefault; } switch (p[idx].ToLowerInvariant()) { case "on": case "true": case "1": case "yes": return true; case "off": case "false": case "0": case "no": return false; default: return toggleDefault; } } internal static void Log(string msg) { Instance log = Core.Log; if (log != null) { log.Msg("[hotline] " + msg); } LogHub.Write("Hotline", 0, msg); } } [HarmonyPatch(typeof(Console), "SubmitCommand", new Type[] { typeof(string) })] internal static class Hotline_Console_SubmitCommand_String_Patch { private static bool Prefix(string args) { try { return !HotlineConsole.TryHandle(args); } catch { return true; } } } [HarmonyPatch(typeof(Console), "SubmitCommand", new Type[] { typeof(List<string>) })] internal static class Hotline_Console_SubmitCommand_List_Patch { private static bool Prefix(List<string> args) { try { return !HotlineConsole.TryHandle(args); } catch { return true; } } } } namespace Hotline.UI { internal sealed class WinState { public float X; public float Y; public float W; public float H; public bool Visible; } internal static class WindowLayout { private static readonly Dictionary<string, WinState> _map = new Dictionary<string, WinState>(); private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; private static bool _loaded; private static void EnsureLoaded() { if (!_loaded) { _loaded = true; Parse(Preferences.WindowLayouts); } } internal static WinState Get(string id, float dx, float dy, float dw, float dh, bool dvis) { EnsureLoaded(); if (!_map.TryGetValue(id, out var value)) { value = new WinState { X = dx, Y = dy, W = dw, H = dh, Visible = dvis }; _map[id] = value; } return value; } internal static bool IsVisible(string id) { EnsureLoaded(); if (_map.TryGetValue(id, out var value)) { return value.Visible; } return false; } internal static void SetPos(string id, float x, float y) { if (_map.TryGetValue(id, out var value)) { value.X = x; value.Y = y; } } internal static void SetSize(string id, float w, float h) { if (_map.TryGetValue(id, out var value)) { value.W = w; value.H = h; } } internal static void SetVisible(string id, bool v) { EnsureLoaded(); if (!_map.TryGetValue(id, out var value)) { value = new WinState { X = 8f, Y = 8f, W = 360f, H = 280f }; _map[id] = value; } value.Visible = v; Save(); } internal static bool Toggle(string id) { bool flag = !IsVisible(id); SetVisible(id, flag); return flag; } internal static void Reset(string id) { EnsureLoaded(); _map.Remove(id); Save(); } internal static void Save() { Preferences.SetWindowLayouts(Serialize()); MelonPreferences.Save(); } private static void Parse(string s) { _map.Clear(); if (string.IsNullOrWhiteSpace(s)) { return; } string[] array = s.Split(';'); foreach (string text in array) { if (string.IsNullOrWhiteSpace(text)) { continue; } int num = text.IndexOf('='); if (num > 0) { string key = text.Substring(0, num).Trim(); string[] array2 = text.Substring(num + 1).Split(','); if (array2.Length >= 5 && F(array2[0], out var v) && F(array2[1], out var v2) && F(array2[2], out var v3) && F(array2[3], out var v4)) { _map[key] = new WinState { X = v, Y = v2, W = v3, H = v4, Visible = (array2[4].Trim() == "1") }; } } } } private static string Serialize() { StringBuilder stringBuilder = new StringBuilder(128); bool flag = true; foreach (KeyValuePair<string, WinState> item in _map) { if (!flag) { stringBuilder.Append(';'); } flag = false; WinState value = item.Value; stringBuilder.Append(item.Key).Append('=').Append(N(value.X)) .Append(',') .Append(N(value.Y)) .Append(',') .Append(N(value.W)) .Append(',') .Append(N(value.H)) .Append(',') .Append(value.Visible ? '1' : '0'); } return stringBuilder.ToString(); } private static bool F(string s, out float v) { return float.TryParse(s.Trim(), NumberStyles.Float, Inv, out v); } private static string N(float v) { return ((int)v).ToString(Inv); } } internal static class WindowManager { private struct Hit { public string Win; public Rect Rect; public int Type; public string Arg; } private enum Drag { None, Move, Resize } private const float TitleH = 20f; private const float Grip = 14f; private const float Pad = 6f; private const float Gap = 4f; private const float MinW = 160f; private const float MinH = 70f; private static float _btnH = 22f; private static bool _stylesReady; private static GUIStyle _bg; private static GUIStyle _title; private static GUIStyle _close; private static GUIStyle _grip; private static GUIStyle _text; private static GUIStyle _btn; private static Texture2D _bgTex; private static Texture2D _titleTex; private static Texture2D _whiteTex; private static Texture2D _gripTex; private static Vector2 _mouse; private static bool _cursorFree; private static readonly Color BtnNormal = new Color(0.3f, 0.32f, 0.38f, 0.98f); private static readonly Color BtnHover = new Color(0.44f, 0.47f, 0.56f, 0.98f); private static readonly Color BtnOn = new Color(0.36f, 0.4f, 0.8f, 0.98f); private static readonly Color BtnOnHover = new Color(0.47f, 0.51f, 0.92f, 0.98f); private static readonly Color BtnGreen = new Color(0.2f, 0.52f, 0.32f, 0.98f); private static readonly Color BtnGreenHover = new Color(0.28f, 0.64f, 0.42f, 0.98f); private static readonly Color BtnRed = new Color(0.6f, 0.27f, 0.31f, 0.98f); private static readonly Color BtnRedHover = new Color(0.73f, 0.35f, 0.39f, 0.98f); private static readonly Color TitleFront = new Color(0.97f, 0.98f, 1f); private static readonly Color TitleDim = new Color(0.6f, 0.62f, 0.7f); private static readonly Color TitleBarCol = new Color(0.12f, 0.13f, 0.2f, 0.99f); private static readonly List<string> _order = new List<string>(); private static readonly Dictionary<string, float> _scroll = new Dictionary<string, float>(); private static readonly Dictionary<string, Rect> _frameRects = new Dictionary<string, Rect>(); private static readonly Dictionary<string, Rect> _titleRects = new Dictionary<string, Rect>(); private static readonly Dictionary<string, Rect> _closeRects = new Dictionary<string, Rect>(); private static readonly Dictionary<string, Rect> _gripRects = new Dictionary<string, Rect>(); private static readonly Dictionary<string, Rect> _bodyRects = new Dictionary<string, Rect>(); private static readonly Dictionary<string, float> _contentH = new Dictionary<string, float>(); private static readonly List<Hit> _hits = new List<Hit>(32); private static readonly Dictionary<string, string> _textCache = new Dictionary<string, string>(); private static float _nextText; private static Drag _drag = Drag.None; private static string _dragId; private static Vector2 _grab; private static Rect _startRect; internal static void Draw() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Invalid comparison between Unknown and I4 //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) EnsureStyles(); int hudFontSize = Preferences.HudFontSize; GUIStyle text = _text; int fontSize = (_btn.fontSize = hudFontSize); text.fontSize = fontSize; _title.fontSize = hudFontSize; _btnH = Mathf.Max(22f, (float)hudFontSize + 10f); _cursorFree = (int)Cursor.lockState != 1; _mouse = new Vector2(Input.mousePosition.x, (float)Screen.height - Input.mousePosition.y); RefreshWindowList(); MaybeRebuildText(); _hits.Clear(); _frameRects.Clear(); _titleRects.Clear(); _closeRects.Clear(); _gripRects.Clear(); _bodyRects.Clear(); string text2 = null; for (int num2 = _order.Count - 1; num2 >= 0; num2--) { if (WindowLayout.IsVisible(_order[num2])) { text2 = _order[num2]; break; } } for (int i = 0; i < _order.Count; i++) { string text3 = _order[i]; if (WindowLayout.IsVisible(text3)) { DrawWindow(text3, text3 == text2); } } } private static void RefreshWindowList() { WindowLayout.Get("overview", 8f, 8f, 320f, 300f, dvis: true); EnsureInOrder("overview"); IReadOnlyList<PanelModel> all = PanelRegistry.All; for (int i = 0; i < all.Count; i++) { WindowLayout.Get(all[i].Id, 360f + (float)(i % 3) * 28f, 40f + (float)i * 34f, 320f, 240f, dvis: false); EnsureInOrder(all[i].Id); } WindowLayout.Get("timeline", 8f, 360f, 560f, 220f, dvis: false); EnsureInOrder("timeline"); } private static void EnsureInOrder(string id) { if (!_order.Contains(id)) { _order.Add(id); } } private static void DrawWindow(string id, bool isFront) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) WinState winState = WindowLayout.Get(id, 8f, 8f, 320f, 240f, dvis: false); float num = Mathf.Clamp(winState.W, 160f, (float)Screen.width); float num2 = Mathf.Clamp(winState.H, 70f, (float)Screen.height); float num3 = Mathf.Clamp(winState.X, 0f, Mathf.Max(0f, (float)Screen.width - num)); float num4 = Mathf.Clamp(winState.Y, 0f, Mathf.Max(0f, (float)Screen.height - num2)); Rect val = default(Rect); ((Rect)(ref val))..ctor(num3, num4, num, num2); _frameRects[id] = val; GUI.Box(val, GUIContent.none, _bg); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(num3, num4, num, 20f); GUI.contentColor = (isFront ? TitleFront : TitleDim); GUI.Box(val2, " " + TitleOf(id), _title); GUI.contentColor = Color.white; _titleRects[id] = val2; Rect val3 = default(Rect); ((Rect)(ref val3))..ctor(((Rect)(ref val)).xMax - 20f, num4, 20f, 20f); GUI.backgroundColor = ((_cursorFree && ((Rect)(ref val3)).Contains(_mouse)) ? BtnRedHover : TitleBarCol); GUI.Box(val3, "x", _close); GUI.backgroundColor = Color.white; _closeRects[id] = val3; Rect val4 = default(Rect); ((Rect)(ref val4))..ctor(num3 + 2f, num4 + 20f, num - 4f, num2 - 20f - 2f); _bodyRects[id] = val4; float scroll = GetScroll(id); float innerW = ((Rect)(ref val4)).width - 12f - 2f; GUI.BeginGroup(val4); float localY = 6f; DrawBody(id, innerW, scroll, val4, ref localY); GUI.EndGroup(); float num5 = localY + 6f - 4f; _contentH[id] = num5; float num6 = Mathf.Max(0f, num5 - ((Rect)(ref val4)).height); if (scroll > num6) { _scroll[id] = num6; } Rect val5 = default(Rect); ((Rect)(ref val5))..ctor(((Rect)(ref val)).xMax - 14f, ((Rect)(ref val)).yMax - 14f, 14f, 14f); GUI.Box(val5, GUIContent.none, _grip); _gripRects[id] = val5; } private static void DrawBody(string id, float innerW, float scroll, Rect bodyRect, ref float localY) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00da: 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_008d: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) string text = KindOf(id); if (text == "overview") { Label(innerW, scroll, bodyRect, ref localY, "<b>Hotline</b> - toggle a mod's panel"); IReadOnlyList<PanelModel> all = PanelRegistry.All; if (all.Count == 0) { Label(innerW, scroll, bodyRect, ref localY, "<color=#8a8f9e>no mod panels registered yet</color>"); } for (int i = 0; i < all.Count; i++) { Button(id, innerW, scroll, bodyRect, ref localY, all[i].Title, 3, all[i].Id, WindowLayout.IsVisible(all[i].Id)); } Button(id, innerW, scroll, bodyRect, ref localY, "Timeline (logs)", 3, "timeline", WindowLayout.IsVisible("timeline")); return; } if (text == "timeline") { Label(innerW, scroll, bodyRect, ref localY, Text("timeline")); return; } PanelModel panelModel = PanelRegistry.Get(id); if (panelModel == null) { Label(innerW, scroll, bodyRect, ref localY, "(panel gone)"); return; } Label(innerW, scroll, bodyRect, ref localY, Text("panel:" + id)); for (int j = 0; j < panelModel.Actions.Count; j++) { Button(id, innerW, scroll, bodyRect, ref localY, panelModel.Actions[j].Label, 1, panelModel.Actions[j].Id, on: false); } for (int k = 0; k < panelModel.Toggles.Count; k++) { bool toggle = PanelRegistry.GetToggle(panelModel.Toggles[k].Id); Button(id, innerW, scroll, bodyRect, ref localY, (toggle ? "[x] " : "[ ] ") + panelModel.Toggles[k].Label, 2, panelModel.Toggles[k].Id, toggle); } if (panelModel.HasLog) { Label(innerW, scroll, bodyRect, ref localY, Text("log:" + id)); } } private static void Label(float innerW, float scroll, Rect bodyRect, ref float localY, string text) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(text)) { GUIContent val = new GUIContent(text); float num = _text.CalcHeight(val, innerW); float num2 = localY - scroll; if (num2 + num > 0f && num2 < ((Rect)(ref bodyRect)).height) { GUI.Label(new Rect(6f, num2, innerW, num), val, _text); } localY += num + 4f; } } private static void Button(string id, float innerW, float scroll, Rect bodyRect, ref float localY, string label, int type, string arg, bool on, int accent = 0) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_000d: 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_0086: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Min(innerW, _btn.CalcSize(new GUIContent(label)).x + 16f); float num2 = localY - scroll; if (num2 + _btnH > 0f && num2 < ((Rect)(ref bodyRect)).height) { Rect rect = default(Rect); ((Rect)(ref rect))..ctor(((Rect)(ref bodyRect)).x + 6f, ((Rect)(ref bodyRect)).y + num2, num, _btnH); bool hover = _cursorFree && ((Rect)(ref rect)).Contains(_mouse); GUI.backgroundColor = ButtonColor(on, accent, hover); GUI.Box(new Rect(6f, num2, num, _btnH), label, _btn); GUI.backgroundColor = Color.white; _hits.Add(new Hit { Win = id, Rect = rect, Type = type, Arg = arg }); } localY += _btnH + 4f; } private static Color ButtonColor(bool on, int accent, bool hover) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (on) { if (!hover) { return BtnOn; } return BtnOnHover; } switch (accent) { case 1: if (!hover) { return BtnGreen; } return BtnGreenHover; case 2: if (!hover) { return BtnRed; } return BtnRedHover; default: if (!hover) { return BtnNormal; } return BtnHover; } } internal static void HandleInput() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: 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_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) if ((int)Cursor.lockState == 1) { _drag = Drag.None; return; } Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(Input.mousePosition.x, (float)Screen.height - Input.mousePosition.y); float y = Input.mouseScrollDelta.y; if (Mathf.Abs(y) > 0.01f) { string text = TopWindowAt(val, bodyOnly: true); if (text != null) { float num = Mathf.Max(0f, GetContentH(text) - GetBodyH(text)); _scroll[text] = Mathf.Clamp(GetScroll(text) - y * 28f, 0f, num); } } if (Input.GetMouseButtonDown(0)) { string text2 = TopWindowAt(val, bodyOnly: false); if (text2 == null) { return; } BringToFront(text2); if (_closeRects.TryGetValue(text2, out var value) && ((Rect)(ref value)).Contains(val)) { WindowLayout.SetVisible(text2, v: false); return; } if (_gripRects.TryGetValue(text2, out var value2) && ((Rect)(ref value2)).Contains(val)) { _drag = Drag.Resize; _dragId = text2; _grab = val; _startRect = _frameRects[text2]; return; } if (_titleRects.TryGetValue(text2, out var value3) && ((Rect)(ref value3)).Contains(val)) { _drag = Drag.Move; _dragId = text2; Rect val2 = _frameRects[text2]; _grab = val - new Vector2(((Rect)(ref val2)).x, ((Rect)(ref val2)).y); return; } for (int i = 0; i < _hits.Count; i++) { Hit hit = _hits[i]; if (!(hit.Win != text2) && ((Rect)(ref hit.Rect)).Contains(val)) { switch (hit.Type) { case 1: PanelRegistry.Invoke(hit.Arg); break; case 2: PanelRegistry.SetToggle(hit.Arg, !PanelRegistry.GetToggle(hit.Arg)); break; case 3: WindowLayout.Toggle(hit.Arg); break; } break; } } } else if (Input.GetMouseButton(0) && _drag != Drag.None) { if (_drag == Drag.Move) { WindowLayout.SetPos(_dragId, val.x - _grab.x, val.y - _grab.y); } else { WindowLayout.SetSize(_dragId, Mathf.Max(160f, ((Rect)(ref _startRect)).width + (val.x - _grab.x)), Mathf.Max(70f, ((Rect)(ref _startRect)).height + (val.y - _grab.y))); } } else if (Input.GetMouseButtonUp(0) && _drag != Drag.None) { _drag = Drag.None; WindowLayout.Save(); } } private static string TopWindowAt(Vector2 m, bool bodyOnly) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) for (int num = _order.Count - 1; num >= 0; num--) { string text = _order[num]; if (WindowLayout.IsVisible(text) && (bodyOnly ? _bodyRects : _frameRects).TryGetValue(text, out var value) && ((Rect)(ref value)).Contains(m)) { return text; } } return null; } private static void BringToFront(string id) { if (_order.Remove(id)) { _order.Add(id); } } private static void MaybeRebuildText() { if (Time.unscaledTime < _nextText) { return; } _nextText = Time.unscaledTime + 0.1f; IReadOnlyList<PanelModel> all = PanelRegistry.All; for (int i = 0; i < all.Count; i++) { PanelModel panelModel = all[i]; _textCache["panel:" + panelModel.Id] = BuildPanelText(panelModel); if (panelModel.HasLog) { _textCache["log:" + panelModel.Id] = BuildLogText(panelModel.Id, 16); } } _textCache["timeline"] = BuildTimelineText(220); } private static string Text(string key) { if (!_textCache.TryGetValue(key, out var value)) { return ""; } return value; } private static string BuildPanelText(PanelModel p) { StringBuilder stringBuilder = new StringBuilder(256); for (int i = 0; i < p.Texts.Count; i++) { string text = null; try { text = p.Texts[i]?.Invoke(); } catch { } if (!string.IsNullOrEmpty(text)) { Line(stringBuilder, text); } } return stringBuilder.ToString(); } private static string BuildLogText(string channel, int n) { List<LogEntry> list = LogHub.Channel(channel, n); if (list.Count == 0) { return ""; } StringBuilder stringBuilder = new StringBuilder(256); Line(stringBuilder, "<b>log</b>"); for (int i = 0; i < list.Count; i++) { AppendLogLine(stringBuilder, list[i], withChannel: false); } return stringBuilder.ToString(); } private static string BuildTimelineText(int n) { List<LogEntry> list = LogHub.Timeline(n); if (list.Count == 0) { return "(no log output yet)"; } StringBuilder stringBuilder = new StringBuilder(1024); for (int i = 0; i < list.Count; i++) { AppendLogLine(stringBuilder, list[i], withChannel: true); } return stringBuilder.ToString(); } private static void AppendLogLine(StringBuilder sb, LogEntry e, bool withChannel) { if (sb.Length > 0) { sb.Append('\n'); } string value = ((e.Lvl == 2) ? "#f55" : ((e.Lvl == 1) ? "#fd5" : "#cdd6f4")); sb.Append("<color=").Append(value).Append('>') .Append(e.Time) .Append(' '); if (withChannel) { sb.Append('[').Append(e.Ch).Append("] "); } sb.Append(e.Msg).Append("</color>"); } private static void Line(StringBuilder sb, string s) { if (sb.Length > 0) { sb.Append('\n'); } sb.Append(s); } private static string KindOf(string id) { if (!(id == "overview")) { if (!(id == "timeline")) { return "panel"; } return "timeline"; } return "overview"; } private static string TitleOf(string id) { if (id == "overview") { return "Hotline"; } if (id == "timeline") { return "Timeline (all logs)"; } PanelModel panelModel = PanelRegistry.Get(id); if (panelModel == null) { return id; } return panelModel.Title; } private static float GetScroll(string id) { if (!_scroll.TryGetValue(id, out var value)) { return 0f; } return value; } private static float GetContentH(string id) { if (!_contentH.TryGetValue(id, out var value)) { return 0f; } return value; } private static float GetBodyH(string id) { if (!_bodyRects.TryGetValue(id, out var value)) { return 0f; } return ((Rect)(ref value)).height; } private static void EnsureStyles() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_009f: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Expected O, but got Unknown //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: 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_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Expected O, but got Unknown //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_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Expected O, but got Unknown //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Expected O, but got Unknown //IL_0229: Unknown result type (might be due to invalid IL or missing references) if (!_stylesReady) { _stylesReady = true; _bgTex = Solid(new Color(0.055f, 0.065f, 0.085f, 0.93f)); _titleTex = Solid(new Color(0.12f, 0.13f, 0.2f, 0.99f)); _whiteTex = Solid(Color.white); _gripTex = Solid(new Color(0.45f, 0.48f, 0.85f, 0.9f)); _bg = new GUIStyle { padding = new RectOffset(0, 0, 0, 0) }; _bg.normal.background = _bgTex; _title = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)3, fontStyle = (FontStyle)1, richText = true }; _title.normal.background = _titleTex; _title.normal.textColor = new Color(0.9f, 0.92f, 1f); _close = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)4, fontStyle = (FontStyle)1 }; _close.normal.background = _whiteTex; _close.normal.textColor = new Color(1f, 0.78f, 0.78f); _grip = new GUIStyle(); _grip.normal.background = _gripTex; _text = new GUIStyle(GUI.skin.label) { richText = true, wordWrap = true, alignment = (TextAnchor)0 }; _text.normal.textColor = new Color(0.84f, 0.87f, 0.93f); _btn = new GUIStyle { alignment = (TextAnchor)4, richText = true, clipping = (TextClipping)0, padding = new RectOffset(6, 6, 2, 2), wordWrap = false }; _btn.normal.background = _whiteTex; _btn.normal.textColor = Color.white; } } private static Texture2D Solid(Color c) { //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) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1); val.SetPixel(0, 0, c); val.Apply(); ((Object)val).hideFlags = (HideFlags)61; return val; } } } namespace Hotline.Panels { internal sealed class ActionItem { public string Id; public string Label; public Action Run; } internal sealed class ToggleItem { public string Id; public string Label; public Func<bool> Get; public Action<bool> Set; } internal sealed class PanelModel { public string Id; public string Title; public bool HasLog; public readonly List<Func<string>> Texts = new List<Func<string>>(2); public readonly List<ActionItem> Actions = new List<ActionItem>(4); public readonly List<ToggleItem> Toggles = new List<ToggleItem>(4); } internal static class PanelRegistry { private static readonly List<PanelModel> _panels = new List<PanelModel>(8); private static readonly Dictionary<string, PanelModel> _byId = new Dictionary<string, PanelModel>(8); private static readonly Dictionary<string, ActionItem> _actions = new Dictionary<string, ActionItem>(16); private static readonly Dictionary<string, ToggleItem> _toggles = new Dictionary<string, ToggleItem>(16); internal static IReadOnlyList<PanelModel> All => _panels; internal static int Count => _panels.Count; internal static PanelModel GetOrCreate(string id, string title) { if (string.IsNullOrEmpty(id)) { id = "misc"; } if (!_byId.TryGetValue(id, out var value)) { value = new PanelModel { Id = id, Title = (string.IsNullOrEmpty(title) ? id : title) }; _byId[id] = value; _panels.Add(value); } else if (!string.IsNullOrEmpty(title)) { value.Title = title; } return value; } internal static PanelModel Get(string id) { if (string.IsNullOrEmpty(id)) { return null; } _byId.TryGetValue(id, out var value); return value; } internal static void RegisterPanel(string id, string title) { GetOrCreate(id, title); } internal static void RegisterText(string panelId, Func<string> provider) { if (provider != null) { GetOrCreate(panelId, null).Texts.Add(provider); } } internal static void RegisterAction(string panelId, string actionId, string label, Action run) { if (run != null && !string.IsNullOrEmpty(actionId)) { PanelModel orCreate = GetOrCreate(panelId, null); ActionItem actionItem = new ActionItem { Id = actionId, Label = (label ?? actionId), Run = run }; orCreate.Actions.RemoveAll((ActionItem a) => a.Id == actionId); orCreate.Actions.Add(actionItem); _actions[actionId] = actionItem; } } internal static void RegisterToggle(string panelId, string toggleId, string label, Func<bool> get, Action<bool> set) { if (get != null && set != null && !string.IsNullOrEmpty(toggleId)) { PanelModel orCreate = GetOrCreate(panelId, null); ToggleItem toggleItem = new ToggleItem { Id = toggleId, Label = (label ?? toggleId), Get = get, Set = set }; orCreate.Toggles.RemoveAll((ToggleItem t) => t.Id == toggleId); orCreate.Toggles.Add(toggleItem); _toggles[toggleId] = toggleItem; } } internal static void BindPanelLog(string panelId) { GetOrCreate(panelId, null).HasLog = true; } internal static bool Invoke(string actionId) { if (string.IsNullOrEmpty(actionId) || !_actions.TryGetValue(actionId, out var value)) { return false; } try { value.Run?.Invoke(); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("[hotline] action '" + actionId + "' threw: " + ex.Message); } } return true; } internal static bool SetToggle(string toggleId, bool value) { if (string.IsNullOrEmpty(toggleId) || !_toggles.TryGetValue(toggleId, out var value2)) { return false; } try { value2.Set?.Invoke(value); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("[hotline] toggle '" + toggleId + "' threw: " + ex.Message); } } return true; } internal static bool GetToggle(string toggleId) { if (!string.IsNullOrEmpty(toggleId) && _toggles.TryGetValue(toggleId, out var value)) { try { return value.Get != null && value.Get(); } catch { } } return false; } } } namespace Hotline.Logging { internal struct LogEntry { public long Seq; public string Ch; public int Lvl; public string Msg; public string Time; } internal static class LogHub { private const int PerChannelMax = 400; private const int TimelineMax = 1200; private static readonly object _lock = new object(); private static readonly Dictionary<string, Queue<LogEntry>> _channels = new Dictionary<string, Queue<LogEntry>>(); private static readonly Queue<LogEntry> _timeline = new Queue<LogEntry>(1200); private static long _seq; internal static void Install() { } internal static void Uninstall() { } internal static void Write(string channel, int lvl, string msg) { if (string.IsNullOrEmpty(msg)) { return; } if (string.IsNullOrEmpty(channel)) { channel = "misc"; } if (lvl < 0) { lvl = 0; } else if (lvl > 2) { lvl = 2; } lock (_lock) { LogEntry item = new LogEntry { Seq = ++_seq, Ch = channel, Lvl = lvl, Msg = msg, Time = DateTime.Now.ToString("HH:mm:ss") }; if (!_channels.TryGetValue(channel, out var value)) { value = new Queue<LogEntry>(64); _channels[channel] = value; } value.Enqueue(item); while (value.Count > 400) { value.Dequeue(); } _timeline.Enqueue(item); while (_timeline.Count > 1200) { _timeline.Dequeue(); } } } internal static List<LogEntry> Timeline(int n) { lock (_lock) { return TakeLast(_timeline, n); } } internal static List<LogEntry> Channel(string channel, int n) { lock (_lock) { Queue<LogEntry> value; return _channels.TryGetValue(channel, out value) ? TakeLast(value, n) : new List<LogEntry>(); } } internal static List<string> Channels() { lock (_lock) { return new List<string>(_channels.Keys); } } internal static void Clear() { lock (_lock) { _channels.Clear(); _timeline.Clear(); } } private static List<LogEntry> TakeLast(Queue<LogEntry> q, int n) { if (n <= 0 || n >= q.Count) { return new List<LogEntry>(q); } List<LogEntry> list = new List<LogEntry>(q); return list.GetRange(list.Count - n, n); } } } namespace Hotline.Hotkeys { internal sealed class HotkeyBinding { public string Owner; public string Label; public KeyCode Key; public Action Run; } internal static class HotkeyRegistry { private static readonly List<HotkeyBinding> _binds = new List<HotkeyBinding>(16); internal static IReadOnlyList<HotkeyBinding> All => _binds; internal static void Register(string owner, string label, KeyCode key, Action run) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) if (run == null || string.IsNullOrEmpty(label)) { return; } owner = (string.IsNullOrEmpty(owner) ? "misc" : owner); if ((int)key != 0) { for (int i = 0; i < _binds.Count; i++) { HotkeyBinding hotkeyBinding = _binds[i]; if (hotkeyBinding.Key == key && hotkeyBinding.Owner != owner) { Instance log = Core.Log; if (log != null) { log.Warning($"[hotline] hotkey conflict: {key} is bound by both '{hotkeyBinding.Owner}' and '{owner}' - both will fire."); } } } } _binds.RemoveAll((HotkeyBinding b) => b.Owner == owner && b.Label == label); _binds.Add(new HotkeyBinding { Owner = owner, Label = label, Key = key, Run = run }); } internal static void Poll() { //IL_0014: 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) for (int i = 0; i < _binds.Count; i++) { HotkeyBinding hotkeyBinding = _binds[i]; if ((int)hotkeyBinding.Key == 0 || !Input.GetKeyDown(hotkeyBinding.Key)) { continue; } try { hotkeyBinding.Run?.Invoke(); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning($"[hotline] hotkey '{hotkeyBinding.Owner}:{hotkeyBinding.Label}' threw: {ex.Message}"); } } } } } internal static class Interceptor { private static bool _installed; private static bool _failed; private static readonly Assembly _selfAsm = typeof(Interceptor).Assembly; [ThreadStatic] private static string _currentOwner; private static readonly Dictionary<MethodBase, string> _ownerOf = new Dictionary<MethodBase, string>(); private static readonly Dictionary<KeyCode, HashSet<string>> _owners = new Dictionary<KeyCode, HashSet<string>>(); private static readonly Dictionary<(KeyCode key, string mod), int> _inject = new Dictionary<(KeyCode, string), int>(); internal static long PostfixCalls; private static readonly KeyCode[] Targets; private static readonly string[] Lifecycle; private static int _lastSummonFrame; internal static bool Installed { get { if (_installed) { return !_failed; } return false; } } internal static IReadOnlyDictionary<KeyCode, HashSet<string>> Owners => _owners; private static bool IsTarget(KeyCode k) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between I4 and Unknown for (int i = 0; i < Targets.Length; i++) { if ((int)Targets[i] == (int)k) { return true; } } return false; } internal static void Install(Harmony h) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Expected O, but got Unknown //IL_017f: Expected O, but got Unknown if (_installed || _failed || h == null) { return; } try { MethodInfo method = typeof(Input).GetMethod("GetKeyDown", new Type[1] { typeof(KeyCode) }); if (method == null) { _failed = true; Instance log = Core.Log; if (log != null) { log.Warning("[hotline] interceptor: Input.GetKeyDown(KeyCode) not found - auto-interception disabled."); } return; } h.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Interceptor), "GetKeyDownPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo = AccessTools.Method(typeof(Interceptor), "OwnerPre", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Interceptor), "OwnerFin", (Type[])null, (Type[])null); foreach (MelonMod item in RegisteredMods()) { if (item == null) { continue; } Type type = ((object)item).GetType(); if (type.Assembly == _selfAsm) { continue; } string value = NameOf(item); for (int i = 0; i < Lifecycle.Length; i++) { MethodInfo method2 = type.GetMethod(Lifecycle[i], BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method2 == null || method2.IsAbstract || method2.DeclaringType == typeof(MelonMod) || _ownerOf.ContainsKey(method2)) { continue; } try { h.Patch((MethodBase)method2, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null); _ownerOf[method2] = value; } catch (Exception ex) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning($"[hotline] wrap {value}.{Lifecycle[i]} failed: {ex.Message}"); } } } } _installed = true; Instance log3 = Core.Log; if (log3 != null) { log3.Msg($"[hotline] auto-interception installed (Input.GetKeyDown + {_ownerOf.Count} mod lifecycle hooks)."); } } catch (Exception ex2) { _failed = true; Instance log4 = Core.Log; if (log4 != null) { log4.Warning("[hotline] interceptor patch failed (auto-interception disabled, game unaffected): " + ex2.Message); } } } private static void OwnerPre(MethodBase __originalMethod, out string __state) { __state = _currentOwner; _ownerOf.TryGetValue(__originalMethod, out var value); _currentOwner = value; } private static void OwnerFin(string __state) { _currentOwner = __state; } private static void GetKeyDownPostfix(KeyCode __0, ref bool __result) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) try { if (_failed || !Preferences.InterceptFunctionKeys || !IsTarget(__0)) { return; } PostfixCalls++; string currentOwner = _currentOwner; if (string.IsNullOrEmpty(currentOwner)) { return; } if (!_owners.TryGetValue(__0, out var value)) { value = new HashSet<string>(); _owners[__0] = value; } if (value.Add(currentOwner)) { Discovered(currentOwner, __0); } (KeyCode, string) key = (__0, currentOwner); if (_inject.TryGetValue(key, out var value2)) { if (Time.frameCount - value2 <= 20) { __result = true; _inject.Remove(key); return; } _inject.Remove(key); } if (__result) { if (__0 == Preferences.MasterKey) { __result = false; } else if (Preferences.SuppressRawKeys) { Summon(currentOwner); __result = false; } } } catch { } } internal static void RequestPress(KeyCode key, string mod) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) if ((int)key != 0 && !string.IsNullOrEmpty(mod)) { _inject[(key, mod)] = Time.frameCount; Instance log = Core.Log; if (log != null) { log.Msg($"[hotline] synthetic press queued: {key} -> {mod}"); } LogHub.Write("Hotline", 0, $"press {key} ({mod})"); } } internal static IEnumerable<string> ModsForKey(KeyCode key) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (!_owners.TryGetValue(key, out var value)) { return Array.Empty<string>(); } return value; } private unsafe static void Discovered(string mod, KeyCode key) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) PanelRegistry.RegisterPanel(mod, null); string actionId = mod + ":key-" + ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(); PanelRegistry.RegisterAction(mod, actionId, "Press " + ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(), delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) RequestPress(key, mod); }); Instance log = Core.Log; if (log != null) { log.Msg($"[hotline] discovered hotkey {key} polled by '{mod}' -> added a button to its overlay panel."); } LogHub.Write("Hotline", 0, $"discovered {key} ({mod})"); } private static void Summon(string mod) { if (Time.frameCount == _lastSummonFrame) { return; } _lastSummonFrame = Time.frameCount; try { Preferences.SetShowHud(v: true); WindowLayout.SetVisible("overview", v: true); WindowLayout.SetVisible(mod, v: true); } catch { } } private static string NameOf(MelonMod mod) { try { MelonInfoAttribute info = ((MelonBase)mod).Info; string text = ((info != null) ? info.Name : null); if (!string.IsNullOrEmpty(text)) { return text; } } catch { } return ((object)mod).GetType().Namespace ?? ((object)mod).GetType().Name; } private static IEnumerable<MelonMod> RegisteredMods() { try { if (MelonTypeBase<MelonMod>.RegisteredMelons != null) { return MelonTypeBase<MelonMod>.RegisteredMelons; } } catch { } Type[] array = new Type[2] { typeof(MelonMod), typeof(MelonBase) }; foreach (Type type in array) { try { if (type.GetProperty("RegisteredMelons", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) is IEnumerable<MelonMod> result) { return result; } } catch { } } return Array.Empty<MelonMod>(); } static Interceptor() { KeyCode[] array = new KeyCode[12]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); Targets = (KeyCode[])(object)array; Lifecycle = new string[3] { "OnUpdate", "OnLateUpdate", "OnFixedUpdate" }; _lastSummonFrame = -1; } } } namespace Hotline.Config { internal static class Preferences { private const string CategoryId = "Hotline_01_Main"; private static MelonPreferences_Category _category; private static MelonPreferences_Entry<bool> _enabled; private static MelonPreferences_Entry<bool> _showHud; private static MelonPreferences_Entry<string> _masterKey; private static MelonPreferences_Entry<int> _hudFontSize; private static MelonPreferences_Entry<float> _hudX; private static MelonPreferences_Entry<float> _hudY; private static MelonPreferences_Entry<string> _windowLayouts; private static MelonPreferences_Entry<bool> _interceptKeys; private static MelonPreferences_Entry<bool> _suppressRawKeys; internal static bool Enabled => _enabled?.Value ?? true; internal static bool ShowHud => _showHud?.Value ?? false; internal static KeyCode MasterKey { get { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) string value = _masterKey?.Value; if (string.IsNullOrEmpty(value) || !Enum.TryParse<KeyCode>(value, ignoreCase: true, out KeyCode result) || (int)result == 0) { return (KeyCode)287; } return result; } } internal static int HudFontSize => Mathf.Clamp(_hudFontSize?.Value ?? 12, 8, 32); internal static float HudX => _hudX?.Value ?? 8f; internal static float HudY => _hudY?.Value ?? 8f; internal static string WindowLayouts => _windowLayouts?.Value ?? ""; internal static bool InterceptFunctionKeys => _interceptKeys?.Value ?? true; internal static bool SuppressRawKeys => _suppressRawKeys?.Value ?? false; internal static void Initialize() { if (_category == null) { _category = MelonPreferences.CreateCategory("Hotline_01_Main", "Hotline (Mod HUD & Hotkey Hub)"); _enabled = Create("Enabled", def: true, "Enable Hotline", "Master switch. When OFF, Hotline does nothing at all. When ON, the unified overlay and the central hotkey table are available; the overlay stays hidden until you press the master key."); _showHud = Create("ShowHud", def: false, "Show overlay", "Whether the Hotline overlay is currently visible. Toggle live with the master key (default F7) or 'hotline hud'. Off by default."); _masterKey = Create("MasterHotkey", "F6", "Master overlay key", "The single key that opens/closes the Hotline overlay - the one hotkey that replaces every mod's own. Any UnityEngine.KeyCode name (e.g. F6, F4, Backslash). Defaults to F6. This key is reserved for Hotline and is never auto-intercepted."); _hudFontSize = Create("HudFontSize", 12, "Overlay font size", "On-screen overlay text size (px). The windows auto-resize to fit. Clamped 8-32. Change live with 'hotline hud font <n>' or by dragging a window's bottom-right corner.", (ValueValidator)(object)new ValueRange<int>(8, 32)); _hudX = Create("HudX", 8f, "Overview position X", "Overview window left edge in pixels. Kept on-screen automatically. Change live with 'hotline hud move <x> <y>' or by dragging.", (ValueValidator)(object)new ValueRange<float>(0f, 4000f)); _hudY = Create("HudY", 8f, "Overview position Y", "Overview window top edge in pixels. Kept on-screen automatically. Change live with 'hotline hud move <x> <y>' or by dragging.", (ValueValidator)(object)new ValueRange<float>(0f, 4000f)); _windowLayouts = Create("WindowLayouts", "", "Overlay window layouts (managed)", "Internal: saved positions, sizes and visibility of the overlay windows (the overview, every mod's panel and the log timeline). Managed by dragging the windows or the 'hotline panel ...' console - you normally do not edit this by hand."); _interceptKeys = Create("InterceptFunctionKeys", def: true, "Auto-intercept mod function keys", "ON (default): detect other mods that bind function keys (F1-F12), attribute each to the mod, and add it to the overlay as a clickable button - so you can trigger any mod's hotkey from one place. Only the polling mod ever receives the synthetic press; vanilla input is never touched. Turn OFF to disable."); _suppressRawKeys = Create("SuppressRawFunctionKeys", def: false, "Take over raw function keys", "OFF (default): mods' function keys keep working normally; Hotline only ADDS a clickable button for each discovered key in the overlay. ON: a physical function-key press no longer reaches the mod - instead it opens this overlay focused on that mod's panel, where each of its keys is a button you click. Turn ON to stop two mods from fighting over the same key (the aggressive 'full takeover' mode)."); } } private static MelonPreferences_Entry<T> Create<T>(string id, T def, string name, string desc = null, ValueValidator validator = null) { if (validator != null) { return _category.CreateEntry<T>(id, def, name, desc, false, false, validator); } return _category.CreateEntry<T>(id, def, name, desc, false, false, (ValueValidator)null, (string)null); } internal static void SetShowHud(bool v) { if (_showHud != null) { _showHud.Value = v; } } internal unsafe static void SetMasterKey(string name) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (_masterKey != null && !string.IsNullOrEmpty(name) && Enum.TryParse<KeyCode>(name, ignoreCase: true, out KeyCode result) && (int)result != 0) { _masterKey.Value = ((object)(*(KeyCode*)(&result))/*cast due to .constrained prefix*/).ToString(); } } internal static void SetHudFontSize(int v) { if (_hudFontSize != null) { _hudFontSize.Value = Mathf.Clamp(v, 8, 32); } } internal static void SetHudPos(float x, float y) { if (_hudX != null) { _hudX.Value = x; } if (_hudY != null) { _hudY.Value = y; } } internal static void SetWindowLayouts(string v) { if (_windowLayouts != null) { _windowLayouts.Value = v ?? ""; } } internal static void SetInterceptFunctionKeys(bool v) { if (_interceptKeys != null) { _interceptKeys.Value = v; } } internal static void SetSuppressRawKeys(bool v) { if (_suppressRawKeys != null) { _suppressRawKeys.Value = v; } } } } namespace Hotline.Bridge { internal static class BridgeHost { internal static void Install() { HotlineBridge.RegisterPanel = delegate(string id, string title) { PanelRegistry.RegisterPanel(id, title); }; HotlineBridge.RegisterAction = delegate(string panelId, string actionId, string label, Action run) { PanelRegistry.RegisterAction(panelId, actionId, label, run); }; HotlineBridge.RegisterToggle = delegate(string panelId, string toggleId, string label, Func<bool> get, Action<bool> set) { PanelRegistry.RegisterToggle(panelId, toggleId, label, get, set); }; HotlineBridge.RegisterText = delegate(string panelId, Func<string> provider) { PanelRegistry.RegisterText(panelId, provider); }; HotlineBridge.BindPanelLog = delegate(string panelId) { PanelRegistry.BindPanelLog(panelId); }; HotlineBridge.Log = delegate(string channel, int level, string message) { LogHub.Write(channel, level, message); }; HotlineBridge.RegisterHotkey = delegate(string owner, string label, int key, Action run) { HotkeyRegistry.Register(owner, label, (KeyCode)key, run); }; } } public static class HotlineBridge { public const int AbiVersion = 1; public static Action<string, string> RegisterPanel; public static Action<string, string, string, Action> RegisterAction; public static Action<string, string, string, Func<bool>, Action<bool>> RegisterToggle; public static Action<string, Func<string>> RegisterText; public static Action<string> BindPanelLog; public static Action<string, int, string> Log; public static Action<string, string, int, Action> RegisterHotkey; } }