fix(context): reallocate when necessary

Hopefully fix the heap corruption issue.
This commit is contained in:
Anna 2021-06-04 19:24:49 -04:00
parent 2f342d2b4a
commit 6abb88d432
Signed by: anna
GPG Key ID: 0B391D8F06FCD9E0

View File

@ -18,6 +18,9 @@ namespace XivCommon.Functions.ContextMenu {
/// </summary> /// </summary>
public class ContextMenu : IDisposable { public class ContextMenu : IDisposable {
private static class Signatures { 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 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 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 ?? ?? ?? ?? ??"; 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 {
/// </summary> /// </summary>
private const int ParentAddonIdOffset = 0x1D2; private const int ParentAddonIdOffset = 0x1D2;
private const int AddonArraySizeOffset = 0x1CA;
private const int AddonArrayOffset = 0x160;
private const int ContextMenuItemOffset = 7;
/// <summary> /// <summary>
/// Offset from agent to actions byte array pointer (have to add the actions offset after) /// Offset from agent to actions byte array pointer (have to add the actions offset after)
/// </summary> /// </summary>
@ -108,6 +116,18 @@ namespace XivCommon.Functions.ContextMenu {
/// </summary> /// </summary>
public delegate void InventoryContextMenuItemSelectedDelegate(InventoryContextMenuItemSelectedArgs args); 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 delegate IntPtr SomeOpenAddonThingDelegate(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, IntPtr a6, IntPtr a7, ushort a8);
private Hook<SomeOpenAddonThingDelegate>? SomeOpenAddonThingHook { get; } private Hook<SomeOpenAddonThingDelegate>? SomeOpenAddonThingHook { get; }
@ -149,6 +169,24 @@ namespace XivCommon.Functions.ContextMenu {
return; 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)")) { if (scanner.TryScanText(Signatures.AtkValueChangeType, out var changeTypePtr, "Context Menu (change type)")) {
this._atkValueChangeType = Marshal.GetDelegateForFunctionPointer<AtkValueChangeTypeDelegate>(changeTypePtr); this._atkValueChangeType = Marshal.GetDelegateForFunctionPointer<AtkValueChangeTypeDelegate>(changeTypePtr);
} else { } else {
@ -263,7 +301,7 @@ namespace XivCommon.Functions.ContextMenu {
[HandleProcessCorruptedStateExceptions] [HandleProcessCorruptedStateExceptions]
private unsafe byte OpenMenuDetour(IntPtr addon, int menuSize, AtkValue* atkValueArgs) { private unsafe byte OpenMenuDetour(IntPtr addon, int menuSize, AtkValue* atkValueArgs) {
try { try {
this.OpenMenuDetourInner(addon, ref menuSize, atkValueArgs); this.OpenMenuDetourInner(addon, ref menuSize, ref atkValueArgs);
} catch (Exception ex) { } catch (Exception ex) {
Logger.LogError(ex, "Exception in OpenMenuDetour"); Logger.LogError(ex, "Exception in OpenMenuDetour");
} }
@ -271,10 +309,38 @@ namespace XivCommon.Functions.ContextMenu {
return this.ContextMenuOpenHook!.Original(addon, menuSize, atkValueArgs); return this.ContextMenuOpenHook!.Original(addon, menuSize, atkValueArgs);
} }
private unsafe void OpenMenuDetourInner(IntPtr addon, ref int menuSize, AtkValue* atkValueArgs) { private unsafe AtkValue* ExpandContextMenuArray(IntPtr addon) {
this.Items.Clear(); 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(); var (agentType, agent) = this.GetContextMenuAgent();
if (agent == IntPtr.Zero) { if (agent == IntPtr.Zero) {
@ -285,11 +351,13 @@ namespace XivCommon.Functions.ContextMenu {
return; return;
} }
atkValueArgs = this.ExpandContextMenuArray(addon);
var inventory = agentType == AgentType.Inventory; var inventory = agentType == AgentType.Inventory;
this.NormalSize = (int) (&atkValueArgs[0])->UInt; 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); var addonName = this.GetParentAddonName(addon);
@ -299,17 +367,17 @@ namespace XivCommon.Functions.ContextMenu {
var nativeItems = new List<NativeContextMenuItem>(); var nativeItems = new List<NativeContextMenuItem>();
for (var i = 0; i < this.NormalSize; i++) { 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 name = Util.ReadSeString((IntPtr) atkItem->String, this.SeStringManager);
var enabled = true; var enabled = true;
if (hasGameDisabled) { if (hasGameDisabled) {
var disabledItem = &atkValueArgs[offset + this.NormalSize + i]; var disabledItem = &atkValueArgs[ContextMenuItemOffset + this.NormalSize + i];
enabled = disabledItem->Int == 0; enabled = disabledItem->Int == 0;
} }
var action = *(menuActions + offset + i); var action = *(menuActions + ContextMenuItemOffset + i);
nativeItems.Add(new NativeContextMenuItem(action, name, enabled)); nativeItems.Add(new NativeContextMenuItem(action, name, enabled));
} }
@ -392,19 +460,19 @@ namespace XivCommon.Functions.ContextMenu {
var item = this.Items[i]; var item = this.Items[i];
if (hasAnyDisabled) { if (hasAnyDisabled) {
var disabledArg = &atkValueArgs[offset + this.Items.Count + i]; var disabledArg = &atkValueArgs[ContextMenuItemOffset + this.Items.Count + i];
this._atkValueChangeType(disabledArg, ValueType.Int); this._atkValueChangeType(disabledArg, ValueType.Int);
disabledArg->Int = item.Enabled ? 0 : 1; disabledArg->Int = item.Enabled ? 0 : 1;
} }
// set up the agent to take the appropriate action for this item // 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, NativeContextMenuItem nativeItem => nativeItem.InternalAction,
_ => inventory ? InventoryNoopContextId : NoopContextId, _ => inventory ? InventoryNoopContextId : NoopContextId,
}; };
// set up the menu item // set up the menu item
var newItem = &atkValueArgs[offset + i]; var newItem = &atkValueArgs[ContextMenuItemOffset + i];
this._atkValueChangeType(newItem, ValueType.String); this._atkValueChangeType(newItem, ValueType.String);
var name = item switch { var name = item switch {
@ -438,7 +506,7 @@ namespace XivCommon.Functions.ContextMenu {
menuSize *= 2; menuSize *= 2;
} }
menuSize += offset; menuSize += ContextMenuItemOffset;
} }
private byte ItemSelectedDetour(IntPtr addon, int index, byte a3) { private byte ItemSelectedDetour(IntPtr addon, int index, byte a3) {