feat(ui): notify on failed tell via RaptureLogModule hook

This commit is contained in:
2026-05-21 10:00:53 +02:00
parent 2e81c42e3b
commit 246f0e2511
8 changed files with 177 additions and 0 deletions
@@ -0,0 +1,74 @@
using System;
using Dalamud.Hooking;
using Dalamud.Interface.ImGuiNotification;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using HellionChat._Helpers;
using HellionChat.Resources;
using HellionChat.Util;
using Microsoft.Extensions.Logging;
namespace HellionChat.Integrations;
// UI-2: a minimal, failed-tell-specific game hook. A locale-robust "tell
// failed" signal is not reachable over the processed message stream (Message
// carries no LogMessage row id, ChatCode 60 is too broad). This hooks the one
// ShowLogMessageString overload and toasts on a pinned id set. It is NOT the
// broad ad-block hook layer — that stays v1.5.6.
internal sealed class FailedTellNotifier : IDisposable
{
private readonly ILogger<FailedTellNotifier> _logger;
private readonly Hook<RaptureLogModule.Delegates.ShowLogMessageString>? _hook;
public unsafe FailedTellNotifier(ILogger<FailedTellNotifier> logger)
{
_logger = logger;
// Creating/enabling a hook is safe off the framework thread (the
// ctor runs during host startup on the framework thread,
// eager-resolved via FailedTellNotifierInitHostedService).
_hook = Plugin.GameInteropProvider
.HookFromAddress<RaptureLogModule.Delegates.ShowLogMessageString>(
RaptureLogModule.MemberFunctionPointers.ShowLogMessageString,
ShowLogMessageStringDetour);
_hook.Enable();
}
private unsafe void ShowLogMessageStringDetour(
RaptureLogModule* module, uint logMessageId, Utf8String* value)
{
try
{
// DISCOVERY (Task 2 Step 9): while the id set is empty, log every
// call so a failed tell can be triggered in-game and its id read.
// Remove this block once FailedTellLogMessageIds is pinned.
_logger.LogInformation(
"ShowLogMessageString id={Id} value={Value}",
logMessageId,
value is null ? "<null>" : value->ToString());
if (FailedTellMatcher.ShouldNotify(
logMessageId, Plugin.Config.NotifyFailedTell,
FailedTellMatcher.FailedTellLogMessageIds))
{
var recipient = value is null ? string.Empty : value->ToString();
var content = string.IsNullOrEmpty(recipient)
? HellionStrings.FailedTell_Notification_Generic
: string.Format(HellionStrings.FailedTell_Notification_Named, recipient);
WrapperUtil.AddNotification(content, NotificationType.Warning);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "FailedTellNotifier detour threw");
}
_hook!.Original(module, logMessageId, value);
}
public void Dispose()
{
_hook?.Disable();
_hook?.Dispose();
}
}