Fixed context menus
Fixed bugs, red button context menus, inventory sub context menus
This commit is contained in:
@@ -77,7 +77,7 @@ namespace PlayerTags.Features
|
||||
var notAddedTags = m_PluginData.CustomTags.Where(customTag => !identity.CustomTagIds.Contains(customTag.CustomId.Value));
|
||||
if (notAddedTags.Any())
|
||||
{
|
||||
contextMenuOpenedArgs.ContextMenuItems.Add(new OpenSubContextMenuItem(Strings.Loc_Static_ContextMenu_AddTag, (subContextMenuOpenedArgs =>
|
||||
contextMenuOpenedArgs.Items.Add(new OpenSubContextMenuItem(Strings.Loc_Static_ContextMenu_AddTag, (subContextMenuOpenedArgs =>
|
||||
{
|
||||
List<ContextMenuItem> newContextMenuItems = new List<ContextMenuItem>();
|
||||
foreach (var notAddedTag in notAddedTags)
|
||||
@@ -89,14 +89,23 @@ namespace PlayerTags.Features
|
||||
})));
|
||||
}
|
||||
|
||||
subContextMenuOpenedArgs.ContextMenuItems.InsertRange(0, newContextMenuItems);
|
||||
newContextMenuItems.Add(new OpenSubContextMenuItem("1", (args) =>
|
||||
{
|
||||
PluginLog.Debug("WOW1");
|
||||
args.Items.Add(new CustomContextMenuItem("2", (args2) =>
|
||||
{
|
||||
PluginLog.Debug("WOW2");
|
||||
}));
|
||||
}));
|
||||
|
||||
subContextMenuOpenedArgs.Items.InsertRange(0, newContextMenuItems);
|
||||
})));
|
||||
}
|
||||
|
||||
var addedTags = m_PluginData.CustomTags.Where(customTag => identity.CustomTagIds.Contains(customTag.CustomId.Value));
|
||||
if (addedTags.Any())
|
||||
{
|
||||
contextMenuOpenedArgs.ContextMenuItems.Add(new OpenSubContextMenuItem(Strings.Loc_Static_ContextMenu_RemoveTag, (subContextMenuOpenedArgs =>
|
||||
contextMenuOpenedArgs.Items.Add(new OpenSubContextMenuItem(Strings.Loc_Static_ContextMenu_RemoveTag, (subContextMenuOpenedArgs =>
|
||||
{
|
||||
List<ContextMenuItem> newContextMenuItems = new List<ContextMenuItem>();
|
||||
foreach (var addedTag in addedTags)
|
||||
@@ -108,7 +117,16 @@ namespace PlayerTags.Features
|
||||
})));
|
||||
}
|
||||
|
||||
subContextMenuOpenedArgs.ContextMenuItems.InsertRange(0, newContextMenuItems);
|
||||
newContextMenuItems.Add(new OpenSubContextMenuItem("1", (args) =>
|
||||
{
|
||||
PluginLog.Debug("WOW1");
|
||||
args.Items.Add(new CustomContextMenuItem("2", (args2) =>
|
||||
{
|
||||
PluginLog.Debug("WOW2");
|
||||
}));
|
||||
}));
|
||||
|
||||
subContextMenuOpenedArgs.Items.InsertRange(0, newContextMenuItems);
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
@@ -52,9 +53,6 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
private const string c_OpenInventoryContextMenu = "44 88 44 24 ?? 88 54 24 10 53";
|
||||
public IntPtr? OpenInventoryContextMenuPtr { get; private set; }
|
||||
|
||||
private const string c_InventoryContextMenuEvent30 = "E8 ?? ?? ?? ?? 48 83 C4 30 5B C3 8B 83";
|
||||
public IntPtr? InventoryContextMenuEvent30Ptr { get; private set; }
|
||||
|
||||
protected override void Setup64Bit(SigScanner scanner)
|
||||
{
|
||||
if (scanner.TryScanText(c_AtkValueChangeType, out var atkValueChangeTypePtr))
|
||||
@@ -106,50 +104,39 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
{
|
||||
OpenInventoryContextMenuPtr = setUpInventoryContextSubMenuPtr;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(c_InventoryContextMenuEvent30, out var inventoryContextMenuEvent30Ptr))
|
||||
{
|
||||
InventoryContextMenuEvent30Ptr = inventoryContextMenuEvent30Ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly AtkValueChangeTypeDelegate_Unmanaged? m_AtkValueChangeType;
|
||||
private readonly AtkValueSetStringDelegate_Unmanaged? m_AtkValueSetString;
|
||||
|
||||
private delegate IntPtr GetAddonByIdDelegate_Unmanaged(IntPtr raptureAtkUnitManager, ushort id);
|
||||
private unsafe delegate AddonInterface* GetAddonByIdDelegate_Unmanaged(RaptureAtkUnitManager* raptureAtkUnitManager, ushort id);
|
||||
private readonly GetAddonByIdDelegate_Unmanaged? m_GetAddonById;
|
||||
|
||||
private delegate bool OpenSubContextMenuDelegate_Unmanaged(IntPtr agent);
|
||||
private unsafe delegate bool OpenSubContextMenuDelegate_Unmanaged(AgentContext* agentContext);
|
||||
private readonly OpenSubContextMenuDelegate_Unmanaged? m_OpenSubContextMenu;
|
||||
|
||||
private delegate IntPtr ContextMenuOpeningDelegate_Unmanaged(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, IntPtr agent, IntPtr a7, ushort a8);
|
||||
private unsafe delegate IntPtr ContextMenuOpeningDelegate_Unmanaged(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, AgentContextInterface* agentContextInterface, IntPtr a7, ushort a8);
|
||||
private Hook<ContextMenuOpeningDelegate_Unmanaged>? m_ContextMenuOpeningHook;
|
||||
|
||||
private unsafe delegate bool ContextMenuOpenedDelegate_Unmanaged(IntPtr addon, int menuSize, AtkValue* atkValueArgs);
|
||||
private unsafe delegate bool ContextMenuOpenedDelegate_Unmanaged(AddonContextMenu* addonContextMenu, int menuSize, AtkValue* atkValueArgs);
|
||||
private Hook<ContextMenuOpenedDelegate_Unmanaged>? m_ContextMenuOpenedHook;
|
||||
private Hook<ContextMenuOpenedDelegate_Unmanaged>? m_SubContextMenuOpenedHook;
|
||||
|
||||
private delegate bool ContextMenuItemSelectedDelegate_Unmanaged(IntPtr addon, int selectedIndex, byte a3);
|
||||
private unsafe delegate bool ContextMenuItemSelectedDelegate_Unmanaged(AddonContextMenu* addonContextMenu, int selectedIndex, byte a3);
|
||||
private Hook<ContextMenuItemSelectedDelegate_Unmanaged>? m_ContextMenuItemSelectedHook;
|
||||
|
||||
private delegate bool SubContextMenuOpeningDelegate_Unmanaged(IntPtr agent);
|
||||
private unsafe delegate bool SubContextMenuOpeningDelegate_Unmanaged(AgentContext* agentContext);
|
||||
private Hook<SubContextMenuOpeningDelegate_Unmanaged>? m_SubContextMenuOpeningHook;
|
||||
|
||||
private delegate IntPtr OpenInventoryContextMenuDelegate_Unmanaged(IntPtr agent, byte hasTitle, byte zero);
|
||||
private readonly OpenInventoryContextMenuDelegate_Unmanaged? m_OpenInventoryContextMenu;
|
||||
|
||||
private delegate void InventoryContextMenuEvent30Delegate_Unmanaged(IntPtr agent, IntPtr a2, int a3, int a4, short a5);
|
||||
private Hook<InventoryContextMenuEvent30Delegate_Unmanaged>? m_InventoryContextMenuEvent30Hook;
|
||||
|
||||
private PluginAddressResolver m_PluginAddressResolver;
|
||||
|
||||
private const int MaxContextMenuItemsPerContextMenu = 32;
|
||||
|
||||
private IntPtr m_CurrentContextMenuAgent;
|
||||
private unsafe AgentContextInterface* m_CurrentAgentContextInterface;
|
||||
private IntPtr m_CurrentSubContextMenuTitle;
|
||||
|
||||
private OpenSubContextMenuItem? m_CurrentSelectedItem;
|
||||
private OpenSubContextMenuItem? m_SelectedOpenSubContextMenuItem;
|
||||
private ContextMenuOpenedArgs? m_CurrentContextMenuOpenedArgs;
|
||||
|
||||
/// <summary>
|
||||
@@ -174,7 +161,6 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
|| !m_PluginAddressResolver.SubContextMenuOpeningPtr.HasValue
|
||||
|| !m_PluginAddressResolver.SubContextMenuOpenedPtr.HasValue
|
||||
|| !m_PluginAddressResolver.OpenInventoryContextMenuPtr.HasValue
|
||||
|| !m_PluginAddressResolver.InventoryContextMenuEvent30Ptr.HasValue
|
||||
)
|
||||
{
|
||||
return false;
|
||||
@@ -215,7 +201,10 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
|
||||
if (m_PluginAddressResolver.ContextMenuOpeningPtr.HasValue)
|
||||
{
|
||||
m_ContextMenuOpeningHook = new Hook<ContextMenuOpeningDelegate_Unmanaged>(m_PluginAddressResolver.ContextMenuOpeningPtr.Value, ContextMenuOpeningDetour);
|
||||
unsafe
|
||||
{
|
||||
m_ContextMenuOpeningHook = new Hook<ContextMenuOpeningDelegate_Unmanaged>(m_PluginAddressResolver.ContextMenuOpeningPtr.Value, ContextMenuOpeningDetour);
|
||||
}
|
||||
m_ContextMenuOpeningHook?.Enable();
|
||||
}
|
||||
|
||||
@@ -230,13 +219,19 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
|
||||
if (m_PluginAddressResolver.ContextMenuItemSelectedPtr.HasValue)
|
||||
{
|
||||
m_ContextMenuItemSelectedHook = new Hook<ContextMenuItemSelectedDelegate_Unmanaged>(m_PluginAddressResolver.ContextMenuItemSelectedPtr.Value, ContextMenuItemSelectedDetour);
|
||||
unsafe
|
||||
{
|
||||
m_ContextMenuItemSelectedHook = new Hook<ContextMenuItemSelectedDelegate_Unmanaged>(m_PluginAddressResolver.ContextMenuItemSelectedPtr.Value, ContextMenuItemSelectedDetour);
|
||||
}
|
||||
m_ContextMenuItemSelectedHook?.Enable();
|
||||
}
|
||||
|
||||
if (m_PluginAddressResolver.SubContextMenuOpeningPtr.HasValue)
|
||||
{
|
||||
m_SubContextMenuOpeningHook = new Hook<SubContextMenuOpeningDelegate_Unmanaged>(m_PluginAddressResolver.SubContextMenuOpeningPtr.Value, SubContextMenuOpeningDetour);
|
||||
unsafe
|
||||
{
|
||||
m_SubContextMenuOpeningHook = new Hook<SubContextMenuOpeningDelegate_Unmanaged>(m_PluginAddressResolver.SubContextMenuOpeningPtr.Value, SubContextMenuOpeningDetour);
|
||||
}
|
||||
m_SubContextMenuOpeningHook?.Enable();
|
||||
}
|
||||
|
||||
@@ -248,22 +243,10 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
}
|
||||
m_SubContextMenuOpenedHook?.Enable();
|
||||
}
|
||||
|
||||
if (m_PluginAddressResolver.OpenInventoryContextMenuPtr.HasValue)
|
||||
{
|
||||
m_OpenInventoryContextMenu = Marshal.GetDelegateForFunctionPointer<OpenInventoryContextMenuDelegate_Unmanaged>(m_PluginAddressResolver.OpenInventoryContextMenuPtr.Value);
|
||||
}
|
||||
|
||||
if (m_PluginAddressResolver.InventoryContextMenuEvent30Ptr.HasValue)
|
||||
{
|
||||
m_InventoryContextMenuEvent30Hook = new Hook<InventoryContextMenuEvent30Delegate_Unmanaged>(m_PluginAddressResolver.InventoryContextMenuEvent30Ptr.Value, InventoryContextMenuEvent30Detour);
|
||||
m_InventoryContextMenuEvent30Hook?.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_InventoryContextMenuEvent30Hook?.Disable();
|
||||
m_SubContextMenuOpeningHook?.Disable();
|
||||
m_ContextMenuItemSelectedHook?.Disable();
|
||||
m_SubContextMenuOpenedHook?.Disable();
|
||||
@@ -271,13 +254,13 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
m_ContextMenuOpeningHook?.Disable();
|
||||
}
|
||||
|
||||
private unsafe IntPtr ContextMenuOpeningDetour(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, IntPtr agent, IntPtr a7, ushort a8)
|
||||
private unsafe IntPtr ContextMenuOpeningDetour(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, AgentContextInterface* agentContextInterface, IntPtr a7, ushort a8)
|
||||
{
|
||||
m_CurrentContextMenuAgent = agent;
|
||||
return m_ContextMenuOpeningHook!.Original(a1, a2, a3, a4, a5, agent, a7, a8);
|
||||
m_CurrentAgentContextInterface = agentContextInterface;
|
||||
return m_ContextMenuOpeningHook!.Original(a1, a2, a3, a4, a5, agentContextInterface, a7, a8);
|
||||
}
|
||||
|
||||
private unsafe bool ContextMenuOpenedDetour(IntPtr addon, int atkValueCount, AtkValue* atkValues)
|
||||
private unsafe bool ContextMenuOpenedDetour(AddonContextMenu* addonContextMenu, int atkValueCount, AtkValue* atkValues)
|
||||
{
|
||||
if (m_ContextMenuOpenedHook == null)
|
||||
{
|
||||
@@ -286,77 +269,91 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
|
||||
try
|
||||
{
|
||||
ContextMenuOpenedImplementation(addon, ref atkValueCount, ref atkValues);
|
||||
ContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, "ContextMenuOpenedDetour");
|
||||
}
|
||||
|
||||
return m_ContextMenuOpenedHook.Original(addon, atkValueCount, atkValues);
|
||||
return m_ContextMenuOpenedHook.Original(addonContextMenu, atkValueCount, atkValues);
|
||||
}
|
||||
|
||||
private unsafe void ContextMenuOpenedImplementation(IntPtr addon, ref int atkValueCount, ref AtkValue* atkValues)
|
||||
private unsafe void ContextMenuOpenedImplementation(AddonContextMenu* addonContextMenu, ref int atkValueCount, ref AtkValue* atkValues)
|
||||
{
|
||||
if (m_AtkValueChangeType == null
|
||||
|| m_AtkValueSetString == null
|
||||
|| ContextMenuOpened == null
|
||||
|| m_CurrentContextMenuAgent == IntPtr.Zero)
|
||||
|| m_CurrentAgentContextInterface == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ContextMenuOpenedDelegate contextMenuOpenedDelegate = ContextMenuOpened;
|
||||
if (m_CurrentSelectedItem is OpenSubContextMenuItem openSubContextMenuItem)
|
||||
{
|
||||
contextMenuOpenedDelegate = openSubContextMenuItem.Opened;
|
||||
}
|
||||
|
||||
// Read the context menu items from the game, then allow subscribers to modify them
|
||||
ContextMenuReaderWriter contextMenuReaderWriter = new ContextMenuReaderWriter(m_CurrentContextMenuAgent, atkValueCount, atkValues);
|
||||
ContextMenuReaderWriter contextMenuReaderWriter = new ContextMenuReaderWriter(m_CurrentAgentContextInterface, atkValueCount, atkValues);
|
||||
|
||||
// Check for a title.
|
||||
string? title = null;
|
||||
if (contextMenuReaderWriter.Title != null)
|
||||
if (m_SelectedOpenSubContextMenuItem != null)
|
||||
{
|
||||
title = m_SelectedOpenSubContextMenuItem.Name.TextValue;
|
||||
}
|
||||
else if (contextMenuReaderWriter.Title != null)
|
||||
{
|
||||
title = contextMenuReaderWriter.Title.TextValue;
|
||||
}
|
||||
|
||||
m_CurrentContextMenuOpenedArgs = NotifyContextMenuOpened(addon, m_CurrentContextMenuAgent, title, contextMenuOpenedDelegate, contextMenuReaderWriter.Read());
|
||||
// Determine which event to raise.
|
||||
ContextMenuOpenedDelegate contextMenuOpenedDelegate = ContextMenuOpened;
|
||||
if (m_SelectedOpenSubContextMenuItem is OpenSubContextMenuItem openSubContextMenuItem)
|
||||
{
|
||||
contextMenuOpenedDelegate = openSubContextMenuItem.Opened;
|
||||
}
|
||||
|
||||
// Get the existing items from the game.
|
||||
// For inventory sub context menus, we take only the last item -- the return item.
|
||||
// This is because we're doing a hack to spawn a Second Tier sub context menu and then appropriating it.
|
||||
var contextMenuItems = contextMenuReaderWriter.Read();
|
||||
if (IsInventoryContext(m_CurrentAgentContextInterface) && m_SelectedOpenSubContextMenuItem != null)
|
||||
{
|
||||
contextMenuItems = contextMenuItems.TakeLast(1).ToArray();
|
||||
}
|
||||
|
||||
// Raise the event and get the context menu changes.
|
||||
m_CurrentContextMenuOpenedArgs = NotifyContextMenuOpened(addonContextMenu, m_CurrentAgentContextInterface, title, contextMenuOpenedDelegate, contextMenuItems);
|
||||
if (m_CurrentContextMenuOpenedArgs == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
contextMenuReaderWriter.Write(m_CurrentContextMenuOpenedArgs, m_CurrentSelectedItem, m_AtkValueChangeType, m_AtkValueSetString);
|
||||
// Write the new changes.
|
||||
contextMenuReaderWriter.Write(m_CurrentContextMenuOpenedArgs, m_SelectedOpenSubContextMenuItem, m_AtkValueChangeType, m_AtkValueSetString);
|
||||
|
||||
// Update the addon
|
||||
var addonContext = (AddonContext*)addon;
|
||||
atkValueCount = *(&addonContext->AtkValuesCount) = (ushort)contextMenuReaderWriter.AtkValueCount;
|
||||
atkValues = *(&addonContext->AtkValues) = contextMenuReaderWriter.AtkValues;
|
||||
// Update the addon.
|
||||
atkValueCount = *(&addonContextMenu->AtkValuesCount) = (ushort)contextMenuReaderWriter.AtkValueCount;
|
||||
atkValues = *(&addonContextMenu->AtkValues) = contextMenuReaderWriter.AtkValues;
|
||||
}
|
||||
|
||||
private bool SubContextMenuOpeningDetour(IntPtr agent)
|
||||
private unsafe bool SubContextMenuOpeningDetour(AgentContext* agentContext)
|
||||
{
|
||||
if (m_SubContextMenuOpeningHook == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SubContextMenuOpeningImplementation(agent))
|
||||
if (SubContextMenuOpeningImplementation(agentContext))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return m_SubContextMenuOpeningHook.Original(agent);
|
||||
return m_SubContextMenuOpeningHook.Original(agentContext);
|
||||
}
|
||||
|
||||
private unsafe bool SubContextMenuOpeningImplementation(IntPtr agent)
|
||||
private unsafe bool SubContextMenuOpeningImplementation(AgentContext* agentContext)
|
||||
{
|
||||
if (m_OpenSubContextMenu == null
|
||||
|| m_OpenInventoryContextMenu == null
|
||||
|| m_AtkValueChangeType == null
|
||||
|| m_AtkValueSetString == null
|
||||
|| !(m_CurrentSelectedItem is OpenSubContextMenuItem))
|
||||
|| !(m_SelectedOpenSubContextMenuItem is OpenSubContextMenuItem))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -369,37 +366,29 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
// We want to exclude context menu item data in this function because the game sometimes includes garbage items which can cause problems.
|
||||
// After this function, the game adds the "< Return" item, and THEN we add our own items after that.
|
||||
|
||||
var agentContext = (AgentContext*)agent;
|
||||
if (IsInventoryContext(agent))
|
||||
{
|
||||
m_OpenInventoryContextMenu(agent, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_OpenSubContextMenu(agent);
|
||||
m_OpenSubContextMenu(agentContext);
|
||||
|
||||
GameInterfaceHelper.GameFree(ref m_CurrentSubContextMenuTitle, (ulong)IntPtr.Size);
|
||||
// Allocate a new 1 byte title. This is required for the game to render the titled context menu style.
|
||||
// The actual value doesn't matter at this point, we'll set it later.
|
||||
GameInterfaceHelper.GameFree(ref m_CurrentSubContextMenuTitle, (ulong)IntPtr.Size);
|
||||
m_CurrentSubContextMenuTitle = GameInterfaceHelper.GameUIAllocate(1);
|
||||
*(&(&agentContext->AgentContextInterface)->SubContextMenuTitle) = (byte*)m_CurrentSubContextMenuTitle;
|
||||
*(byte*)m_CurrentSubContextMenuTitle = 0;
|
||||
|
||||
// Allocate a new 1 byte title. We need this for the game to render the titled context menu style.
|
||||
// The actual value doesn't matter at this point, we'll set it later.
|
||||
m_CurrentSubContextMenuTitle = GameInterfaceHelper.GameUIAllocate(1);
|
||||
*(&agentContext->SubContextMenuTitle) = (byte*)m_CurrentSubContextMenuTitle;
|
||||
*(byte*)m_CurrentSubContextMenuTitle = 0;
|
||||
}
|
||||
// Expect at least 1 context menu item.
|
||||
(&agentContext->Items->AtkValues)[0].UInt = 1;
|
||||
|
||||
var atkValues = &agentContext->ItemData->AtkValues;
|
||||
atkValues[0].UInt = 1;
|
||||
// Expect a title. This isn't needed by the game, it's needed by ContextMenuReaderWriter which uses this to check if it's a context menu
|
||||
m_AtkValueChangeType(&(&agentContext->Items->AtkValues)[1], FFXIVClientStructs.FFXIV.Component.GUI.ValueType.String);
|
||||
(&agentContext->Items->AtkValues)[1].String = (byte*)0;
|
||||
|
||||
// This isn't needed by the game, it's needed by ContextMenuReaderWriter which uses this to check if it's a context menu
|
||||
m_AtkValueChangeType(&atkValues[1], FFXIVClientStructs.FFXIV.Component.GUI.ValueType.String);
|
||||
|
||||
ContextMenuReaderWriter contextMenuReaderWriter = new ContextMenuReaderWriter(agent, agentContext->ItemData->AtkValuesCount, atkValues);
|
||||
*(&agentContext->ItemData->AtkValuesCount) = (ushort)contextMenuReaderWriter.FirstContextMenuItemIndex;
|
||||
ContextMenuReaderWriter contextMenuReaderWriter = new ContextMenuReaderWriter(&agentContext->AgentContextInterface, agentContext->Items->AtkValueCount, &agentContext->Items->AtkValues);
|
||||
*(&agentContext->Items->AtkValueCount) = (ushort)contextMenuReaderWriter.FirstContextMenuItemIndex;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe bool SubContextMenuOpenedDetour(IntPtr addon, int atkValueCount, AtkValue* atkValues)
|
||||
private unsafe bool SubContextMenuOpenedDetour(AddonContextMenu* addonContextMenu, int atkValueCount, AtkValue* atkValues)
|
||||
{
|
||||
if (m_SubContextMenuOpenedHook == null)
|
||||
{
|
||||
@@ -408,51 +397,51 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
|
||||
try
|
||||
{
|
||||
SubContextMenuOpenedImplementation(addon, ref atkValueCount, ref atkValues);
|
||||
SubContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, "SubContextMenuOpenedDetour");
|
||||
}
|
||||
|
||||
return m_SubContextMenuOpenedHook.Original(addon, atkValueCount, atkValues);
|
||||
return m_SubContextMenuOpenedHook.Original(addonContextMenu, atkValueCount, atkValues);
|
||||
}
|
||||
|
||||
private unsafe void SubContextMenuOpenedImplementation(IntPtr addon, ref int atkValueCount, ref AtkValue* atkValues)
|
||||
private unsafe void SubContextMenuOpenedImplementation(AddonContextMenu* addonContextMenu, ref int atkValueCount, ref AtkValue* atkValues)
|
||||
{
|
||||
ContextMenuOpenedImplementation(addon, ref atkValueCount, ref atkValues);
|
||||
ContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues);
|
||||
}
|
||||
|
||||
private unsafe ContextMenuOpenedArgs? NotifyContextMenuOpened(IntPtr addon, IntPtr agent, string? title, ContextMenuOpenedDelegate contextMenuOpenedDelegate, IEnumerable<ContextMenuItem> initialContextMenuItems)
|
||||
private unsafe ContextMenuOpenedArgs? NotifyContextMenuOpened(AddonContextMenu* addonContextMenu, AgentContextInterface* agentContextInterface, string? title, ContextMenuOpenedDelegate contextMenuOpenedDelegate, IEnumerable<ContextMenuItem> initialContextMenuItems)
|
||||
{
|
||||
var parentAddonName = GetParentAddonName(addon);
|
||||
var parentAddonName = GetParentAddonName(&addonContextMenu->AddonInterface);
|
||||
|
||||
ItemContext? itemContext = null;
|
||||
InventoryItemContext? inventoryItemContext = null;
|
||||
GameObjectContext? gameObjectContext = null;
|
||||
if (IsInventoryContext(agent))
|
||||
if (IsInventoryContext(agentContextInterface))
|
||||
{
|
||||
var agentInventoryContext = (AgentInventoryContext*)agent;
|
||||
itemContext = new ItemContext(agentInventoryContext->ItemId, agentInventoryContext->ItemCount, agentInventoryContext->IsHighQuality);
|
||||
var agentInventoryContext = (AgentInventoryContext*)agentContextInterface;
|
||||
inventoryItemContext = new InventoryItemContext(agentInventoryContext->InventoryItemId, agentInventoryContext->InventoryItemCount, agentInventoryContext->InventoryItemIsHighQuality);
|
||||
}
|
||||
else
|
||||
{
|
||||
var agentContext = (AgentContext*)agent;
|
||||
var agentContext = (AgentContext*)agentContextInterface;
|
||||
if (agentContext->GameObjectContentId != 0 || agentContext->GameObjectWorldId != 0)
|
||||
{
|
||||
SeString objectName;
|
||||
unsafe
|
||||
{
|
||||
objectName = GameInterfaceHelper.ReadSeString((IntPtr)agentContext->ObjectName.StringPtr);
|
||||
objectName = GameInterfaceHelper.ReadSeString((IntPtr)agentContext->GameObjectName.StringPtr);
|
||||
}
|
||||
|
||||
gameObjectContext = new GameObjectContext(agentContext->GameObjectId, agentContext->GameObjectContentId, objectName, agentContext->GameObjectWorldId);
|
||||
}
|
||||
}
|
||||
|
||||
var contextMenuOpenedArgs = new ContextMenuOpenedArgs(addon, agent, parentAddonName, initialContextMenuItems)
|
||||
var contextMenuOpenedArgs = new ContextMenuOpenedArgs((IntPtr)addonContextMenu, (IntPtr)agentContextInterface, parentAddonName, initialContextMenuItems)
|
||||
{
|
||||
Title = title,
|
||||
ItemContext = itemContext,
|
||||
InventoryItemContext = inventoryItemContext,
|
||||
GameObjectContext = gameObjectContext
|
||||
};
|
||||
|
||||
@@ -466,21 +455,28 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var contextMenuItem in contextMenuOpenedArgs.ContextMenuItems)
|
||||
foreach (var contextMenuItem in contextMenuOpenedArgs.Items.ToArray())
|
||||
{
|
||||
contextMenuItem.Agent = agent;
|
||||
contextMenuItem.Agent = (IntPtr)agentContextInterface;
|
||||
|
||||
// TODO: Game doesn't support nested sub context menus, but we might be able to.
|
||||
if (contextMenuItem is OpenSubContextMenuItem && contextMenuOpenedArgs.Title != null)
|
||||
{
|
||||
contextMenuOpenedArgs.Items.Remove(contextMenuItem);
|
||||
PluginLog.Warning($"Context menu '{contextMenuOpenedArgs.Title}' item '{contextMenuItem}' has been removed because nested sub context menus are not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
if (contextMenuOpenedArgs.ContextMenuItems.Count > MaxContextMenuItemsPerContextMenu)
|
||||
if (contextMenuOpenedArgs.Items.Count > MaxContextMenuItemsPerContextMenu)
|
||||
{
|
||||
PluginLog.LogWarning($"Context menu requesting {contextMenuOpenedArgs.ContextMenuItems.Count} of max {MaxContextMenuItemsPerContextMenu} items. Resizing list to compensate.");
|
||||
contextMenuOpenedArgs.ContextMenuItems.RemoveRange(MaxContextMenuItemsPerContextMenu, contextMenuOpenedArgs.ContextMenuItems.Count - MaxContextMenuItemsPerContextMenu);
|
||||
PluginLog.LogWarning($"Context menu requesting {contextMenuOpenedArgs.Items.Count} of max {MaxContextMenuItemsPerContextMenu} items. Resizing list to compensate.");
|
||||
contextMenuOpenedArgs.Items.RemoveRange(MaxContextMenuItemsPerContextMenu, contextMenuOpenedArgs.Items.Count - MaxContextMenuItemsPerContextMenu);
|
||||
}
|
||||
|
||||
return contextMenuOpenedArgs;
|
||||
}
|
||||
|
||||
private unsafe bool ContextMenuItemSelectedDetour(IntPtr addon, int selectedIndex, byte a3)
|
||||
private unsafe bool ContextMenuItemSelectedDetour(AddonContextMenu* addonContextMenu, int selectedIndex, byte a3)
|
||||
{
|
||||
if (m_ContextMenuItemSelectedHook == null)
|
||||
{
|
||||
@@ -489,28 +485,27 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
|
||||
try
|
||||
{
|
||||
ContextMenuItemSelectedImplementation(addon, selectedIndex);
|
||||
ContextMenuItemSelectedImplementation(addonContextMenu, selectedIndex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, "ContextMenuItemSelectedDetour");
|
||||
}
|
||||
|
||||
return m_ContextMenuItemSelectedHook.Original(addon, selectedIndex, a3);
|
||||
return m_ContextMenuItemSelectedHook.Original(addonContextMenu, selectedIndex, a3);
|
||||
}
|
||||
|
||||
private unsafe void ContextMenuItemSelectedImplementation(IntPtr addon, int selectedIndex)
|
||||
private unsafe void ContextMenuItemSelectedImplementation(AddonContextMenu* addonContextMenu, int selectedIndex)
|
||||
{
|
||||
if (m_CurrentContextMenuOpenedArgs == null || selectedIndex == -1)
|
||||
{
|
||||
m_CurrentContextMenuOpenedArgs = null;
|
||||
m_CurrentSelectedItem = null;
|
||||
m_SelectedOpenSubContextMenuItem = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the selected item directly from the game
|
||||
var addonContext = (AddonContext*)addon;
|
||||
ContextMenuReaderWriter contextMenuReaderWriter = new ContextMenuReaderWriter(m_CurrentContextMenuAgent, addonContext->AtkValuesCount, addonContext->AtkValues);
|
||||
ContextMenuReaderWriter contextMenuReaderWriter = new ContextMenuReaderWriter(m_CurrentAgentContextInterface, addonContextMenu->AtkValuesCount, addonContextMenu->AtkValues);
|
||||
var gameContextMenuItems = contextMenuReaderWriter.Read();
|
||||
var gameSelectedItem = gameContextMenuItems.ElementAtOrDefault(selectedIndex);
|
||||
|
||||
@@ -518,15 +513,15 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
if (gameSelectedItem == null)
|
||||
{
|
||||
m_CurrentContextMenuOpenedArgs = null;
|
||||
m_CurrentSelectedItem = null;
|
||||
m_SelectedOpenSubContextMenuItem = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Match it with the items we already know about based on its name.
|
||||
// We can get into a state where we have a game item we don't recognize when another plugin has added one.
|
||||
var selectedItem = m_CurrentContextMenuOpenedArgs.ContextMenuItems.FirstOrDefault(item => item.Name.Encode().SequenceEqual(gameSelectedItem.Name.Encode()));
|
||||
var selectedItem = m_CurrentContextMenuOpenedArgs.Items.FirstOrDefault(item => item.Name.Encode().SequenceEqual(gameSelectedItem.Name.Encode()));
|
||||
|
||||
m_CurrentSelectedItem = null;
|
||||
m_SelectedOpenSubContextMenuItem = null;
|
||||
if (selectedItem is CustomContextMenuItem customContextMenuItem)
|
||||
{
|
||||
try
|
||||
@@ -541,49 +536,33 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
}
|
||||
else if (selectedItem is OpenSubContextMenuItem openSubContextMenuItem)
|
||||
{
|
||||
m_CurrentSelectedItem = openSubContextMenuItem;
|
||||
m_SelectedOpenSubContextMenuItem = openSubContextMenuItem;
|
||||
}
|
||||
|
||||
m_CurrentContextMenuOpenedArgs = null;
|
||||
}
|
||||
|
||||
private void InventoryContextMenuEvent30Detour(IntPtr agent, IntPtr a2, int a3, int a4, short a5)
|
||||
{
|
||||
if (m_InventoryContextMenuEvent30Hook == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (SubContextMenuOpeningImplementation(agent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_InventoryContextMenuEvent30Hook.Original(agent, a2, a3, a4, a5);
|
||||
}
|
||||
|
||||
private unsafe string? GetParentAddonName(IntPtr addon)
|
||||
private unsafe string? GetParentAddonName(AddonInterface* addonInterface)
|
||||
{
|
||||
if (m_GetAddonById == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var parentAddonId = Marshal.PtrToStructure<Addon>(addon).ParentAddonId;
|
||||
var parentAddonId = addonInterface->ParentAddonId;
|
||||
if (parentAddonId == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var atkStage = AtkStage.GetSingleton();
|
||||
var parentAddonPtr = m_GetAddonById((IntPtr)atkStage->RaptureAtkUnitManager, parentAddonId);
|
||||
|
||||
return GameInterfaceHelper.ReadString(parentAddonPtr + 8);
|
||||
var parentAddon = m_GetAddonById(atkStage->RaptureAtkUnitManager, parentAddonId);
|
||||
return GameInterfaceHelper.ReadString((IntPtr)(&parentAddon->Name));
|
||||
}
|
||||
|
||||
private unsafe bool IsInventoryContext(IntPtr agent)
|
||||
private unsafe bool IsInventoryContext(AgentContextInterface* agentContextInterface)
|
||||
{
|
||||
if (agent == (IntPtr)AgentInventoryContext.Instance())
|
||||
if (agentContextInterface == AgentInventoryContext.Instance())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -591,20 +570,19 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe IntPtr GetAddonFromAgent(IntPtr agent)
|
||||
private unsafe AddonInterface* GetAddonFromAgent(AgentInterface* agentInterface)
|
||||
{
|
||||
if (m_GetAddonById == null)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
return null;
|
||||
}
|
||||
|
||||
var agentInterface = (AgentInterface*)agent;
|
||||
if (agentInterface->AddonId == 0)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
return null;
|
||||
}
|
||||
|
||||
return m_GetAddonById((IntPtr)AtkStage.GetSingleton()->RaptureAtkUnitManager, (ushort)agentInterface->AddonId);
|
||||
return m_GetAddonById(AtkStage.GetSingleton()->RaptureAtkUnitManager, (ushort)agentInterface->AddonId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
/// <summary>
|
||||
/// The items in the context menu.
|
||||
/// </summary>
|
||||
public List<ContextMenuItem> ContextMenuItems { get; }
|
||||
public List<ContextMenuItem> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The game object context associated with the context menu.
|
||||
@@ -42,7 +42,7 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
/// <summary>
|
||||
/// The item context associated with the context menu.
|
||||
/// </summary>
|
||||
public ItemContext? ItemContext { get; init; }
|
||||
public InventoryItemContext? InventoryItemContext { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContextMenuOpenedArgs"/> class.
|
||||
@@ -50,13 +50,13 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
/// <param name="addon">The addon associated with the context menu.</param>
|
||||
/// <param name="agent">The agent associated with the context menu.</param>
|
||||
/// <param name="parentAddonName">The the name of the parent addon associated with the context menu.</param>
|
||||
/// <param name="contextMenuItems">The items in the context menu.</param>
|
||||
public ContextMenuOpenedArgs(IntPtr addon, IntPtr agent, string? parentAddonName, IEnumerable<ContextMenuItem> contextMenuItems)
|
||||
/// <param name="items">The items in the context menu.</param>
|
||||
public ContextMenuOpenedArgs(IntPtr addon, IntPtr agent, string? parentAddonName, IEnumerable<ContextMenuItem> items)
|
||||
{
|
||||
Addon = addon;
|
||||
Agent = agent;
|
||||
ParentAddonName = parentAddonName;
|
||||
ContextMenuItems = new List<ContextMenuItem>(contextMenuItems);
|
||||
Items = new List<ContextMenuItem>(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
Alternate
|
||||
}
|
||||
|
||||
private IntPtr m_Agent;
|
||||
private AgentContextInterface* m_AgentContextInterface;
|
||||
|
||||
private int m_AtkValueCount;
|
||||
public int AtkValueCount
|
||||
@@ -129,6 +129,22 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 0x14000000 | action
|
||||
/// </summary>
|
||||
public int? MaskedActionIndexOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
if (HasTitle && StructLayout == SubContextMenuStructLayout.Alternate)
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int SequentialAtkValuesPerContextMenuItem
|
||||
{
|
||||
get
|
||||
@@ -172,7 +188,7 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Agent == (IntPtr)AgentInventoryContext.Instance())
|
||||
if ((IntPtr)m_AgentContextInterface == (IntPtr)AgentInventoryContext.Instance())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -201,9 +217,53 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
}
|
||||
}
|
||||
|
||||
public ContextMenuReaderWriter(IntPtr agent, int atkValueCount, AtkValue* atkValues)
|
||||
public byte NoopAction
|
||||
{
|
||||
m_Agent = agent;
|
||||
get
|
||||
{
|
||||
if (IsInventoryContext)
|
||||
{
|
||||
return 0xff;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0x67;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte OpenSubContextMenuAction
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsInventoryContext)
|
||||
{
|
||||
// This is actually the action to open the Second Tier context menu and we just hack around it
|
||||
return 0x31;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0x66;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte? FirstUnhandledAction
|
||||
{
|
||||
get
|
||||
{
|
||||
if (StructLayout != null && StructLayout == SubContextMenuStructLayout.Alternate)
|
||||
{
|
||||
return 0x68;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ContextMenuReaderWriter(AgentContextInterface* agentContextInterface, int atkValueCount, AtkValue* atkValues)
|
||||
{
|
||||
m_AgentContextInterface = agentContextInterface;
|
||||
m_AtkValueCount = atkValueCount;
|
||||
m_AtkValues = atkValues;
|
||||
}
|
||||
@@ -236,17 +296,17 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
byte action = 0;
|
||||
if (IsInventoryContext)
|
||||
{
|
||||
var actions = &((AgentInventoryContext*)m_Agent)->Actions;
|
||||
var actions = &((AgentInventoryContext*)m_AgentContextInterface)->Actions;
|
||||
action = *(actions + contextMenuItemAtkValueBaseIndex);
|
||||
}
|
||||
else if (StructLayout != null && StructLayout.Value == SubContextMenuStructLayout.Alternate)
|
||||
{
|
||||
var actions = &((AgentContext*)m_Agent)->ItemData->RedButtonActions;
|
||||
action = (byte)*(actions + contextMenuItemIndex);
|
||||
var redButtonActions = &((AgentContext*)m_AgentContextInterface)->Items->RedButtonActions;
|
||||
action = (byte)*(redButtonActions + contextMenuItemIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
var actions = &((AgentContext*)m_Agent)->ItemData->Actions;
|
||||
var actions = &((AgentContext*)m_AgentContextInterface)->Items->Actions;
|
||||
action = *(actions + contextMenuItemAtkValueBaseIndex);
|
||||
}
|
||||
|
||||
@@ -270,7 +330,7 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
|
||||
var gameContextMenuItem = new GameContextMenuItem(name, action)
|
||||
{
|
||||
Agent = m_Agent,
|
||||
Agent = (IntPtr)m_AgentContextInterface,
|
||||
IsEnabled = isEnabled,
|
||||
Indicator = indicator
|
||||
};
|
||||
@@ -281,37 +341,40 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
return gameContextMenuItems.ToArray();
|
||||
}
|
||||
|
||||
public unsafe void Write(ContextMenuOpenedArgs contextMenuOpenedArgs, ContextMenuItem? selectedContextMenuItem, AtkValueChangeTypeDelegate_Unmanaged atkValueChangeType, AtkValueSetStringDelegate_Unmanaged atkValueSetString)
|
||||
public unsafe void Write(ContextMenuOpenedArgs contextMenuOpenedArgs, ContextMenuItem? selectedContextMenuItem, AtkValueChangeTypeDelegate_Unmanaged atkValueChangeType, AtkValueSetStringDelegate_Unmanaged atkValueSetString, bool allowReallocate = true)
|
||||
{
|
||||
var newAtkValuesCount = FirstContextMenuItemIndex + (contextMenuOpenedArgs.ContextMenuItems.Count() * TotalDesiredAtkValuesPerContextMenuItem);
|
||||
|
||||
// Allocate the new array. We have to do a little dance with the first 8 bytes which represents the array count
|
||||
const int arrayCountSize = 8;
|
||||
var newAtkValuesArraySize = arrayCountSize + Marshal.SizeOf<AtkValue>() * newAtkValuesCount;
|
||||
var newAtkValuesArray = GameInterfaceHelper.GameUIAllocate((ulong)newAtkValuesArraySize);
|
||||
if (newAtkValuesArray == IntPtr.Zero)
|
||||
if (allowReallocate)
|
||||
{
|
||||
return;
|
||||
var newAtkValuesCount = FirstContextMenuItemIndex + (contextMenuOpenedArgs.Items.Count() * TotalDesiredAtkValuesPerContextMenuItem);
|
||||
|
||||
// Allocate the new array. We have to do a little dance with the first 8 bytes which represents the array count
|
||||
const int arrayCountSize = 8;
|
||||
var newAtkValuesArraySize = arrayCountSize + Marshal.SizeOf<AtkValue>() * newAtkValuesCount;
|
||||
var newAtkValuesArray = GameInterfaceHelper.GameUIAllocate((ulong)newAtkValuesArraySize);
|
||||
if (newAtkValuesArray == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newAtkValues = (AtkValue*)(newAtkValuesArray + arrayCountSize);
|
||||
|
||||
// Zero the memory, then copy the atk values up to the first context menu item atk value
|
||||
Marshal.Copy(new byte[newAtkValuesArraySize], 0, newAtkValuesArray, newAtkValuesArraySize);
|
||||
Buffer.MemoryCopy(m_AtkValues, newAtkValues, newAtkValuesArraySize - arrayCountSize, (long)sizeof(AtkValue) * FirstContextMenuItemIndex);
|
||||
|
||||
// Free the old array
|
||||
IntPtr oldArray = (IntPtr)m_AtkValues - arrayCountSize;
|
||||
ulong oldArrayCount = *(ulong*)oldArray;
|
||||
ulong oldArraySize = arrayCountSize + ((ulong)sizeof(AtkValue) * oldArrayCount);
|
||||
GameInterfaceHelper.GameFree(ref oldArray, oldArraySize);
|
||||
|
||||
// Set the array count
|
||||
*(ulong*)newAtkValuesArray = (ulong)newAtkValuesCount;
|
||||
|
||||
m_AtkValueCount = newAtkValuesCount;
|
||||
m_AtkValues = newAtkValues;
|
||||
}
|
||||
|
||||
var newAtkValues = (AtkValue*)(newAtkValuesArray + arrayCountSize);
|
||||
|
||||
// Zero the memory, then copy the atk values up to the first context menu item atk value
|
||||
Marshal.Copy(new byte[newAtkValuesArraySize], 0, newAtkValuesArray, newAtkValuesArraySize);
|
||||
Buffer.MemoryCopy(m_AtkValues, newAtkValues, newAtkValuesArraySize - arrayCountSize, (long)sizeof(AtkValue) * FirstContextMenuItemIndex);
|
||||
|
||||
// Free the old array
|
||||
IntPtr oldArray = (IntPtr)m_AtkValues - arrayCountSize;
|
||||
ulong oldArrayCount = *(ulong*)oldArray;
|
||||
ulong oldArraySize = arrayCountSize + ((ulong)sizeof(AtkValue) * oldArrayCount);
|
||||
GameInterfaceHelper.GameFree(ref oldArray, oldArraySize);
|
||||
|
||||
// Set the array count
|
||||
*(ulong*)newAtkValuesArray = (ulong)newAtkValuesCount;
|
||||
|
||||
m_AtkValueCount = newAtkValuesCount;
|
||||
m_AtkValues = newAtkValues;
|
||||
|
||||
// Set the custom title if appropriate
|
||||
if (selectedContextMenuItem is OpenSubContextMenuItem)
|
||||
{
|
||||
@@ -325,7 +388,7 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
// Set the context menu item count
|
||||
const int contextMenuItemCountAtkValueIndex = 0;
|
||||
var contextMenuItemCountAtkValue = &m_AtkValues[contextMenuItemCountAtkValueIndex];
|
||||
contextMenuItemCountAtkValue->UInt = (uint)contextMenuOpenedArgs.ContextMenuItems.Count();
|
||||
contextMenuItemCountAtkValue->UInt = (uint)contextMenuOpenedArgs.Items.Count();
|
||||
|
||||
// Clear the previous arrow flags
|
||||
var hasPreviousIndicatorAtkValue = &m_AtkValues[HasPreviousIndicatorFlagsIndex];
|
||||
@@ -335,9 +398,9 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
var hasNextIndiactorFlagsAtkValue = &m_AtkValues[HasNextIndicatorFlagsIndex];
|
||||
hasNextIndiactorFlagsAtkValue->UInt = 0;
|
||||
|
||||
for (int contextMenuItemIndex = 0; contextMenuItemIndex < contextMenuOpenedArgs.ContextMenuItems.Count(); ++contextMenuItemIndex)
|
||||
for (int contextMenuItemIndex = 0; contextMenuItemIndex < contextMenuOpenedArgs.Items.Count(); ++contextMenuItemIndex)
|
||||
{
|
||||
var contextMenuItem = contextMenuOpenedArgs.ContextMenuItems.ElementAt(contextMenuItemIndex);
|
||||
var contextMenuItem = contextMenuOpenedArgs.Items.ElementAt(contextMenuItemIndex);
|
||||
|
||||
var contextMenuItemAtkValueBaseIndex = FirstContextMenuItemIndex + (contextMenuItemIndex * SequentialAtkValuesPerContextMenuItem);
|
||||
|
||||
@@ -362,41 +425,36 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
}
|
||||
else if (contextMenuItem is CustomContextMenuItem customContextMenuItem)
|
||||
{
|
||||
if (IsInventoryContext)
|
||||
{
|
||||
action = 0xff;
|
||||
}
|
||||
else
|
||||
{
|
||||
action = 0x67;
|
||||
}
|
||||
action = NoopAction;
|
||||
}
|
||||
else if (contextMenuItem is OpenSubContextMenuItem openSubContextMenuItem)
|
||||
{
|
||||
if (IsInventoryContext)
|
||||
{
|
||||
// TODO: Fix inventory sub context menus
|
||||
action = /*0x30*/ 0xff;
|
||||
}
|
||||
else
|
||||
{
|
||||
action = 0x66;
|
||||
}
|
||||
action = OpenSubContextMenuAction;
|
||||
}
|
||||
|
||||
if (IsInventoryContext)
|
||||
{
|
||||
var actions = &((AgentInventoryContext*)m_Agent)->Actions;
|
||||
var actions = &((AgentInventoryContext*)m_AgentContextInterface)->Actions;
|
||||
*(actions + FirstContextMenuItemIndex + contextMenuItemIndex) = action;
|
||||
}
|
||||
else if (StructLayout != null && StructLayout.Value == SubContextMenuStructLayout.Alternate)
|
||||
else if (StructLayout != null && StructLayout.Value == SubContextMenuStructLayout.Alternate && FirstUnhandledAction != null)
|
||||
{
|
||||
var actions = &((AgentContext*)m_Agent)->ItemData->RedButtonActions;
|
||||
*(actions + contextMenuItemIndex) = action;
|
||||
// Some weird placeholder goes here
|
||||
var actions = &((AgentContext*)m_AgentContextInterface)->Items->Actions;
|
||||
*(actions + FirstContextMenuItemIndex + contextMenuItemIndex) = (byte)(FirstUnhandledAction.Value + contextMenuItemIndex);
|
||||
|
||||
// Make sure there's one of these function pointers for every item.
|
||||
// The function needs to be the same, so we just copy the first one into every index.
|
||||
var unkFunctionPointers = &((AgentContext*)m_AgentContextInterface)->Items->UnkFunctionPointers;
|
||||
*(unkFunctionPointers + FirstContextMenuItemIndex + contextMenuItemIndex) = *(unkFunctionPointers + FirstContextMenuItemIndex);
|
||||
|
||||
// The real action goes here
|
||||
var redButtonActions = &((AgentContext*)m_AgentContextInterface)->Items->RedButtonActions;
|
||||
*(redButtonActions + contextMenuItemIndex) = action;
|
||||
}
|
||||
else
|
||||
{
|
||||
var actions = &((AgentContext*)m_Agent)->ItemData->Actions;
|
||||
var actions = &((AgentContext*)m_AgentContextInterface)->Items->Actions;
|
||||
*(actions + FirstContextMenuItemIndex + contextMenuItemIndex) = action;
|
||||
}
|
||||
|
||||
@@ -433,7 +491,7 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
|
||||
public static void Log(int atkValueCount, AtkValue* atkValues)
|
||||
{
|
||||
PluginLog.Debug($"ContextMenuReader.Print");
|
||||
PluginLog.Debug($"ContextMenuReader.Log");
|
||||
|
||||
for (int atkValueIndex = 0; atkValueIndex < atkValueCount; ++atkValueIndex)
|
||||
{
|
||||
|
||||
@@ -4,8 +4,9 @@ using System.Runtime.InteropServices;
|
||||
namespace FFXIVClientStructs.FFXIV.Client.UI
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct AddonContext
|
||||
public struct AddonContextMenu
|
||||
{
|
||||
[FieldOffset(0x0)] public unsafe AddonInterface AddonInterface;
|
||||
[FieldOffset(0x160)] public unsafe AtkValue* AtkValues;
|
||||
[FieldOffset(0x1CA)] public ushort AtkValuesCount;
|
||||
[FieldOffset(0x690)] public /*long*/ bool IsInitialMenu;
|
||||
@@ -3,8 +3,9 @@
|
||||
namespace FFXIVClientStructs.FFXIV.Client.UI
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Addon
|
||||
public unsafe struct AddonInterface
|
||||
{
|
||||
[FieldOffset(0x8)] public byte Name;
|
||||
[FieldOffset(0x1D2)] public ushort ParentAddonId;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FFXIVClientStructs.FFXIV.Client.UI.Agent
|
||||
@@ -11,14 +10,11 @@ namespace FFXIVClientStructs.FFXIV.Client.UI.Agent
|
||||
public static AgentContext* Instance() => (AgentContext*)System.Framework.Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Context);
|
||||
|
||||
[FieldOffset(0x0)] public AgentInterface AgentInterface;
|
||||
[FieldOffset(0x670)] public unsafe byte SelectedIndex;
|
||||
[FieldOffset(0x690)] public byte* Unk1;
|
||||
[FieldOffset(0xD08)] public byte* SubContextMenuTitle;
|
||||
[FieldOffset(0xD18)] public unsafe AgentContextItemData* ItemData;
|
||||
[FieldOffset(0xE08)] public Utf8String ObjectName;
|
||||
[FieldOffset(0x0)] public AgentContextInterface AgentContextInterface;
|
||||
[FieldOffset(0xD18)] public unsafe AgentContextMenuItems* Items;
|
||||
[FieldOffset(0xE08)] public Utf8String GameObjectName;
|
||||
[FieldOffset(0xEE0)] public ulong GameObjectContentId;
|
||||
[FieldOffset(0xEF0)] public uint GameObjectId;
|
||||
[FieldOffset(0xF00)] public ushort GameObjectWorldId;
|
||||
[FieldOffset(0x1740)] public bool IsSubContextMenu;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FFXIVClientStructs.FFXIV.Client.UI.Agent
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct AgentContextInterface
|
||||
{
|
||||
[FieldOffset(0x0)] public AgentInterface AgentInterface;
|
||||
[FieldOffset(0x670)] public unsafe byte SelectedIndex;
|
||||
[FieldOffset(0x690)] public byte* Unk1;
|
||||
[FieldOffset(0xD08)] public byte* SubContextMenuTitle;
|
||||
[FieldOffset(0x1740)] public bool IsSubContextMenu;
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,12 @@ using System.Runtime.InteropServices;
|
||||
namespace FFXIVClientStructs.FFXIV.Client.UI.Agent
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct AgentContextItemData
|
||||
public struct AgentContextMenuItems
|
||||
{
|
||||
[FieldOffset(0x0)] public ushort AtkValuesCount;
|
||||
[FieldOffset(0x0)] public ushort AtkValueCount;
|
||||
[FieldOffset(0x8)] public AtkValue AtkValues;
|
||||
[FieldOffset(0x428)] public byte Actions;
|
||||
[FieldOffset(0x450)] public ulong UnkFunctionPointers;
|
||||
[FieldOffset(0x598)] public ulong RedButtonActions;
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,16 @@ namespace FFXIVClientStructs.FFXIV.Client.UI.Agent
|
||||
public static AgentInventoryContext* Instance() => (AgentInventoryContext*)System.Framework.Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.InventoryContext);
|
||||
|
||||
[FieldOffset(0x0)] public AgentInterface AgentInterface;
|
||||
[FieldOffset(0x0)] public AgentContextInterface AgentContextInterface;
|
||||
[FieldOffset(0x2C)] public uint FirstContextMenuItemAtkValueIndex;
|
||||
[FieldOffset(0x30)] public uint ContextMenuItemCount;
|
||||
[FieldOffset(0x38)] public AtkValue AtkValues;
|
||||
[FieldOffset(0x558)] public unsafe byte Actions;
|
||||
[FieldOffset(0x5F8)] public uint ItemId;
|
||||
[FieldOffset(0x5FC)] public uint ItemCount;
|
||||
[FieldOffset(0x604)] public bool IsHighQuality;
|
||||
[FieldOffset(0x670)] public unsafe byte SelectedIndex;
|
||||
[FieldOffset(0x690)] public byte* Unk1;
|
||||
[FieldOffset(0xD08)] public byte* SubContextMenuTitle;
|
||||
[FieldOffset(0x1740)] public bool IsSubContextMenu;
|
||||
[FieldOffset(0x5A8)] public uint UnkFlags;
|
||||
[FieldOffset(0x5B0)] public uint PositionX;
|
||||
[FieldOffset(0x5B4)] public uint PositionY;
|
||||
[FieldOffset(0x5F8)] public uint InventoryItemId;
|
||||
[FieldOffset(0x5FC)] public uint InventoryItemCount;
|
||||
[FieldOffset(0x604)] public bool InventoryItemIsHighQuality;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
namespace PlayerTags.GameInterface.ContextMenus
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides item context to a context menu.
|
||||
/// Provides inventory item context to a context menu.
|
||||
/// </summary>
|
||||
public class ItemContext
|
||||
public class InventoryItemContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The id of the item.
|
||||
@@ -21,12 +21,12 @@
|
||||
public bool IsHighQuality { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemContext"/> class.
|
||||
/// Initializes a new instance of the <see cref="InventoryItemContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the item.</param>
|
||||
/// <param name="count">The count of the item in the stack.</param>
|
||||
/// <param name="isHighQuality">Whether the item is high quality.</param>
|
||||
public ItemContext(uint id, uint count, bool isHighQuality)
|
||||
public InventoryItemContext(uint id, uint count, bool isHighQuality)
|
||||
{
|
||||
Id = id;
|
||||
Count = count;
|
||||
@@ -10,6 +10,11 @@ namespace PlayerTags.GameInterface
|
||||
{
|
||||
public static SeString ReadSeString(IntPtr ptr)
|
||||
{
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
return new SeString();
|
||||
}
|
||||
|
||||
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
|
||||
{
|
||||
return SeString.Parse(bytes);
|
||||
@@ -21,6 +26,10 @@ namespace PlayerTags.GameInterface
|
||||
public static bool TryReadSeString(IntPtr ptr, out SeString? seString)
|
||||
{
|
||||
seString = null;
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
|
||||
{
|
||||
@@ -33,6 +42,11 @@ namespace PlayerTags.GameInterface
|
||||
|
||||
public static string? ReadString(IntPtr ptr)
|
||||
{
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
@@ -44,6 +58,10 @@ namespace PlayerTags.GameInterface
|
||||
public static bool TryReadString(IntPtr ptr, out string? str)
|
||||
{
|
||||
str = null;
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user