diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs
index aa6798171..078ce8c50 100644
--- a/Dalamud/Game/Command/CommandManager.cs
+++ b/Dalamud/Game/Command/CommandManager.cs
@@ -2,56 +2,45 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
-using System.Text.RegularExpressions;
using Dalamud.Console;
-using Dalamud.Game.Gui;
-using Dalamud.Game.Text;
-using Dalamud.Game.Text.SeStringHandling;
+using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.System.String;
+using FFXIVClientStructs.FFXIV.Client.UI;
+using FFXIVClientStructs.FFXIV.Component.Shell;
+
namespace Dalamud.Game.Command;
///
/// This class manages registered in-game slash commands.
///
[ServiceManager.EarlyLoadedService]
-internal sealed class CommandManager : IInternalDisposableService, ICommandManager
+internal sealed unsafe class CommandManager : IInternalDisposableService, ICommandManager
{
private static readonly ModuleLog Log = new("Command");
private readonly ConcurrentDictionary commandMap = new();
private readonly ConcurrentDictionary<(string, IReadOnlyCommandInfo), string> commandAssemblyNameMap = new();
- private readonly Regex commandRegexEn = new(@"^The command (?.+) does not exist\.$", RegexOptions.Compiled);
- private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?.+)$", RegexOptions.Compiled);
- private readonly Regex commandRegexDe = new(@"^„(?.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
- private readonly Regex commandRegexFr = new(@"^La commande texte “(?.+)” n'existe pas\.$", RegexOptions.Compiled);
- private readonly Regex commandRegexCn = new(@"^^(“|「)(?.+)(”|」)(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled);
- private readonly Regex currentLangCommandRegex;
- [ServiceManager.ServiceDependency]
- private readonly ChatGui chatGui = Service.Get();
-
+ private readonly Hook? tryInvokeDebugCommandHook;
+
[ServiceManager.ServiceDependency]
private readonly ConsoleManager console = Service.Get();
[ServiceManager.ServiceConstructor]
private CommandManager(Dalamud dalamud)
{
- this.currentLangCommandRegex = (ClientLanguage)dalamud.StartInfo.Language switch
- {
- ClientLanguage.Japanese => this.commandRegexJp,
- ClientLanguage.English => this.commandRegexEn,
- ClientLanguage.German => this.commandRegexDe,
- ClientLanguage.French => this.commandRegexFr,
- _ => this.commandRegexEn,
- };
+ this.tryInvokeDebugCommandHook = Hook.FromAddress(
+ (nint)ShellCommands.MemberFunctionPointers.TryInvokeDebugCommand,
+ this.OnTryInvokeDebugCommand);
+ this.tryInvokeDebugCommandHook.Enable();
- this.chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
this.console.Invoke += this.ConsoleOnInvoke;
}
@@ -113,7 +102,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument);
}
}
-
+
///
/// Add a command handler, which you can use to add your own custom commands to the in-game chat.
///
@@ -131,7 +120,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
Log.Error("Command {CommandName} is already registered", command);
return false;
}
-
+
if (!this.commandAssemblyNameMap.TryAdd((command, info), loaderAssemblyName))
{
this.commandMap.Remove(command, out _);
@@ -184,7 +173,8 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
///
/// The name of the assembly.
/// A list of commands and their associated activation string.
- public List> GetHandlersByAssemblyName(string assemblyName)
+ public List> GetHandlersByAssemblyName(
+ string assemblyName)
{
return this.commandAssemblyNameMap.Where(c => c.Value == assemblyName).ToList();
}
@@ -193,37 +183,20 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
void IInternalDisposableService.DisposeService()
{
this.console.Invoke -= this.ConsoleOnInvoke;
- this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled;
+ this.tryInvokeDebugCommandHook?.Dispose();
}
-
+
private bool ConsoleOnInvoke(string arg)
{
return arg.StartsWith('/') && this.ProcessCommand(arg);
}
- private void OnCheckMessageHandled(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled)
+ private int OnTryInvokeDebugCommand(ShellCommands* self, Utf8String* command, UIModule* uiModule)
{
- if (type == XivChatType.ErrorMessage && timestamp == 0)
- {
- var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"];
- if (cmdMatch.Success)
- {
- // Yes, it's a chat command.
- var command = cmdMatch.Value;
- if (this.ProcessCommand(command)) isHandled = true;
- }
- else
- {
- // Always match for china, since they patch in language files without changing the ClientLanguage.
- cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"];
- if (cmdMatch.Success)
- {
- // Yes, it's a Chinese fallback chat command.
- var command = cmdMatch.Value;
- if (this.ProcessCommand(command)) isHandled = true;
- }
- }
- }
+ var result = this.tryInvokeDebugCommandHook!.OriginalDisposeSafe(self, command, uiModule);
+ if (result != -1) return result;
+
+ return this.ProcessCommand(command->ToString()) ? 0 : result;
}
}
@@ -238,7 +211,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
internal class CommandManagerPluginScoped : IInternalDisposableService, ICommandManager
{
private static readonly ModuleLog Log = new("Command");
-
+
[ServiceManager.ServiceDependency]
private readonly CommandManager commandManagerService = Service.Get();
@@ -253,10 +226,10 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand
{
this.pluginInfo = localPlugin;
}
-
+
///
public ReadOnlyDictionary Commands => this.commandManagerService.Commands;
-
+
///
void IInternalDisposableService.DisposeService()
{
@@ -264,7 +237,7 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand
{
this.commandManagerService.RemoveHandler(command);
}
-
+
this.pluginRegisteredCommands.Clear();
}
@@ -275,7 +248,7 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand
///
public void DispatchCommand(string command, string argument, IReadOnlyCommandInfo info)
=> this.commandManagerService.DispatchCommand(command, argument, info);
-
+
///
public bool AddHandler(string command, CommandInfo info)
{
@@ -294,7 +267,7 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand
return false;
}
-
+
///
public bool RemoveHandler(string command)
{
diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs
index 7d7bb1380..d8e05716e 100644
--- a/Dalamud/Utility/Util.cs
+++ b/Dalamud/Utility/Util.cs
@@ -158,7 +158,7 @@ public static class Util
var asm = typeof(Util).Assembly;
var attrs = asm.GetCustomAttributes();
- return gitHashInternal = attrs.First(a => a.Key == "GitHash").Value;
+ return gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value ?? "N/A";
}
///