diff --git a/XivCommon/Functions/PartyFinder.cs b/XivCommon/Functions/PartyFinder.cs index fdc0616..0a8cfec 100755 --- a/XivCommon/Functions/PartyFinder.cs +++ b/XivCommon/Functions/PartyFinder.cs @@ -1,7 +1,11 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using Dalamud.Game; +using Dalamud.Game.Internal.Gui; +using Dalamud.Game.Internal.Gui.Structs; using Dalamud.Hooking; +using Dalamud.Plugin; namespace XivCommon.Functions { /// @@ -10,13 +14,36 @@ namespace XivCommon.Functions { public class PartyFinder : IDisposable { private delegate byte RequestPartyFinderListingsDelegate(IntPtr agent, byte categoryIdx); + private delegate IntPtr JoinPfDelegate(IntPtr manager, IntPtr a2, int a3, IntPtr packetData, uint a5); + private RequestPartyFinderListingsDelegate RequestPartyFinderListings { get; } private Hook? RequestPfListingsHook { get; } + private Hook? JoinPfHook { get; } + /// + /// The delegate for party join events. + /// + public delegate void JoinPfEventDelegate(PartyFinderListing listing); + + /// + /// + /// The event that is fired when the player joins a party via Party Finder. + /// + /// + /// Requires the hook to be enabled. + /// + /// + public event JoinPfEventDelegate? JoinParty; + + private PartyFinderGui PartyFinderGui { get; } private bool Enabled { get; } private IntPtr PartyFinderAgent { get; set; } = IntPtr.Zero; + private Dictionary Listings { get; } = new(); + private int LastBatch { get; set; } = -1; + + internal PartyFinder(SigScanner scanner, PartyFinderGui partyFinderGui, bool hook) { + this.PartyFinderGui = partyFinderGui; - internal PartyFinder(SigScanner scanner, bool hook) { var requestPfPtr = scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 40 0F 10 81 ?? ?? ?? ??"); this.RequestPartyFinderListings = Marshal.GetDelegateForFunctionPointer(requestPfPtr); @@ -29,11 +56,29 @@ namespace XivCommon.Functions { this.RequestPfListingsHook = new Hook(requestPfPtr, new RequestPartyFinderListingsDelegate(this.OnRequestPartyFinderListings)); this.RequestPfListingsHook.Enable(); + + this.PartyFinderGui.ReceiveListing += this.ReceiveListing; + + var joinPtr = scanner.ScanText("E8 ?? ?? ?? ?? 0F B7 47 28"); + this.JoinPfHook = new Hook(joinPtr, new JoinPfDelegate(this.JoinPfDetour)); + this.JoinPfHook.Enable(); } /// public void Dispose() { + this.PartyFinderGui.ReceiveListing -= this.ReceiveListing; this.RequestPfListingsHook?.Dispose(); + this.JoinPfHook?.Dispose(); + } + + private void ReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args) { + if (args.BatchNumber != this.LastBatch) { + this.Listings.Clear(); + } + + this.LastBatch = args.BatchNumber; + + this.Listings[listing.Id] = listing; } private byte OnRequestPartyFinderListings(IntPtr agent, byte categoryIdx) { @@ -41,6 +86,24 @@ namespace XivCommon.Functions { return this.RequestPfListingsHook!.Original(agent, categoryIdx); } + private IntPtr JoinPfDetour(IntPtr manager, IntPtr a2, int a3, IntPtr packetData, uint a5) { + // Updated: 5.5 + const int idOffset = -0x20; + + var ret = this.JoinPfHook!.Original(manager, a2, a3, packetData, a5); + + try { + var id = (uint) Marshal.ReadInt32(packetData + idOffset); + if (this.Listings.TryGetValue(id, out var listing)) { + this.JoinParty?.Invoke(listing); + } + } catch (Exception ex) { + PluginLog.LogError(ex, "Exception in PF join detour"); + } + + return ret; + } + /// /// /// Refresh the Party Finder listings. This does not open the Party Finder. diff --git a/XivCommon/GameFunctions.cs b/XivCommon/GameFunctions.cs index 3a04ddd..1ba245d 100755 --- a/XivCommon/GameFunctions.cs +++ b/XivCommon/GameFunctions.cs @@ -16,7 +16,7 @@ namespace XivCommon { public Chat Chat { get; } /// - /// Party Finder functions + /// Party Finder functions and events /// public PartyFinder PartyFinder { get; } @@ -24,6 +24,7 @@ namespace XivCommon { /// BattleTalk functions and events /// public BattleTalk BattleTalk { get; } + /// /// Examine functions /// @@ -49,7 +50,7 @@ namespace XivCommon { var dalamud = (Dalamud.Dalamud) dalamudField!.GetValue(@interface); this.Chat = new Chat(this, scanner); - this.PartyFinder = new PartyFinder(scanner, hooks.HasFlag(Hooks.PartyFinder)); + this.PartyFinder = new PartyFinder(scanner, @interface.Framework.Gui.PartyFinder, hooks.HasFlag(Hooks.PartyFinder)); this.BattleTalk = new BattleTalk(this, scanner, seStringManager, hooks.HasFlag(Hooks.BattleTalk)); this.Examine = new Examine(this, scanner); this.Talk = new Talk(scanner, seStringManager, hooks.HasFlag(Hooks.Talk));