2021-04-06 08:41:38 +00:00
using System ;
2021-04-22 17:54:32 +00:00
using System.Collections.Generic ;
2021-04-06 08:41:38 +00:00
using System.Runtime.InteropServices ;
using Dalamud.Game ;
2021-04-22 17:54:32 +00:00
using Dalamud.Game.Internal.Gui ;
using Dalamud.Game.Internal.Gui.Structs ;
2021-04-06 08:41:38 +00:00
using Dalamud.Hooking ;
namespace XivCommon.Functions {
2021-04-11 14:13:10 +00:00
/// <summary>
/// A class containing Party Finder functionality
/// </summary>
2021-04-06 08:41:38 +00:00
public class PartyFinder : IDisposable {
2021-04-24 17:10:13 +00:00
private static class Signatures {
internal const string RequestListings = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 40 0F 10 81 ?? ?? ?? ??" ;
internal const string JoinCrossParty = "E8 ?? ?? ?? ?? 0F B7 47 28" ;
}
2021-04-06 08:41:38 +00:00
private delegate byte RequestPartyFinderListingsDelegate ( IntPtr agent , byte categoryIdx ) ;
2021-04-23 02:05:14 +00:00
private delegate IntPtr JoinPfDelegate ( IntPtr manager , IntPtr a2 , int type , IntPtr packetData , uint a5 ) ;
2021-04-22 17:54:32 +00:00
2021-04-24 17:10:13 +00:00
private RequestPartyFinderListingsDelegate ? RequestPartyFinderListings { get ; }
2021-04-11 13:24:56 +00:00
private Hook < RequestPartyFinderListingsDelegate > ? RequestPfListingsHook { get ; }
2021-04-22 17:54:32 +00:00
private Hook < JoinPfDelegate > ? JoinPfHook { get ; }
/// <summary>
/// The delegate for party join events.
/// </summary>
public delegate void JoinPfEventDelegate ( PartyFinderListing listing ) ;
2021-04-06 08:41:38 +00:00
2021-04-22 17:54:32 +00:00
/// <summary>
/// <para>
2021-04-23 15:39:26 +00:00
/// The event that is fired when the player joins a <b>cross-world</b> party via Party Finder.
2021-04-22 17:54:32 +00:00
/// </para>
/// <para>
2021-04-24 16:29:39 +00:00
/// Requires the <see cref="Hooks.PartyFinderJoins"/> hook to be enabled.
2021-04-22 17:54:32 +00:00
/// </para>
/// </summary>
public event JoinPfEventDelegate ? JoinParty ;
private PartyFinderGui PartyFinderGui { get ; }
2021-04-24 16:28:18 +00:00
private bool JoinsEnabled { get ; }
private bool ListingsEnabled { get ; }
2021-04-06 08:41:38 +00:00
private IntPtr PartyFinderAgent { get ; set ; } = IntPtr . Zero ;
2021-04-22 17:54:32 +00:00
private Dictionary < uint , PartyFinderListing > Listings { get ; } = new ( ) ;
private int LastBatch { get ; set ; } = - 1 ;
2021-04-27 14:23:43 +00:00
/// <summary>
/// <para>
/// The current Party Finder listings that have been displayed.
/// </para>
/// <para>
/// This dictionary is cleared and updated each time the Party Finder is requested, and it only contains the category selected in the Party Finder addon.
/// </para>
/// <para>
/// Keys are the listing ID for fast lookup by ID. Values are the listing itself.
/// </para>
/// </summary>
public IReadOnlyDictionary < uint , PartyFinderListing > CurrentListings = > this . Listings ;
2021-04-24 16:28:18 +00:00
internal PartyFinder ( SigScanner scanner , PartyFinderGui partyFinderGui , Hooks hooks ) {
2021-04-22 17:54:32 +00:00
this . PartyFinderGui = partyFinderGui ;
2021-04-06 08:41:38 +00:00
2021-04-24 16:28:18 +00:00
this . ListingsEnabled = hooks . HasFlag ( Hooks . PartyFinderListings ) ;
this . JoinsEnabled = hooks . HasFlag ( Hooks . PartyFinderJoins ) ;
2021-05-29 05:22:31 +00:00
if ( this . ListingsEnabled | | this . JoinsEnabled ) {
this . PartyFinderGui . ReceiveListing + = this . ReceiveListing ;
}
2021-04-24 17:13:09 +00:00
if ( scanner . TryScanText ( Signatures . RequestListings , out var requestPfPtr , "Party Finder listings" ) ) {
2021-04-24 17:10:13 +00:00
this . RequestPartyFinderListings = Marshal . GetDelegateForFunctionPointer < RequestPartyFinderListingsDelegate > ( requestPfPtr ) ;
2021-04-06 08:41:38 +00:00
2021-04-24 17:10:13 +00:00
if ( this . ListingsEnabled ) {
2021-07-22 20:33:54 +00:00
this . RequestPfListingsHook = new Hook < RequestPartyFinderListingsDelegate > ( requestPfPtr , this . OnRequestPartyFinderListings ) ;
2021-04-24 17:10:13 +00:00
this . RequestPfListingsHook . Enable ( ) ;
}
2021-04-11 13:24:56 +00:00
}
2021-04-24 17:43:01 +00:00
if ( this . JoinsEnabled & & scanner . TryScanText ( Signatures . JoinCrossParty , out var joinPtr , "Party Finder joins" ) ) {
2021-07-22 20:33:54 +00:00
this . JoinPfHook = new Hook < JoinPfDelegate > ( joinPtr , this . JoinPfDetour ) ;
2021-04-24 17:43:01 +00:00
this . JoinPfHook . Enable ( ) ;
2021-04-24 16:28:18 +00:00
}
2021-04-06 08:41:38 +00:00
}
2021-04-11 14:13:10 +00:00
/// <inheritdoc />
2021-04-06 08:41:38 +00:00
public void Dispose ( ) {
2021-04-24 17:43:01 +00:00
this . PartyFinderGui . ReceiveListing - = this . ReceiveListing ;
2021-04-22 17:54:32 +00:00
this . JoinPfHook ? . Dispose ( ) ;
2021-04-24 16:11:34 +00:00
this . RequestPfListingsHook ? . Dispose ( ) ;
2021-04-22 17:54:32 +00:00
}
private void ReceiveListing ( PartyFinderListing listing , PartyFinderListingEventArgs args ) {
if ( args . BatchNumber ! = this . LastBatch ) {
this . Listings . Clear ( ) ;
}
this . LastBatch = args . BatchNumber ;
this . Listings [ listing . Id ] = listing ;
2021-04-06 08:41:38 +00:00
}
private byte OnRequestPartyFinderListings ( IntPtr agent , byte categoryIdx ) {
this . PartyFinderAgent = agent ;
2021-04-11 13:24:56 +00:00
return this . RequestPfListingsHook ! . Original ( agent , categoryIdx ) ;
2021-04-06 08:41:38 +00:00
}
2021-04-23 02:05:14 +00:00
private IntPtr JoinPfDetour ( IntPtr manager , IntPtr a2 , int type , IntPtr packetData , uint a5 ) {
2021-04-22 17:54:32 +00:00
// Updated: 5.5
const int idOffset = - 0x20 ;
2021-04-23 02:05:14 +00:00
var ret = this . JoinPfHook ! . Original ( manager , a2 , type , packetData , a5 ) ;
2021-04-24 16:18:28 +00:00
if ( this . JoinParty = = null | | ( JoinType ) type ! = JoinType . PartyFinder | | packetData = = IntPtr . Zero ) {
2021-04-23 02:05:14 +00:00
return ret ;
}
2021-04-22 17:54:32 +00:00
try {
2021-04-24 16:18:28 +00:00
var id = ( uint ) Marshal . ReadInt32 ( packetData + idOffset ) ;
if ( this . Listings . TryGetValue ( id , out var listing ) ) {
this . JoinParty ? . Invoke ( listing ) ;
2021-04-22 17:54:32 +00:00
}
} catch ( Exception ex ) {
2021-04-29 03:57:29 +00:00
Logger . LogError ( ex , "Exception in PF join detour" ) ;
2021-04-22 17:54:32 +00:00
}
return ret ;
}
2021-04-11 13:42:59 +00:00
/// <summary>
/// <para>
/// Refresh the Party Finder listings. This does not open the Party Finder.
/// </para>
/// <para>
/// This maintains the currently selected category.
/// </para>
/// </summary>
2021-04-24 17:10:13 +00:00
/// <exception cref="InvalidOperationException">If the <see cref="Hooks.PartyFinderListings"/> hook is not enabled or if the signature for this function could not be found</exception>
2021-04-06 08:41:38 +00:00
public void RefreshListings ( ) {
2021-04-24 17:10:13 +00:00
if ( this . RequestPartyFinderListings = = null ) {
throw new InvalidOperationException ( "Could not find signature for Party Finder listings" ) ;
}
2021-04-24 16:28:18 +00:00
if ( ! this . ListingsEnabled ) {
2021-04-11 13:24:56 +00:00
throw new InvalidOperationException ( "PartyFinder hooks are not enabled" ) ;
}
2021-04-13 09:33:38 +00:00
// Updated 5.5
2021-04-06 08:41:38 +00:00
const int categoryOffset = 10_655 ;
if ( this . PartyFinderAgent = = IntPtr . Zero ) {
return ;
}
var categoryIdx = Marshal . ReadByte ( this . PartyFinderAgent + categoryOffset ) ;
this . RequestPartyFinderListings ( this . PartyFinderAgent , categoryIdx ) ;
}
}
2021-04-24 16:11:34 +00:00
internal enum JoinType : byte {
/// <summary>
/// Join via invite or party conversion.
/// </summary>
Normal = 0 ,
/// <summary>
/// Join via Party Finder.
/// </summary>
PartyFinder = 1 ,
Unknown2 = 2 ,
/// <summary>
/// Remain in cross-world party after leaving a duty.
/// </summary>
LeaveDuty = 3 ,
Unknown4 = 4 ,
}
2021-04-06 08:41:38 +00:00
}