Refactor ChatGui internals to use CS additions (#1520)

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
This commit is contained in:
Haselnussbomber 2023-12-08 00:22:09 +01:00 committed by GitHub
parent 6637bd8207
commit 9489c4ec20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 153 deletions

View file

@ -4,26 +4,34 @@ using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Configuration.Internal;
using Dalamud.Game.Libc;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Serilog;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
namespace Dalamud.Game.Gui;
// TODO(api10): Update IChatGui, ChatGui and XivChatEntry to use correct types and names:
// "uint SenderId" should be "int Timestamp".
// "IntPtr Parameters" should be something like "bool Silent". It suppresses new message sounds in certain channels.
/// <summary>
/// This class handles interacting with the native chat UI.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
internal sealed unsafe class ChatGui : IDisposable, IServiceType, IChatGui
{
private static readonly ModuleLog Log = new("ChatGui");
private readonly ChatGuiAddressResolver address;
private readonly Queue<XivChatEntry> chatQueue = new();
@ -36,10 +44,6 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
[ServiceManager.ServiceDependency]
private readonly LibcFunction libcFunction = Service<LibcFunction>.Get();
private IntPtr baseAddress = IntPtr.Zero;
private ImmutableDictionary<(string PluginName, uint CommandId), Action<uint, SeString>>? dalamudLinkHandlersCopy;
[ServiceManager.ServiceConstructor]
@ -48,7 +52,7 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
this.address = new ChatGuiAddressResolver();
this.address.Setup(sigScanner);
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(this.address.PrintMessage, this.HandlePrintMessageDetour);
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress((nint)RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour);
this.populateItemLinkHook = Hook<PopulateItemLinkDelegate>.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
this.interactableLinkClickedHook = Hook<InteractableLinkClickedDelegate>.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
@ -58,7 +62,7 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter);
private delegate uint PrintMessageDelegate(RaptureLogModule* manager, XivChatType chatType, Utf8String* sender, Utf8String* message, int timestamp, bool silent);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
@ -150,18 +154,13 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
{
var chat = this.chatQueue.Dequeue();
if (this.baseAddress == IntPtr.Zero)
{
continue;
}
var sender = Utf8String.FromSequence(chat.Name.Encode());
var message = Utf8String.FromSequence(chat.Message.Encode());
var senderRaw = (chat.Name ?? string.Empty).Encode();
using var senderOwned = this.libcFunction.NewString(senderRaw);
this.HandlePrintMessageDetour(RaptureLogModule.Instance(), chat.Type, sender, message, (int)chat.SenderId, chat.Parameters != 0);
var messageRaw = (chat.Message ?? string.Empty).Encode();
using var messageOwned = this.libcFunction.NewString(messageRaw);
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
sender->Dtor(true);
message->Dtor(true);
}
}
@ -279,29 +278,17 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
}
}
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chatType, IntPtr pSenderName, IntPtr pMessage, uint senderId, IntPtr parameter)
private uint HandlePrintMessageDetour(RaptureLogModule* manager, XivChatType chatType, Utf8String* sender, Utf8String* message, int timestamp, bool silent)
{
var retVal = IntPtr.Zero;
var messageId = 0u;
try
{
var sender = StdString.ReadFromPointer(pSenderName);
var parsedSender = SeString.Parse(sender.RawData);
var originalSenderData = (byte[])sender.RawData.Clone();
var oldEditedSender = parsedSender.Encode();
var senderPtr = pSenderName;
OwnedStdString allocatedString = null;
var originalSenderData = sender->Span.ToArray();
var originalMessageData = message->Span.ToArray();
var message = StdString.ReadFromPointer(pMessage);
var parsedMessage = SeString.Parse(message.RawData);
var originalMessageData = (byte[])message.RawData.Clone();
var oldEdited = parsedMessage.Encode();
var messagePtr = pMessage;
OwnedStdString allocatedStringSender = null;
// Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
// Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
var parsedSender = SeString.Parse(originalSenderData);
var parsedMessage = SeString.Parse(originalMessageData);
// Call events
var isHandled = false;
@ -312,7 +299,7 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
try
{
var messageHandledDelegate = @delegate as IChatGui.OnCheckMessageHandledDelegate;
messageHandledDelegate!.Invoke(chatType, senderId, ref parsedSender, ref parsedMessage, ref isHandled);
messageHandledDelegate!.Invoke(chatType, (uint)timestamp, ref parsedSender, ref parsedMessage, ref isHandled);
}
catch (Exception e)
{
@ -328,7 +315,7 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
try
{
var messageHandledDelegate = @delegate as IChatGui.OnMessageDelegate;
messageHandledDelegate!.Invoke(chatType, senderId, ref parsedSender, ref parsedMessage, ref isHandled);
messageHandledDelegate!.Invoke(chatType, (uint)timestamp, ref parsedSender, ref parsedMessage, ref isHandled);
}
catch (Exception e)
{
@ -337,61 +324,39 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
}
}
var newEdited = parsedMessage.Encode();
if (!Util.FastByteArrayCompare(oldEdited, newEdited))
var possiblyModifiedSenderData = parsedSender.Encode();
var possiblyModifiedMessageData = parsedMessage.Encode();
if (!Util.FastByteArrayCompare(originalSenderData, possiblyModifiedSenderData))
{
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
message.RawData = newEdited;
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
Log.Verbose($"HandlePrintMessageDetour Sender modified: {SeString.Parse(originalSenderData)} -> {parsedSender}");
sender->SetString(possiblyModifiedSenderData);
}
if (!Util.FastByteArrayCompare(originalMessageData, message.RawData))
if (!Util.FastByteArrayCompare(originalMessageData, possiblyModifiedMessageData))
{
allocatedString = this.libcFunction.NewString(message.RawData);
Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
messagePtr = allocatedString.Address;
}
var newEditedSender = parsedSender.Encode();
if (!Util.FastByteArrayCompare(oldEditedSender, newEditedSender))
{
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
sender.RawData = newEditedSender;
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
}
if (!Util.FastByteArrayCompare(originalSenderData, sender.RawData))
{
allocatedStringSender = this.libcFunction.NewString(sender.RawData);
Log.Debug(
$"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})");
senderPtr = allocatedStringSender.Address;
Log.Verbose($"HandlePrintMessageDetour Message modified: {SeString.Parse(originalMessageData)} -> {parsedMessage}");
message->SetString(possiblyModifiedMessageData);
}
// Print the original chat if it's handled.
if (isHandled)
{
this.ChatMessageHandled?.Invoke(chatType, senderId, parsedSender, parsedMessage);
this.ChatMessageHandled?.Invoke(chatType, (uint)timestamp, parsedSender, parsedMessage);
}
else
{
retVal = this.printMessageHook.Original(manager, chatType, senderPtr, messagePtr, senderId, parameter);
this.ChatMessageUnhandled?.Invoke(chatType, senderId, parsedSender, parsedMessage);
messageId = this.printMessageHook.Original(manager, chatType, sender, message, timestamp, silent);
this.ChatMessageUnhandled?.Invoke(chatType, (uint)timestamp, parsedSender, parsedMessage);
}
if (this.baseAddress == IntPtr.Zero)
this.baseAddress = manager;
allocatedString?.Dispose();
allocatedStringSender?.Dispose();
}
catch (Exception ex)
{
Log.Error(ex, "Exception on OnChatMessage hook.");
retVal = this.printMessageHook.Original(manager, chatType, pSenderName, pMessage, senderId, parameter);
messageId = this.printMessageHook.Original(manager, chatType, sender, message, timestamp, silent);
}
return retVal;
return messageId;
}
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
@ -409,11 +374,7 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
var messageSize = 0;
while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++;
var payloadBytes = new byte[messageSize];
Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize);
var seStr = SeString.Parse(payloadBytes);
var seStr = MemoryHelper.ReadSeStringNullTerminated(messagePtr);
var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator);
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
if (payloads.Count == 0) return;

