Use ContentIdResolver hook
This commit is contained in:
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user