Ejemplo n.º 1
0
        /// <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);
    }
Ejemplo n.º 3
0
    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);
    }
Ejemplo n.º 4
0
 private unsafe void SubContextMenuOpenedImplementation(AddonContextMenu *addonContextMenu, ref int atkValueCount, ref AtkValue *atkValues)
 {
     this.ContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues);
 }
Ejemplo n.º 5
0
        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));
        }
Ejemplo n.º 6
0
        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;
            }
        }
Ejemplo n.º 7
0
        /// <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);
                }
            }
        }
Ejemplo n.º 8
0
 [VirtualFunction(0)] public partial void *ReceiveEvent(void *eventData, AtkValue *values, uint valueCount, ulong eventKind);