mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
235 lines
11 KiB
C#
235 lines
11 KiB
C#
using Dalamud.Hooking;
|
|
using Dalamud.Plugin.Services;
|
|
using Dalamud.Utility.Signatures;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
|
using Glamourer.Events;
|
|
using OtterGui.Services;
|
|
using Penumbra.GameData.Enums;
|
|
using Penumbra.GameData.Structs;
|
|
using Penumbra.String;
|
|
|
|
namespace Glamourer.Interop;
|
|
|
|
public sealed unsafe class InventoryService : IDisposable, IRequiredService
|
|
{
|
|
// Called by EquipGearset, but returns a pointer instead of an int.
|
|
// This is the internal function processed by all sources of Equipping a gearset,
|
|
// such as hotbar gearset application and command gearset application
|
|
public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA";
|
|
private delegate nint ChangeGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId);
|
|
|
|
[Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))]
|
|
private readonly Hook<ChangeGearsetInternalDelegate> _equipGearsetInternalHook = null!;
|
|
|
|
// The following above is currently pending for an accepted PR in FFXIVCLientStructs.
|
|
// Once accepted, remove everything above this comment and replace EquipGearset with EquipGearsetInternal.
|
|
|
|
private readonly MovedEquipment _movedItemsEvent;
|
|
private readonly EquippedGearset _gearsetEvent;
|
|
private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12);
|
|
public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent)
|
|
{
|
|
_movedItemsEvent = movedItemsEvent;
|
|
_gearsetEvent = gearsetEvent;
|
|
|
|
_moveItemHook = interop.HookFromAddress<MoveItemDelegate>((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour);
|
|
_equipGearsetHook = interop.HookFromAddress<EquipGearsetDelegate>((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour);
|
|
interop.InitializeFromAttributes(this);
|
|
|
|
_moveItemHook.Enable();
|
|
_equipGearsetHook.Enable();
|
|
_equipGearsetInternalHook.Enable();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_moveItemHook.Dispose();
|
|
_equipGearsetHook.Dispose();
|
|
_equipGearsetInternalHook.Dispose();
|
|
}
|
|
|
|
private delegate int EquipGearsetDelegate(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId);
|
|
|
|
private readonly Hook<EquipGearsetDelegate> _equipGearsetHook;
|
|
|
|
private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId)
|
|
{
|
|
var prior = module->CurrentGearsetIndex;
|
|
var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId);
|
|
var set = module->GetGearset((int)gearsetId);
|
|
_gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob);
|
|
Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})");
|
|
if (ret == 0)
|
|
{
|
|
var entry = module->GetGearset((int)gearsetId);
|
|
if (entry == null)
|
|
return ret;
|
|
|
|
if (glamourPlateId == 0)
|
|
glamourPlateId = entry->GlamourSetLink;
|
|
|
|
_itemList.Clear();
|
|
|
|
|
|
if (glamourPlateId != 0)
|
|
{
|
|
void Add(EquipSlot slot, uint glamourId, StainIds glamourStain, ref RaptureGearsetModule.GearsetItem item)
|
|
{
|
|
if (item.ItemId == 0)
|
|
_itemList.Add((slot, 0, StainIds.None));
|
|
else if (glamourId != 0)
|
|
_itemList.Add((slot, glamourId, glamourStain));
|
|
else if (item.GlamourId != 0)
|
|
_itemList.Add((slot, item.GlamourId, StainIds.FromGearsetItem(item)));
|
|
else
|
|
_itemList.Add((slot, FixId(item.ItemId), StainIds.FromGearsetItem(item)));
|
|
}
|
|
|
|
var plate = MirageManager.Instance()->GlamourPlates[glamourPlateId - 1];
|
|
Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]);
|
|
Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]);
|
|
Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]);
|
|
Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]);
|
|
Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]);
|
|
Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]);
|
|
Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]);
|
|
Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]);
|
|
Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]);
|
|
Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]);
|
|
Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]);
|
|
Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]);
|
|
}
|
|
else
|
|
{
|
|
void Add(EquipSlot slot, ref RaptureGearsetModule.GearsetItem item)
|
|
{
|
|
if (item.ItemId == 0)
|
|
_itemList.Add((slot, 0, StainIds.None));
|
|
else if (item.GlamourId != 0)
|
|
_itemList.Add((slot, item.GlamourId, StainIds.FromGearsetItem(item)));
|
|
else
|
|
_itemList.Add((slot, FixId(item.ItemId), StainIds.FromGearsetItem(item)));
|
|
}
|
|
|
|
Add(EquipSlot.MainHand, ref entry->Items[0]);
|
|
Add(EquipSlot.OffHand, ref entry->Items[1]);
|
|
Add(EquipSlot.Head, ref entry->Items[2]);
|
|
Add(EquipSlot.Body, ref entry->Items[3]);
|
|
Add(EquipSlot.Hands, ref entry->Items[5]);
|
|
Add(EquipSlot.Legs, ref entry->Items[6]);
|
|
Add(EquipSlot.Feet, ref entry->Items[7]);
|
|
Add(EquipSlot.Ears, ref entry->Items[8]);
|
|
Add(EquipSlot.Neck, ref entry->Items[9]);
|
|
Add(EquipSlot.Wrists, ref entry->Items[10]);
|
|
Add(EquipSlot.RFinger, ref entry->Items[11]);
|
|
Add(EquipSlot.LFinger, ref entry->Items[12]);
|
|
}
|
|
|
|
_movedItemsEvent.Invoke(_itemList.ToArray());
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Remove once internal is added. This no longer serves any purpose.
|
|
private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId)
|
|
{
|
|
var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId);
|
|
Glamourer.Log.Excessive($"[InventoryService] (old) Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})");
|
|
return ret;
|
|
}
|
|
|
|
private static uint FixId(uint itemId)
|
|
=> itemId % 50000;
|
|
|
|
private delegate int MoveItemDelegate(InventoryManager* manager, InventoryType sourceContainer, ushort sourceSlot,
|
|
InventoryType targetContainer, ushort targetSlot, byte unk);
|
|
|
|
private readonly Hook<MoveItemDelegate> _moveItemHook;
|
|
|
|
private int MoveItemDetour(InventoryManager* manager, InventoryType sourceContainer, ushort sourceSlot,
|
|
InventoryType targetContainer, ushort targetSlot, byte unk)
|
|
{
|
|
var ret = _moveItemHook.Original(manager, sourceContainer, sourceSlot, targetContainer, targetSlot, unk);
|
|
Glamourer.Log.Excessive($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})");
|
|
if (ret == 0)
|
|
{
|
|
if (InvokeSource(sourceContainer, sourceSlot, out var source))
|
|
if (InvokeTarget(manager, targetContainer, targetSlot, out var target))
|
|
_movedItemsEvent.Invoke(new[]
|
|
{
|
|
source,
|
|
target,
|
|
});
|
|
else
|
|
_movedItemsEvent.Invoke(new[]
|
|
{
|
|
source,
|
|
});
|
|
else if (InvokeTarget(manager, targetContainer, targetSlot, out var target))
|
|
_movedItemsEvent.Invoke(new[]
|
|
{
|
|
target,
|
|
});
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
private static bool InvokeSource(InventoryType sourceContainer, uint sourceSlot, out (EquipSlot, uint, StainIds) tuple)
|
|
{
|
|
tuple = default;
|
|
if (sourceContainer is not InventoryType.EquippedItems)
|
|
return false;
|
|
|
|
var slot = GetSlot(sourceSlot);
|
|
if (slot is EquipSlot.Unknown)
|
|
return false;
|
|
|
|
tuple = (slot, 0u, StainIds.None);
|
|
return true;
|
|
}
|
|
|
|
private static bool InvokeTarget(InventoryManager* manager, InventoryType targetContainer, uint targetSlot,
|
|
out (EquipSlot, uint, StainIds) tuple)
|
|
{
|
|
tuple = default;
|
|
if (targetContainer is not InventoryType.EquippedItems)
|
|
return false;
|
|
|
|
var slot = GetSlot(targetSlot);
|
|
if (slot is EquipSlot.Unknown)
|
|
return false;
|
|
|
|
// Invoked after calling Original, so the item is already moved.
|
|
var inventory = manager->GetInventoryContainer(targetContainer);
|
|
if (inventory == null || inventory->Loaded == 0 || inventory->Size <= targetSlot)
|
|
return false;
|
|
|
|
var item = inventory->GetInventorySlot((int)targetSlot);
|
|
if (item == null)
|
|
return false;
|
|
|
|
tuple = (slot, item->GlamourId != 0 ? item->GlamourId : item->ItemId, new StainIds(item->Stains));
|
|
return true;
|
|
}
|
|
|
|
private static EquipSlot GetSlot(uint slot)
|
|
=> slot switch
|
|
{
|
|
0 => EquipSlot.MainHand,
|
|
1 => EquipSlot.OffHand,
|
|
2 => EquipSlot.Head,
|
|
3 => EquipSlot.Body,
|
|
4 => EquipSlot.Hands,
|
|
6 => EquipSlot.Legs,
|
|
7 => EquipSlot.Feet,
|
|
8 => EquipSlot.Ears,
|
|
9 => EquipSlot.Neck,
|
|
10 => EquipSlot.Wrists,
|
|
11 => EquipSlot.RFinger,
|
|
12 => EquipSlot.LFinger,
|
|
_ => EquipSlot.Unknown,
|
|
};
|
|
}
|