feat: add nameplates without docs
Refactor allocs into their own private class.
This commit is contained in:
parent
918826f913
commit
eee4f1e6a5
|
@ -18,9 +18,6 @@ namespace XivCommon.Functions.ContextMenu {
|
|||
/// </summary>
|
||||
public class ContextMenu : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string GameAlloc = "E8 ?? ?? ?? ?? 45 8D 67 23";
|
||||
internal const string GameFree = "E8 ?? ?? ?? ?? 4C 89 7B 60";
|
||||
internal const string GetGameAllocator = "E8 ?? ?? ?? ?? 8B 75 08";
|
||||
internal const string SomeOpenAddonThing = "E8 ?? ?? ?? ?? 0F B7 C0 48 83 C4 60";
|
||||
internal const string ContextMenuOpen = "48 8B C4 57 41 56 41 57 48 81 EC ?? ?? ?? ??";
|
||||
internal const string ContextMenuSelected = "48 89 5C 24 ?? 55 57 41 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 80 B9 ?? ?? ?? ?? ??";
|
||||
|
@ -116,18 +113,6 @@ namespace XivCommon.Functions.ContextMenu {
|
|||
/// </summary>
|
||||
public delegate void InventoryContextMenuItemSelectedDelegate(InventoryContextMenuItemSelectedArgs args);
|
||||
|
||||
private delegate IntPtr GameAllocDelegate(ulong size, IntPtr unk, IntPtr allocator, IntPtr alignment);
|
||||
|
||||
private readonly GameAllocDelegate? _gameAlloc;
|
||||
|
||||
private delegate IntPtr GameFreeDelegate(IntPtr a1);
|
||||
|
||||
private readonly GameFreeDelegate? _gameFree;
|
||||
|
||||
private delegate IntPtr GetGameAllocatorDelegate();
|
||||
|
||||
private readonly GetGameAllocatorDelegate? _getGameAllocator;
|
||||
|
||||
private delegate IntPtr SomeOpenAddonThingDelegate(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, IntPtr a6, IntPtr a7, ushort a8);
|
||||
|
||||
private Hook<SomeOpenAddonThingDelegate>? SomeOpenAddonThingHook { get; }
|
||||
|
@ -169,24 +154,6 @@ namespace XivCommon.Functions.ContextMenu {
|
|||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.GameAlloc, out var gameAllocPtr, "Context Menu (GameAlloc)")) {
|
||||
this._gameAlloc = Marshal.GetDelegateForFunctionPointer<GameAllocDelegate>(gameAllocPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.GameFree, out var gameFreePtr, "Context Menu (GameFree)")) {
|
||||
this._gameFree = Marshal.GetDelegateForFunctionPointer<GameFreeDelegate>(gameFreePtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.GetGameAllocator, out var getAllocatorPtr, "Context Menu (GetGameAllocator)")) {
|
||||
this._getGameAllocator = Marshal.GetDelegateForFunctionPointer<GetGameAllocatorDelegate>(getAllocatorPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.AtkValueChangeType, out var changeTypePtr, "Context Menu (change type)")) {
|
||||
this._atkValueChangeType = Marshal.GetDelegateForFunctionPointer<AtkValueChangeTypeDelegate>(changeTypePtr);
|
||||
} else {
|
||||
|
@ -322,7 +289,7 @@ namespace XivCommon.Functions.ContextMenu {
|
|||
|
||||
// reallocate
|
||||
var size = (ulong) sizeof(AtkValue) * newItemCount + 8;
|
||||
var newArray = this._gameAlloc!(size, IntPtr.Zero, this._getGameAllocator!(), IntPtr.Zero);
|
||||
var newArray = this.Functions.UiAlloc.Alloc(size);
|
||||
// zero new memory
|
||||
Marshal.Copy(new byte[size], 0, newArray, (int) size);
|
||||
// update size and pointer
|
||||
|
@ -333,7 +300,7 @@ namespace XivCommon.Functions.ContextMenu {
|
|||
// copy old memory if existing
|
||||
if (oldArray != null) {
|
||||
Buffer.MemoryCopy(oldArray, (void*) (newArray + 8), size, (ulong) sizeof(AtkValue) * oldArrayItemCount);
|
||||
this._gameFree!((IntPtr) oldArray - 8);
|
||||
this.Functions.UiAlloc.Free((IntPtr) oldArray - 8);
|
||||
}
|
||||
|
||||
return (AtkValue*) (newArray + 8);
|
||||
|
|
18
XivCommon/Functions/NamePlates/NamePlateUpdateEventArgs.cs
Executable file
18
XivCommon/Functions/NamePlates/NamePlateUpdateEventArgs.cs
Executable file
|
@ -0,0 +1,18 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics;
|
||||
|
||||
namespace XivCommon.Functions.NamePlates {
|
||||
public class NamePlateUpdateEventArgs {
|
||||
public uint ActorId { get; }
|
||||
public SeString Name { get; set; }
|
||||
public SeString FreeCompany { get; set; }
|
||||
public SeString Title { get; set; }
|
||||
public SeString Level { get; set; }
|
||||
public uint Icon { get; set; }
|
||||
public ByteColor Colour { get; set; }
|
||||
|
||||
internal NamePlateUpdateEventArgs(uint actorId) {
|
||||
this.ActorId = actorId;
|
||||
}
|
||||
}
|
||||
}
|
155
XivCommon/Functions/NamePlates/NamePlates.cs
Executable file
155
XivCommon/Functions/NamePlates/NamePlates.cs
Executable file
|
@ -0,0 +1,155 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
|
||||
namespace XivCommon.Functions.NamePlates {
|
||||
public class NamePlates : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string NamePlateUpdate = "48 8B C4 41 56 48 81 EC ?? ?? ?? ?? 48 89 58 F0";
|
||||
}
|
||||
|
||||
private unsafe delegate IntPtr NamePlateUpdateDelegate(AddonNamePlate* addon, NumberArrayData** numberData, StringArrayData** stringData);
|
||||
|
||||
public delegate void NamePlateUpdateEvent(NamePlateUpdateEventArgs args);
|
||||
|
||||
public event NamePlateUpdateEvent? OnUpdate;
|
||||
|
||||
private GameFunctions Functions { get; }
|
||||
private SeStringManager SeStringManager { get; }
|
||||
private readonly Hook<NamePlateUpdateDelegate>? _namePlateUpdateHook;
|
||||
|
||||
public bool ForceRedraw { get; set; }
|
||||
|
||||
internal NamePlates(GameFunctions functions, SigScanner scanner, SeStringManager manager, bool hookEnabled) {
|
||||
this.Functions = functions;
|
||||
this.SeStringManager = manager;
|
||||
|
||||
if (!hookEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.NamePlateUpdate, out var updatePtr)) {
|
||||
unsafe {
|
||||
this._namePlateUpdateHook = new Hook<NamePlateUpdateDelegate>(updatePtr, new NamePlateUpdateDelegate(this.NamePlateUpdateDetour));
|
||||
}
|
||||
|
||||
this._namePlateUpdateHook.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this._namePlateUpdateHook?.Dispose();
|
||||
}
|
||||
|
||||
private const int UpdateIndex = 2;
|
||||
private const int ColourIndex = 8;
|
||||
private const int IconIndex = 13;
|
||||
private const int NamePlateObjectIndex = 15;
|
||||
private const int NameIndex = 0;
|
||||
private const int TitleIndex = 50;
|
||||
private const int FreeCompanyIndex = 100;
|
||||
private const int LevelIndex = 150;
|
||||
|
||||
private unsafe IntPtr NamePlateUpdateDetour(AddonNamePlate* addon, NumberArrayData** numberData, StringArrayData** stringData) {
|
||||
// don't skip to original if no subscribers because of ForceRedraw
|
||||
|
||||
var numbers = numberData[5];
|
||||
var strings = stringData[4];
|
||||
var atkModule = (RaptureAtkModule*) this.Functions.GetAtkModule();
|
||||
|
||||
var active = numbers->IntArray[0];
|
||||
|
||||
var force = this.ForceRedraw;
|
||||
if (force) {
|
||||
this.ForceRedraw = false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < active; i++) {
|
||||
var numbersIndex = i * 19 + 5;
|
||||
|
||||
if (force) {
|
||||
numbers->SetValue(numbersIndex + UpdateIndex, numbers->IntArray[numbersIndex + UpdateIndex] | 1 | 2);
|
||||
}
|
||||
|
||||
if (this.OnUpdate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (numbers->IntArray[numbersIndex + UpdateIndex] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var npObjIndex = numbers->IntArray[numbersIndex + NamePlateObjectIndex];
|
||||
var info = (&atkModule->NamePlateInfoArray)[npObjIndex];
|
||||
|
||||
var icon = numbers->IntArray[numbersIndex + IconIndex];
|
||||
var nameColour = *(ByteColor*) &numbers->IntArray[numbersIndex + ColourIndex];
|
||||
|
||||
var nameRaw = strings->StringArray[NameIndex + i];
|
||||
var name = Util.ReadSeString((IntPtr) nameRaw, this.SeStringManager);
|
||||
|
||||
var titleRaw = strings->StringArray[TitleIndex + i];
|
||||
var title = Util.ReadSeString((IntPtr) titleRaw, this.SeStringManager);
|
||||
|
||||
var fcRaw = strings->StringArray[FreeCompanyIndex + i];
|
||||
var fc = Util.ReadSeString((IntPtr) fcRaw, this.SeStringManager);
|
||||
|
||||
var levelRaw = strings->StringArray[LevelIndex + i];
|
||||
var level = Util.ReadSeString((IntPtr) levelRaw, this.SeStringManager);
|
||||
|
||||
var args = new NamePlateUpdateEventArgs((uint) info.ActorID) {
|
||||
Name = name,
|
||||
FreeCompany = fc,
|
||||
Title = title,
|
||||
Level = level,
|
||||
Colour = nameColour,
|
||||
Icon = (uint) icon,
|
||||
};
|
||||
|
||||
this.OnUpdate?.Invoke(args);
|
||||
|
||||
void Replace(byte[] bytes, int i) {
|
||||
var mem = this.Functions.UiAlloc.Alloc((ulong) bytes.Length + 1);
|
||||
Marshal.Copy(bytes, 0, mem, bytes.Length);
|
||||
*(byte*) (mem + bytes.Length) = 0;
|
||||
this.Functions.UiAlloc.Free((IntPtr) strings->StringArray[i]);
|
||||
strings->StringArray[i] = (byte*) mem;
|
||||
}
|
||||
|
||||
if (name != args.Name) {
|
||||
Replace(args.Name.Encode(), NameIndex + i);
|
||||
}
|
||||
|
||||
if (title != args.Title) {
|
||||
Replace(args.Title.Encode(), TitleIndex + i);
|
||||
}
|
||||
|
||||
if (fc != args.FreeCompany) {
|
||||
Replace(args.FreeCompany.Encode(), FreeCompanyIndex + i);
|
||||
}
|
||||
|
||||
if (level != args.Level) {
|
||||
Replace(args.Level.Encode(), LevelIndex + i);
|
||||
}
|
||||
|
||||
if (icon != args.Icon) {
|
||||
numbers->SetValue(numbersIndex + IconIndex, (int) args.Icon);
|
||||
}
|
||||
|
||||
var colour = args.Colour;
|
||||
var colourInt = *(int*) &colour;
|
||||
if (colourInt != numbers->IntArray[numbersIndex + ColourIndex]) {
|
||||
numbers->SetValue(numbersIndex + ColourIndex, colourInt);
|
||||
}
|
||||
}
|
||||
|
||||
Original:
|
||||
return this._namePlateUpdateHook!.Original(addon, numberData, stringData);
|
||||
}
|
||||
}
|
||||
}
|
58
XivCommon/Functions/NamePlates/Structs.cs
Executable file
58
XivCommon/Functions/NamePlates/Structs.cs
Executable file
|
@ -0,0 +1,58 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace XivCommon.Functions.NamePlates {
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x28)]
|
||||
internal unsafe struct NumberArrayData {
|
||||
[FieldOffset(0x0)]
|
||||
public AtkArrayData AtkArrayData;
|
||||
|
||||
[FieldOffset(0x20)]
|
||||
public int* IntArray;
|
||||
|
||||
public void SetValue(int index, int value) {
|
||||
if (index >= this.AtkArrayData.Size) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.IntArray[index] == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.IntArray[index] = value;
|
||||
this.AtkArrayData.HasModifiedData = 1;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x20)]
|
||||
internal unsafe struct AtkArrayData {
|
||||
[FieldOffset(0x0)]
|
||||
public void* vtbl;
|
||||
|
||||
[FieldOffset(0x8)]
|
||||
public int Size;
|
||||
|
||||
[FieldOffset(0x1C)]
|
||||
public byte Unk1C;
|
||||
|
||||
[FieldOffset(0x1D)]
|
||||
public byte Unk1D;
|
||||
|
||||
[FieldOffset(0x1E)]
|
||||
public byte HasModifiedData;
|
||||
|
||||
[FieldOffset(0x1F)]
|
||||
public byte Unk1F; // initialized to -1
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x30)]
|
||||
internal unsafe struct StringArrayData {
|
||||
[FieldOffset(0x0)]
|
||||
public AtkArrayData AtkArrayData;
|
||||
|
||||
[FieldOffset(0x20)]
|
||||
public byte** StringArray; // char * *
|
||||
|
||||
[FieldOffset(0x28)]
|
||||
public byte* UnkString; // char *
|
||||
}
|
||||
}
|
59
XivCommon/Functions/UiAlloc.cs
Executable file
59
XivCommon/Functions/UiAlloc.cs
Executable file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game;
|
||||
|
||||
namespace XivCommon.Functions {
|
||||
internal class UiAlloc {
|
||||
private static class Signatures {
|
||||
internal const string GameAlloc = "E8 ?? ?? ?? ?? 45 8D 67 23";
|
||||
internal const string GameFree = "E8 ?? ?? ?? ?? 4C 89 7B 60";
|
||||
internal const string GetGameAllocator = "E8 ?? ?? ?? ?? 8B 75 08";
|
||||
}
|
||||
|
||||
private delegate IntPtr GameAllocDelegate(ulong size, IntPtr unk, IntPtr allocator, IntPtr alignment);
|
||||
|
||||
private readonly GameAllocDelegate? _gameAlloc;
|
||||
|
||||
private delegate IntPtr GameFreeDelegate(IntPtr a1);
|
||||
|
||||
private readonly GameFreeDelegate? _gameFree;
|
||||
|
||||
private delegate IntPtr GetGameAllocatorDelegate();
|
||||
|
||||
private readonly GetGameAllocatorDelegate? _getGameAllocator;
|
||||
|
||||
internal UiAlloc(SigScanner scanner) {
|
||||
if (scanner.TryScanText(Signatures.GameAlloc, out var gameAllocPtr, "UiAlloc (GameAlloc)")) {
|
||||
this._gameAlloc = Marshal.GetDelegateForFunctionPointer<GameAllocDelegate>(gameAllocPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.GameFree, out var gameFreePtr, "UiAlloc (GameFree)")) {
|
||||
this._gameFree = Marshal.GetDelegateForFunctionPointer<GameFreeDelegate>(gameFreePtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.GetGameAllocator, out var getAllocatorPtr, "UiAlloc (GetGameAllocator)")) {
|
||||
this._getGameAllocator = Marshal.GetDelegateForFunctionPointer<GetGameAllocatorDelegate>(getAllocatorPtr);
|
||||
}
|
||||
}
|
||||
|
||||
internal IntPtr Alloc(ulong size) {
|
||||
if (this._getGameAllocator == null || this._gameAlloc == null) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return this._gameAlloc(size, IntPtr.Zero, this._getGameAllocator(), IntPtr.Zero);
|
||||
}
|
||||
|
||||
internal void Free(IntPtr ptr) {
|
||||
if (this._gameFree == null) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
this._gameFree(ptr);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
|
|||
using Dalamud.Plugin;
|
||||
using XivCommon.Functions;
|
||||
using XivCommon.Functions.ContextMenu;
|
||||
using XivCommon.Functions.NamePlates;
|
||||
using XivCommon.Functions.Tooltips;
|
||||
|
||||
namespace XivCommon {
|
||||
|
@ -31,6 +32,8 @@ namespace XivCommon {
|
|||
|
||||
private GetAtkStageSingletonDelegate? GetAtkStageSingletonInternal { get; }
|
||||
|
||||
internal UiAlloc UiAlloc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Chat functions
|
||||
/// </summary>
|
||||
|
@ -71,6 +74,8 @@ namespace XivCommon {
|
|||
/// </summary>
|
||||
public Tooltips Tooltips { get; }
|
||||
|
||||
public NamePlates NamePlates { get; }
|
||||
|
||||
internal GameFunctions(Hooks hooks, DalamudPluginInterface @interface) {
|
||||
this.Interface = @interface;
|
||||
|
||||
|
@ -80,6 +85,7 @@ namespace XivCommon {
|
|||
var dalamudField = @interface.GetType().GetField("dalamud", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
var dalamud = (Dalamud.Dalamud) dalamudField!.GetValue(@interface);
|
||||
|
||||
this.UiAlloc = new UiAlloc(scanner);
|
||||
this.Chat = new Chat(this, scanner);
|
||||
this.PartyFinder = new PartyFinder(scanner, @interface.Framework.Gui.PartyFinder, hooks);
|
||||
this.BattleTalk = new BattleTalk(this, scanner, seStringManager, hooks.HasFlag(Hooks.BattleTalk));
|
||||
|
@ -88,6 +94,7 @@ namespace XivCommon {
|
|||
this.ChatBubbles = new ChatBubbles(dalamud, scanner, seStringManager, hooks.HasFlag(Hooks.ChatBubbles));
|
||||
this.ContextMenu = new ContextMenu(this, scanner, seStringManager, @interface.ClientState.ClientLanguage, hooks);
|
||||
this.Tooltips = new Tooltips(scanner, @interface.Framework, @interface.Framework.Gui, seStringManager, hooks.HasFlag(Hooks.Tooltips));
|
||||
this.NamePlates = new NamePlates(this, scanner, seStringManager, hooks.HasFlag(Hooks.NamePlates));
|
||||
|
||||
if (scanner.TryScanText(Signatures.GetAgentByInternalId, out var byInternalIdPtr, "GetAgentByInternalId")) {
|
||||
this.GetAgentByInternalIdInternal = Marshal.GetDelegateForFunctionPointer<GetAgentByInternalIdDelegate>(byInternalIdPtr);
|
||||
|
@ -100,6 +107,7 @@ namespace XivCommon {
|
|||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.NamePlates.Dispose();
|
||||
this.Tooltips.Dispose();
|
||||
this.ContextMenu.Dispose();
|
||||
this.ChatBubbles.Dispose();
|
||||
|
@ -119,19 +127,14 @@ namespace XivCommon {
|
|||
/// <summary>
|
||||
/// Gets the pointer to the RaptureAtkModule
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns>Pointer</returns>
|
||||
public IntPtr GetAtkModule() {
|
||||
var uiModule = this.GetUiModule();
|
||||
if (uiModule == IntPtr.Zero) {
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
var vtbl = Marshal.ReadIntPtr(uiModule);
|
||||
if (vtbl == IntPtr.Zero) {
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
var getAtkModulePtr = Marshal.ReadIntPtr(vtbl + 0x38);
|
||||
var getAtkModulePtr = FollowPtrChain(uiModule, new[] { 0, 0x38 });
|
||||
if (getAtkModulePtr == IntPtr.Zero) {
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
|
|
@ -64,6 +64,8 @@ namespace XivCommon {
|
|||
/// This hook is used in order to enable context menu functions.
|
||||
/// </summary>
|
||||
ContextMenu = 1 << 6,
|
||||
|
||||
NamePlates = 1 << 7,
|
||||
}
|
||||
|
||||
internal static class HooksExt {
|
||||
|
|
Loading…
Reference in New Issue
Block a user