feat: add nameplates without docs

Refactor allocs into their own private class.
This commit is contained in:
Anna 2021-06-21 05:09:35 -04:00
parent 918826f913
commit eee4f1e6a5
Signed by: anna
GPG Key ID: 0B391D8F06FCD9E0
7 changed files with 304 additions and 42 deletions

View File

@ -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);

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

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

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

View File

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

View File

@ -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 {