Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of Custom Commands v1.0.1
CustomCommandsFramework.dll
Decompiled a month agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using CustomCommandsFramework; using HarmonyLib; using Il2CppFishNet; using Il2CppScheduleOne; using Il2CppScheduleOne.AvatarFramework.Customization; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.MainMenu; using Il2CppScheduleOne.UI.Settings; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Newtonsoft.Json; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(global::CustomCommandsFramework.CustomCommandsFramework), "Custom Commands Framework", "1.1.1", "Cubandsweety", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonColor(255, 100, 0, 255)] [assembly: MelonAuthorColor(255, 0, 255, 0)] [assembly: AssemblyTitle("CustomCommandsFramework")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CustomCommandsFramework")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("54b45064-bd91-4967-91a7-9684f6a5fbbc")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace CustomCommandsFramework; internal static class AutoRegisterCommands { internal static void RegisterCommands() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { foreach (Type safeType in CustomCommandsHelper.GetSafeTypes(assemblies[i])) { RegisterCommandsFromType(safeType); } } } private static void RegisterCommandsFromType(Type type) { MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { CustomCommandAttribute customAttribute = methodInfo.GetCustomAttribute<CustomCommandAttribute>(); if (customAttribute == null) { continue; } if (string.IsNullOrWhiteSpace(customAttribute.Command)) { Logs.LogWarning("Skipped registering method '" + methodInfo.Name + "' due to empty command name."); continue; } try { TryRegisterMethod(customAttribute, methodInfo); } catch (Exception ex) { Logs.LogError("Failed to register command '" + methodInfo.Name + "' in type '" + type.FullName + "': " + ex.Message + "."); } } } private static void TryRegisterMethod(CustomCommandAttribute attr, MethodInfo method) { ParameterInfo[] parameters = method.GetParameters(); try { string modNameFromType = CustomCommandsHelper.GetModNameFromType(method.DeclaringType); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string)) { Action<string> action2 = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), method); CommandRegistry.AddCustomCommandHint(attr.Command, modNameFromType, attr.Example, attr.Description, attr.Log); CommandRegistry.AddCustomCommand(attr.Command, action2, modNameFromType, attr.Log); return; } if (parameters.Length == 0) { Action action = (Action)Delegate.CreateDelegate(typeof(Action), method); CommandRegistry.AddCustomCommandHint(attr.Command, modNameFromType, attr.Example, attr.Description, attr.Log); CommandRegistry.AddCustomCommand(attr.Command, delegate { action(); }, modNameFromType, attr.Log); return; } Logs.LogWarning("Invalid parameter setup in method '" + method.Name + "' for custom command '" + attr.Command + "' for mod '" + modNameFromType + "'."); } catch (Exception ex) { Logs.LogError("Failed to register method '" + method.Name + "' for command '" + attr.Command + "': " + ex.Message + "."); } } } [HarmonyPatch] internal static class BindPatch { [HarmonyPatch(typeof(Bind), "Execute")] [HarmonyPrefix] private static bool Execute(List<string> args) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) if (args.Contains("consolekey") || args.Contains("bind")) { CustomNotification.ShowNotification("<b>" + CustomCommandsHelper.SetModPrefix() + " <color=red>Invalid command:</color> Cannot bind current command.</b>"); Logs.LogWarning("Cannot bind current command."); return false; } string text = args.ToArray()[0]; string text2 = string.Join(" ", ((IEnumerable<string>)args.ToArray()).Skip(1)); if (!CustomCommandsHelper.IsValidCommand(text2)) { CustomNotification.ShowNotification("<b>" + CustomCommandsHelper.SetModPrefix() + " <color=red>Invalid command:</color> Binding failed '" + text2 + "' was unknown.</b>"); Logs.LogWarning("Binding failed '" + text2 + "' was unknown"); return false; } if (Enum.TryParse<KeyCode>(text, ignoreCase: true, out KeyCode result)) { Singleton<Console>.Instance.AddBinding(result, text2); } CustomNotification.ShowNotification("<b>" + CustomCommandsHelper.SetModPrefix() + " <color=green>Binding</color> key <color=orange>'" + text + "'</color> to '" + text2 + "'</b>"); Logs.LogSuccess("Binding key '" + text + "' to command '" + text2 + "'"); return true; } } [HarmonyPatch] internal static class CharacterCreatorPatch { private static bool wasEnabled; [HarmonyPatch(typeof(CharacterCreator), "Done")] [HarmonyPrefix] private static bool Done() { if (wasEnabled) { return true; } wasEnabled = true; SettingsScreenPatch.EnableConsole(); return true; } } [HarmonyPatch] internal static class ClearBindsPatch { [HarmonyPatch(typeof(ClearBinds), "Execute")] [HarmonyPrefix] private static bool Execute(List<string> args) { CustomBinds.ClearBinds(); return true; } } internal static class Command { internal const string KEY_COMMAND_NAME = "consolekey"; internal const string LOG_COMMAND_NAME = "logcustomcommands"; [CustomCommand("consolekey", "consolekey f2", "Changes the console key bind.", false)] private static void SetConsoleKey(string args) { //IL_0017: 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_00bd: 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) if (args.Length == 0) { CustomNotification.ShowNotification($"<b>{CustomCommandsHelper.SetModPrefix()} Current Console Key: <color=orange>'{CustomCommandsFramework.ConsoleKey.Value}'</color>.</b>", 0f, 7.5f); Logs.LogInfo($"Current Console Key: '{CustomCommandsFramework.ConsoleKey.Value}'."); return; } string[] array = args.Trim().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length > 1) { CustomNotification.ShowNotification("<b>" + CustomCommandsHelper.SetModPrefix() + " <color=red>Invalid command.</color> \nPlease specify a key. \nExample: consolekey <color=orange>'f2'</color>.</b>", 0f, 7.5f); Logs.LogWarning("Invalid command. Please specify a key. Example: consolekey 'f2'."); return; } string text = array[0]; if (CustomConsoleKeys.KeyAliases.TryGetValue(text, out var value)) { CustomConsoleKeys.SetKey(value); return; } if (Enum.TryParse<KeyCode>(text, ignoreCase: true, out KeyCode result)) { CustomConsoleKeys.SetKey(result); return; } CustomNotification.ShowNotification("<b>" + CustomCommandsHelper.SetModPrefix() + " <color=red>Invalid console key:</color> <color=orange>'" + text + "'</color>.</b>", 0f, 7.5f); Logs.LogWarning("Invalid console key: '" + text + "'."); } [CustomCommand("logcustomcommands", "logcustomcommands", "Logs all custom commands registered via the framework in the MelonLoader console.", false)] private static void LogConsoleCommands() { Logs.LogRegisteredCommands(); } } [HarmonyPatch] internal static class CommandListScreenPatch { private static bool hasTriggered; [HarmonyPatch(typeof(CommandListScreen), "Start")] [HarmonyPostfix] private static void Start(CommandListScreen __instance) { if (hasTriggered || !Object.op_Implicit((Object)(object)__instance)) { return; } hasTriggered = true; ScrollRect componentInChildren = ((Component)__instance).GetComponentInChildren<ScrollRect>(); if (!Object.op_Implicit((Object)(object)componentInChildren) || !Object.op_Implicit((Object)(object)componentInChildren.content)) { Logs.LogError("Could not find ScrollRect or content for CommandListScreen!"); return; } Transform content = (Transform)(object)componentInChildren.content; if (!Object.op_Implicit((Object)(object)__instance.CommandEntryPrefab)) { Logs.LogError("CommandEntryPrefab is null!"); return; } RegisterAllHints(__instance, content); DestroyDuplicateCommandListUI(content); UpdateText(__instance); } private static void RegisterAllHints(CommandListScreen instance, Transform content) { foreach (CustomCommandsData registeredCommandHint in CommandRegistry.RegisteredCommandHints) { try { RectTransform val = Object.Instantiate<RectTransform>(instance.CommandEntryPrefab, content); if (!Object.op_Implicit((Object)(object)val)) { Logs.LogError("Failed to instantiate command entry for: " + registeredCommandHint.Command + "!"); continue; } ((Object)val).name = ((registeredCommandHint.Command != null) ? (registeredCommandHint.Command + "_CCF") : "CommandEntry(Clone)"); Transform transform = ((Component)val).transform; SetText(transform, "Command", registeredCommandHint.Command); SetText(transform, "Example", registeredCommandHint.Example); SetText(transform, "Description", registeredCommandHint.Description); } catch (Exception arg) { Logs.LogError($"Error adding command entry: {arg}."); } } } private static void SetText(Transform parent, string name, string value) { Transform obj = parent.Find(name); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent<TextMeshProUGUI>() : null); if (Object.op_Implicit((Object)(object)val)) { ((TMP_Text)val).text = value; } } private static void UpdateText(CommandListScreen instance) { Transform val = ((Component)instance).transform.GetChild(1); if (!Object.op_Implicit((Object)(object)val)) { val = ((Component)instance).transform.Find("Title"); } if (!Object.op_Implicit((Object)(object)val)) { Logs.LogError("CommandListScreen Title is null!"); return; } TextMeshProUGUI component = ((Component)val).GetComponent<TextMeshProUGUI>(); if (!(((TMP_Text)component).text == "<color=#6400FF>Custom Console Commands</color>")) { ((TMP_Text)component).text = "<color=#6400FF>Custom Console Commands</color>"; } } private static void DestroyDuplicateCommandListUI(Transform content) { HashSet<string> hashSet = new HashSet<string>(); for (int num = content.childCount - 1; num >= 0; num--) { Transform child = content.GetChild(num); Transform obj = child.Find("Command"); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent<TextMeshProUGUI>() : null); if (Object.op_Implicit((Object)(object)val)) { string item = ((TMP_Text)val).text.Trim(); if (!hashSet.Add(item)) { Object.Destroy((Object)(object)((Component)child).gameObject); } } } } internal static void Reset() { hasTriggered = false; } } public static class CommandRegistry { internal static readonly List<CustomCommandsData> RegisteredCommandHints = new List<CustomCommandsData>(); internal static readonly Dictionary<string, Action<string>> RegisteredCommands = new Dictionary<string, Action<string>>(); private static bool hasLogged; public static void AddCustomCommandHint(string command, string example = null, string description = null, bool log = false) { try { string callingModName = CustomCommandsHelper.GetCallingModName(); if (string.IsNullOrWhiteSpace(command)) { Logs.LogWarning("Cannot register a command hint with a null or empty name for '" + callingModName + "'."); return; } string key = command.ToLowerInvariant(); if (RegisteredCommandHints.Any((CustomCommandsData hint) => hint.Command.Equals(key, StringComparison.InvariantCultureIgnoreCase))) { Logs.LogWarning("Command Hint '" + command + "' is already registered, but '" + callingModName + "' tried to add it."); return; } RegisteredCommandHints.Add(new CustomCommandsData { Command = key, Example = example, Description = description, RegisteredBy = callingModName }); if (log) { Logs.LogSuccess("Loaded Command Hint - \n\n • Command Name: '" + command + "' \n • Example: '" + example + "' \n • Description: '" + description + "' \n • Mod Name: '" + callingModName + "'.\n"); } } catch (Exception ex) { Logs.LogError("Failed to register command hint '" + command + "' due to an unexpected error: " + ex.Message); } } internal static void AddCustomCommandHint(string command, string modName, string example = null, string description = null, bool log = false) { if (string.IsNullOrWhiteSpace(command)) { Logs.LogWarning("Cannot register a command hint with a null or empty name for '" + modName + "'."); return; } string key = command.ToLowerInvariant(); if (RegisteredCommandHints.Any((CustomCommandsData hint) => hint.Command.Equals(key, StringComparison.InvariantCultureIgnoreCase))) { Logs.LogWarning("Command Hint '" + command + "' is already registered, but '" + modName + "' tried to add it."); return; } RegisteredCommandHints.Add(new CustomCommandsData { Command = key, Example = example, Description = description, RegisteredBy = modName }); if (log) { Logs.LogSuccess("Loaded Command Hint - \n\n • Command Name: '" + command + "' \n • Example: '" + example + "' \n • Description: '" + description + "' \n • Mod Name: '" + modName + "'.\n"); } } public static void AddCustomCommand(string command, Action<string> action, bool log = false) { string callingModName = CustomCommandsHelper.GetCallingModName(); try { if (string.IsNullOrWhiteSpace(command)) { Logs.LogWarning("Cannot register a command with a null or empty name for '" + callingModName + "'."); return; } if (action == null) { Logs.LogWarning("Command '" + command + "' has a null handler and won't be registered for '" + callingModName + "'."); return; } string key = command.ToLowerInvariant(); if (RegisteredCommands.ContainsKey(key)) { Logs.LogWarning($"Command '{command}' is already registered for '{action.Method}', but '{callingModName}' tried to add it."); return; } RegisteredCommands[key] = action; if (log) { Logs.LogSuccess("Loaded Command - \n\n • Command Name: '" + command + "' \n • Mod Name: '" + callingModName + "'.\n"); } } catch (Exception ex) { Logs.LogError($"Failed to register command '{command}' from mod '{callingModName}' for action '{action}' due to an unexpected error: {ex.Message}"); } } internal static void AddCustomCommand(string command, Action<string> action, string modName, bool log = false) { try { if (string.IsNullOrWhiteSpace(command)) { Logs.LogWarning("Cannot register command with a null or empty name for '" + modName + "'."); return; } if (action == null) { Logs.LogWarning("Command '" + command + "' has a null handler and won't be registered for '" + modName + "'."); return; } string key = command.ToLowerInvariant(); if (RegisteredCommands.ContainsKey(key)) { Logs.LogWarning($"Command '{command}' is already registered for '{action.Method}', but '{modName}' tried to add it."); return; } RegisteredCommands[key] = action; if (log) { Logs.LogSuccess("Loaded Command - \n\n • Command Name: '" + command + "' \n • Mod Name: '" + modName + "'.\n"); } } catch (Exception ex) { Logs.LogError($"Failed to register command '{command}' from mod '{modName}' for action '{action}' due to an unexpected error: {ex.Message}"); } } internal static bool TryHandle(string input) { if (string.IsNullOrWhiteSpace(input)) { return false; } input = input.Trim().ToLowerInvariant(); foreach (string item in RegisteredCommands.Keys.OrderByDescending((string k) => k.Length)) { if (input.StartsWith(item)) { Action<string> action = RegisteredCommands[item]; string obj = ((input.Length > item.Length) ? input.Substring(item.Length).TrimStart(Array.Empty<char>()) : string.Empty); action?.Invoke(obj); return true; } } return false; } internal static void HandleRegisteredLog() { if (!hasLogged) { Logs.LogRegisteredCommands(); hasLogged = true; } } } internal static class CustomBinds { internal const string BINDS_FILENAME = "CustomCommandFrameworkBinds.json"; internal static Dictionary<KeyCode, string> Binds = new Dictionary<KeyCode, string>(); internal static void LoadBinds() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) string text = Path.Combine(MelonEnvironment.UserDataDirectory, "CustomCommandFrameworkBinds.json"); if (!File.Exists(text)) { File.WriteAllText(text, "{}"); return; } string text2 = File.ReadAllText(text); try { Dictionary<string, string> obj = JsonConvert.DeserializeObject<Dictionary<string, string>>(text2) ?? new Dictionary<string, string>(); Binds = new Dictionary<KeyCode, string>(); foreach (KeyValuePair<string, string> item in obj) { if (Enum.TryParse<KeyCode>(item.Key, out KeyCode result) && (int)result != 0) { Binds[result] = item.Value; } } LogBindCount(Binds.Count); } catch (Exception arg) { Logs.LogError($"Failed to load binds: {arg}"); Logs.LogError("If this keeps happening, try deleting '" + text + "'."); } } internal static void AddBinds(KeyCode key, string command) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (Binds.ContainsKey(key)) { Binds[key] = command; SaveBinds(); } else { Binds.Add(key, command); SaveBinds(); } } internal static void RemoveBinds(KeyCode key) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) Binds.Remove(key); SaveBinds(); } internal static void ClearBinds() { if (Binds.Count != 0) { Binds.Clear(); File.WriteAllText(Path.Combine(MelonEnvironment.UserDataDirectory, "CustomCommandFrameworkBinds.json"), "{}"); CustomNotification.ShowNotification("<b>" + CustomCommandsHelper.SetModPrefix() + " All binds <color=green>cleared</color>.</b>"); Logs.LogSuccess("All binds have been cleared!"); } } internal static void SaveBinds() { string path = Path.Combine(MelonEnvironment.UserDataDirectory, "CustomCommandFrameworkBinds.json"); Dictionary<string, string> dictionary = Binds.ToDictionary(delegate(KeyValuePair<KeyCode, string> kvp) { //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) KeyCode key = kvp.Key; return ((object)(KeyCode)(ref key)).ToString(); }, (KeyValuePair<KeyCode, string> kvp) => kvp.Value); File.WriteAllText(path, JsonConvert.SerializeObject((object)dictionary, (Formatting)1)); } private static void LogBindCount(int bindCount) { switch (bindCount) { case 0: break; case 1: Logs.LogSuccess($"Loaded {Binds.Count} bind!"); break; default: Logs.LogSuccess($"Loaded {Binds.Count} binds!"); break; } } } internal class CustomCommandsData { public string Command; public string Example; public string Description; public string RegisteredBy; } [HarmonyPatch] internal static class ConsolePatch { private static DateTime lastChangeTime = DateTime.MinValue; private static readonly TimeSpan changeCooldown = TimeSpan.FromMilliseconds(500.0); [HarmonyPatch(typeof(Console), "Awake")] [HarmonyPostfix] private static void Awake(Console __instance) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) Dictionary<KeyCode, string> binds = CustomBinds.Binds; if (binds.Count == 0) { return; } foreach (KeyValuePair<KeyCode, string> item in binds) { if (!__instance.keyBindings.ContainsKey(item.Key)) { __instance.keyBindings.Add(item.Key, item.Value); } } } [HarmonyPatch(typeof(Console), "SubmitCommand", new Type[] { typeof(string) })] [HarmonyPrefix] private static bool SubmitCommand(string args) { if (DateTime.Now - lastChangeTime < changeCooldown) { return true; } lastChangeTime = DateTime.Now; return !CommandRegistry.TryHandle(args); } [HarmonyPatch(typeof(Console), "AddBinding")] [HarmonyPrefix] private static bool AddBinding(Console __instance, KeyCode key, string command) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) CustomBinds.AddBinds(key, command); return true; } [HarmonyPatch(typeof(Console), "RemoveBinding")] [HarmonyPrefix] private static bool RemoveBinding(Console __instance, KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) CustomBinds.RemoveBinds(key); return true; } } [HarmonyPatch] internal static class ConsoleUIPatch { [HarmonyPatch(typeof(ConsoleUI), "Update")] [HarmonyPrefix] private static bool Prefix(ConsoleUI __instance) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown((KeyCode)96) && ((Behaviour)__instance.canvas).enabled) { return false; } if (Input.GetKeyDown(CustomCommandsFramework.ConsoleKey.Value) && !Singleton<PauseMenu>.Instance.IsPaused && !((Behaviour)__instance.canvas).enabled) { GameManager instance = NetworkSingleton<GameManager>.Instance; if (!Object.op_Implicit((Object)(object)instance)) { return true; } if (instance.Settings.ConsoleEnabled) { __instance.SetIsOpen(true); } } if (!((Behaviour)__instance.canvas).enabled) { return false; } if (!Player.Local.Health.IsAlive) { __instance.SetIsOpen(false); } return true; } } [AttributeUsage(AttributeTargets.Method)] public class CustomCommandAttribute : Attribute { public string Command { get; } public string Example { get; } public string Description { get; } public bool Log { get; } public CustomCommandAttribute(string command, string example = null, string description = null, bool log = false) { Command = command; Example = example; Description = description; Log = log; } } internal class CustomCommandsFramework : MelonMod { private readonly Harmony harmony = new Harmony("CustomCommandsFramework"); internal static MelonPreferences_Entry<KeyCode> ConsoleKey; internal static MelonPreferences_Category MyCategory; internal static bool IsGame { get; private set; } public override void OnInitializeMelon() { harmony.PatchAll(); InitializeConfig(); AutoRegisterCommands.RegisterCommands(); CustomConsoleKeys.CheckKeys(); CustomBinds.LoadBinds(); CustomCommandsHelper.CheckPossibleModConflicts(); } public override void OnDeinitializeMelon() { CustomCommandsHelper.ShutDown(); SettingsScreenPatch.ShutDown(); CustomNotification.ShutDown(); CommandRegistry.RegisteredCommandHints.Clear(); CommandRegistry.RegisteredCommands.Clear(); harmony.UnpatchSelf(); Logs.ShutDown(); } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { IsGame = sceneName.Equals("Main"); if (sceneName.Equals("Menu")) { CommandRegistry.HandleRegisteredLog(); CommandListScreenPatch.Reset(); } } private void InitializeConfig() { MyCategory = MelonPreferences.CreateCategory("Custom Commands Framework"); ConsoleKey = MyCategory.CreateEntry<KeyCode>("Key", (KeyCode)96, (string)null, "Keybind to toggle the console.", false, false, (ValueValidator)null, (string)null); string text = Path.Combine(MelonEnvironment.UserDataDirectory, "CustomCommandsFramework.cfg"); MyCategory.SetFilePath(text); if (File.Exists(text)) { MyCategory.LoadFromFile(false); } else { MyCategory.SaveToFile(false); } } } public static class CustomCommandsHelper { public const string DLLName = "CustomCommandsFramework.dll"; public static string FormattedName { get; private set; } public static bool GetDLLPath() { return File.Exists(Path.Combine(MelonEnvironment.ModsDirectory, "CustomCommandsFramework.dll")); } internal static bool IsValidCommand(string command) { if (string.IsNullOrWhiteSpace(command)) { return false; } string cmdKeyword = command.Split(new char[1] { ' ' })[0].Trim().ToLowerInvariant(); if (CommandRegistry.RegisteredCommands.Keys.Any((string k) => k.Equals(cmdKeyword, StringComparison.OrdinalIgnoreCase))) { return true; } Enumerator<string, ConsoleCommand> enumerator = Console.commands.GetEnumerator(); while (enumerator.MoveNext()) { KeyValuePair<string, ConsoleCommand> current = enumerator.Current; if (current.Key.Equals(cmdKeyword, StringComparison.OrdinalIgnoreCase) || current.Value.CommandWord.Equals(cmdKeyword, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } internal static string GetCallingModName() { StackTrace stackTrace = new StackTrace(); for (int i = 2; i < stackTrace.FrameCount; i++) { Type type = stackTrace.GetFrame(i)?.GetMethod()?.DeclaringType; if (!(type == null)) { MelonMod val = ((IEnumerable<MelonMod>)MelonTypeBase<MelonMod>.RegisteredMelons).FirstOrDefault((Func<MelonMod, bool>)((MelonMod m) => ((MelonBase)m).Info.SystemType == type || ((object)m).GetType() == type)); if (val != null) { return ((MelonBase)val).Info.Name; } } } return "Unknown"; } internal static string GetModNameFromType(Type type) { try { foreach (MelonMod registeredMelon in MelonTypeBase<MelonMod>.RegisteredMelons) { if (((MelonBase)registeredMelon).Info.SystemType.Assembly == type.Assembly || ((object)registeredMelon).GetType().Assembly == type.Assembly) { return ((MelonBase)registeredMelon).Info.Name; } } } catch (Exception ex) { Logs.LogError("Error in GetModNameFromType for type '" + type.Name + "': " + ex.Message); } return "Unknown"; } internal static IEnumerable<Type> GetSafeTypes(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type t) => t != null); } catch { return Enumerable.Empty<Type>(); } } internal static string SetModPrefix() { if (string.IsNullOrEmpty(FormattedName)) { FormattedName = Regex.Replace("CustomCommandsFramework", "(?<!^)([A-Z])", " $1"); } return "[<color=#6400FF>" + FormattedName + "</color>]"; } internal static void CheckPossibleModConflicts() { List<string> list = (from f in Directory.EnumerateFiles(MelonEnvironment.ModsDirectory, "*", SearchOption.TopDirectoryOnly) where Path.GetFileName(f).IndexOf("console", StringComparison.OrdinalIgnoreCase) >= 0 select f).ToList(); if (list.Count != 0) { Logs.LogWarning("Possible conflict found: " + string.Join(", ", list.Select((string f) => "'" + Path.GetFileName(f) + "'"))); } } internal static void ShutDown() { FormattedName = null; } } internal static class CustomConsoleKeys { internal const KeyCode defaultKey = 96; internal static readonly Dictionary<string, KeyCode> KeyAliases = new Dictionary<string, KeyCode>(StringComparer.OrdinalIgnoreCase) { { "`", (KeyCode)96 }, { "tilde", (KeyCode)96 }, { "-", (KeyCode)45 }, { "=", (KeyCode)61 }, { "[", (KeyCode)91 }, { "]", (KeyCode)93 }, { "\\", (KeyCode)92 }, { ";", (KeyCode)59 }, { "'", (KeyCode)39 }, { ",", (KeyCode)44 }, { ".", (KeyCode)46 }, { "/", (KeyCode)47 } }; internal static readonly List<KeyCode> BlockedKeyCodes = new List<KeyCode> { (KeyCode)323, (KeyCode)324, (KeyCode)325, (KeyCode)13, (KeyCode)271, (KeyCode)27 }; internal static void SetKey(KeyCode key) { //IL_0005: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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) if (BlockedKeyCodes.Contains(key)) { CustomNotification.ShowNotification($"<b>{CustomCommandsHelper.SetModPrefix()} <color=red>Invalid console key:</color> <color=orange>'{key}'</color>.</b>"); Logs.LogWarning($"Invalid console key: '{key}'."); return; } CustomCommandsFramework.ConsoleKey.Value = key; CustomNotification.ShowNotification($"<b>{CustomCommandsHelper.SetModPrefix()} Console key set to: <color=orange>'{key}'</color>.</b>"); Logs.LogSuccess($"Console key set to: '{key}'."); SettingsScreenPatch.UpdateText(); CustomCommandsFramework.MyCategory.SaveToFile(false); } internal static void CheckKeys() { //IL_0005: 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_0015: 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_0027: Unknown result type (might be due to invalid IL or missing references) KeyCode value = CustomCommandsFramework.ConsoleKey.Value; if (!Enum.IsDefined(typeof(KeyCode), value) || BlockedKeyCodes.Contains(value)) { Logs.LogWarning($"Console key '{value}' was invalid and reset to default {(object)(KeyCode)96}."); CustomCommandsFramework.ConsoleKey.Value = (KeyCode)96; CustomCommandsFramework.MyCategory.SaveToFile(false); } } } public static class CustomNotification { [CompilerGenerated] private sealed class <NotificationDelay>d__3 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float delay; public HintDisplay instance; public string message; public float duration; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <NotificationDelay>d__3(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(delay); <>1__state = 1; return true; case 1: <>1__state = -1; if (instance.IsOpen) { instance.QueueHint(message, duration); notificationCoroutine = null; return false; } instance.ShowHint(message, duration); notificationCoroutine = null; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <TextDelay>d__5 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float delay; public NotificationsManager instance; public string header; public string message; public Sprite sprite; public float duration; public bool sound; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <TextDelay>d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(delay); <>1__state = 1; return true; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; } if (instance.NotificationPrefab.activeSelf && instance.entries.Count >= 6) { <>2__current = (object)new WaitForSeconds(1f); <>1__state = 2; return true; } instance.SendNotification(header, message, sprite, duration, sound); textCoroutine = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static Coroutine notificationCoroutine; private static Coroutine textCoroutine; public static void ShowNotification(string message, float delay = 0f, float duration = 5f) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if (!CustomCommandsFramework.IsGame || string.IsNullOrEmpty(message)) { return; } HintDisplay instance = Singleton<HintDisplay>.Instance; if (!Object.op_Implicit((Object)(object)instance)) { Logs.LogError("HintDisplay.Instance is null!"); return; } if (notificationCoroutine != null) { MelonCoroutines.Stop((object)notificationCoroutine); notificationCoroutine = null; } notificationCoroutine = (Coroutine)MelonCoroutines.Start(NotificationDelay(instance, message, delay, duration)); } [IteratorStateMachine(typeof(<NotificationDelay>d__3))] private static IEnumerator NotificationDelay(HintDisplay instance, string message, float delay, float duration) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <NotificationDelay>d__3(0) { instance = instance, message = message, delay = delay, duration = duration }; } public static void ShowTextNotification(string header, string message, Sprite sprite = null, float delay = 0f, float duration = 5f, bool sound = true) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown if (CustomCommandsFramework.IsGame) { NotificationsManager instance = Singleton<NotificationsManager>.Instance; if (!Object.op_Implicit((Object)(object)instance)) { Logs.LogError("NotificationsManager.Instance is null!"); } else if (textCoroutine == null) { textCoroutine = (Coroutine)MelonCoroutines.Start(TextDelay(instance, header, message, sprite, delay, duration, sound)); } } } [IteratorStateMachine(typeof(<TextDelay>d__5))] private static IEnumerator TextDelay(NotificationsManager instance, string header, string message, Sprite sprite, float delay, float duration, bool sound) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <TextDelay>d__5(0) { instance = instance, header = header, message = message, sprite = sprite, delay = delay, duration = duration, sound = sound }; } internal static void ShutDown() { if (notificationCoroutine != null) { MelonCoroutines.Stop((object)notificationCoroutine); notificationCoroutine = null; } if (textCoroutine != null) { MelonCoroutines.Stop((object)textCoroutine); textCoroutine = null; } } } internal static class Logs { internal enum LogType { Error, Warning, Success, Info, None } [CompilerGenerated] private sealed class <DebounceLog>d__24 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float time; public LogType logType; public string message; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DebounceLog>d__24(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSecondsRealtime(SetMaxTime(time)); <>1__state = 1; return true; case 1: <>1__state = -1; Log(logType, message); logCoroutine = null; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly HashSet<string> infoLogs = new HashSet<string>(); private static readonly HashSet<string> successLogs = new HashSet<string>(); private static readonly HashSet<string> warningLogs = new HashSet<string>(); private static readonly HashSet<string> errorLogs = new HashSet<string>(); private static readonly HashSet<string> loggedMessages = new HashSet<string>(); private static Coroutine logCoroutine; internal static Color ToColor(this ConsoleColor consoleColor) { return ColorTranslator.FromHtml("#6400ff"); } internal static void ColoredLog(string prefix, string message, int prefixColor = 51) { MelonLogger.Msg($"\u001b[38;5;{prefixColor}m{prefix}:\u001b[38;5;57m {message}"); } internal static void LogSuccess(string message, float duration = 5f) { Log(LogType.Success, message, duration); } internal static void LogInfo(string message, float duration = 5f) { Log(LogType.Info, message, duration); } internal static void LogWarning(string message, float duration = 5f) { Log(LogType.Warning, message, duration); } internal static void LogError(string message, float duration = 5f) { Log(LogType.Error, message, duration); } internal static void Log(LogType type, string message, float duration = 5f, ConsoleColor color = ConsoleColor.White) { switch (type) { case LogType.Error: LogOnceError(message, duration); break; case LogType.Warning: LogOnceWarning(message, duration); break; case LogType.Success: LogOnceSuccess(message, duration); break; case LogType.Info: LogOnceInfo(message, duration); break; case LogType.None: LogOnce(color, message, duration); break; default: throw new ArgumentOutOfRangeException("type", type, null); } } private static void LogOnceInfo(string message, float delaySeconds) { if (infoLogs.Add(message)) { ColoredLog("Info", message); RemoveInfoAfterDelay(message, delaySeconds); } } private static async Task RemoveInfoAfterDelay(string message, float delaySeconds) { await Task.Delay(TimeSpan.FromSeconds(SetMaxTime(delaySeconds))); infoLogs.Remove(message); } private static void LogOnceSuccess(string message, float delaySeconds) { if (successLogs.Add(message)) { ColoredLog("Success", message, 40); RemoveSuccessAfterDelay(message, delaySeconds); } } private static async Task RemoveSuccessAfterDelay(string message, float delaySeconds) { await Task.Delay(TimeSpan.FromSeconds(SetMaxTime(delaySeconds))); successLogs.Remove(message); } private static void LogOnceWarning(string message, float delaySeconds) { if (warningLogs.Add(message)) { ColoredLog("Warning", message, 226); RemoveWarningAfterDelay(message, delaySeconds); } } private static async Task RemoveWarningAfterDelay(string message, float delaySeconds) { await Task.Delay(TimeSpan.FromSeconds(SetMaxTime(delaySeconds))); warningLogs.Remove(message); } private static void LogOnceError(string message, float delaySeconds) { if (errorLogs.Add(message)) { ColoredLog("Error", message, 1); RemoveErrorAfterDelay(message, delaySeconds); } } private static async Task RemoveErrorAfterDelay(string message, float delaySeconds) { await Task.Delay(TimeSpan.FromSeconds(SetMaxTime(delaySeconds))); errorLogs.Remove(message); } private static void LogOnce(ConsoleColor color, string message, float delaySeconds) { if (loggedMessages.Add(message)) { MelonLogger.Msg(color, message); RemoveAfterDelay(loggedMessages, message, delaySeconds); } } private static async Task RemoveAfterDelay(HashSet<string> logSet, string message, float delaySeconds) { await Task.Delay(TimeSpan.FromSeconds(SetMaxTime(delaySeconds))); logSet.Remove(message); } internal static void DelayLog(LogType logType, string message, float time) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown if (logCoroutine != null) { MelonCoroutines.Stop((object)logCoroutine); logCoroutine = null; } logCoroutine = (Coroutine)MelonCoroutines.Start(DebounceLog(logType, message, time)); } [IteratorStateMachine(typeof(<DebounceLog>d__24))] private static IEnumerator DebounceLog(LogType logType, string message, float time) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DebounceLog>d__24(0) { logType = logType, message = message, time = time }; } private static float SetMaxTime(float time) { return Math.Max(time, 0.1f); } internal static void LogRegisteredCommands() { StringBuilder stringBuilder = new StringBuilder(); int count = CommandRegistry.RegisteredCommands.Count; stringBuilder.AppendLine($"\n =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Registered {count} command(s) =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="); if (CommandRegistry.RegisteredCommandHints.Count > 0) { foreach (CustomCommandsData item in CommandRegistry.RegisteredCommandHints.OrderBy((CustomCommandsData h) => h.Command)) { stringBuilder.AppendLine(" • " + item.Command + " — " + item.Description + " (Mod: " + item.RegisteredBy + ")"); } } stringBuilder.AppendLine($" =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Registered {count} command(s) =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="); MelonLogger.Msg($"\u001b[38;5;57m {stringBuilder}"); } internal static void ClearAllLogs() { infoLogs.Clear(); successLogs.Clear(); warningLogs.Clear(); errorLogs.Clear(); loggedMessages.Clear(); } internal static void ShutDown() { ClearAllLogs(); if (logCoroutine != null) { MelonCoroutines.Stop((object)logCoroutine); logCoroutine = null; } } } [HarmonyPatch] internal static class PlayerPatch { [HarmonyPatch(typeof(Player), "LoadVariable")] [HarmonyPrefix] private static bool Start() { SettingsScreenPatch.EnableConsole(); return true; } } [HarmonyPatch] internal static class SettingsScreenPatch { private static TextMeshProUGUI consoleKeyText; [HarmonyPatch(typeof(SettingsScreen), "Start")] [HarmonyPostfix] private static void Start(SettingsScreen __instance) { if (InstanceFinder.IsHost && Player.Local.IsLocalPlayer) { EnableConsole(); GameSettingsWindow componentInChildren = ((Component)__instance).GetComponentInChildren<GameSettingsWindow>(); if (!componentInChildren.ConsoleToggle.isOn) { componentInChildren.ConsoleToggle.isOn = true; } FindCheckMark(componentInChildren); FindText(componentInChildren); } } private static void FindText(GameSettingsWindow gameSettingsWindow) { Transform val = ((Component)gameSettingsWindow).transform.GetChild(0).GetChild(1); if (!Object.op_Implicit((Object)(object)val)) { val = ((Component)gameSettingsWindow).transform.Find("Console/Label (1)"); } if (!Object.op_Implicit((Object)(object)val)) { Logs.LogError("Console Label (1) is null!"); return; } consoleKeyText = ((Component)val).GetComponent<TextMeshProUGUI>(); UpdateText(); ((TMP_Text)consoleKeyText).enableWordWrapping = false; } private static void FindCheckMark(GameSettingsWindow gameSettingsWindow) { //IL_0057: 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) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) Transform val = ((Component)gameSettingsWindow).transform.GetChild(0).GetChild(2).GetChild(0) .GetChild(0); if (!Object.op_Implicit((Object)(object)val)) { val = ((Component)gameSettingsWindow).transform.Find("Console/Toggle/Background/Checkmark"); } if (!Object.op_Implicit((Object)(object)val)) { Logs.LogError("Checkmark is null!"); return; } Image component = ((Component)val).GetComponent<Image>(); Color green = Color.green; if (!(((Graphic)component).color == green)) { ((Graphic)component).color = green; } } internal static void UpdateText() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)consoleKeyText)) { string text = $"Press <color=orange>{CustomCommandsFramework.ConsoleKey.Value}</color> to toggle the console"; if (!(((TMP_Text)consoleKeyText).text == text)) { ((TMP_Text)consoleKeyText).text = text; } } } internal static void EnableConsole() { if (InstanceFinder.IsHost && Player.Local.IsLocalPlayer) { GameManager instance = NetworkSingleton<GameManager>.Instance; if (!Object.op_Implicit((Object)(object)instance)) { Logs.LogError("GameManager.Instance is null!"); } else if (!instance.Settings.ConsoleEnabled) { instance.Settings.ConsoleEnabled = true; } } } internal static void ShutDown() { consoleKeyText = null; } } [HarmonyPatch] internal static class UnbindPatch { [HarmonyPatch(typeof(Unbind), "Execute")] [HarmonyPrefix] private static bool Execute(List<string> args) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) string text = args.ToArray()[0]; CustomNotification.ShowNotification("<b>" + CustomCommandsHelper.SetModPrefix() + " <color=red>Unbound</color> key <color=orange>'" + text + "'</color></b>"); Logs.LogSuccess("Unbound key '" + text + "'"); if (Enum.TryParse<KeyCode>(text, ignoreCase: true, out KeyCode result)) { Singleton<Console>.Instance.RemoveBinding(result); } return true; } }