Skip to content

Commit

Permalink
[WIP] Increase hotbar watcher safety
Browse files Browse the repository at this point in the history
- Don't write the Icon ID back into game memory anymore
- Add method to calculate what the slot's icon should be
- Swap HTTP and watcher hook over to the new methods
  • Loading branch information
KazWolfe committed Jul 9, 2022
1 parent 03216b0 commit cd41714
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 17 deletions.
26 changes: 10 additions & 16 deletions FFXIVPlugin/Game/HotbarWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,23 @@ private unsafe void OnGameUpdate(Framework framework) {

for (var slotId = 0; slotId < 16; slotId++) {
var gameSlot = hotbar->Slot[slotId];

// while we could put this refresh into the getIcon call instead, that will cause a few headaches:
// 1. Any hotbarIcon call that grabs an icon will *also* trigger a refresh call on the next framework tick.
// 2. A hidden slot icon updating won't actually trigger an update until the icon refreshes otherwise.
//
// This will tamper with game memory in a less-than-desirable way, but this should still be relatively safe
// as this method (loading from Slot B) is done by the game itself too. This *may* cause a problem with
// other plugins though, maybe?
if (gameSlot->Icon == 0 && gameSlot->CommandType != HotbarSlotType.Empty) {
gameSlot->LoadIconFromSlotB();
}

var cachedSlot = this._hotbarCache[hotbarId, slotId];

// ToDo: Maybe performance optimizations here by caching SlotB information instead?
// Would also involve making HotbarController read icon IDs from this cache, which might be
// a good idea anyways. To my knowledge, IconB will *always* change with Icon, so this might be
// the way to go.
var calculatedIcon = XIVDeckPlugin.Instance.SigHelper.CalcIconForSlot(gameSlot);

if (gameSlot->CommandId == cachedSlot.CommandId &&
gameSlot->Icon == cachedSlot.Icon &&
calculatedIcon == cachedSlot.Icon &&
gameSlot->CommandType == cachedSlot.CommandType) continue;

hotbarUpdated = true;
this._hotbarCache[hotbarId, slotId] = new HotBarSlot {
CommandId = gameSlot->CommandId,
Icon = gameSlot->Icon,
CommandType = gameSlot->CommandType
Icon = calculatedIcon,
CommandType = gameSlot->CommandType,
};
}
}
Expand Down
32 changes: 32 additions & 0 deletions FFXIVPlugin/Game/SigHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using XIVDeck.FFXIVPlugin.Game.Structs;
using XIVDeck.FFXIVPlugin.Server;
using XIVDeck.FFXIVPlugin.Server.Messages.Outbound;
Expand All @@ -31,12 +32,17 @@ private static class Signatures {
internal const string SanitizeChatString = "E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D";

internal const string IsQuestCompleted = "E8 ?? ?? ?? ?? 41 88 84 2C";

internal const string RefreshHotbarSlotInfo = "E8 ?? ?? ?? ?? 0F B6 54 24 ?? 8B 44 24 30";
}

/***** functions *****/
[Signature(Signatures.SanitizeChatString, Fallibility = Fallibility.Fallible)]
private readonly delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitizeChatString = null!;

[Signature(Signatures.RefreshHotbarSlotInfo, Fallibility = Fallibility.Fallible)]
private readonly delegate* unmanaged<out byte, out uint, out ushort, IntPtr, IntPtr, uint> _getSlotInfo = null!;

// UIModule, message, unused, byte
[Signature(Signatures.SendChatMessage, Fallibility = Fallibility.Fallible)]
private readonly delegate* unmanaged<IntPtr, IntPtr, IntPtr, byte, void> _processChatBoxEntry = null!;
Expand Down Expand Up @@ -112,6 +118,32 @@ internal bool IsQuestCompleted(uint questId) {
return this._isQuestCompleted((ushort) (questId & 0xFFFF)) != 0;
}

internal void CalcBForSlot(HotBarSlot* slot, out HotbarSlotType actionType, out uint actionId) {
if (this._getSlotInfo == null) {
actionType = slot->IconTypeB;
actionId = slot->IconB;
}

var hotbarModule = Framework.Instance()->GetUiModule()->GetRaptureHotbarModule();

this._getSlotInfo(out var rSlotActionType, out actionId, out _, (IntPtr) hotbarModule, (IntPtr) slot);
actionType = (HotbarSlotType) rSlotActionType;
}

internal int CalcIconForSlot(HotBarSlot* slot) {
if (this._getSlotInfo == null) {
// Fall back and return the slot icon as-is if this signature nulls out.
return slot->Icon;
}

if (slot->CommandType == HotbarSlotType.Empty) {
return 0;
}

this.CalcBForSlot(slot, out var slotActionType, out var slotActionId);
return slot->GetIconIdForSlot(slotActionType, slotActionId);
}

private IntPtr DetourGearsetSave(IntPtr a1, IntPtr a2) {
PluginLog.Debug("Gearset update!");
var tmp = this.RGM_WriteFileHook!.Original(a1, a2);
Expand Down
2 changes: 1 addition & 1 deletion FFXIVPlugin/Server/Controllers/HotbarController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public unsafe SerializableHotbarSlot GetHotbarSlot(int hotbarId, int slotId) {
}

var hotbarItem = hotbarModule->HotBar[hotbarId]->Slot[slotId];
var iconId = hotbarItem->Icon;
var iconId = plugin.SigHelper.CalcIconForSlot(hotbarItem);

return new SerializableHotbarSlot {
HotbarId = hotbarId,
Expand Down

0 comments on commit cd41714

Please sign in to comment.