View file

@ -5,11 +5,6 @@ namespace Dalamud.Game.Gui;
/// </summary>
internal sealed class ChatGuiAddressResolver : BaseAddressResolver
{
/// <summary>
/// Gets the address of the native PrintMessage method.
/// </summary>
public IntPtr PrintMessage { get; private set; }
/// <summary>
/// Gets the address of the native PopulateItemLinkObject method.
/// </summary>
@ -20,77 +15,9 @@ internal sealed class ChatGuiAddressResolver : BaseAddressResolver
/// </summary>
public IntPtr InteractableLinkClicked { get; private set; }
/*
--- for reference: 4.57 ---
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201p
.text:00000001405CD210 ; sub_140141D10+220p ...
.text:00000001405CD210
.text:00000001405CD210 var_220 = qword ptr -220h
.text:00000001405CD210 var_218 = byte ptr -218h
.text:00000001405CD210 var_210 = word ptr -210h
.text:00000001405CD210 var_208 = byte ptr -208h
.text:00000001405CD210 var_200 = word ptr -200h
.text:00000001405CD210 var_1FC = dword ptr -1FCh
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
.text:00000001405CD210 var_1DC = word ptr -1DCh
.text:00000001405CD210 var_1DA = word ptr -1DAh
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
.text:00000001405CD210 var_1AC = dword ptr -1ACh
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
.text:00000001405CD210 var_160 = dword ptr -160h
.text:00000001405CD210 var_15C = dword ptr -15Ch
.text:00000001405CD210 var_140 = dword ptr -140h
.text:00000001405CD210 var_138 = dword ptr -138h
.text:00000001405CD210 var_130 = byte ptr -130h
.text:00000001405CD210 var_C0 = byte ptr -0C0h
.text:00000001405CD210 var_50 = qword ptr -50h
.text:00000001405CD210 var_38 = qword ptr -38h
.text:00000001405CD210 var_30 = qword ptr -30h
.text:00000001405CD210 var_28 = qword ptr -28h
.text:00000001405CD210 var_20 = qword ptr -20h
.text:00000001405CD210 senderActorId = dword ptr 30h
.text:00000001405CD210 isLocal = byte ptr 38h
.text:00000001405CD210
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
.text:00000001405CD210 push rbp
.text:00000001405CD212 push rdi
.text:00000001405CD213 push r14
.text:00000001405CD215 push r15
.text:00000001405CD217 lea rbp, [rsp-128h]
.text:00000001405CD21F sub rsp, 228h
.text:00000001405CD226 mov rax, cs:__security_cookie
.text:00000001405CD22D xor rax, rsp
.text:00000001405CD230 mov [rbp+140h+var_50], rax
.text:00000001405CD237 xor r10b, r10b
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
.text:00000001405CD23F xor eax, eax
.text:00000001405CD241 mov r11, r9
.text:00000001405CD244 mov r14, r8
.text:00000001405CD247 mov r9d, eax
.text:00000001405CD24A movzx r15d, dx
.text:00000001405CD24E lea r8, [rcx+0C10h]
.text:00000001405CD255 mov rdi, rcx
*/
/// <inheritdoc/>
protected override void Setup64Bit(ISigScanner sig)
{
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05");
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
// PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0