diff --git a/AetherBags/Extensions/InventoryItemExtensions.cs b/AetherBags/Extensions/InventoryItemExtensions.cs index 634fc69..7189894 100644 --- a/AetherBags/Extensions/InventoryItemExtensions.cs +++ b/AetherBags/Extensions/InventoryItemExtensions.cs @@ -2,6 +2,7 @@ using System.Text.RegularExpressions; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Lumina.Excel.Sheets; using Lumina.Text.ReadOnly; @@ -62,6 +63,13 @@ public static unsafe class InventoryItemExtensions { return null; } + public ItemOrderModuleSorterItemEntry* GetItemOrderData() + { + InventoryType type = item.GetInventoryType(); + int slot = item.GetSlot(); + return type.GetInventorySorter->Items[slot + type.GetInventoryStartIndex]; + } + public bool IsRegexMatch(string searchString) { // Skip any data access if string is empty if (searchString.IsNullOrEmpty()) return true; diff --git a/AetherBags/Extensions/InventoryTypeExtensions.cs b/AetherBags/Extensions/InventoryTypeExtensions.cs new file mode 100644 index 0000000..a703d96 --- /dev/null +++ b/AetherBags/Extensions/InventoryTypeExtensions.cs @@ -0,0 +1,98 @@ +using System; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; + +namespace AetherBags.Extensions; + +public static unsafe class InventoryTypeExtensions +{ + extension(InventoryType inventoryType) + { + public uint AgentItemContainerId => + inventoryType switch + { + InventoryType.EquippedItems => 4, + InventoryType.KeyItems => 7, + InventoryType.Inventory1 => 48, + InventoryType.Inventory2 => 49, + InventoryType.Inventory3 => 50, + InventoryType.Inventory4 => 51, + InventoryType.ArmoryMainHand => 57, + InventoryType.ArmoryHead => 58, + InventoryType.ArmoryBody => 59, + InventoryType.ArmoryHands => 60, + InventoryType.ArmoryLegs => 61, + InventoryType.ArmoryFeets => 62, + InventoryType.ArmoryOffHand => 63, + InventoryType.ArmoryEar => 64, + InventoryType.ArmoryNeck => 65, + InventoryType.ArmoryWrist => 66, + InventoryType.ArmoryRings => 67, + InventoryType.ArmorySoulCrystal => 68, + InventoryType.SaddleBag1 => 69, + InventoryType.SaddleBag2 => 70, + InventoryType.PremiumSaddleBag1 => 71, + InventoryType.PremiumSaddleBag2 => 72, + _ => 0 + }; + + public static InventoryType GetInventoryTypeFromContainerId(int id) => + id switch + { + 4 => InventoryType.EquippedItems, + 7 => InventoryType.KeyItems, + 48 => InventoryType.Inventory1, + 49 => InventoryType.Inventory2, + 50 => InventoryType.Inventory3, + 51 => InventoryType.Inventory4, + 57 => InventoryType.ArmoryMainHand, + 58 => InventoryType.ArmoryHead, + 59 => InventoryType.ArmoryBody, + 60 => InventoryType.ArmoryHands, + 61 => InventoryType.ArmoryLegs, + 62 => InventoryType.ArmoryFeets, + 63 => InventoryType.ArmoryOffHand, + 64 => InventoryType.ArmoryEar, + 65 => InventoryType.ArmoryNeck, + 66 => InventoryType.ArmoryWrist, + 67 => InventoryType.ArmoryRings, + 68 => InventoryType.ArmorySoulCrystal, + 69 => InventoryType.SaddleBag1, + 70 => InventoryType.SaddleBag2, + 71 => InventoryType.PremiumSaddleBag1, + 72 => InventoryType.PremiumSaddleBag2, + _ => (InventoryType)0 + }; + + public ItemOrderModuleSorter* GetInventorySorter => inventoryType switch { + InventoryType.Inventory1 => ItemOrderModule.Instance()->InventorySorter, + InventoryType.Inventory2 => ItemOrderModule.Instance()->InventorySorter, + InventoryType.Inventory3 => ItemOrderModule.Instance()->InventorySorter, + InventoryType.Inventory4 => ItemOrderModule.Instance()->InventorySorter, + InventoryType.ArmoryMainHand => ItemOrderModule.Instance()->ArmouryMainHandSorter, + InventoryType.ArmoryOffHand => ItemOrderModule.Instance()->ArmouryOffHandSorter, + InventoryType.ArmoryHead => ItemOrderModule.Instance()->ArmouryHeadSorter, + InventoryType.ArmoryBody => ItemOrderModule.Instance()->ArmouryBodySorter, + InventoryType.ArmoryHands => ItemOrderModule.Instance()->ArmouryHandsSorter, + InventoryType.ArmoryLegs => ItemOrderModule.Instance()->ArmouryLegsSorter, + InventoryType.ArmoryFeets => ItemOrderModule.Instance()->ArmouryFeetSorter, + InventoryType.ArmoryEar => ItemOrderModule.Instance()->ArmouryEarsSorter, + InventoryType.ArmoryNeck => ItemOrderModule.Instance()->ArmouryNeckSorter, + InventoryType.ArmoryWrist => ItemOrderModule.Instance()->ArmouryWristsSorter, + InventoryType.ArmoryRings => ItemOrderModule.Instance()->ArmouryRingsSorter, + InventoryType.ArmorySoulCrystal => ItemOrderModule.Instance()->ArmourySoulCrystalSorter, + InventoryType.SaddleBag1 => ItemOrderModule.Instance()->SaddleBagSorter, + InventoryType.SaddleBag2 => ItemOrderModule.Instance()->SaddleBagSorter, + InventoryType.PremiumSaddleBag1 => ItemOrderModule.Instance()->PremiumSaddleBagSorter, + InventoryType.PremiumSaddleBag2 => ItemOrderModule.Instance()->PremiumSaddleBagSorter, + _ => throw new Exception($"Type Not Implemented: {inventoryType}"), + }; + + public int GetInventoryStartIndex => inventoryType switch { + InventoryType.Inventory2 => inventoryType.GetInventorySorter->ItemsPerPage, + InventoryType.Inventory3 => inventoryType.GetInventorySorter->ItemsPerPage * 2, + InventoryType.Inventory4 => inventoryType.GetInventorySorter->ItemsPerPage * 3, + _ => 0, + }; + } +} \ No newline at end of file diff --git a/AetherBags/Extensions/ItemOrderModuleSorterExtensions.cs b/AetherBags/Extensions/ItemOrderModuleSorterExtensions.cs new file mode 100644 index 0000000..11fbe06 --- /dev/null +++ b/AetherBags/Extensions/ItemOrderModuleSorterExtensions.cs @@ -0,0 +1,26 @@ +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; + +namespace AetherBags.Extensions; + +public static unsafe class ItemOrderModuleSorterExtensions { + extension(ref ItemOrderModuleSorter sorter) { + public long GetSlotIndex(ItemOrderModuleSorterItemEntry* entry) + => entry->Slot + sorter.ItemsPerPage * entry->Page; + + public InventoryItem* GetInventoryItem(ItemOrderModuleSorterItemEntry* entry) + => sorter.GetInventoryItem(sorter.GetSlotIndex(entry)); + + public InventoryItem* GetInventoryItem(long slotIndex) { + if (sorter.Items.LongCount <= slotIndex) return null; + + var item = sorter.Items[slotIndex].Value; + if (item == null) return null; + + var container = InventoryManager.Instance()->GetInventoryContainer(sorter.InventoryType + item->Page); + if (container == null) return null; + + return container->GetInventorySlot(item->Slot); + } + } +} diff --git a/AetherBags/Inventory/InventoryState.cs b/AetherBags/Inventory/InventoryState.cs index 7271686..5de62e4 100644 --- a/AetherBags/Inventory/InventoryState.cs +++ b/AetherBags/Inventory/InventoryState.cs @@ -630,6 +630,11 @@ public static unsafe class InventoryState }; } + public static InventoryContainer* GetInventoryContainer(InventoryType inventoryType) + { + return InventoryManager.Instance()->GetInventoryContainer(inventoryType); + } + private struct AggregatedItem { public InventoryItem First; diff --git a/AetherBags/Inventory/ItemInfo.cs b/AetherBags/Inventory/ItemInfo.cs index 83fca5f..0047377 100644 --- a/AetherBags/Inventory/ItemInfo.cs +++ b/AetherBags/Inventory/ItemInfo.cs @@ -5,6 +5,7 @@ using Lumina.Excel.Sheets; using System; using System.Numerics; using System.Text.RegularExpressions; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; namespace AetherBags.Inventory; diff --git a/AetherBags/Nodes/InventoryCategoryNode.cs b/AetherBags/Nodes/InventoryCategoryNode.cs index 77af556..f1a0fdf 100644 --- a/AetherBags/Nodes/InventoryCategoryNode.cs +++ b/AetherBags/Nodes/InventoryCategoryNode.cs @@ -7,9 +7,12 @@ using KamiToolKit.Classes; using KamiToolKit.Nodes; using System; using System.Numerics; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; // TODO: Switch back to CS version when Dalamud Updated using DragDropFixedNode = AetherBags.Nodes.DragDropNode; +using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; namespace AetherBags.Nodes; @@ -285,11 +288,45 @@ public class InventoryCategoryNode : SimpleComponentNode private unsafe void OnPayloadAccepted(DragDropNode node, DragDropPayload payload, ItemInfo itemInfo) { - Services.Logger.Debug($"Inventory DragDropNode Payload Accepted: {payload.Type} Int1: {payload.Int1} Int2: {payload.Int2}"); - InventoryType inventoryType = (InventoryType)payload.Int1; + if (payload.Type != DragDropType.Item) return; + InventoryItem item = itemInfo.Item; + Services.Logger.Debug($"Inventory DragDropNode Payload Accepted: {payload.Type} Int1: {payload.Int1} Int2: {payload.Int2} ReferenceIndex: {payload.ReferenceIndex}"); + InventoryType inventoryType = InventoryType.GetInventoryTypeFromContainerId(payload.Int1); ushort sourceSlot = (ushort)payload.Int2; - System.AddonInventoryWindow.ManualInventoryRefresh(); + ItemOrderModuleSorterItemEntry* itemEntry = item.GetItemOrderData(); + Services.Logger.Debug($"{item.Slot} vs {item.GetSlot()}: entry: {itemEntry->Slot}"); + Services.Logger.Info($"[OnPayload] Moving {inventoryType}@{sourceSlot} -> {item.Container}@{item.Slot} -> {item.Name.ExtractText()}"); + InventoryManager.Instance()->MoveItemSlot(inventoryType, sourceSlot, item.Container, item.GetSlot(), true); + + + // System.AddonInventoryWindow.ManualInventoryRefresh(); + // Should work for swapping item but need a fake empty slot to put new items in probably. - InventoryManager.Instance()->MoveItemSlot(inventoryType, sourceSlot, itemInfo.Item.Container, itemInfo.Item.GetSlot()); + // Services.Logger.Debug($"Moving Item from {inventoryType} Slot {sourceSlot} to {itemInfo.Item.Container} Slot {itemInfo.Item.GetSlot()}"); + //MoveItem(inventoryType, sourceSlot, itemInfo.Item.Container, itemInfo.Item.GetSlot()); + } + + // Possibly still use this + private unsafe void MoveItem(InventoryType sourceInventory, uint sourceSlot, InventoryType destinationInventory, uint destinationSlot) + { + var sourceContainerId = sourceInventory.AgentItemContainerId; + var destinationContainerId = destinationInventory.AgentItemContainerId; + + if (sourceContainerId != 0 && destinationContainerId != 0) { + var atkValues = stackalloc AtkValue[4]; + for (var i = 0; i < 4; i++) atkValues[i].Type = ValueType.UInt; + + atkValues[0].UInt = sourceContainerId; + atkValues[1].UInt = sourceSlot; + atkValues[2].UInt = destinationContainerId; + atkValues[3].UInt = destinationSlot; + + var retVal = stackalloc AtkValue[1]; + + RaptureAtkModule* atkModule = RaptureAtkModule.Instance(); + // (RaptureAtkModule* a1, void* outValue, AtkValue* atkValues); + // (AtkValue* returnValue, AtkValue* values, uint valueCount) + atkModule->HandleItemMove(retVal, atkValues, 4); + } } } diff --git a/AetherBags/Plugin.cs b/AetherBags/Plugin.cs index 258fa71..2c83b1c 100644 --- a/AetherBags/Plugin.cs +++ b/AetherBags/Plugin.cs @@ -1,14 +1,16 @@ +using System; using System.Numerics; using AetherBags.Addons; -using AetherBags.Configuration; using AetherBags.Helpers; using Dalamud.Plugin; using Dalamud.Game.Command; +using Dalamud.Hooking; +using FFXIVClientStructs.FFXIV.Client.Game; using KamiToolKit; namespace AetherBags; -public class Plugin : IDalamudPlugin +public unsafe class Plugin : IDalamudPlugin { private static string HelpDescription => "Opens your inventory."; public Plugin(IDalamudPluginInterface pluginInterface) @@ -56,6 +58,32 @@ public class Plugin : IDalamudPlugin if (Services.ClientState.IsLoggedIn) { Services.Framework.RunOnFrameworkThread(OnLogin); } + + try + { + _moveItemSlotHook = Services.GameInteropProvider.HookFromSignature("E8 ?? ?? ?? ?? 48 8B 03 66 FF C5", MoveItemSlotDetour); + _moveItemSlotHook.Enable(); + + Services.Logger.Debug("MoveItemSlot hooked successfully."); + } + catch (Exception e) + { + Services.Logger.Error(e, "Failed to hook MoveItemSlot"); + } + } + + private unsafe delegate int MoveItemSlotDelegate(InventoryManager* inventoryManager, InventoryType srcContainer, ushort srcSlot, InventoryType dstContainer, ushort dstSlot, bool unk); + + private Hook? _moveItemSlotHook; + + private unsafe int MoveItemSlotDetour(InventoryManager* manager, InventoryType srcType, ushort srcSlot, InventoryType dstType, ushort dstSlot, bool unk) + { + InventoryItem* sourceItem = InventoryManager.Instance()->GetInventorySlot(srcType, srcSlot); + InventoryItem* destItem = InventoryManager.Instance()->GetInventorySlot(dstType, dstSlot); + Services.Logger.Info($"[MoveItemSlot] Moving {srcType}@{srcSlot} ID:{sourceItem->ItemId} -> {dstType}@{dstSlot} ID:{destItem->ItemId} Unk: {unk}"); + + // Call the original function + return _moveItemSlotHook!.Original(manager, srcType, srcSlot, dstType, dstSlot, unk); } public void Dispose() @@ -72,6 +100,8 @@ public class Plugin : IDalamudPlugin System.AddonConfigurationWindow.Dispose(); KamiToolKitLibrary.Dispose(); + + _moveItemSlotHook?.Dispose(); } private void OnCommand(string command, string args) diff --git a/AetherBags/Services.cs b/AetherBags/Services.cs index 77fe4c8..00eeddf 100644 --- a/AetherBags/Services.cs +++ b/AetherBags/Services.cs @@ -16,4 +16,7 @@ public class Services [PluginService] public static IKeyState KeyState { get; private set; } = null!; [PluginService] public static IPluginLog Logger { get; private set; } = null!; [PluginService] public static INotificationManager NotificationManager { get; private set; } = null!; + // TODO: Remove cause temp + [PluginService] public static ISigScanner SigScanner { get; private set; } = null!; + [PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; } = null!; } \ No newline at end of file