feat: add basic tooltip support

This commit is contained in:
Anna 2021-05-23 05:43:48 -04:00
parent 290af584ab
commit fd44b711a3
Signed by: anna
GPG Key ID: 0B391D8F06FCD9E0
9 changed files with 267 additions and 3 deletions

View 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;
}
}
}

View 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,
}
}

View 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);
}
}
}
}
}

View 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;
}
}
}

View 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,
}
}

View 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);
}
}
}

View File

@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
using Dalamud.Plugin;
using XivCommon.Functions;
using XivCommon.Functions.ContextMenu;
using XivCommon.Functions.Tooltips;
namespace XivCommon {
/// <summary>
@ -54,7 +55,7 @@ namespace XivCommon {
public Talk Talk { get; }
/// <summary>
/// Chat bubble functions and events
/// Chat bubble functions and events
/// </summary>
public ChatBubbles ChatBubbles { get; }
@ -63,6 +64,11 @@ namespace XivCommon {
/// </summary>
public ContextMenu ContextMenu { get; }
/// <summary>
/// Tooltip events
/// </summary>
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<GetAgentByInternalIdDelegate>(byInternalIdPtr);
@ -91,6 +98,7 @@ namespace XivCommon {
/// <inheritdoc />
public void Dispose() {
this.Tooltips.Dispose();
this.ContextMenu.Dispose();
this.ChatBubbles.Dispose();
this.Talk.Dispose();
@ -112,7 +120,7 @@ namespace XivCommon {
/// <returns>Pointer</returns>
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<GetAgentModuleDelegate>(getAgentModulePtr);
return getAgentModule(uiModule);
}

View File

@ -13,6 +13,13 @@ namespace XivCommon {
/// </summary>
None = 0,
/// <summary>
/// The Tooltips hooks.
///
/// This hook is used in order to enable the tooltip events.
/// </summary>
Tooltips = 1 << 0,
/// <summary>
/// The BattleTalk hook.
///

View File

@ -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.");
}