From fd44b711a3b4bcaf500453fc395c051fe296a187 Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Sun, 23 May 2021 05:43:48 -0400 Subject: [PATCH] feat: add basic tooltip support --- XivCommon/Functions/Tooltips/ActionTooltip.cs | 13 ++ .../Functions/Tooltips/ActionTooltipString.cs | 19 +++ XivCommon/Functions/Tooltips/BaseTooltip.cs | 30 +++++ XivCommon/Functions/Tooltips/ItemTooltip.cs | 13 ++ .../Functions/Tooltips/ItemTooltipString.cs | 51 ++++++++ XivCommon/Functions/Tooltips/Tooltips.cs | 118 ++++++++++++++++++ XivCommon/GameFunctions.cs | 12 +- XivCommon/Hooks.cs | 7 ++ XivCommon/Util.cs | 7 +- 9 files changed, 267 insertions(+), 3 deletions(-) create mode 100755 XivCommon/Functions/Tooltips/ActionTooltip.cs create mode 100755 XivCommon/Functions/Tooltips/ActionTooltipString.cs create mode 100755 XivCommon/Functions/Tooltips/BaseTooltip.cs create mode 100755 XivCommon/Functions/Tooltips/ItemTooltip.cs create mode 100755 XivCommon/Functions/Tooltips/ItemTooltipString.cs create mode 100755 XivCommon/Functions/Tooltips/Tooltips.cs diff --git a/XivCommon/Functions/Tooltips/ActionTooltip.cs b/XivCommon/Functions/Tooltips/ActionTooltip.cs new file mode 100755 index 0000000..a051556 --- /dev/null +++ b/XivCommon/Functions/Tooltips/ActionTooltip.cs @@ -0,0 +1,13 @@ +using Dalamud.Game.Text.SeStringHandling; + +namespace XivCommon.Functions.Tooltips { + public unsafe class ActionTooltip : BaseTooltip { + public ActionTooltip(SeStringManager manager, Tooltips.StringArrayDataSetStringDelegate sadSetString, byte*** pointer) : base(manager, sadSetString, pointer) { + } + + public SeString this[ActionTooltipString ats] { + get => this[(int) ats]; + set => this[(int) ats] = value; + } + } +} diff --git a/XivCommon/Functions/Tooltips/ActionTooltipString.cs b/XivCommon/Functions/Tooltips/ActionTooltipString.cs new file mode 100755 index 0000000..5eab584 --- /dev/null +++ b/XivCommon/Functions/Tooltips/ActionTooltipString.cs @@ -0,0 +1,19 @@ +namespace XivCommon.Functions.Tooltips { + public enum ActionTooltipString { + Name = 0, + Type = 1, + RangeLabel = 3, + Range = 4, + RadiusLabel = 5, + Radius = 6, + CostLabel = 7, + Cost = 8, + RecastLabel = 9, + Recast = 10, + CastLabel = 11, + Cast = 12, + Description = 13, + Acquired = 14, + Affinity = 15, + } +} diff --git a/XivCommon/Functions/Tooltips/BaseTooltip.cs b/XivCommon/Functions/Tooltips/BaseTooltip.cs new file mode 100755 index 0000000..978d65e --- /dev/null +++ b/XivCommon/Functions/Tooltips/BaseTooltip.cs @@ -0,0 +1,30 @@ +using System; +using Dalamud.Game.Text.SeStringHandling; + +namespace XivCommon.Functions.Tooltips { + public abstract unsafe class BaseTooltip { + protected SeStringManager Manager { get; } + protected Tooltips.StringArrayDataSetStringDelegate SadSetString { get; } + protected readonly byte*** _pointer; // this is StringArrayData* when ClientStructs is updated + + internal BaseTooltip(SeStringManager manager, Tooltips.StringArrayDataSetStringDelegate sadSetString, byte*** pointer) { + this.Manager = manager; + this.SadSetString = sadSetString; + this._pointer = pointer; + } + + protected SeString this[int offset] { + get { + var ptr = *(this._pointer + 4) + offset; + return Util.ReadSeString((IntPtr) (*ptr), this.Manager); + } + set { + var encoded = value.Encode().Terminate(); + + fixed (byte* encodedPtr = encoded) { + this.SadSetString((IntPtr) this._pointer, offset, encodedPtr, 0, 1, 1); + } + } + } + } +} diff --git a/XivCommon/Functions/Tooltips/ItemTooltip.cs b/XivCommon/Functions/Tooltips/ItemTooltip.cs new file mode 100755 index 0000000..800aea3 --- /dev/null +++ b/XivCommon/Functions/Tooltips/ItemTooltip.cs @@ -0,0 +1,13 @@ +using Dalamud.Game.Text.SeStringHandling; + +namespace XivCommon.Functions.Tooltips { + public unsafe class ItemTooltip : BaseTooltip { + public ItemTooltip(SeStringManager manager, Tooltips.StringArrayDataSetStringDelegate sadSetString, byte*** pointer) : base(manager, sadSetString, pointer) { + } + + public SeString this[ItemTooltipString its] { + get => this[(int) its]; + set => this[(int) its] = value; + } + } +} diff --git a/XivCommon/Functions/Tooltips/ItemTooltipString.cs b/XivCommon/Functions/Tooltips/ItemTooltipString.cs new file mode 100755 index 0000000..c1630a9 --- /dev/null +++ b/XivCommon/Functions/Tooltips/ItemTooltipString.cs @@ -0,0 +1,51 @@ +namespace XivCommon.Functions.Tooltips { + public enum ItemTooltipString { + Name = 0, + GlamourName = 1, + Type = 2, + Description = 13, + Quantity = 14, + Level = 27, + EffectsLabel = 15, + Effects = 16, + VendorSellPrice = 25, + VendorBuyPrice = 63, + Crafter = 26, + Stat1Label = 4, + Stat1 = 7, + Stat1Delta = 10, + Stat2Label = 5, + Stat2 = 8, + Stat2Delta = 11, + Stat3Label = 6, + Stat3 = 9, + Stat3Delta = 12, + EquipJobs = 22, + EquipLevel = 23, + Condition = 28, + SpriritbondLabel = 29, + Spiritbond = 30, + RepairLevel = 31, + Materials = 32, + QuickRepairs = 33, + MateriaMelding = 34, + AbleToDoShit = 35, + BonusesLabel = 36, + Bonus1 = 37, + Bonus2 = 38, + Bonus3 = 39, + Bonus4 = 40, + MateriaLabel = 52, + Materia1 = 53, + Materia2 = 54, + Materia3 = 55, + Materia4 = 56, + Materia5 = 57, + Materia1Effect = 58, + Materia2Effect = 59, + Materia3Effect = 60, + Materia4Effect = 61, + Materia5Effect = 62, + ControllerControls = 64, + } +} diff --git a/XivCommon/Functions/Tooltips/Tooltips.cs b/XivCommon/Functions/Tooltips/Tooltips.cs new file mode 100755 index 0000000..940ad55 --- /dev/null +++ b/XivCommon/Functions/Tooltips/Tooltips.cs @@ -0,0 +1,118 @@ +using System; +using System.Runtime.InteropServices; +using Dalamud.Game; +using Dalamud.Game.Internal.Gui; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Hooking; + +namespace XivCommon.Functions.Tooltips { + public class Tooltips : IDisposable { + private static class Signatures { + internal const string ItemGenerateTooltip = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 83 EC 50 48 8B 42 ??"; + internal const string ActionGenerateTooltip = "E8 ?? ?? ?? ?? 48 8B D5 48 8B CF E8 ?? ?? ?? ?? 41 8D 45 FF 83 F8 01 77 6D"; + internal const string SadSetString = "E8 ?? ?? ?? ?? F6 47 14 08"; + } + + public unsafe delegate void StringArrayDataSetStringDelegate(IntPtr self, int index, byte* str, byte updatePtr, byte copyToUi, byte dontSetModified); + + private unsafe delegate IntPtr ItemGenerateTooltipDelegate(IntPtr addon, uint** numberArrayData, byte*** stringArrayData); + + private unsafe delegate IntPtr ActionGenerateTooltipDelegate(IntPtr addon, uint** numberArrayData, byte*** stringArrayData); + + private StringArrayDataSetStringDelegate? SadSetString { get; } + private Hook? ItemGenerateTooltipHook { get; } + private Hook? ActionGenerateTooltipHook { get; } + + public delegate void ItemTooltipEventDelegate(ItemTooltip itemTooltip, ulong itemId); + + public delegate void ActionTooltipEventDelegate(ActionTooltip actionTooltip, HoveredAction action); + + public event ItemTooltipEventDelegate? OnItemTooltip; + public event ActionTooltipEventDelegate? OnActionTooltip; + + private GameGui GameGui { get; } + private SeStringManager SeStringManager { get; } + private ItemTooltip? ItemTooltip { get; set; } + private ActionTooltip? ActionTooltip { get; set; } + + internal Tooltips(SigScanner scanner, GameGui gui, SeStringManager manager, bool enabled) { + this.GameGui = gui; + this.SeStringManager = manager; + + if (scanner.TryScanText(Signatures.SadSetString, out var setStringPtr, "Tooltips - StringArrayData::SetString")) { + this.SadSetString = Marshal.GetDelegateForFunctionPointer(setStringPtr); + } else { + return; + } + + if (!enabled) { + return; + } + + if (scanner.TryScanText(Signatures.ItemGenerateTooltip, out var generateItemPtr, "Tooltips - Items")) { + unsafe { + this.ItemGenerateTooltipHook = new Hook(generateItemPtr, new ItemGenerateTooltipDelegate(this.ItemGenerateTooltipDetour)); + } + + this.ItemGenerateTooltipHook.Enable(); + } + + if (scanner.TryScanText(Signatures.ActionGenerateTooltip, out var actionItemPtr, "Tooltips - Actions")) { + unsafe { + this.ActionGenerateTooltipHook = new Hook(actionItemPtr, new ActionGenerateTooltipDelegate(this.ActionGenerateTooltipDetour)); + } + + this.ActionGenerateTooltipHook.Enable(); + } + } + + /// + public void Dispose() { + this.ActionGenerateTooltipHook?.Dispose(); + this.ItemGenerateTooltipHook?.Dispose(); + } + + private unsafe IntPtr ItemGenerateTooltipDetour(IntPtr addon, uint** numberArrayData, byte*** stringArrayData) { + try { + return this.ItemGenerateTooltipDetourInner(addon, numberArrayData, stringArrayData); + } catch (Exception ex) { + Logger.LogError(ex, "Exception in item tooltip detour"); + } + + return this.ItemGenerateTooltipHook!.Original(addon, numberArrayData, stringArrayData); + } + + private unsafe IntPtr ItemGenerateTooltipDetourInner(IntPtr addon, uint** numberArrayData, byte*** stringArrayData) { + // var v3 = *(numberArrayData + 4); + // var v9 = *(v3 + 4); + // + // if ((v9 & 2) == 0) { + // goto Original; + // } + + this.ItemTooltip = new ItemTooltip(this.SeStringManager, this.SadSetString!, stringArrayData); + + this.OnItemTooltip?.Invoke(this.ItemTooltip, this.GameGui.HoveredItem); + + return this.ItemGenerateTooltipHook!.Original(addon, numberArrayData, stringArrayData); + } + + private unsafe IntPtr ActionGenerateTooltipDetour(IntPtr addon, uint** numberArrayData, byte*** stringArrayData) { + try { + return this.ActionGenerateTooltipDetourInner(addon, numberArrayData, stringArrayData); + } catch (Exception ex) { + Logger.LogError(ex, "Exception in action tooltip detour"); + } + + return this.ActionGenerateTooltipHook!.Original(addon, numberArrayData, stringArrayData); + } + + private unsafe IntPtr ActionGenerateTooltipDetourInner(IntPtr addon, uint** numberArrayData, byte*** stringArrayData) { + this.ActionTooltip = new ActionTooltip(this.SeStringManager, this.SadSetString!, stringArrayData); + + this.OnActionTooltip?.Invoke(this.ActionTooltip, this.GameGui.HoveredAction); + + return this.ActionGenerateTooltipHook!.Original(addon, numberArrayData, stringArrayData); + } + } +} diff --git a/XivCommon/GameFunctions.cs b/XivCommon/GameFunctions.cs index bd0e8b2..fb10441 100755 --- a/XivCommon/GameFunctions.cs +++ b/XivCommon/GameFunctions.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using Dalamud.Plugin; using XivCommon.Functions; using XivCommon.Functions.ContextMenu; +using XivCommon.Functions.Tooltips; namespace XivCommon { /// @@ -54,7 +55,7 @@ namespace XivCommon { public Talk Talk { get; } /// - /// Chat bubble functions and events + /// Chat bubble functions and events /// public ChatBubbles ChatBubbles { get; } @@ -63,6 +64,11 @@ namespace XivCommon { /// public ContextMenu ContextMenu { get; } + /// + /// Tooltip events + /// + public Tooltips Tooltips { get; } + internal GameFunctions(Hooks hooks, DalamudPluginInterface @interface) { this.Interface = @interface; @@ -79,6 +85,7 @@ namespace XivCommon { this.Talk = new Talk(scanner, seStringManager, hooks.HasFlag(Hooks.Talk)); this.ChatBubbles = new ChatBubbles(dalamud, scanner, seStringManager, hooks.HasFlag(Hooks.ChatBubbles)); this.ContextMenu = new ContextMenu(this, scanner, @interface.Framework.Gui, @interface.ClientState.ClientLanguage, hooks); + this.Tooltips = new Tooltips(scanner, @interface.Framework.Gui, seStringManager, hooks.HasFlag(Hooks.Tooltips)); if (scanner.TryScanText(Signatures.GetAgentByInternalId, out var byInternalIdPtr, "GetAgentByInternalId")) { this.GetAgentByInternalIdInternal = Marshal.GetDelegateForFunctionPointer(byInternalIdPtr); @@ -91,6 +98,7 @@ namespace XivCommon { /// public void Dispose() { + this.Tooltips.Dispose(); this.ContextMenu.Dispose(); this.ChatBubbles.Dispose(); this.Talk.Dispose(); @@ -112,7 +120,7 @@ namespace XivCommon { /// Pointer public IntPtr GetAgentModule() { var uiModule = this.GetUiModule(); - var getAgentModulePtr = FollowPtrChain(uiModule, new[] {0, 0x110}); + var getAgentModulePtr = FollowPtrChain(uiModule, new[] { 0, 0x110 }); var getAgentModule = Marshal.GetDelegateForFunctionPointer(getAgentModulePtr); return getAgentModule(uiModule); } diff --git a/XivCommon/Hooks.cs b/XivCommon/Hooks.cs index 8ea2915..6fb9dfb 100755 --- a/XivCommon/Hooks.cs +++ b/XivCommon/Hooks.cs @@ -13,6 +13,13 @@ namespace XivCommon { /// None = 0, + /// + /// The Tooltips hooks. + /// + /// This hook is used in order to enable the tooltip events. + /// + Tooltips = 1 << 0, + /// /// The BattleTalk hook. /// diff --git a/XivCommon/Util.cs b/XivCommon/Util.cs index 4c3fceb..af49419 100755 --- a/XivCommon/Util.cs +++ b/XivCommon/Util.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Dalamud.Plugin; +using Dalamud.Game.Text.SeStringHandling; namespace XivCommon { internal static class Util { @@ -28,6 +28,11 @@ namespace XivCommon { return buf.ToArray(); } + internal static SeString ReadSeString(IntPtr memory, SeStringManager manager) { + var terminated = ReadTerminated(memory); + return manager.Parse(terminated); + } + internal static void PrintMissingSig(string name) { Logger.LogWarning($"Could not find signature for {name}. This functionality will be disabled."); }