XivCommon/XivCommon/Functions/Tooltips/Tooltips.cs

152 lines
6.1 KiB
C#
Executable File

using System;
using System.Runtime.InteropServices;
using Dalamud.Game;
using Dalamud.Game.Gui;
using Dalamud.Hooking;
namespace XivCommon.Functions.Tooltips {
/// <summary>
/// The class containing tooltip functionality
/// </summary>
public class Tooltips : IDisposable {
private static class Signatures {
internal const string AgentItemDetailUpdateTooltip = "E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ?? 48 89 AE";
internal const string AgentActionDetailUpdateTooltip = "E8 ?? ?? ?? ?? EB 68 FF 50 40";
internal const string SadSetString = "E8 ?? ?? ?? ?? F6 47 14 08";
}
// Last checked: 6.0
// E8 ?? ?? ?? ?? EB 68 FF 50 40
private const int AgentActionDetailUpdateFlagOffset = 0x58;
internal unsafe delegate void StringArrayDataSetStringDelegate(IntPtr self, int index, byte* str, byte updatePtr, byte copyToUi, byte dontSetModified);
private unsafe delegate ulong ItemUpdateTooltipDelegate(IntPtr agent, int** numberArrayData, byte*** stringArrayData, float a4);
private unsafe delegate void ActionUpdateTooltipDelegate(IntPtr agent, int** numberArrayData, byte*** stringArrayData);
private StringArrayDataSetStringDelegate? SadSetString { get; }
private Hook<ItemUpdateTooltipDelegate>? ItemUpdateTooltipHook { get; }
private Hook<ActionUpdateTooltipDelegate>? ActionGenerateTooltipHook { get; }
/// <summary>
/// The delegate for item tooltip events.
/// </summary>
public delegate void ItemTooltipEventDelegate(ItemTooltip itemTooltip, ulong itemId);
/// <summary>
/// The tooltip for action tooltip events.
/// </summary>
public delegate void ActionTooltipEventDelegate(ActionTooltip actionTooltip, HoveredAction action);
/// <summary>
/// <para>
/// The event that is fired when an item tooltip is being generated for display.
/// </para>
/// <para>
/// Requires the <see cref="Hooks.Tooltips"/> hook to be enabled.
/// </para>
/// </summary>
public event ItemTooltipEventDelegate? OnItemTooltip;
/// <summary>
/// <para>
/// The event that is fired when an action tooltip is being generated for display.
/// </para>
/// <para>
/// Requires the <see cref="Hooks.Tooltips"/> hook to be enabled.
/// </para>
/// </summary>
public event ActionTooltipEventDelegate? OnActionTooltip;
private GameGui GameGui { get; }
private ItemTooltip? ItemTooltip { get; set; }
private ActionTooltip? ActionTooltip { get; set; }
internal Tooltips(SigScanner scanner, GameGui gui, bool enabled) {
this.GameGui = gui;
if (scanner.TryScanText(Signatures.SadSetString, out var setStringPtr, "Tooltips - StringArrayData::SetString")) {
this.SadSetString = Marshal.GetDelegateForFunctionPointer<StringArrayDataSetStringDelegate>(setStringPtr);
} else {
return;
}
if (!enabled) {
return;
}
if (scanner.TryScanText(Signatures.AgentItemDetailUpdateTooltip, out var updateItemPtr, "Tooltips - Items")) {
unsafe {
this.ItemUpdateTooltipHook = Hook<ItemUpdateTooltipDelegate>.FromAddress(updateItemPtr, this.ItemUpdateTooltipDetour);
}
this.ItemUpdateTooltipHook.Enable();
}
if (scanner.TryScanText(Signatures.AgentActionDetailUpdateTooltip, out var updateActionPtr, "Tooltips - Actions")) {
unsafe {
this.ActionGenerateTooltipHook = Hook<ActionUpdateTooltipDelegate>.FromAddress(updateActionPtr, this.ActionUpdateTooltipDetour);
}
this.ActionGenerateTooltipHook.Enable();
}
}
/// <inheritdoc />
public void Dispose() {
this.ActionGenerateTooltipHook?.Dispose();
this.ItemUpdateTooltipHook?.Dispose();
}
private unsafe ulong ItemUpdateTooltipDetour(IntPtr agent, int** numberArrayData, byte*** stringArrayData, float a4) {
var ret = this.ItemUpdateTooltipHook!.Original(agent, numberArrayData, stringArrayData, a4);
if (ret > 0) {
try {
this.ItemUpdateTooltipDetourInner(numberArrayData, stringArrayData);
} catch (Exception ex) {
Logger.LogError(ex, "Exception in item tooltip detour");
}
}
return ret;
}
private unsafe void ItemUpdateTooltipDetourInner(int** numberArrayData, byte*** stringArrayData) {
this.ItemTooltip = new ItemTooltip(this.SadSetString!, stringArrayData, numberArrayData);
try {
this.OnItemTooltip?.Invoke(this.ItemTooltip, this.GameGui.HoveredItem);
} catch (Exception ex) {
Logger.LogError(ex, "Exception in OnItemTooltip event");
}
}
private unsafe void ActionUpdateTooltipDetour(IntPtr agent, int** numberArrayData, byte*** stringArrayData) {
var flag = *(byte*) (agent + AgentActionDetailUpdateFlagOffset);
this.ActionGenerateTooltipHook!.Original(agent, numberArrayData, stringArrayData);
if (flag == 0) {
return;
}
try {
this.ActionUpdateTooltipDetourInner(numberArrayData, stringArrayData);
} catch (Exception ex) {
Logger.LogError(ex, "Exception in action tooltip detour");
}
}
private unsafe void ActionUpdateTooltipDetourInner(int** numberArrayData, byte*** stringArrayData) {
this.ActionTooltip = new ActionTooltip(this.SadSetString!, stringArrayData, numberArrayData);
try {
this.OnActionTooltip?.Invoke(this.ActionTooltip, this.GameGui.HoveredAction);
} catch (Exception ex) {
Logger.LogError(ex, "Exception in OnActionTooltip event");
}
}
}
}