diff --git a/ChatCommands/ChatCommands.csproj b/ChatCommands/ChatCommands.csproj index bf69f03..b6fb8ee 100644 --- a/ChatCommands/ChatCommands.csproj +++ b/ChatCommands/ChatCommands.csproj @@ -1,10 +1,10 @@  1.13.0 - net452 + net6.0 - + diff --git a/ChatCommands/ChatCommandsMod.cs b/ChatCommands/ChatCommandsMod.cs index d312f3a..53a439a 100644 --- a/ChatCommands/ChatCommandsMod.cs +++ b/ChatCommands/ChatCommandsMod.cs @@ -20,6 +20,7 @@ public class ChatCommandsMod : Mod, ICommandHandler private NotifyingTextWriter consoleNotifier; private InputState inputState; private ChatCommandsConfig modConfig; + private CommandInvoker commandInvoker; private int repeatWaitPeriod = BaseWaitPeriod; @@ -42,7 +43,7 @@ public void Handle(string input) parts[0] = "help"; this.consoleNotifier.Notify = true; - this.Helper.ConsoleCommands.Trigger(parts[0], parts.Skip(1).ToArray()); + this.commandInvoker.InvokeCommand(parts[0], parts.Skip(1).ToArray()); this.consoleNotifier.Notify = false; } @@ -52,6 +53,7 @@ public override void Entry(IModHelper helper) { this.commandValidator = new CommandValidator(helper.ConsoleCommands); this.consoleNotifier = new NotifyingTextWriter(Console.Out, this.OnLineWritten); + this.commandInvoker = new CommandInvoker(this.Helper, this.Monitor); this.inputState = helper.Reflection.GetField(typeof(Game1), "input").GetValue(); diff --git a/ChatCommands/ClassReplacements/CommandChatBox.cs b/ChatCommands/ClassReplacements/CommandChatBox.cs index d31a789..fbbffc1 100644 --- a/ChatCommands/ClassReplacements/CommandChatBox.cs +++ b/ChatCommands/ClassReplacements/CommandChatBox.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text.RegularExpressions; namespace ChatCommands.ClassReplacements @@ -50,7 +51,9 @@ public CommandChatBox(IModHelper helper, ICommandHandler handler, ChatCommandsCo this.bChoosingEmoji = helper.Reflection.GetField(this, "choosingEmoji"); Texture2D chatBoxTexture = Game1.content.Load("LooseSprites\\chatBox"); - this.chatBox.OnEnterPressed -= helper.Reflection.GetField(this, "e").GetValue(); + Type[] onEnterDelegateParamTypes = { typeof(TextBox) }; + MethodInfo textBoxEnterInfo = this.GetType().GetMethod("textBoxEnter", onEnterDelegateParamTypes); + this.chatBox.OnEnterPressed -= (TextBoxEvent)textBoxEnterInfo.CreateDelegate(typeof(TextBoxEvent), this); this.chatBox = this.commandChatTextBox = new CommandChatTextBox(chatBoxTexture, null, Game1.smallFont, Color.White); Game1.keyboardDispatcher.Subscriber = this.chatBox; diff --git a/ChatCommands/Util/CommandInvoker.cs b/ChatCommands/Util/CommandInvoker.cs new file mode 100644 index 0000000..578dfd3 --- /dev/null +++ b/ChatCommands/Util/CommandInvoker.cs @@ -0,0 +1,113 @@ +using StardewModdingAPI; +using StardewModdingAPI.Events; +using StardewValley; +using System; +using System.Reflection; + +namespace ChatCommands.Util +{ + /// + /// Uses disgusting Reflection shenanigans to emulate the old behaviour of `ConsoleCommands.Trigger` + /// + internal class CommandInvoker + { + private object? commandManager; + private MethodInfo? getCommandMethod; + private PropertyInfo? callbackPropInfo; + private MethodInfo? cmdInvokeMethod; + private IMonitor monitor; + + public CommandInvoker(IModHelper helper, IMonitor monitor) + { + this.monitor = monitor; + ObtainGetCommandMethod(helper); + } + + private void ObtainGetCommandMethod(IModHelper helper) + { + FieldInfo? commandManagerFieldInfo = helper.ConsoleCommands.GetType().GetField("CommandManager", BindingFlags.NonPublic | BindingFlags.Instance); + if (commandManagerFieldInfo == null) + { + this.monitor.Log($"Could not obtain CommandManager field info", LogLevel.Error); + return; + } + //this.monitor.Log($"CommandManager has type {commandManagerFieldInfo.FieldType.FullName}", LogLevel.Debug); + + this.commandManager = commandManagerFieldInfo.GetValue(helper.ConsoleCommands); + if (this.commandManager == null) + { + this.monitor.Log("Could not obtain ConsoleCommands object", LogLevel.Error); + return; + } + //this.monitor.Log("Obtained ConsoleCommands object", LogLevel.Debug); + + this.getCommandMethod = commandManager.GetType().GetMethod("Get"); + if (this.getCommandMethod == null) + { + this.monitor.Log("Could not obtain ConsoleCommands.Get method", LogLevel.Error); + return; + } + //this.monitor.Log("Obtained ConsoleCommands.Get MethodInfo", LogLevel.Debug); + + Type commandType = this.getCommandMethod.ReturnType; + //this.monitor.Log($"Command type is {commandType.AssemblyQualifiedName}", LogLevel.Debug); + + PropertyInfo[] propertyInfos = commandType.GetProperties(BindingFlags.Instance | BindingFlags.Public); + foreach (PropertyInfo propertyInfo in propertyInfos) + { + if (propertyInfo.Name == "Callback") + { + this.callbackPropInfo = propertyInfo; + } + } + + if (this.callbackPropInfo == null) + { + this.monitor.Log($"Could not obtain Command.Callback PropertyInfo", LogLevel.Error); + return; + } + //this.monitor.Log("Obtained Command.Callback PropertyInfo", LogLevel.Debug); + //this.monitor.Log($"Command.Callback type is {this.callbackPropInfo.PropertyType}", LogLevel.Debug); + + this.cmdInvokeMethod = this.callbackPropInfo.PropertyType.GetMethod("Invoke"); + if (this.cmdInvokeMethod == null) + { + this.monitor.Log($"Could not obtain Action.Invoke MethodInfo", LogLevel.Error); + return; + } + //this.monitor.Log("Obtained Action.Invoke MethodInfo", LogLevel.Debug); + } + + /// + /// Invokes the requested console command. + /// + /// The command name. + /// The command aarguments. + /// Returns whether the command was actually triggered; We may not have found the `CommandManager` properly, or the requested command may not exist. + public bool InvokeCommand(string name, string[] args) + { + object[] getArguments = { name }; + object? command = this.getCommandMethod?.Invoke(this.commandManager, getArguments); + if (command == null) + { + this.monitor.Log($"Could not obtain `{name}` command", LogLevel.Error); + return false; + } + + object? callback = this.callbackPropInfo?.GetValue(command); + if (callback == null) + { + this.monitor.Log($"Could not obtain Callback value", LogLevel.Error); + return false; + } + //this.monitor.Log($"Obtained callback for command `{name}`, calling with args `{args}`", LogLevel.Debug); + + if (this.cmdInvokeMethod == null) + return false; + + object[] callbackArgs = { name, args }; + this.cmdInvokeMethod.Invoke(callback, callbackArgs); + return true; + } + } +} diff --git a/ChatCommands/manifest.json b/ChatCommands/manifest.json index 608cae4..1c544e9 100644 --- a/ChatCommands/manifest.json +++ b/ChatCommands/manifest.json @@ -1,10 +1,10 @@ { "Name": "Chat Commands", "Author": "Cat", - "Version": "1.15.2", - "Description": "Lets you run SMAPI commands from the chat window!", + "Version": "1.15.3", + "Description": "Lets you run SMAPI commands from the chat window! Updated for SV 1.6 by Lino5000", "UniqueID": "cat.chatcommands", "EntryDll": "ChatCommands.dll", - "MinimumApiVersion": "3.0.0", + "MinimumApiVersion": "4.0.0", "UpdateKeys": [ "Nexus:2092" ] }