Use ContentIdResolver hook

This commit is contained in:
Dean Sheather
2024-05-26 00:33:21 +10:00
parent e9ae8362f5
commit 748a963acc
3 changed files with 33 additions and 35 deletions
-5
View File
@@ -69,11 +69,6 @@ internal unsafe class GameFunctions : IDisposable
return (nint) infoModule->GetInfoProxyById(idx); return (nint) infoModule->GetInfoProxyById(idx);
} }
internal int GetCurrentChatLogEntryIndex()
{
return Framework.Instance()->GetUiModule()->GetRaptureLogModule()->LogModule.LogMessageCount;
}
internal void SendFriendRequest(string name, ushort world) internal void SendFriendRequest(string name, ushort world)
{ {
ListCommand(name, world, "friendlist"); ListCommand(name, world, "friendlist");
+31 -30
View File
@@ -5,8 +5,11 @@ using ChatTwo.Resources;
using ChatTwo.Util; using ChatTwo.Util;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
namespace ChatTwo; namespace ChatTwo;
@@ -20,23 +23,25 @@ internal class MessageManager : IAsyncDisposable
private Dictionary<ChatType, NameFormatting> Formats { get; } = new(); private Dictionary<ChatType, NameFormatting> Formats { get; } = new();
private ulong LastContentId { get; set; } private ulong LastContentId { get; set; }
private int LastMessageIndex { get; set; }
// Messages go into the PendingSync queue first, which will be consumed one // Messages go into the PendingSync queue first, which will be consumed one
// at a time in the main thread. The only processing that will be done is // at a time in the main thread. This is to delay the async processing until
// to fetch the ContentId for the message if the int is not 0. Fetching // after we've received the content ID from the ContentIdResolver hook.
// content ID must happen in the next tick AFTER receiving the message via
// the event listener, because when the event handler is called the message
// isn't in the log yet.
// //
// After that, the message is enqueued in the PendingAsync queue, which will // After that, the message is enqueued in the PendingAsync queue, which will
// be consumed in a separate thread and perform more processing (emotes, // be consumed in a separate thread and perform more processing (emotes,
// URLs) as well as inserting the message into the database. // URLs) as well as inserting the message into the database.
private Queue<(int, PendingMessage pendingMessage)> PendingSync { get; } = new(); private Queue<PendingMessage> PendingSync { get; } = new();
private ConcurrentQueue<PendingMessage> PendingAsync { get; } = new(); private ConcurrentQueue<PendingMessage> PendingAsync { get; } = new();
private readonly Thread PendingMessageThread; private readonly Thread PendingMessageThread;
private readonly CancellationTokenSource PendingThreadCancellationToken = new(); private readonly CancellationTokenSource PendingThreadCancellationToken = new();
// TODO: replace with CS version
private unsafe delegate void ContentIdResolverDelegate(RaptureLogModule* param1, ulong param2, int param3, short param4, short param5);
[Signature("4C 8B D1 48 8B 89 ?? ?? ?? ?? 48 85 C9", DetourName = nameof(ContentIdResolver))]
private Hook<ContentIdResolverDelegate>? ContentIdResolverHook { get; init; }
internal ulong CurrentContentId internal ulong CurrentContentId
{ {
get get
@@ -54,6 +59,7 @@ internal class MessageManager : IAsyncDisposable
PendingMessageThread = new Thread(() => ProcessPendingMessages(PendingThreadCancellationToken.Token)); PendingMessageThread = new Thread(() => ProcessPendingMessages(PendingThreadCancellationToken.Token));
PendingMessageThread.Start(); PendingMessageThread.Start();
ContentIdResolverHook?.Enable();
Plugin.ChatGui.ChatMessageUnhandled += ChatMessage; Plugin.ChatGui.ChatMessageUnhandled += ChatMessage;
Plugin.Framework.Update += OnFrameworkUpdate; Plugin.Framework.Update += OnFrameworkUpdate;
Plugin.ClientState.Logout += Logout; Plugin.ClientState.Logout += Logout;
@@ -61,6 +67,7 @@ internal class MessageManager : IAsyncDisposable
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
ContentIdResolverHook?.Dispose();
Plugin.ClientState.Logout -= Logout; Plugin.ClientState.Logout -= Logout;
Plugin.Framework.Update -= OnFrameworkUpdate; Plugin.Framework.Update -= OnFrameworkUpdate;
Plugin.ChatGui.ChatMessageUnhandled -= ChatMessage; Plugin.ChatGui.ChatMessageUnhandled -= ChatMessage;
@@ -88,7 +95,6 @@ internal class MessageManager : IAsyncDisposable
private void Logout() private void Logout()
{ {
LastContentId = 0; LastContentId = 0;
LastMessageIndex = 0;
} }
private void OnFrameworkUpdate(IFramework framework) private void OnFrameworkUpdate(IFramework framework)
@@ -97,13 +103,12 @@ internal class MessageManager : IAsyncDisposable
if (contentId != 0) if (contentId != 0)
LastContentId = contentId; LastContentId = contentId;
// Drain the PendingSync queue into the PendingAsync queue.
while (true) while (true)
{ {
if (!PendingSync.TryDequeue(out var pending)) return; if (!PendingSync.TryDequeue(out var pending))
return;
if (pending.Item1 > 0) PendingAsync.Enqueue(pending);
pending.Item2.ContentId = Plugin.Functions.Chat.GetContentIdForEntry(pending.Item1);
PendingAsync.Enqueue(pending.Item2);
} }
} }
@@ -205,24 +210,20 @@ internal class MessageManager : IAsyncDisposable
// Update colour codes. // Update colour codes.
GlobalParametersCache.Refresh(); GlobalParametersCache.Refresh();
// If the message was rendered in the vanilla chat log window it has an // We delay messages to be handed off to the async processing thread
// index, and we can use that to get the sender's content ID. The // in the next tick, otherwise we can't get the content ID from the hook
// content ID is used to show "invite to party" buttons in the context // below.
// menu. PendingSync.Enqueue(pendingMessage);
var idx = Plugin.Functions.GetCurrentChatLogEntryIndex(); }
if (idx <= LastMessageIndex)
idx = 0;
else
LastMessageIndex = idx;
// You can't call GetContentIdForEntry in the same framework tick // This hook is called immediately after receiving a message with the
// that you received the message, or you just get null. // message's content ID. If multiple messages are received in the same tick,
// // this will be called for each message immediately after ChatMessage is
// We delay all messages to be enqueued in the next framework tick // called for each message.
// because of this. We used to only delay messages that we wanted to private unsafe void ContentIdResolver(RaptureLogModule* param1, ulong param2, int param3, short param4, short param5)
// fetch a content ID for, but this results in out-of-order messages {
// occasionally. PendingSync.Last().ContentId = param2;
PendingSync.Enqueue((idx - 1, pendingMessage)); ContentIdResolverHook?.Original(param1, param2, param3, param4, param5);
} }
private void ProcessMessage(PendingMessage pendingMessage) private void ProcessMessage(PendingMessage pendingMessage)
+2
View File
@@ -5,8 +5,10 @@ using ChatTwo.Ipc;
using ChatTwo.Resources; using ChatTwo.Resources;
using ChatTwo.Ui; using ChatTwo.Ui;
using ChatTwo.Util; using ChatTwo.Util;
using Dalamud.Game;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Hooking;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.Plugin; using Dalamud.Plugin;