From 6abb88d432256159450e1f2aa00d36244918cdba Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Fri, 4 Jun 2021 19:24:49 -0400 Subject: [PATCH] fix(context): reallocate when necessary Hopefully fix the heap corruption issue. --- .../Functions/ContextMenu/ContextMenu.cs | 92 ++++++++++++++++--- 1 file changed, 80 insertions(+), 12 deletions(-) diff --git a/XivCommon/Functions/ContextMenu/ContextMenu.cs b/XivCommon/Functions/ContextMenu/ContextMenu.cs index 7be3f6c..58bfc80 100755 --- a/XivCommon/Functions/ContextMenu/ContextMenu.cs +++ b/XivCommon/Functions/ContextMenu/ContextMenu.cs @@ -18,6 +18,9 @@ namespace XivCommon.Functions.ContextMenu { /// 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 ?? ?? ?? ?? ??"; @@ -36,6 +39,11 @@ namespace XivCommon.Functions.ContextMenu { /// private const int ParentAddonIdOffset = 0x1D2; + private const int AddonArraySizeOffset = 0x1CA; + private const int AddonArrayOffset = 0x160; + + private const int ContextMenuItemOffset = 7; + /// /// Offset from agent to actions byte array pointer (have to add the actions offset after) /// @@ -108,6 +116,18 @@ namespace XivCommon.Functions.ContextMenu { /// 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? SomeOpenAddonThingHook { get; } @@ -149,6 +169,24 @@ namespace XivCommon.Functions.ContextMenu { return; } + if (scanner.TryScanText(Signatures.GameAlloc, out var gameAllocPtr, "Context Menu (GameAlloc)")) { + this._gameAlloc = Marshal.GetDelegateForFunctionPointer(gameAllocPtr); + } else { + return; + } + + if (scanner.TryScanText(Signatures.GameFree, out var gameFreePtr, "Context Menu (GameFree)")) { + this._gameFree = Marshal.GetDelegateForFunctionPointer(gameFreePtr); + } else { + return; + } + + if (scanner.TryScanText(Signatures.GetGameAllocator, out var getAllocatorPtr, "Context Menu (GetGameAllocator)")) { + this._getGameAllocator = Marshal.GetDelegateForFunctionPointer(getAllocatorPtr); + } else { + return; + } + if (scanner.TryScanText(Signatures.AtkValueChangeType, out var changeTypePtr, "Context Menu (change type)")) { this._atkValueChangeType = Marshal.GetDelegateForFunctionPointer(changeTypePtr); } else { @@ -263,7 +301,7 @@ namespace XivCommon.Functions.ContextMenu { [HandleProcessCorruptedStateExceptions] private unsafe byte OpenMenuDetour(IntPtr addon, int menuSize, AtkValue* atkValueArgs) { try { - this.OpenMenuDetourInner(addon, ref menuSize, atkValueArgs); + this.OpenMenuDetourInner(addon, ref menuSize, ref atkValueArgs); } catch (Exception ex) { Logger.LogError(ex, "Exception in OpenMenuDetour"); } @@ -271,10 +309,38 @@ namespace XivCommon.Functions.ContextMenu { return this.ContextMenuOpenHook!.Original(addon, menuSize, atkValueArgs); } - private unsafe void OpenMenuDetourInner(IntPtr addon, ref int menuSize, AtkValue* atkValueArgs) { - this.Items.Clear(); + private unsafe AtkValue* ExpandContextMenuArray(IntPtr addon) { + const ulong newItemCount = MaxItems * 2 + ContextMenuItemOffset; - const int offset = 7; + var oldArray = *(AtkValue**) (addon + AddonArrayOffset); + var oldArrayItemCount = *(ushort*) (addon + AddonArraySizeOffset); + + // if the array has enough room, don't reallocate + if (oldArrayItemCount >= newItemCount) { + return oldArray; + } + + // reallocate + var size = (ulong) sizeof(AtkValue) * newItemCount + 8; + var newArray = this._gameAlloc!(size, IntPtr.Zero, this._getGameAllocator!(), IntPtr.Zero); + // zero new memory + Marshal.Copy(new byte[size], 0, newArray, (int) size); + // update size and pointer + *(ulong*) newArray = newItemCount; + *(void**) (addon + AddonArrayOffset) = (void*) (newArray + 8); + *(ushort*) (addon + AddonArraySizeOffset) = (ushort) newItemCount; + + // copy old memory if existing + if (oldArray != null) { + Buffer.MemoryCopy(oldArray, (void*) (newArray + 8), size, (ulong) sizeof(AtkValue) * oldArrayItemCount); + this._gameFree!((IntPtr) oldArray - 8); + } + + return (AtkValue*) (newArray + 8); + } + + private unsafe void OpenMenuDetourInner(IntPtr addon, ref int menuSize, ref AtkValue* atkValueArgs) { + this.Items.Clear(); var (agentType, agent) = this.GetContextMenuAgent(); if (agent == IntPtr.Zero) { @@ -285,11 +351,13 @@ namespace XivCommon.Functions.ContextMenu { return; } + atkValueArgs = this.ExpandContextMenuArray(addon); + var inventory = agentType == AgentType.Inventory; this.NormalSize = (int) (&atkValueArgs[0])->UInt; - var hasGameDisabled = menuSize - offset - this.NormalSize > 0; + var hasGameDisabled = menuSize - ContextMenuItemOffset - this.NormalSize > 0; var addonName = this.GetParentAddonName(addon); @@ -299,17 +367,17 @@ namespace XivCommon.Functions.ContextMenu { var nativeItems = new List(); for (var i = 0; i < this.NormalSize; i++) { - var atkItem = &atkValueArgs[offset + i]; + var atkItem = &atkValueArgs[ContextMenuItemOffset + i]; var name = Util.ReadSeString((IntPtr) atkItem->String, this.SeStringManager); var enabled = true; if (hasGameDisabled) { - var disabledItem = &atkValueArgs[offset + this.NormalSize + i]; + var disabledItem = &atkValueArgs[ContextMenuItemOffset + this.NormalSize + i]; enabled = disabledItem->Int == 0; } - var action = *(menuActions + offset + i); + var action = *(menuActions + ContextMenuItemOffset + i); nativeItems.Add(new NativeContextMenuItem(action, name, enabled)); } @@ -392,19 +460,19 @@ namespace XivCommon.Functions.ContextMenu { var item = this.Items[i]; if (hasAnyDisabled) { - var disabledArg = &atkValueArgs[offset + this.Items.Count + i]; + var disabledArg = &atkValueArgs[ContextMenuItemOffset + this.Items.Count + i]; this._atkValueChangeType(disabledArg, ValueType.Int); disabledArg->Int = item.Enabled ? 0 : 1; } // set up the agent to take the appropriate action for this item - *(menuActions + offset + i) = item switch { + *(menuActions + ContextMenuItemOffset + i) = item switch { NativeContextMenuItem nativeItem => nativeItem.InternalAction, _ => inventory ? InventoryNoopContextId : NoopContextId, }; // set up the menu item - var newItem = &atkValueArgs[offset + i]; + var newItem = &atkValueArgs[ContextMenuItemOffset + i]; this._atkValueChangeType(newItem, ValueType.String); var name = item switch { @@ -438,7 +506,7 @@ namespace XivCommon.Functions.ContextMenu { menuSize *= 2; } - menuSize += offset; + menuSize += ContextMenuItemOffset; } private byte ItemSelectedDetour(IntPtr addon, int index, byte a3) {