diff --git a/PlayerTags/Features/CustomTagsContextMenuFeature.cs b/PlayerTags/Features/CustomTagsContextMenuFeature.cs index 7c9a4d1..a63f13f 100644 --- a/PlayerTags/Features/CustomTagsContextMenuFeature.cs +++ b/PlayerTags/Features/CustomTagsContextMenuFeature.cs @@ -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 newContextMenuItems = new List(); 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 newContextMenuItems = new List(); 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); }))); } } diff --git a/PlayerTags/GameInterface/ContextMenus/ContextMenu.cs b/PlayerTags/GameInterface/ContextMenus/ContextMenu.cs index df599ed..1ebfa48 100644 --- a/PlayerTags/GameInterface/ContextMenus/ContextMenu.cs +++ b/PlayerTags/GameInterface/ContextMenus/ContextMenu.cs @@ -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? 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? m_ContextMenuOpenedHook; private Hook? 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? m_ContextMenuItemSelectedHook; - private delegate bool SubContextMenuOpeningDelegate_Unmanaged(IntPtr agent); + private unsafe delegate bool SubContextMenuOpeningDelegate_Unmanaged(AgentContext* agentContext); private Hook? 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? 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; /// @@ -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(m_PluginAddressResolver.ContextMenuOpeningPtr.Value, ContextMenuOpeningDetour); + unsafe + { + m_ContextMenuOpeningHook = new Hook(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(m_PluginAddressResolver.ContextMenuItemSelectedPtr.Value, ContextMenuItemSelectedDetour); + unsafe + { + m_ContextMenuItemSelectedHook = new Hook(m_PluginAddressResolver.ContextMenuItemSelectedPtr.Value, ContextMenuItemSelectedDetour); + } m_ContextMenuItemSelectedHook?.Enable(); } if (m_PluginAddressResolver.SubContextMenuOpeningPtr.HasValue) { - m_SubContextMenuOpeningHook = new Hook(m_PluginAddressResolver.SubContextMenuOpeningPtr.Value, SubContextMenuOpeningDetour); + unsafe + { + m_SubContextMenuOpeningHook = new Hook(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(m_PluginAddressResolver.OpenInventoryContextMenuPtr.Value); - } - - if (m_PluginAddressResolver.InventoryContextMenuEvent30Ptr.HasValue) - { - m_InventoryContextMenuEvent30Hook = new Hook(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 initialContextMenuItems) + private unsafe ContextMenuOpenedArgs? NotifyContextMenuOpened(AddonContextMenu* addonContextMenu, AgentContextInterface* agentContextInterface, string? title, ContextMenuOpenedDelegate contextMenuOpenedDelegate, IEnumerable 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).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); } } } diff --git a/PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedArgs.cs b/PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedArgs.cs index ed6dee6..51abe36 100644 --- a/PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedArgs.cs +++ b/PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedArgs.cs @@ -32,7 +32,7 @@ namespace PlayerTags.GameInterface.ContextMenus /// /// The items in the context menu. /// - public List ContextMenuItems { get; } + public List Items { get; } /// /// The game object context associated with the context menu. @@ -42,7 +42,7 @@ namespace PlayerTags.GameInterface.ContextMenus /// /// The item context associated with the context menu. /// - public ItemContext? ItemContext { get; init; } + public InventoryItemContext? InventoryItemContext { get; init; } /// /// Initializes a new instance of the class. @@ -50,13 +50,13 @@ namespace PlayerTags.GameInterface.ContextMenus /// The addon associated with the context menu. /// The agent associated with the context menu. /// The the name of the parent addon associated with the context menu. - /// The items in the context menu. - public ContextMenuOpenedArgs(IntPtr addon, IntPtr agent, string? parentAddonName, IEnumerable contextMenuItems) + /// The items in the context menu. + public ContextMenuOpenedArgs(IntPtr addon, IntPtr agent, string? parentAddonName, IEnumerable items) { Addon = addon; Agent = agent; ParentAddonName = parentAddonName; - ContextMenuItems = new List(contextMenuItems); + Items = new List(items); } } } diff --git a/PlayerTags/GameInterface/ContextMenus/ContextMenuReaderWriter.cs b/PlayerTags/GameInterface/ContextMenus/ContextMenuReaderWriter.cs index 90f7fc9..cace936 100644 --- a/PlayerTags/GameInterface/ContextMenus/ContextMenuReaderWriter.cs +++ b/PlayerTags/GameInterface/ContextMenus/ContextMenuReaderWriter.cs @@ -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 } } + /// + /// 0x14000000 | action + /// + 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() * 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() * 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) { diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonContext.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonContextMenu.cs similarity index 77% rename from PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonContext.cs rename to PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonContextMenu.cs index c879986..182afc5 100644 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonContext.cs +++ b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonContextMenu.cs @@ -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; diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Addon.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonInterface.cs similarity index 70% rename from PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Addon.cs rename to PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonInterface.cs index 9fa3ceb..44e5e1e 100644 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Addon.cs +++ b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonInterface.cs @@ -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; } } diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContext.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContext.cs index 613132b..a34c8ca 100644 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContext.cs +++ b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContext.cs @@ -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; } } diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextInterface.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextInterface.cs new file mode 100644 index 0000000..29895a6 --- /dev/null +++ b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextInterface.cs @@ -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; + } +} diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextItemData.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextMenuItems.cs similarity index 69% rename from PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextItemData.cs rename to PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextMenuItems.cs index 501f62f..6b2e10b 100644 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextItemData.cs +++ b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextMenuItems.cs @@ -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; } } diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentInventoryContext.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentInventoryContext.cs index e928124..a1134cf 100644 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentInventoryContext.cs +++ b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentInventoryContext.cs @@ -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; } } diff --git a/PlayerTags/GameInterface/ContextMenus/ItemContext.cs b/PlayerTags/GameInterface/ContextMenus/InventoryItemContext.cs similarity index 76% rename from PlayerTags/GameInterface/ContextMenus/ItemContext.cs rename to PlayerTags/GameInterface/ContextMenus/InventoryItemContext.cs index df197ab..f9d6ce2 100644 --- a/PlayerTags/GameInterface/ContextMenus/ItemContext.cs +++ b/PlayerTags/GameInterface/ContextMenus/InventoryItemContext.cs @@ -1,9 +1,9 @@ namespace PlayerTags.GameInterface.ContextMenus { /// - /// Provides item context to a context menu. + /// Provides inventory item context to a context menu. /// - public class ItemContext + public class InventoryItemContext { /// /// The id of the item. @@ -21,12 +21,12 @@ public bool IsHighQuality { get; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The id of the item. /// The count of the item in the stack. /// Whether the item is high quality. - public ItemContext(uint id, uint count, bool isHighQuality) + public InventoryItemContext(uint id, uint count, bool isHighQuality) { Id = id; Count = count; diff --git a/PlayerTags/GameInterface/GameInterfaceHelper.cs b/PlayerTags/GameInterface/GameInterfaceHelper.cs index 607f589..fe4d52d 100644 --- a/PlayerTags/GameInterface/GameInterfaceHelper.cs +++ b/PlayerTags/GameInterface/GameInterfaceHelper.cs @@ -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) {