Process chat messages correctly when using /ss commands

This commit is contained in:
Loporrit
2025-07-25 22:06:14 +00:00
parent 4b6978c1c7
commit b5d5892ec3
2 changed files with 44 additions and 6 deletions

View File

@@ -10,6 +10,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using FFXIVClientStructs.FFXIV.Client.UI.Shell; using FFXIVClientStructs.FFXIV.Client.UI.Shell;
using FFXIVClientStructs.FFXIV.Component.Shell; using FFXIVClientStructs.FFXIV.Component.Shell;
using MareSynchronos.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace MareSynchronos.Interop; namespace MareSynchronos.Interop;
@@ -25,6 +26,7 @@ public unsafe sealed class GameChatHooks : IDisposable
// Based on https://git.anna.lgbt/anna/ExtraChat/src/branch/main/client/ExtraChat/GameFunctions.cs // Based on https://git.anna.lgbt/anna/ExtraChat/src/branch/main/client/ExtraChat/GameFunctions.cs
private readonly ILogger<GameChatHooks> _logger; private readonly ILogger<GameChatHooks> _logger;
private readonly Action<int, byte[]> _ssCommandHandler;
#region signatures #region signatures
#pragma warning disable CS0649 #pragma warning disable CS0649
@@ -126,9 +128,10 @@ public unsafe sealed class GameChatHooks : IDisposable
} }
} }
public GameChatHooks(ILogger<GameChatHooks> logger, IGameInteropProvider gameInteropProvider) public GameChatHooks(ILogger<GameChatHooks> logger, IGameInteropProvider gameInteropProvider, Action<int, byte[]> ssCommandHandler)
{ {
_logger = logger; _logger = logger;
_ssCommandHandler = ssCommandHandler;
logger.LogInformation("Initializing GameChatHooks"); logger.LogInformation("Initializing GameChatHooks");
gameInteropProvider.InitializeFromAttributes(this); gameInteropProvider.InitializeFromAttributes(this);
@@ -153,6 +156,14 @@ public unsafe sealed class GameChatHooks : IDisposable
UnfocusTickHook?.Dispose(); UnfocusTickHook?.Dispose();
} }
private byte[] ProcessChatMessage(Utf8String* message)
{
var pronounModule = UIModule.Instance()->GetPronounModule();
var chatString1 = pronounModule->ProcessString(message, true);
var chatString2 = _processStringStep2(pronounModule, chatString1, 1);
return MemoryHelper.ReadRaw((nint)chatString2->StringPtr.Value, chatString2->Length);
}
private void SendMessageDetour(ShellCommandModule* thisPtr, Utf8String* message, UIModule* uiModule) private void SendMessageDetour(ShellCommandModule* thisPtr, Utf8String* message, UIModule* uiModule)
{ {
try try
@@ -196,6 +207,23 @@ public unsafe sealed class GameChatHooks : IDisposable
if (isReply) if (isReply)
_nextMessageIsReply = utcNow + TimeSpan.FromMilliseconds(100); _nextMessageIsReply = utcNow + TimeSpan.FromMilliseconds(100);
// If it is a command, check if it begins with /ss first so we can handle the message directly
// Letting Dalamud handle the commands causes all of the special payloads to be dropped
if (isCommand && messageSpan.StartsWith(System.Text.Encoding.ASCII.GetBytes("/ss")))
{
for (int i = 1; i <= ChatService.CommandMaxNumber; ++i)
{
var cmdString = $"/ss{i} ";
if (messageSpan.StartsWith(System.Text.Encoding.ASCII.GetBytes(cmdString)))
{
var ssChatBytes = ProcessChatMessage(message);
ssChatBytes = ssChatBytes.Skip(cmdString.Length).ToArray();
_ssCommandHandler?.Invoke(i, ssChatBytes);
return;
}
}
}
// If not a command, or no override is set, then call the original chat handler // If not a command, or no override is set, then call the original chat handler
if (isCommand || _chatChannelOverride == null) if (isCommand || _chatChannelOverride == null)
{ {
@@ -205,10 +233,7 @@ public unsafe sealed class GameChatHooks : IDisposable
// Otherwise, the text is to be sent to the emulated chat channel handler // Otherwise, the text is to be sent to the emulated chat channel handler
// The chat input string is rendered in to a payload for display first // The chat input string is rendered in to a payload for display first
var pronounModule = UIModule.Instance()->GetPronounModule(); var chatBytes = ProcessChatMessage(message);
var chatString1 = pronounModule->ProcessString(message, true);
var chatString2 = _processStringStep2(pronounModule, chatString1, 1);
var chatBytes = MemoryHelper.ReadRaw((nint)chatString2->StringPtr.Value, chatString2->Length);
if (chatBytes.Length > 0) if (chatBytes.Length > 0)
_chatChannelOverride.ChatMessageHandler?.Invoke(chatBytes); _chatChannelOverride.ChatMessageHandler?.Invoke(chatBytes);

View File

@@ -45,7 +45,20 @@ public class ChatService : DisposableMediatorSubscriberBase
Mediator.Subscribe<UserChatMsgMessage>(this, HandleUserChat); Mediator.Subscribe<UserChatMsgMessage>(this, HandleUserChat);
Mediator.Subscribe<GroupChatMsgMessage>(this, HandleGroupChat); Mediator.Subscribe<GroupChatMsgMessage>(this, HandleGroupChat);
_gameChatHooks = new(() => new GameChatHooks(loggerFactory.CreateLogger<GameChatHooks>(), gameInteropProvider)); _gameChatHooks = new(() => new GameChatHooks(loggerFactory.CreateLogger<GameChatHooks>(), gameInteropProvider, SendChatShell));
// Initialize chat hooks in advance
_ = Task.Run(() =>
{
try
{
_ = _gameChatHooks.Value;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize chat hooks");
}
});
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)