feat: add basic tooltip support
This commit is contained in:
parent
290af584ab
commit
fd44b711a3
13
XivCommon/Functions/Tooltips/ActionTooltip.cs
Executable file
13
XivCommon/Functions/Tooltips/ActionTooltip.cs
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
XivCommon/Functions/Tooltips/ActionTooltipString.cs
Executable file
19
XivCommon/Functions/Tooltips/ActionTooltipString.cs
Executable file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
30
XivCommon/Functions/Tooltips/BaseTooltip.cs
Executable file
30
XivCommon/Functions/Tooltips/BaseTooltip.cs
Executable file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
XivCommon/Functions/Tooltips/ItemTooltip.cs
Executable file
13
XivCommon/Functions/Tooltips/ItemTooltip.cs
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
XivCommon/Functions/Tooltips/ItemTooltipString.cs
Executable file
51
XivCommon/Functions/Tooltips/ItemTooltipString.cs
Executable file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
118
XivCommon/Functions/Tooltips/Tooltips.cs
Executable file
118
XivCommon/Functions/Tooltips/Tooltips.cs
Executable file
@ -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<ItemGenerateTooltipDelegate>? ItemGenerateTooltipHook { get; }
|
||||||
|
private Hook<ActionGenerateTooltipDelegate>? 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<StringArrayDataSetStringDelegate>(setStringPtr);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scanner.TryScanText(Signatures.ItemGenerateTooltip, out var generateItemPtr, "Tooltips - Items")) {
|
||||||
|
unsafe {
|
||||||
|
this.ItemGenerateTooltipHook = new Hook<ItemGenerateTooltipDelegate>(generateItemPtr, new ItemGenerateTooltipDelegate(this.ItemGenerateTooltipDetour));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ItemGenerateTooltipHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scanner.TryScanText(Signatures.ActionGenerateTooltip, out var actionItemPtr, "Tooltips - Actions")) {
|
||||||
|
unsafe {
|
||||||
|
this.ActionGenerateTooltipHook = new Hook<ActionGenerateTooltipDelegate>(actionItemPtr, new ActionGenerateTooltipDelegate(this.ActionGenerateTooltipDetour));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ActionGenerateTooltipHook.Enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
|
|||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using XivCommon.Functions;
|
using XivCommon.Functions;
|
||||||
using XivCommon.Functions.ContextMenu;
|
using XivCommon.Functions.ContextMenu;
|
||||||
|
using XivCommon.Functions.Tooltips;
|
||||||
|
|
||||||
namespace XivCommon {
|
namespace XivCommon {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -54,7 +55,7 @@ namespace XivCommon {
|
|||||||
public Talk Talk { get; }
|
public Talk Talk { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Chat bubble functions and events
|
/// Chat bubble functions and events
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ChatBubbles ChatBubbles { get; }
|
public ChatBubbles ChatBubbles { get; }
|
||||||
|
|
||||||
@ -63,6 +64,11 @@ namespace XivCommon {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ContextMenu ContextMenu { get; }
|
public ContextMenu ContextMenu { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tooltip events
|
||||||
|
/// </summary>
|
||||||
|
public Tooltips Tooltips { get; }
|
||||||
|
|
||||||
internal GameFunctions(Hooks hooks, DalamudPluginInterface @interface) {
|
internal GameFunctions(Hooks hooks, DalamudPluginInterface @interface) {
|
||||||
this.Interface = @interface;
|
this.Interface = @interface;
|
||||||
|
|
||||||
@ -79,6 +85,7 @@ namespace XivCommon {
|
|||||||
this.Talk = new Talk(scanner, seStringManager, hooks.HasFlag(Hooks.Talk));
|
this.Talk = new Talk(scanner, seStringManager, hooks.HasFlag(Hooks.Talk));
|
||||||
this.ChatBubbles = new ChatBubbles(dalamud, scanner, seStringManager, hooks.HasFlag(Hooks.ChatBubbles));
|
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.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")) {
|
if (scanner.TryScanText(Signatures.GetAgentByInternalId, out var byInternalIdPtr, "GetAgentByInternalId")) {
|
||||||
this.GetAgentByInternalIdInternal = Marshal.GetDelegateForFunctionPointer<GetAgentByInternalIdDelegate>(byInternalIdPtr);
|
this.GetAgentByInternalIdInternal = Marshal.GetDelegateForFunctionPointer<GetAgentByInternalIdDelegate>(byInternalIdPtr);
|
||||||
@ -91,6 +98,7 @@ namespace XivCommon {
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
|
this.Tooltips.Dispose();
|
||||||
this.ContextMenu.Dispose();
|
this.ContextMenu.Dispose();
|
||||||
this.ChatBubbles.Dispose();
|
this.ChatBubbles.Dispose();
|
||||||
this.Talk.Dispose();
|
this.Talk.Dispose();
|
||||||
@ -112,7 +120,7 @@ namespace XivCommon {
|
|||||||
/// <returns>Pointer</returns>
|
/// <returns>Pointer</returns>
|
||||||
public IntPtr GetAgentModule() {
|
public IntPtr GetAgentModule() {
|
||||||
var uiModule = this.GetUiModule();
|
var uiModule = this.GetUiModule();
|
||||||
var getAgentModulePtr = FollowPtrChain(uiModule, new[] {0, 0x110});
|
var getAgentModulePtr = FollowPtrChain(uiModule, new[] { 0, 0x110 });
|
||||||
var getAgentModule = Marshal.GetDelegateForFunctionPointer<GetAgentModuleDelegate>(getAgentModulePtr);
|
var getAgentModule = Marshal.GetDelegateForFunctionPointer<GetAgentModuleDelegate>(getAgentModulePtr);
|
||||||
return getAgentModule(uiModule);
|
return getAgentModule(uiModule);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,13 @@ namespace XivCommon {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Tooltips hooks.
|
||||||
|
///
|
||||||
|
/// This hook is used in order to enable the tooltip events.
|
||||||
|
/// </summary>
|
||||||
|
Tooltips = 1 << 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The BattleTalk hook.
|
/// The BattleTalk hook.
|
||||||
///
|
///
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
|
||||||
namespace XivCommon {
|
namespace XivCommon {
|
||||||
internal static class Util {
|
internal static class Util {
|
||||||
@ -28,6 +28,11 @@ namespace XivCommon {
|
|||||||
return buf.ToArray();
|
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) {
|
internal static void PrintMissingSig(string name) {
|
||||||
Logger.LogWarning($"Could not find signature for {name}. This functionality will be disabled.");
|
Logger.LogWarning($"Could not find signature for {name}. This functionality will be disabled.");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user