/// <summary> /// Initializes a new instance of the <see cref="ContextMenuReaderWriter"/> class. /// </summary> /// <param name="agentContextInterface">The AgentContextInterface to act upon.</param> /// <param name="atkValueCount">The number of ATK values to consider.</param> /// <param name="atkValues">Pointer to the array of ATK values.</param> public ContextMenuReaderWriter(AgentContextInterface *agentContextInterface, int atkValueCount, AtkValue *atkValues) { PluginLog.Warning($"{(IntPtr)atkValues:X}"); this.agentContextInterface = agentContextInterface; this.atkValueCount = atkValueCount; this.atkValues = atkValues; }
private byte ExamineRefresh(AtkUnitBase *atkUnitBase, int a2, AtkValue *loadingStage) { var retVal = onExamineRefresh.Original(atkUnitBase, a2, loadingStage); if (loadingStage != null && a2 > 0) { if (loadingStage->UInt == 4) { ShowItemLevel(); } } return(retVal); }
private void AgentHudOpenSystemMenuDetour(void *thisPtr, AtkValue *atkValueArgs, uint menuSize) { if (WindowSystem.HasAnyWindowSystemFocus && Service <DalamudConfiguration> .Get().IsFocusManagementEnabled) { Log.Verbose($"Cancelling OpenSystemMenu due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); return; } var configuration = Service <DalamudConfiguration> .Get(); if (!configuration.DoButtonsSystemMenu) { this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); return; } // the max size (hardcoded) is 0xE/15, but the system menu currently uses 0xC/12 // this is a just in case that doesnt really matter // see if we can add 2 entries if (menuSize >= 0xD) { this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); return; } // atkValueArgs is actually an array of AtkValues used as args. all their UI code works like this. // in this case, menu size is stored in atkValueArgs[4], and the next 15 slots are the MainCommand // the 15 slots after that, if they exist, are the entry names, but they are otherwise pulled from MainCommand EXD // reference the original function for more details :) // step 1) move all the current menu items down so we can put Dalamud at the top like it deserves this.atkValueChangeType(&atkValueArgs[menuSize + 5], ValueType.Int); // currently this value has no type, set it to int this.atkValueChangeType(&atkValueArgs[menuSize + 5 + 1], ValueType.Int); for (var i = menuSize + 2; i > 1; i--) { var curEntry = &atkValueArgs[i + 5 - 2]; var nextEntry = &atkValueArgs[i + 5]; nextEntry->Int = curEntry->Int; } // step 2) set our new entries to dummy commands var firstEntry = &atkValueArgs[5]; firstEntry->Int = 69420; var secondEntry = &atkValueArgs[6]; secondEntry->Int = 69421; // step 3) create strings for them // since the game first checks for strings in the AtkValue argument before pulling them from the exd, if we create strings we dont have to worry // about hooking the exd reader, thank god var firstStringEntry = &atkValueArgs[5 + 15]; this.atkValueChangeType(firstStringEntry, ValueType.String); var secondStringEntry = &atkValueArgs[6 + 15]; this.atkValueChangeType(secondStringEntry, ValueType.String); var strPlugins = Encoding.UTF8.GetBytes(Loc.Localize("SystemMenuPlugins", "Dalamud Plugins")); var strSettings = Encoding.UTF8.GetBytes(Loc.Localize("SystemMenuSettings", "Dalamud Settings")); // do this the most terrible way possible since im lazy var bytes = stackalloc byte[strPlugins.Length + 1]; Marshal.Copy(strPlugins, 0, new IntPtr(bytes), strPlugins.Length); bytes[strPlugins.Length] = 0x0; this.atkValueSetString(firstStringEntry, bytes); // this allocs the string properly using the game's allocators and copies it, so we dont have to worry about memory fuckups var bytes2 = stackalloc byte[strSettings.Length + 1]; Marshal.Copy(strSettings, 0, new IntPtr(bytes2), strSettings.Length); bytes2[strSettings.Length] = 0x0; this.atkValueSetString(secondStringEntry, bytes2); // open menu with new size var sizeEntry = &atkValueArgs[4]; sizeEntry->UInt = menuSize + 2; this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize + 2); }
private unsafe void SubContextMenuOpenedImplementation(AddonContextMenu *addonContextMenu, ref int atkValueCount, ref AtkValue *atkValues) { this.ContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues); }
private unsafe bool SubContextMenuOpenedDetour(AddonContextMenu *addonContextMenu, int atkValueCount, AtkValue *atkValues) { try { this.SubContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues); } catch (Exception ex) { PluginLog.Error(ex, "SubContextMenuOpenedDetour"); } return(this.subContextMenuOpenedHook.Original(addonContextMenu, atkValueCount, atkValues)); }
private unsafe void ContextMenuOpenedImplementation(AddonContextMenu *addonContextMenu, ref int atkValueCount, ref AtkValue *atkValues) { if (this.ContextMenuOpened == null || this.currentAgentContextInterface == null) { return; } var contextMenuReaderWriter = new ContextMenuReaderWriter(this.currentAgentContextInterface, atkValueCount, atkValues); // Check for a title. string?title = null; if (this.selectedOpenSubContextMenuItem != null) { title = this.selectedOpenSubContextMenuItem.Name.TextValue; // Write the custom title var titleAtkValue = &atkValues[1]; fixed(byte *titlePtr = this.selectedOpenSubContextMenuItem.Name.Encode().NullTerminate()) { titleAtkValue->SetString(titlePtr); } } else if (contextMenuReaderWriter.Title != null) { title = contextMenuReaderWriter.Title.TextValue; } // Determine which event to raise. var contextMenuOpenedDelegate = this.ContextMenuOpened; // this.selectedOpenSubContextMenuItem is OpenSubContextMenuItem openSubContextMenuItem if (this.selectedOpenSubContextMenuItem != null) { contextMenuOpenedDelegate = this.selectedOpenSubContextMenuItem.Opened; } // Get the existing items from the game. // TODO: 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(this.currentAgentContextInterface) && this.selectedOpenSubContextMenuItem != null) { contextMenuItems = contextMenuItems.TakeLast(1).ToArray(); } var beforeHashCode = GetContextMenuItemsHashCode(contextMenuItems); // Raise the event and get the context menu changes. this.currentContextMenuOpenedArgs = this.NotifyContextMenuOpened(addonContextMenu, this.currentAgentContextInterface, title, contextMenuOpenedDelegate, contextMenuItems); if (this.currentContextMenuOpenedArgs == null) { return; } var afterHashCode = GetContextMenuItemsHashCode(this.currentContextMenuOpenedArgs.Items); PluginLog.Warning($"{beforeHashCode}={afterHashCode}"); // Only write to memory if the items were actually changed. if (beforeHashCode != afterHashCode) { // Write the new changes. contextMenuReaderWriter.Write(this.currentContextMenuOpenedArgs.Items); // Update the addon. atkValueCount = *(&addonContextMenu->AtkValuesCount) = (ushort)contextMenuReaderWriter.AtkValueCount; atkValues = *(&addonContextMenu->AtkValues) = contextMenuReaderWriter.AtkValues; } }
/// <summary> /// Write items to the context menu. /// </summary> /// <param name="contextMenuItems">The items to write.</param> /// <param name="allowReallocate">Whether or not reallocation is allowed.</param> public void Write(IEnumerable <ContextMenuItem> contextMenuItems, bool allowReallocate = true) { if (allowReallocate) { var newAtkValuesCount = this.FirstContextMenuItemIndex + (contextMenuItems.Count() * this.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 = MemoryHelper.GameAllocateUi((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(this.atkValues, newAtkValues, newAtkValuesArraySize - arrayCountSize, (long)sizeof(AtkValue) * this.FirstContextMenuItemIndex); // Free the old array var oldArray = (IntPtr)this.atkValues - arrayCountSize; var oldArrayCount = *(ulong *)oldArray; var oldArraySize = arrayCountSize + ((ulong)sizeof(AtkValue) * oldArrayCount); MemoryHelper.GameFree(ref oldArray, oldArraySize); // Set the array count *(ulong *)newAtkValuesArray = (ulong)newAtkValuesCount; this.atkValueCount = newAtkValuesCount; this.atkValues = newAtkValues; } // Set the context menu item count const int contextMenuItemCountAtkValueIndex = 0; var contextMenuItemCountAtkValue = &this.atkValues[contextMenuItemCountAtkValueIndex]; contextMenuItemCountAtkValue->UInt = (uint)contextMenuItems.Count(); // Clear the previous arrow flags var hasPreviousIndicatorAtkValue = &this.atkValues[this.HasPreviousIndicatorFlagsIndex]; hasPreviousIndicatorAtkValue->UInt = 0; // Clear the next arrow flags var hasNextIndiactorFlagsAtkValue = &this.atkValues[this.HasNextIndicatorFlagsIndex]; hasNextIndiactorFlagsAtkValue->UInt = 0; for (var contextMenuItemIndex = 0; contextMenuItemIndex < contextMenuItems.Count(); ++contextMenuItemIndex) { var contextMenuItem = contextMenuItems.ElementAt(contextMenuItemIndex); var contextMenuItemAtkValueBaseIndex = this.FirstContextMenuItemIndex + (contextMenuItemIndex * this.SequentialAtkValuesPerContextMenuItem); // Set the name var nameAtkValue = &this.atkValues[contextMenuItemAtkValueBaseIndex + this.NameIndexOffset]; nameAtkValue->ChangeType(ValueType.String); fixed(byte *nameBytesPtr = contextMenuItem.Name.Encode().NullTerminate()) { nameAtkValue->SetString(nameBytesPtr); } // Set the enabled state. Note that SE stores this as IsDisabled, NOT IsEnabled (those heathens) var disabledAtkValue = &this.atkValues[contextMenuItemAtkValueBaseIndex + this.IsDisabledIndexOffset]; disabledAtkValue->ChangeType(ValueType.Int); disabledAtkValue->Int = contextMenuItem.IsEnabled ? 0 : 1; // Set the action byte action = 0; if (contextMenuItem is GameContextMenuItem gameContextMenuItem) { action = gameContextMenuItem.SelectedAction; } else if (contextMenuItem is CustomContextMenuItem customContextMenuItem) { action = this.NoopAction; } else if (contextMenuItem is OpenSubContextMenuItem openSubContextMenuItem) { action = this.OpenSubContextMenuAction; } if (this.IsInventoryContext) { var actions = &((AgentInventoryContext *)this.agentContextInterface)->Actions; *(actions + this.FirstContextMenuItemIndex + contextMenuItemIndex) = action; } else if (this.StructLayout is SubContextMenuStructLayout.Alternate && this.FirstUnhandledAction != null) { // Some weird placeholder goes here var actions = &((AgentContext *)this.agentContextInterface)->Items->Actions; *(actions + this.FirstContextMenuItemIndex + contextMenuItemIndex) = (byte)(this.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 *)this.agentContextInterface)->Items->UnkFunctionPointers; *(unkFunctionPointers + this.FirstContextMenuItemIndex + contextMenuItemIndex) = *(unkFunctionPointers + this.FirstContextMenuItemIndex); // The real action goes here var redButtonActions = &((AgentContext *)this.agentContextInterface)->Items->RedButtonActions; *(redButtonActions + contextMenuItemIndex) = action; } else { var actions = &((AgentContext *)this.agentContextInterface)->Items->Actions; *(actions + this.FirstContextMenuItemIndex + contextMenuItemIndex) = action; } if (contextMenuItem.Indicator == ContextMenuItemIndicator.Previous) { this.SetFlag(ref hasPreviousIndicatorAtkValue->UInt, contextMenuItemIndex, true); } else if (contextMenuItem.Indicator == ContextMenuItemIndicator.Next) { this.SetFlag(ref hasNextIndiactorFlagsAtkValue->UInt, contextMenuItemIndex, true); } } }
[VirtualFunction(0)] public partial void *ReceiveEvent(void *eventData, AtkValue *values, uint valueCount, ulong eventKind);